And graphql search with pagination.
This commit is contained in:
parent
f52a76dba3
commit
a7b172099b
@ -1,17 +1,8 @@
|
|||||||
#[macro_use]
|
#[macro_use]
|
||||||
extern crate rocket;
|
extern crate rocket;
|
||||||
|
use std::{error::Error, io::Cursor, str::FromStr};
|
||||||
|
|
||||||
use std::{
|
use async_graphql::{http::GraphiQLSource, EmptyMutation, EmptySubscription, Schema};
|
||||||
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_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};
|
||||||
@ -23,7 +14,11 @@ use rocket::{
|
|||||||
Response, State,
|
Response, State,
|
||||||
};
|
};
|
||||||
use rocket_cors::{AllowedHeaders, AllowedOrigins};
|
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;
|
use shared::Message;
|
||||||
|
|
||||||
#[get("/refresh")]
|
#[get("/refresh")]
|
||||||
@ -148,38 +143,6 @@ async fn graphql_request(
|
|||||||
request.execute(schema.inner()).await
|
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]
|
#[rocket::main]
|
||||||
async fn main() -> Result<(), Box<dyn Error>> {
|
async fn main() -> Result<(), Box<dyn Error>> {
|
||||||
glog::new()
|
glog::new()
|
||||||
|
|||||||
138
server/src/graphql.rs
Normal file
138
server/src/graphql.rs
Normal 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>;
|
||||||
@ -1,2 +1,3 @@
|
|||||||
pub mod error;
|
pub mod error;
|
||||||
|
pub mod graphql;
|
||||||
pub mod nm;
|
pub mod nm;
|
||||||
|
|||||||
@ -7,7 +7,7 @@ pub fn threadset_to_messages(
|
|||||||
thread_set: notmuch::ThreadSet,
|
thread_set: notmuch::ThreadSet,
|
||||||
) -> Result<Vec<Message>, error::ServerError> {
|
) -> Result<Vec<Message>, error::ServerError> {
|
||||||
for t in thread_set.0 {
|
for t in thread_set.0 {
|
||||||
for tn in t.0 {}
|
for _tn in t.0 {}
|
||||||
}
|
}
|
||||||
Ok(Vec::new())
|
Ok(Vec::new())
|
||||||
}
|
}
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user