server: add mutation to mark messages as read
This commit is contained in:
parent
81ed3a8ca2
commit
5451dd2056
@ -2,7 +2,7 @@
|
|||||||
extern crate rocket;
|
extern crate rocket;
|
||||||
use std::{error::Error, io::Cursor, str::FromStr};
|
use std::{error::Error, io::Cursor, str::FromStr};
|
||||||
|
|
||||||
use async_graphql::{http::GraphiQLSource, EmptyMutation, EmptySubscription, Schema};
|
use async_graphql::{http::GraphiQLSource, EmptySubscription, Schema};
|
||||||
use async_graphql_rocket::{GraphQLQuery, GraphQLRequest, GraphQLResponse};
|
use async_graphql_rocket::{GraphQLQuery, GraphQLRequest, GraphQLResponse};
|
||||||
use glog::Flags;
|
use glog::Flags;
|
||||||
use notmuch::{Notmuch, NotmuchError, ThreadSet};
|
use notmuch::{Notmuch, NotmuchError, ThreadSet};
|
||||||
@ -16,7 +16,7 @@ use rocket::{
|
|||||||
use rocket_cors::{AllowedHeaders, AllowedOrigins};
|
use rocket_cors::{AllowedHeaders, AllowedOrigins};
|
||||||
use server::{
|
use server::{
|
||||||
error::ServerError,
|
error::ServerError,
|
||||||
graphql::{GraphqlSchema, QueryRoot},
|
graphql::{GraphqlSchema, Mutation, QueryRoot},
|
||||||
};
|
};
|
||||||
|
|
||||||
#[get("/refresh")]
|
#[get("/refresh")]
|
||||||
@ -182,7 +182,7 @@ async fn main() -> Result<(), Box<dyn Error>> {
|
|||||||
}
|
}
|
||||||
.to_cors()?;
|
.to_cors()?;
|
||||||
|
|
||||||
let schema = Schema::build(QueryRoot, EmptyMutation, EmptySubscription)
|
let schema = Schema::build(QueryRoot, Mutation, EmptySubscription)
|
||||||
.data(Notmuch::default())
|
.data(Notmuch::default())
|
||||||
.extension(async_graphql::extensions::Logger)
|
.extension(async_graphql::extensions::Logger)
|
||||||
.finish();
|
.finish();
|
||||||
|
|||||||
@ -7,8 +7,7 @@ use std::{
|
|||||||
|
|
||||||
use async_graphql::{
|
use async_graphql::{
|
||||||
connection::{self, Connection, Edge},
|
connection::{self, Connection, Edge},
|
||||||
Context, EmptyMutation, EmptySubscription, Enum, Error, FieldResult, Object, Schema,
|
Context, EmptySubscription, Enum, Error, FieldResult, Object, Schema, SimpleObject, Union,
|
||||||
SimpleObject, Union,
|
|
||||||
};
|
};
|
||||||
use log::{error, info, warn};
|
use log::{error, info, warn};
|
||||||
use mailparse::{parse_mail, MailHeader, MailHeaderMap, ParsedMail};
|
use mailparse::{parse_mail, MailHeader, MailHeaderMap, ParsedMail};
|
||||||
@ -18,8 +17,6 @@ use rocket::time::Instant;
|
|||||||
|
|
||||||
use crate::{linkify_html, sanitize_html};
|
use crate::{linkify_html, sanitize_html};
|
||||||
|
|
||||||
pub struct QueryRoot;
|
|
||||||
|
|
||||||
/// # Number of seconds since the Epoch
|
/// # Number of seconds since the Epoch
|
||||||
pub type UnixTime = isize;
|
pub type UnixTime = isize;
|
||||||
|
|
||||||
@ -44,6 +41,7 @@ pub struct ThreadSummary {
|
|||||||
|
|
||||||
#[derive(Debug, SimpleObject)]
|
#[derive(Debug, SimpleObject)]
|
||||||
pub struct Thread {
|
pub struct Thread {
|
||||||
|
thread_id: String,
|
||||||
subject: String,
|
subject: String,
|
||||||
messages: Vec<Message>,
|
messages: Vec<Message>,
|
||||||
}
|
}
|
||||||
@ -192,6 +190,7 @@ struct Tag {
|
|||||||
unread: usize,
|
unread: usize,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub struct QueryRoot;
|
||||||
#[Object]
|
#[Object]
|
||||||
impl QueryRoot {
|
impl QueryRoot {
|
||||||
async fn count<'ctx>(&self, ctx: &Context<'ctx>, query: String) -> Result<usize, Error> {
|
async fn count<'ctx>(&self, ctx: &Context<'ctx>, query: String) -> Result<usize, Error> {
|
||||||
@ -316,13 +315,8 @@ impl QueryRoot {
|
|||||||
.exists();
|
.exists();
|
||||||
let mut messages = Vec::new();
|
let mut messages = Vec::new();
|
||||||
for (path, id) in std::iter::zip(nm.files(&thread_id)?, nm.message_ids(&thread_id)?) {
|
for (path, id) in std::iter::zip(nm.files(&thread_id)?, nm.message_ids(&thread_id)?) {
|
||||||
info!("{id}\nfile: {path}");
|
let tags = nm.tags_for_query(&format!("id:{id}"))?;
|
||||||
let msg = nm.show(&format!("id:{id}"))?;
|
info!("{id}: {tags:?}\nfile: {path}");
|
||||||
let tags = msg.0[0].0[0]
|
|
||||||
.0
|
|
||||||
.as_ref()
|
|
||||||
.map(|m| m.tags.clone())
|
|
||||||
.unwrap_or_else(Vec::default);
|
|
||||||
let file = File::open(&path)?;
|
let file = File::open(&path)?;
|
||||||
let mmap = unsafe { MmapOptions::new().map(&file)? };
|
let mmap = unsafe { MmapOptions::new().map(&file)? };
|
||||||
let m = parse_mail(&mmap)?;
|
let m = parse_mail(&mmap)?;
|
||||||
@ -401,7 +395,31 @@ impl QueryRoot {
|
|||||||
.next()
|
.next()
|
||||||
.and_then(|m| m.subject.clone())
|
.and_then(|m| m.subject.clone())
|
||||||
.unwrap_or("(NO SUBJECT)".to_string());
|
.unwrap_or("(NO SUBJECT)".to_string());
|
||||||
Ok(Thread { subject, messages })
|
Ok(Thread {
|
||||||
|
thread_id,
|
||||||
|
subject,
|
||||||
|
messages,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct Mutation;
|
||||||
|
#[Object]
|
||||||
|
impl Mutation {
|
||||||
|
async fn set_read_status<'ctx>(
|
||||||
|
&self,
|
||||||
|
ctx: &Context<'ctx>,
|
||||||
|
query: String,
|
||||||
|
unread: bool,
|
||||||
|
) -> Result<bool, Error> {
|
||||||
|
let nm = ctx.data_unchecked::<Notmuch>();
|
||||||
|
info!("set_read_status({unread})");
|
||||||
|
if unread {
|
||||||
|
nm.tag_add("unread", &format!("{query}"))?;
|
||||||
|
} else {
|
||||||
|
nm.tag_remove("unread", &format!("{query}"))?;
|
||||||
|
}
|
||||||
|
Ok(true)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -587,7 +605,7 @@ fn render_content_type_tree(m: &ParsedMail) -> String {
|
|||||||
render_rec(m, 1)
|
render_rec(m, 1)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub type GraphqlSchema = Schema<QueryRoot, EmptyMutation, EmptySubscription>;
|
pub type GraphqlSchema = Schema<QueryRoot, Mutation, EmptySubscription>;
|
||||||
|
|
||||||
fn email_addresses(path: &str, m: &ParsedMail, header_name: &str) -> Result<Vec<Email>, Error> {
|
fn email_addresses(path: &str, m: &ParsedMail, header_name: &str) -> Result<Vec<Email>, Error> {
|
||||||
let mut addrs = Vec::new();
|
let mut addrs = Vec::new();
|
||||||
|
|||||||
@ -19,7 +19,7 @@ pub enum SanitizeError {
|
|||||||
|
|
||||||
pub fn linkify_html(text: &str) -> String {
|
pub fn linkify_html(text: &str) -> String {
|
||||||
let mut finder = LinkFinder::new();
|
let mut finder = LinkFinder::new();
|
||||||
finder.url_must_have_scheme(false);
|
let finder = finder.url_must_have_scheme(false).kinds(&[LinkKind::Url]);
|
||||||
let mut parts = Vec::new();
|
let mut parts = Vec::new();
|
||||||
for span in finder.spans(text) {
|
for span in finder.spans(text) {
|
||||||
// TODO(wathiede): use Cow<str>?
|
// TODO(wathiede): use Cow<str>?
|
||||||
@ -35,7 +35,6 @@ pub fn linkify_html(text: &str) -> String {
|
|||||||
"http://"
|
"http://"
|
||||||
};
|
};
|
||||||
let a = format!(r#"<a href="{schema}{0}">{0}</a>"#, text);
|
let a = format!(r#"<a href="{schema}{0}">{0}</a>"#, text);
|
||||||
log::info!("link {} {a}", span.as_str());
|
|
||||||
parts.push(a);
|
parts.push(a);
|
||||||
}
|
}
|
||||||
_ => todo!("unhandled kind: {:?}", span.kind().unwrap()),
|
_ => todo!("unhandled kind: {:?}", span.kind().unwrap()),
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user