From 5451dd20569399399f80debdd22bb2bf5b481159 Mon Sep 17 00:00:00 2001 From: Bill Thiede Date: Sun, 11 Feb 2024 19:43:34 -0800 Subject: [PATCH] server: add mutation to mark messages as read --- server/src/bin/server.rs | 6 +++--- server/src/graphql.rs | 44 ++++++++++++++++++++++++++++------------ server/src/lib.rs | 3 +-- 3 files changed, 35 insertions(+), 18 deletions(-) diff --git a/server/src/bin/server.rs b/server/src/bin/server.rs index a070d67..0086c50 100644 --- a/server/src/bin/server.rs +++ b/server/src/bin/server.rs @@ -2,7 +2,7 @@ extern crate rocket; 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 glog::Flags; use notmuch::{Notmuch, NotmuchError, ThreadSet}; @@ -16,7 +16,7 @@ use rocket::{ use rocket_cors::{AllowedHeaders, AllowedOrigins}; use server::{ error::ServerError, - graphql::{GraphqlSchema, QueryRoot}, + graphql::{GraphqlSchema, Mutation, QueryRoot}, }; #[get("/refresh")] @@ -182,7 +182,7 @@ async fn main() -> Result<(), Box> { } .to_cors()?; - let schema = Schema::build(QueryRoot, EmptyMutation, EmptySubscription) + let schema = Schema::build(QueryRoot, Mutation, EmptySubscription) .data(Notmuch::default()) .extension(async_graphql::extensions::Logger) .finish(); diff --git a/server/src/graphql.rs b/server/src/graphql.rs index ce8debc..de7409e 100644 --- a/server/src/graphql.rs +++ b/server/src/graphql.rs @@ -7,8 +7,7 @@ use std::{ use async_graphql::{ connection::{self, Connection, Edge}, - Context, EmptyMutation, EmptySubscription, Enum, Error, FieldResult, Object, Schema, - SimpleObject, Union, + Context, EmptySubscription, Enum, Error, FieldResult, Object, Schema, SimpleObject, Union, }; use log::{error, info, warn}; use mailparse::{parse_mail, MailHeader, MailHeaderMap, ParsedMail}; @@ -18,8 +17,6 @@ use rocket::time::Instant; use crate::{linkify_html, sanitize_html}; -pub struct QueryRoot; - /// # Number of seconds since the Epoch pub type UnixTime = isize; @@ -44,6 +41,7 @@ pub struct ThreadSummary { #[derive(Debug, SimpleObject)] pub struct Thread { + thread_id: String, subject: String, messages: Vec, } @@ -192,6 +190,7 @@ struct Tag { unread: usize, } +pub struct QueryRoot; #[Object] impl QueryRoot { async fn count<'ctx>(&self, ctx: &Context<'ctx>, query: String) -> Result { @@ -316,13 +315,8 @@ impl QueryRoot { .exists(); let mut messages = Vec::new(); for (path, id) in std::iter::zip(nm.files(&thread_id)?, nm.message_ids(&thread_id)?) { - info!("{id}\nfile: {path}"); - let msg = nm.show(&format!("id:{id}"))?; - let tags = msg.0[0].0[0] - .0 - .as_ref() - .map(|m| m.tags.clone()) - .unwrap_or_else(Vec::default); + let tags = nm.tags_for_query(&format!("id:{id}"))?; + info!("{id}: {tags:?}\nfile: {path}"); let file = File::open(&path)?; let mmap = unsafe { MmapOptions::new().map(&file)? }; let m = parse_mail(&mmap)?; @@ -401,7 +395,31 @@ impl QueryRoot { .next() .and_then(|m| m.subject.clone()) .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 { + let nm = ctx.data_unchecked::(); + 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) } -pub type GraphqlSchema = Schema; +pub type GraphqlSchema = Schema; fn email_addresses(path: &str, m: &ParsedMail, header_name: &str) -> Result, Error> { let mut addrs = Vec::new(); diff --git a/server/src/lib.rs b/server/src/lib.rs index d2652c5..c67d982 100644 --- a/server/src/lib.rs +++ b/server/src/lib.rs @@ -19,7 +19,7 @@ pub enum SanitizeError { pub fn linkify_html(text: &str) -> String { 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(); for span in finder.spans(text) { // TODO(wathiede): use Cow? @@ -35,7 +35,6 @@ pub fn linkify_html(text: &str) -> String { "http://" }; let a = format!(r#"{0}"#, text); - log::info!("link {} {a}", span.as_str()); parts.push(a); } _ => todo!("unhandled kind: {:?}", span.kind().unwrap()),