And graphql search with pagination.

This commit is contained in:
Bill Thiede 2023-11-20 20:56:16 -08:00
parent f52a76dba3
commit a7b172099b
4 changed files with 147 additions and 45 deletions

View File

@ -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<Vec<Tag>> {
let nm = ctx.data_unchecked::<Notmuch>();
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<QueryRoot, EmptyMutation, EmptySubscription>;
#[rocket::main]
async fn main() -> Result<(), Box<dyn Error>> {
glog::new()

138
server/src/graphql.rs Normal file
View File

@ -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<String>,
}
#[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<String>,
before: Option<String>,
first: Option<i32>,
last: Option<i32>,
query: String,
) -> Result<Connection<usize, ThreadSummary>, Error> {
let nm = ctx.data_unchecked::<Notmuch>();
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<ThreadSummary> = 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<Vec<Tag>> {
let nm = ctx.data_unchecked::<Notmuch>();
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<QueryRoot, EmptyMutation, EmptySubscription>;

View File

@ -1,2 +1,3 @@
pub mod error;
pub mod graphql;
pub mod nm;

View File

@ -7,7 +7,7 @@ pub fn threadset_to_messages(
thread_set: notmuch::ThreadSet,
) -> Result<Vec<Message>, error::ServerError> {
for t in thread_set.0 {
for tn in t.0 {}
for _tn in t.0 {}
}
Ok(Vec::new())
}