Compare commits

..

8 Commits

Author SHA1 Message Date
cffc228b3a chore: Release
Some checks failed
Continuous integration / Check (push) Successful in 36s
Continuous integration / Test Suite (push) Successful in 41s
Continuous integration / Trunk (push) Failing after 35s
Continuous integration / Rustfmt (push) Successful in 32s
Continuous integration / build (push) Successful in 49s
Continuous integration / Disallow unused dependencies (push) Successful in 3m24s
2025-04-15 19:25:41 -07:00
318c366d82 server: disable per-email counts in tags, it's breaking production 2025-04-15 19:25:22 -07:00
90d7f79ca0 server: slow refresh interval as procmail should be on demand 2025-04-15 19:24:59 -07:00
3f87038776 web: proxy /notifcation 2025-04-15 18:39:36 -07:00
92b880f03b chore: Release
Some checks failed
Continuous integration / Check (push) Successful in 36s
Continuous integration / Test Suite (push) Successful in 41s
Continuous integration / Trunk (push) Failing after 35s
Continuous integration / Rustfmt (push) Successful in 31s
Continuous integration / build (push) Successful in 48s
Continuous integration / Disallow unused dependencies (push) Successful in 54s
2025-04-15 17:46:18 -07:00
94f1e84857 server: add notification handlers for refreshing mail and news 2025-04-15 17:45:47 -07:00
221b4f10df chore: Release
Some checks failed
Continuous integration / Check (push) Successful in 36s
Continuous integration / Test Suite (push) Successful in 41s
Continuous integration / Trunk (push) Failing after 35s
Continuous integration / Rustfmt (push) Successful in 31s
Continuous integration / build (push) Successful in 47s
Continuous integration / Disallow unused dependencies (push) Successful in 54s
2025-04-15 16:36:40 -07:00
225615f4ea server: move config to cmdline args 2025-04-15 16:36:19 -07:00
9 changed files with 197 additions and 64 deletions

119
Cargo.lock generated
View File

