web & server: implement handling for text and html bodies.

This commit is contained in:
Bill Thiede 2023-11-26 16:37:29 -08:00
parent 1cdabc348b
commit 11366b6fac
4 changed files with 204 additions and 7 deletions

View File

@ -6,6 +6,7 @@ use std::{
use async_graphql::{
connection::{self, Connection, Edge},
Context, EmptyMutation, EmptySubscription, Error, FieldResult, Object, Schema, SimpleObject,
Union,
};
use log::{info, warn};
use mailparse::{parse_mail, MailHeaderMap, ParsedMail};
@ -55,6 +56,51 @@ pub struct Message {
pub subject: Option<String>,
// Parsed Date header, if found and valid
pub timestamp: Option<i64>,
// The body contents
pub body: Body,
}
#[derive(Debug)]
struct UnhandledContentType {
text: String,
}
#[Object]
impl UnhandledContentType {
async fn contents(&self) -> &str {
&self.text
}
}
#[derive(Debug)]
struct PlainText {
text: String,
}
#[Object]
impl PlainText {
async fn contents(&self) -> &str {
&self.text
}
}
#[derive(Debug)]
struct Html {
html: String,
}
#[Object]
impl Html {
async fn contents(&self) -> &str {
&self.html
}
}
#[derive(Debug, Union)]
pub enum Body {
UnhandledContentType(UnhandledContentType),
PlainText(PlainText),
Html(Html),
}
#[derive(Debug, SimpleObject)]
@ -195,12 +241,23 @@ impl QueryRoot {
.headers
.get_first_value("date")
.and_then(|d| mailparse::dateparse(&d).ok());
let body = m.get_body()?;
let body = match m.ctype.mimetype.as_str() {
"text/plain" => Body::PlainText(PlainText { text: body }),
"text/html" => Body::Html(Html { html: body }),
_ => {
let msg = format!("Unhandled body content type: {}", m.ctype.mimetype);
warn!("{}", msg);
Body::UnhandledContentType(UnhandledContentType { text: msg })
}
};
messages.push(Message {
from,
to,
cc,
subject,
timestamp,
body,
});
}
messages.reverse();

View File

@ -59,6 +59,32 @@
},
"subscriptionType": null,
"types": [
{
"description": null,
"enumValues": null,
"fields": null,
"inputFields": null,
"interfaces": null,
"kind": "UNION",
"name": "Body",
"possibleTypes": [
{
"kind": "OBJECT",
"name": "UnhandledContentType",
"ofType": null
},
{
"kind": "OBJECT",
"name": "PlainText",
"ofType": null
},
{
"kind": "OBJECT",
"name": "Html",
"ofType": null
}
]
},
{
"description": "The `Boolean` scalar type represents `true` or `false`.",
"enumValues": null,
@ -114,6 +140,33 @@
"name": "Float",
"possibleTypes": null
},
{
"description": null,
"enumValues": null,
"fields": [
{
"args": [],
"deprecationReason": null,
"description": null,
"isDeprecated": false,
"name": "contents",
"type": {
"kind": "NON_NULL",
"name": null,
"ofType": {
"kind": "SCALAR",
"name": "String",
"ofType": null
}
}
}
],
"inputFields": null,
"interfaces": [],
"kind": "OBJECT",
"name": "Html",
"possibleTypes": null
},
{
"description": null,
"enumValues": null,
@ -221,6 +274,22 @@
"name": "Int",
"ofType": null
}
},
{
"args": [],
"deprecationReason": null,
"description": null,
"isDeprecated": false,
"name": "body",
"type": {
"kind": "NON_NULL",
"name": null,
"ofType": {
"kind": "UNION",
"name": "Body",
"ofType": null
}
}
}
],
"inputFields": null,
@ -296,6 +365,33 @@
"name": "PageInfo",
"possibleTypes": null
},
{
"description": null,
"enumValues": null,
"fields": [
{
"args": [],
"deprecationReason": null,
"description": null,
"isDeprecated": false,
"name": "contents",
"type": {
"kind": "NON_NULL",
"name": null,
"ofType": {
"kind": "SCALAR",
"name": "String",
"ofType": null
}
}
}
],
"inputFields": null,
"interfaces": [],
"kind": "OBJECT",
"name": "PlainText",
"possibleTypes": null
},
{
"description": null,
"enumValues": null,
@ -865,6 +961,33 @@
"name": "ThreadSummaryEdge",
"possibleTypes": null
},
{
"description": null,
"enumValues": null,
"fields": [
{
"args": [],
"deprecationReason": null,
"description": null,
"isDeprecated": false,
"name": "contents",
"type": {
"kind": "NON_NULL",
"name": null,
"ofType": {
"kind": "SCALAR",
"name": "String",
"ofType": null
}
}
}
],
"inputFields": null,
"interfaces": [],
"kind": "OBJECT",
"name": "UnhandledContentType",
"possibleTypes": null
},
{
"description": "A Directive provides a way to describe alternate runtime execution and type\nvalidation behavior in a GraphQL document.\n\nIn some cases, you need to provide options to alter GraphQL's execution\nbehavior in ways field arguments will not suffice, such as conditionally\nincluding or skipping a field. Directives provide this by describing\nadditional information to the executor.",
"enumValues": null,

View File

@ -16,6 +16,18 @@ query ShowThreadQuery($threadId: String!) {
addr
}
timestamp
body {
__typename
... on UnhandledContentType {
contents
}
... on PlainText {
contents
}
... on Html {
contents
}
}
}
}
tags {

View File

@ -1054,15 +1054,20 @@ fn view_thread(thread: &ShowThreadQueryThread) -> Node<Msg> {
.map(|ts| div![C!["header"], "Date: ", human_age(ts)]),
IF!(!msg.to.is_empty() => div![C!["header"], "To: ", view_addresses(&msg.to)]),
IF!(!msg.cc.is_empty() => div![C!["header"], "CC: ", view_addresses(&msg.cc)]),
/*
div![
C!["body"],
match &message.body {
Some(body) => view_body(body.as_slice()),
None => div!["<no body>"],
},
C!["body"],
match &msg.body {
ShowThreadQueryThreadMessagesBody::UnhandledContentType(
ShowThreadQueryThreadMessagesBodyOnUnhandledContentType { contents },
) => div![C!["error"], contents],
ShowThreadQueryThreadMessagesBody::PlainText(
ShowThreadQueryThreadMessagesBodyOnPlainText { contents },
) => div![C!["view-part-text-plain"], contents],
ShowThreadQueryThreadMessagesBody::Html(
ShowThreadQueryThreadMessagesBodyOnHtml { contents },
) => div![C!["view-part-text-html"], raw![contents]],
}
],
*/
]
});
div![