diff --git a/server/src/bin/server.rs b/server/src/bin/server.rs index 96b6200..76933d5 100644 --- a/server/src/bin/server.rs +++ b/server/src/bin/server.rs @@ -1,17 +1,8 @@ #[macro_use] extern crate rocket; +use std::{error::Error, io::Cursor, str::FromStr}; -use std::{ - error::Error, - hash::{DefaultHasher, Hash, Hasher}, - io::Cursor, - str::FromStr, -}; - -use async_graphql::{ - http::GraphiQLSource, Context, EmptyMutation, EmptySubscription, FieldResult, Object, Schema, - SimpleObject, -}; +use async_graphql::{http::GraphiQLSource, EmptyMutation, EmptySubscription, Schema}; use async_graphql_rocket::{GraphQLQuery, GraphQLRequest, GraphQLResponse}; use glog::Flags; use notmuch::{Notmuch, NotmuchError, ThreadSet}; @@ -23,7 +14,11 @@ use rocket::{ Response, State, }; use rocket_cors::{AllowedHeaders, AllowedOrigins}; -use server::{error::ServerError, nm::threadset_to_messages}; +use server::{ + error::ServerError, + graphql::{GraphqlSchema, QueryRoot}, + nm::threadset_to_messages, +}; use shared::Message; #[get("/refresh")] @@ -148,38 +143,6 @@ async fn graphql_request( request.execute(schema.inner()).await } -pub struct QueryRoot; - -#[derive(SimpleObject)] -struct Tag { - name: String, - fg_color: String, - bg_color: String, -} - -#[Object] -impl QueryRoot { - async fn tags<'ctx>(&self, ctx: &Context<'ctx>) -> FieldResult> { - let nm = ctx.data_unchecked::(); - Ok(nm - .tags()? - .into_iter() - .map(|tag| { - let mut hasher = DefaultHasher::new(); - tag.hash(&mut hasher); - let hex = format!("#{:06x}", hasher.finish() % (1 << 24)); - Tag { - name: tag, - fg_color: "white".to_string(), - bg_color: hex, - } - }) - .collect()) - } -} - -pub type GraphqlSchema = Schema; - #[rocket::main] async fn main() -> Result<(), Box> { glog::new() diff --git a/server/src/graphql.rs b/server/src/graphql.rs new file mode 100644 index 0000000..dcc9400 --- /dev/null +++ b/server/src/graphql.rs @@ -0,0 +1,138 @@ +use std::hash::{DefaultHasher, Hash, Hasher}; + +use async_graphql::{ + connection::{self, Connection, Edge}, + Context, EmptyMutation, EmptySubscription, Error, FieldResult, Object, Schema, SimpleObject, +}; +use log::info; +use notmuch::Notmuch; + +pub struct QueryRoot; + +/// # Number of seconds since the Epoch +pub type UnixTime = isize; + +/// # Thread ID, sans "thread:" +pub type ThreadId = String; + +#[derive(Debug, SimpleObject)] +pub struct ThreadSummary { + pub thread: ThreadId, + pub timestamp: UnixTime, + /// user-friendly timestamp + pub date_relative: String, + /// number of matched messages + pub matched: isize, + /// total messages in thread + pub total: isize, + /// comma-separated names with | between matched and unmatched + pub authors: String, + pub subject: String, + pub tags: Vec, +} + +#[derive(SimpleObject)] +struct Tag { + name: String, + fg_color: String, + bg_color: String, +} + +#[Object] +impl QueryRoot { + async fn search<'ctx>( + &self, + ctx: &Context<'ctx>, + after: Option, + before: Option, + first: Option, + last: Option, + query: String, + ) -> Result, Error> { + let nm = ctx.data_unchecked::(); + connection::query( + after, + before, + first, + last, + |after, before, first, last| async move { + info!("{after:?} {before:?} {first:?} {last:?} {query}"); + let mut start = 0usize; + let total = nm.count(&query)?; + let mut end = total; + + if let Some(after) = after { + if after >= total { + return Ok(Connection::new(false, false)); + } + start = after + 1; + } + + // TODO(wathiede): handle last/end. + if let Some(before) = before { + if before == 0 { + return Ok(Connection::new(false, false)); + } + end = before; + } + + let slice: Vec = nm + .search(&query, start, first.unwrap_or(20))? + .0 + .into_iter() + .map(|ts| ThreadSummary { + thread: ts.thread, + timestamp: ts.timestamp, + date_relative: ts.date_relative, + matched: ts.matched, + total: ts.total, + authors: ts.authors, + subject: ts.subject, + tags: ts.tags, + }) + .collect(); + //let mut slice = &res[..]; + + /* + if let Some(first) = first { + slice = &slice[..first.min(slice.len())]; + end -= first.min(slice.len()); + } else if let Some(last) = last { + slice = &slice[slice.len() - last.min(slice.len())..]; + start = end - last.min(slice.len()); + } + */ + + let mut connection = Connection::new(start > 0, end < total); + connection.edges.extend( + slice + .into_iter() + .enumerate() + .map(|(idx, item)| Edge::new(start + idx, item)), + ); + Ok::<_, Error>(connection) + }, + ) + .await + } + + async fn tags<'ctx>(&self, ctx: &Context<'ctx>) -> FieldResult> { + let nm = ctx.data_unchecked::(); + Ok(nm + .tags()? + .into_iter() + .map(|tag| { + let mut hasher = DefaultHasher::new(); + tag.hash(&mut hasher); + let hex = format!("#{:06x}", hasher.finish() % (1 << 24)); + Tag { + name: tag, + fg_color: "white".to_string(), + bg_color: hex, + } + }) + .collect()) + } +} + +pub type GraphqlSchema = Schema; diff --git a/server/src/lib.rs b/server/src/lib.rs index 147b8f1..c2c4236 100644 --- a/server/src/lib.rs +++ b/server/src/lib.rs @@ -1,2 +1,3 @@ pub mod error; +pub mod graphql; pub mod nm; diff --git a/server/src/nm.rs b/server/src/nm.rs index 11098b8..e91172c 100644 --- a/server/src/nm.rs +++ b/server/src/nm.rs @@ -7,7 +7,7 @@ pub fn threadset_to_messages( thread_set: notmuch::ThreadSet, ) -> Result, error::ServerError> { for t in thread_set.0 { - for tn in t.0 {} + for _tn in t.0 {} } Ok(Vec::new()) }