@@ -81,6 +81,56 @@ dependencies = [
"libc",
]
[[package]]
name = "anstream"
version = "0.6.18"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8acc5369981196006228e28809f761875c0327210a891e941f4c683b3a99529b"
dependencies = [
"anstyle",
"anstyle-parse",
"anstyle-query",
"anstyle-wincon",
"colorchoice",
"is_terminal_polyfill",
"utf8parse",
]
[[package]]
name = "anstyle"
version = "1.0.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "55cc3b69f167a1ef2e161439aa98aed94e6028e5f9a59be9a6ffb47aef1651f9"
[[package]]
name = "anstyle-parse"
version = "0.2.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3b2d16507662817a6a20a9ea92df6652ee4f94f914589377d69f3b21bc5798a9"
dependencies = [
"utf8parse",
]
[[package]]
name = "anstyle-query"
version = "1.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "79947af37f4177cfead1110013d678905c37501914fba0efea834c3fe9a8d60c"
dependencies = [
"windows-sys 0.59.0",
]
[[package]]
name = "anstyle-wincon"
version = "3.0.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ca3534e77181a9cc07539ad51f2141fe32f6c3ffd4df76db8ad92346b003ae4e"
dependencies = [
"anstyle",
"once_cell",
"windows-sys 0.59.0",
]
[[package]]
name = "anyhow"
version = "1.0.98"
@@ -811,6 +861,46 @@ dependencies = [
"windows-link",
]
[[package]]
name = "clap"
version = "4.5.36"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2df961d8c8a0d08aa9945718ccf584145eee3f3aa06cddbeac12933781102e04"
dependencies = [
"clap_builder",
"clap_derive",
]
[[package]]
name = "clap_builder"
version = "4.5.36"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "132dbda40fb6753878316a489d5a1242a8ef2f0d9e47ba01c951ea8aa7d013a5"
dependencies = [
"anstream",
"anstyle",
"clap_lex",
"strsim 0.11.1",
]
[[package]]
name = "clap_derive"
version = "4.5.32"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "09176aae279615badda0765c0c0b3f6ed53f4709118af73cf4655d85d1530cd7"
dependencies = [
"heck 0.5.0",
"proc-macro2",
"quote",
"syn 2.0.100",
]
[[package]]
name = "clap_lex"
version = "0.7.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f46ad14479a25103f283c0f10005961cf086d8dc42205bb44c46ac563475dca6"
[[package]]
name = "cloudabi"
version = "0.0.3"
@@ -820,6 +910,12 @@ dependencies = [
"bitflags 1.3.2",
]
[[package]]
name = "colorchoice"
version = "1.0.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5b63caa9aa9397e2d9480a9b13673856c78d8ac123288526c37d7839f2a86990"
[[package]]
name = "combine"
version = "4.6.7"
@@ -2831,6 +2927,12 @@ version = "2.11.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "469fb0b9cefa57e3ef31275ee7cacb78f2fdca44e4765491884a2b119d4eb130"
[[package]]
name = "is_terminal_polyfill"
version = "1.70.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7943c866cc5cd64cbc25b2e01621d07fa8eb2a1a23160ee81ce38704e97b8ecf"
[[package]]
name = "itertools"
version = "0.14.0"
@@ -2893,7 +2995,7 @@ dependencies = [
[[package]]
name = "letterbox-notmuch"
version = "0.15.0"
version = "0.15.3"
dependencies = [
"itertools",
"log",
@@ -2908,14 +3010,14 @@ dependencies = [
[[package]]
name = "letterbox-procmail2notmuch"
version = "0.15.0"
version = "0.15.3"
dependencies = [
"anyhow",
]
[[package]]
name = "letterbox-server"
version = "0.15.0"
version = "0.15.3"
dependencies = [
"ammonia",
"anyhow",
@@ -2928,6 +3030,7 @@ dependencies = [
"build-info-build",
"cacher",
"chrono",
"clap",
"css-inline",
"futures 0.3.31",
"headers",
@@ -2958,7 +3061,7 @@ dependencies = [
[[package]]
name = "letterbox-shared"
version = "0.15.0"
version = "0.15.3"
dependencies = [
"build-info",
"letterbox-notmuch",
@@ -2968,7 +3071,7 @@ dependencies = [
[[package]]
name = "letterbox-web"
version = "0.15.0"
version = "0.15.3"
dependencies = [
"build-info",
"build-info-build",
@@ -6816,6 +6919,12 @@ version = "1.0.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b6c140620e7ffbb22c2dee59cafe6084a59b5ffc27a8859a5f0d494b5d52b6be"
[[package]]
name = "utf8parse"
version = "0.2.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821"
[[package]]
name = "uuid"
version = "1.16.0"

View File

@@ -8,7 +8,7 @@ authors = ["Bill Thiede <git@xinu.tv>"]
edition = "2021"
license = "UNLICENSED"
publish = ["xinu"]
version = "0.15.0"
version = "0.15.3"
repository = "https://git.z.xinu.tv/wathiede/letterbox"
[profile.dev]

2
dev.sh
View File

@@ -3,5 +3,5 @@ tmux new-session -d -s letterbox-dev
tmux rename-window web
tmux send-keys "cd web; trunk serve -w ../.git -w ../shared -w ../notmuch -w ./" C-m
tmux new-window -n server
tmux send-keys "cd server; cargo watch -c -x run -w ../.git -w ../shared -w ../notmuch -w ./" C-m
tmux send-keys "cd server; cargo watch -c -w ../.git -w ../shared -w ../notmuch -w ./ -x 'run postgres://newsreader@nixos-07.h.xinu.tv/newsreader ../target/database/newsreader /tmp/letterbox/slurp'" C-m
tmux attach -d -t letterbox-dev

View File

@@ -22,12 +22,13 @@ axum-macros = "0.5.0"
build-info = "0.0.40"
cacher = { version = "0.2.0", registry = "xinu" }
chrono = "0.4.39"
clap = { version = "4.5.36", features = ["derive"] }
css-inline = "0.14.0"
futures = "0.3.31"
headers = "0.4.0"
html-escape = "0.2.13"
letterbox-notmuch = { version = "0.15.0", path = "../notmuch", registry = "xinu" }
letterbox-shared = { version = "0.15.0", path = "../shared", registry = "xinu" }
letterbox-notmuch = { version = "0.15.3", path = "../notmuch", registry = "xinu" }
letterbox-shared = { version = "0.15.3", path = "../shared", registry = "xinu" }
linkify = "0.10.0"
log = "0.4.17"
lol_html = "2.0.0"

View File

@@ -8,23 +8,24 @@ use async_graphql_axum::{GraphQL, GraphQLSubscription};
//allows to extract the IP of connecting user
use axum::extract::connect_info::ConnectInfo;
use axum::{
extract::{self, ws::WebSocketUpgrade, State},
extract::{self, ws::WebSocketUpgrade, Query, State},
http::{header, StatusCode},
response::{self, IntoResponse, Response},
routing::{any, get},
routing::{any, get, post},
Router,
};
use cacher::FilesystemCacher;
use clap::Parser;
use letterbox_notmuch::Notmuch;
#[cfg(feature = "tantivy")]
use letterbox_server::tantivy::TantivyConnection;
use letterbox_server::{
config::Config,
graphql::{compute_catchup_ids, Attachment, MutationRoot, QueryRoot, SubscriptionRoot},
nm::{attachment_bytes, cid_attachment_bytes},
ws::ConnectionTracker,
};
use letterbox_shared::WebsocketMessage;
use serde::Deserialize;
use sqlx::postgres::PgPool;
use tokio::{net::TcpListener, sync::Mutex};
use tower_http::trace::{DefaultMakeSpan, TraceLayer};
@@ -159,18 +160,30 @@ async fn start_ws(
) -> impl IntoResponse {
ws.on_upgrade(async move |socket| connection_tracker.lock().await.add_peer(socket, addr).await)
}
#[axum_macros::debug_handler]
async fn test_handler(
#[derive(Debug, Deserialize)]
struct NotificationParams {
delay_ms: Option<u64>,
}
async fn send_refresh_websocket_handler(
State(AppState {
connection_tracker, ..
}): State<AppState>,
params: Query<NotificationParams>,
) -> impl IntoResponse {
info!("send_refresh_websocket_handler params {params:?}");
if let Some(delay_ms) = params.delay_ms {
let delay = Duration::from_millis(delay_ms);
info!("sleeping {delay:?}");
tokio::time::sleep(delay).await;
}
connection_tracker
.lock()
.await
.send_message_all(WebsocketMessage::RefreshMessages)
.await;
"test triggered"
"refresh triggered"
}
async fn watch_new(
@@ -201,29 +214,33 @@ struct AppState {
connection_tracker: Arc<Mutex<ConnectionTracker>>,
}
#[derive(Parser)]
#[command(version, about, long_about = None)]
struct Cli {
#[arg(short, long, default_value = "0.0.0.0:9345")]
addr: SocketAddr,
newsreader_database_url: String,
newsreader_tantivy_db_path: String,
slurp_cache_path: String,
}
#[tokio::main]
async fn main() -> Result<(), Box<dyn Error>> {
let cli = Cli::parse();
let _guard = xtracing::init(env!("CARGO_BIN_NAME"))?;
build_info::build_info!(fn bi);
info!("Build Info: {}", letterbox_shared::build_version(bi));
// TODO: move these to config
let port = 9345;
let config = Config {
newsreader_database_url: "postgres://newsreader@nixos-07.h.xinu.tv/newsreader".to_string(),
newsreader_tantivy_db_path: "../target/database/newsreader".to_string(),
slurp_cache_path: "/tmp/letterbox/slurp".to_string(),
};
if !std::fs::exists(&config.slurp_cache_path)? {
info!("Creating slurp cache @ '{}'", &config.slurp_cache_path);
std::fs::create_dir_all(&config.slurp_cache_path)?;
if !std::fs::exists(&cli.slurp_cache_path)? {
info!("Creating slurp cache @ '{}'", &cli.slurp_cache_path);
std::fs::create_dir_all(&cli.slurp_cache_path)?;
}
let pool = PgPool::connect(&config.newsreader_database_url).await?;
let pool = PgPool::connect(&cli.newsreader_database_url).await?;
let nm = Notmuch::default();
sqlx::migrate!("./migrations").run(&pool).await?;
#[cfg(feature = "tantivy")]
let tantivy_conn = TantivyConnection::new(&config.newsreader_tantivy_db_path)?;
let tantivy_conn = TantivyConnection::new(&cli.newsreader_tantivy_db_path)?;
let cacher = FilesystemCacher::new(&config.slurp_cache_path)?;
let cacher = FilesystemCacher::new(&cli.slurp_cache_path)?;
let schema = Schema::build(QueryRoot, MutationRoot, SubscriptionRoot)
.data(nm.clone())
.data(cacher)
@@ -233,26 +250,29 @@ async fn main() -> Result<(), Box<dyn Error>> {
let connection_tracker = Arc::new(Mutex::new(ConnectionTracker::default()));
let ct = Arc::clone(&connection_tracker);
let poll_time = Duration::from_secs(10);
let poll_time = Duration::from_secs(60);
let _h = tokio::spawn(watch_new(nm.clone(), pool, ct, poll_time));
let app = Router::new()
.route("/test", get(test_handler))
let api_routes = Router::new()
.route(
"/api/download/attachment/{id}/{idx}/{*rest}",
"/download/attachment/{id}/{idx}/{*rest}",
get(download_attachment),
)
.route("/view/attachment/{id}/{idx}/{*rest}", get(view_attachment))
.route("/cid/{id}/{cid}", get(view_cid))
.route("/ws", any(start_ws))
.route_service("/graphql/ws", GraphQLSubscription::new(schema.clone()))
.route(
"/api/view/attachment/{id}/{idx}/{*rest}",
get(view_attachment),
)
.route("/api/cid/{id}/{cid}", get(view_cid))
.route("/api/ws", any(start_ws))
.route_service("/api/graphql/ws", GraphQLSubscription::new(schema.clone()))
.route(
"/api/graphql/",
"/graphql/",
get(graphiql).post_service(GraphQL::new(schema.clone())),
)
);
let notification_routes = Router::new()
.route("/mail", post(send_refresh_websocket_handler))
.route("/news", post(send_refresh_websocket_handler));
let app = Router::new()
.nest("/api", api_routes)
.nest("/notification", notification_routes)
.with_state(AppState {
nm,
connection_tracker,
@@ -262,9 +282,7 @@ async fn main() -> Result<(), Box<dyn Error>> {
.make_span_with(DefaultMakeSpan::default().include_headers(true)),
);
let listener = TcpListener::bind(SocketAddr::from(([0, 0, 0, 0], port)))
.await
.unwrap();
let listener = TcpListener::bind(cli.addr).await.unwrap();
tracing::info!("listening on {}", listener.local_addr().unwrap());
axum::serve(
listener,

View File

@@ -135,23 +135,25 @@ pub fn tags(nm: &Notmuch, needs_unread: bool) -> Result<Vec<Tag>, ServerError> {
unread,
}
})
.chain(
nm.unread_recipients()?
.into_iter()
.filter_map(|(name, unread)| {
let Some(idx) = name.find('@') else {
return None;
};
let name = format!("{}/{}", &name[idx..], &name[..idx]);
let bg_color = compute_color(&name);
Some(Tag {
name,
fg_color: "white".to_string(),
bg_color,
unread,
})
}),
)
/*
.chain(
nm.unread_recipients()?
.into_iter()
.filter_map(|(name, unread)| {
let Some(idx) = name.find('@') else {
return None;
};
let name = format!("{}/{}", &name[idx..], &name[..idx]);
let bg_color = compute_color(&name);
Some(Tag {
name,
fg_color: "white".to_string(),
bg_color,
unread,
})
}),
)
*/
.collect();
Ok(tags)
}

View File

@@ -12,6 +12,6 @@ version.workspace = true
[dependencies]
build-info = "0.0.40"
letterbox-notmuch = { version = "0.15.0", path = "../notmuch", registry = "xinu" }
letterbox-notmuch = { version = "0.15.3", path = "../notmuch", registry = "xinu" }
serde = { version = "1.0.147", features = ["derive"] }
strum_macros = "0.27.1"

View File

@@ -33,8 +33,8 @@ wasm-bindgen = "=0.2.100"
uuid = { version = "1.13.1", features = [
"js",
] } # direct dep to set js feature, prevents Rng issues
letterbox-shared = { version = "0.15.0", path = "../shared", registry = "xinu" }
letterbox-notmuch = { version = "0.15.0", path = "../notmuch", registry = "xinu" }
letterbox-shared = { version = "0.15.3", path = "../shared", registry = "xinu" }
letterbox-notmuch = { version = "0.15.3", path = "../notmuch", registry = "xinu" }
seed_hooks = { version = "0.4.0", registry = "xinu" }
strum_macros = "0.27.1"
gloo-console = "0.3.0"

View File

@@ -13,6 +13,9 @@ backend = "ws://localhost:9345/api/ws"
[[proxy]]
backend = "http://localhost:9345/api/"
[[proxy]]
backend = "http://localhost:9345/notification/"
[[hooks]]
stage = "pre_build"
command = "printf"