Implement newsreader counting

This commit is contained in:
Bill Thiede 2024-07-21 15:13:09 -07:00
parent abaaddae3a
commit 9746c9912b
5 changed files with 67 additions and 18 deletions

10
server/sql/count.sql Normal file
View File

@ -0,0 +1,10 @@
SELECT
COUNT(*) count
FROM
post
WHERE
site = $1
AND (
NOT $2
OR NOT is_read
)

View File

@ -1,4 +1,4 @@
use std::{str::Utf8Error, string::FromUtf8Error};
use std::{convert::Infallible, str::Utf8Error, string::FromUtf8Error};
use mailparse::MailParseError;
use thiserror::Error;
@ -27,4 +27,6 @@ pub enum ServerError {
FromUtf8Error(#[from] FromUtf8Error),
#[error("error")]
StringError(String),
#[error("impossible")]
InfaillibleError(#[from] Infallible),
}

View File

@ -209,7 +209,14 @@ pub struct QueryRoot;
impl QueryRoot {
async fn count<'ctx>(&self, ctx: &Context<'ctx>, query: String) -> Result<usize, Error> {
let nm = ctx.data_unchecked::<Notmuch>();
Ok(nm.count(&query)?)
let pool = ctx.data_unchecked::<PgPool>();
// TODO: make this search both copra and merge results
if newsreader::is_newsreader_search(&query) {
Ok(newsreader::count(pool, &query).await?)
} else {
Ok(nm::count(nm, &query).await?)
}
}
async fn search<'ctx>(

View File

@ -1,4 +1,8 @@
use std::hash::{DefaultHasher, Hash, Hasher};
use std::{
convert::Infallible,
hash::{DefaultHasher, Hash, Hasher},
str::FromStr,
};
use async_graphql::connection::{self, Connection, Edge};
use log::info;
@ -20,6 +24,15 @@ pub fn is_newsreader_thread(query: &str) -> bool {
query.starts_with(THREAD_PREFIX)
}
pub async fn count(pool: &PgPool, query: &str) -> Result<usize, ServerError> {
let query: Query = query.parse()?;
let site = query.site.expect("search has no site");
let row = sqlx::query_file!("sql/count.sql", site, query.unread_only)
.fetch_one(pool)
.await?;
Ok(row.count.unwrap_or(0).try_into().unwrap_or(0))
}
pub async fn search(
pool: &PgPool,
after: Option<String>,
@ -28,27 +41,16 @@ pub async fn search(
last: Option<i32>,
query: String,
) -> Result<Connection<usize, ThreadSummary>, async_graphql::Error> {
let mut unread_only = false;
let mut site = None;
let site_prefix = format!("tag:{TAG_PREFIX}");
for word in query.split_whitespace() {
if word == "is:unread" {
unread_only = true
};
if word.starts_with(&site_prefix) {
site = Some(word[site_prefix.len()..].to_string())
}
}
let site = site.expect("search has no site");
info!("news search unread_only {unread_only} site {site:?}");
let query: Query = query.parse()?;
info!("news search query {query:?}");
let site = query.site.expect("search has no site");
connection::query(
after,
before,
first,
last,
|after, before, first, last| async move {
// TODO: handle `unread_only`
let rows = sqlx::query_file!("sql/threads.sql", site)
let rows = sqlx::query_file!("sql/threads.sql", site, query.unread_only)
.fetch_all(pool)
.await?;
@ -160,3 +162,27 @@ pub async fn thread(pool: &PgPool, thread_id: String) -> Result<Thread, ServerEr
}],
})
}
#[derive(Debug)]
struct Query {
unread_only: bool,
site: Option<String>,
}
impl FromStr for Query {
type Err = Infallible;
fn from_str(s: &str) -> Result<Self, Self::Err> {
let mut unread_only = false;
let mut site = None;
let site_prefix = format!("tag:{TAG_PREFIX}");
for word in s.split_whitespace() {
if word == "is:unread" {
unread_only = true
};
if word.starts_with(&site_prefix) {
site = Some(word[site_prefix.len()..].to_string())
}
}
Ok(Query { unread_only, site })
}
}

View File

@ -41,6 +41,10 @@ pub fn threadset_to_messages(thread_set: notmuch::ThreadSet) -> Result<Vec<Messa
Ok(Vec::new())
}
pub async fn count(nm: &Notmuch, query: &str) -> Result<usize, ServerError> {
Ok(nm.count(query)?)
}
pub async fn search(
nm: &Notmuch,
after: Option<String>,