diff --git a/Cargo.lock b/Cargo.lock index 91ced70..bb45697 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -73,6 +73,20 @@ dependencies = [ "stable_deref_trait", ] +[[package]] +name = "async_tiny" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "972ac50620540846a056f3e94384a25a0ff465c361c8e6a061d79be45a504ee6" +dependencies = [ + "bytes", + "http", + "http-body-util", + "hyper", + "hyper-util", + "tokio", +] + [[package]] name = "atomic-waker" version = "1.1.2" @@ -83,6 +97,7 @@ checksum = "1505bd5d3d116872e7271a6d4e16d81d0c8570876c8de68093a09ac269d8aac0" name = "atspool" version = "0.1.0" dependencies = [ + "async_tiny", "chrono", "escpos", "futures-util", @@ -710,6 +725,12 @@ version = "1.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6dbf3de79e51f3d586ab4cb9d5c3e2c14aa28ed23d180cf89b4df0454a69cc87" +[[package]] +name = "httpdate" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df3b46402a9d5adb4c86a0cf463f42e19994e3ee891101b1841f30a545cb49a9" + [[package]] name = "hyper" version = "1.9.0" @@ -723,6 +744,7 @@ dependencies = [ "http", "http-body", "httparse", + "httpdate", "itoa", "pin-project-lite", "smallvec", diff --git a/Cargo.toml b/Cargo.toml index 4ad7dc3..3989db0 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -16,6 +16,7 @@ serde_json = { version = "1.0.149", features = ["raw_value"] } chrono = "0.4" reqwest = { version = "0.13", default-features = false, features = ["json", "rustls"] } linemd = { version = "0.5.0", default-features = false } +async_tiny = "0.2" [profile.dev] opt-level = 1 diff --git a/src/main.rs b/src/main.rs index 45b3143..5e39198 100644 --- a/src/main.rs +++ b/src/main.rs @@ -85,6 +85,8 @@ async fn main() -> Result<()> { Default::default(), )?; + let password = std::env::var("SPOOL_PASSWORD").ok(); + let allowed_dids: Vec = tokio::fs::read_to_string("allowed.txt") .await .into_diagnostic()? @@ -106,11 +108,13 @@ async fn main() -> Result<()> { let (task_tx, task_rx) = mpsc::channel(16); let job_handler = tokio::task::spawn_blocking(move || handle_jobs(printer, task_rx)); - let job_stream = tokio::spawn(stream_jobs(jetstream_query, task_tx, resolver)); + let job_stream = tokio::spawn(stream_jobs(jetstream_query, task_tx.clone(), resolver)); + let api = tokio::spawn(serve_api(password, task_tx)); tokio::select! { r = job_stream => r.into_diagnostic().flatten(), r = job_handler => r.into_diagnostic().flatten(), + r = api => r.into_diagnostic().flatten(), } } @@ -215,3 +219,53 @@ fn handle_jobs(mut printer: Printer, mut task_rx: TaskRx) -> Result<()> { Ok(()) } + +async fn serve_api(password: Option, task_tx: TaskTx) -> Result<()> { + use async_tiny::{Response, Server}; + + let mut server = Server::http("0.0.0.0:9889", true).await.into_diagnostic()?; + + while let Some(request) = server.next().await { + let url = request.url().to_string(); + let (path, query_str) = url.split_once('?').unwrap_or((&url, "")); + + let params: std::collections::HashMap<&str, &str> = query_str + .split('&') + .filter_map(|p| p.split_once('=')) + .collect(); + + if let Some(ref pw) = password { + if params.get("token").copied() != Some(pw.as_str()) { + let _ = request.respond(Response::from_status_and_string(400, "400 Bad Request")); + continue; + } + } + + let Some(&name) = params.get("name") else { + let _ = request.respond(Response::from_status_and_string(400, "400 Bad Request")); + continue; + }; + let name = name.to_string(); + + let response = match path { + "/print" => { + let text = String::from_utf8_lossy(request.body()).into_owned(); + let _ = task_tx + .send(Task { + date: Utc::now(), + identifier: name, + job: Job { + content: JobContent::Text(TextContent { text }), + }, + }) + .await; + Response::from_string("ok").with_content_type("application/json") + } + _ => Response::from_status_and_string(404, "404 Not Found"), + }; + + let _ = request.respond(response); + } + + Ok(()) +}