diff --git a/Cargo.lock b/Cargo.lock index 1c11b05..561d992 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -667,6 +667,41 @@ dependencies = [ "typenum", ] +[[package]] +name = "darling" +version = "0.20.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "83b2eb4d90d12bdda5ed17de686c2acb4c57914f8f921b8da7e112b5a36f3fe1" +dependencies = [ + "darling_core", + "darling_macro", +] + +[[package]] +name = "darling_core" +version = "0.20.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "622687fe0bac72a04e5599029151f5796111b90f1baaa9b544d807a5e31cd120" +dependencies = [ + "fnv", + "ident_case", + "proc-macro2", + "quote", + "strsim", + "syn 2.0.60", +] + +[[package]] +name = "darling_macro" +version = "0.20.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "733cabb43482b1a1b53eee8583c2b9e8684d592215ea83efd305dd31bc2f0178" +dependencies = [ + "darling_core", + "quote", + "syn 2.0.60", +] + [[package]] name = "der" version = "0.7.9" @@ -685,6 +720,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b42b6fa04a440b495c8b04d0e71b707c585f83cb9cb28cf8cd0d976c315e31b4" dependencies = [ "powerfmt", + "serde", ] [[package]] @@ -906,6 +942,16 @@ version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8fcfdc7a0362c9f4444381a9e697c79d435fe65b52a37466fc2c1184cee9edc6" +[[package]] +name = "flate2" +version = "1.0.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5f54427cfd1c7829e2a139fcefea601bf088ebca651d2bf53ebc600eac295dae" +dependencies = [ + "crc32fast", + "miniz_oxide", +] + [[package]] name = "flume" version = "0.11.0" @@ -1197,7 +1243,7 @@ dependencies = [ "futures-sink", "futures-util", "http 0.2.12", - "indexmap", + "indexmap 2.2.6", "slab", "tokio", "tokio-util", @@ -1218,6 +1264,12 @@ dependencies = [ "rand_distr", ] +[[package]] +name = "hashbrown" +version = "0.12.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" + [[package]] name = "hashbrown" version = "0.13.2" @@ -1480,6 +1532,12 @@ dependencies = [ "cc", ] +[[package]] +name = "ident_case" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39" + [[package]] name = "idna" version = "0.5.0" @@ -1490,6 +1548,17 @@ dependencies = [ "unicode-normalization", ] +[[package]] +name = "indexmap" +version = "1.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bd070e393353796e801d209ad339e89596eb4c8d430d18ede6a1cced8fafbd99" +dependencies = [ + "autocfg", + "hashbrown 0.12.3", + "serde", +] + [[package]] name = "indexmap" version = "2.2.6" @@ -1609,7 +1678,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0c2a198fb6b0eada2a8df47933734e6d35d350665a33a3593d7164fa52c75c19" dependencies = [ "cfg-if", - "windows-targets 0.48.5", + "windows-targets 0.52.5", ] [[package]] @@ -2433,7 +2502,7 @@ dependencies = [ "either", "figment", "futures", - "indexmap", + "indexmap 2.2.6", "log", "memchr", "multer", @@ -2465,7 +2534,7 @@ checksum = "a2238066abf75f21be6cd7dc1a09d5414a671f4246e384e49fe3f8a4936bd04c" dependencies = [ "devise", "glob", - "indexmap", + "indexmap 2.2.6", "proc-macro2", "quote", "rocket_http", @@ -2485,7 +2554,7 @@ dependencies = [ "futures", "http 0.2.12", "hyper 0.14.28", - "indexmap", + "indexmap 2.2.6", "log", "memchr", "pear", @@ -2726,7 +2795,7 @@ version = "1.0.116" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3e17db7126d17feb94eb3fad46bf1a96b034e8aacbc2e775fe81505f8b0b2813" dependencies = [ - "indexmap", + "indexmap 2.2.6", "itoa", "ryu", "serde", @@ -2762,6 +2831,36 @@ dependencies = [ "serde", ] +[[package]] +name = "serde_with" +version = "3.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ad483d2ab0149d5a5ebcd9972a3852711e0153d863bf5a5d0391d28883c4a20" +dependencies = [ + "base64 0.22.0", + "chrono", + "hex", + "indexmap 1.9.3", + "indexmap 2.2.6", + "serde", + "serde_derive", + "serde_json", + "serde_with_macros", + "time", +] + +[[package]] +name = "serde_with_macros" +version = "3.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "65569b702f41443e8bc8bbb1c5779bd0450bbe723b56198980e80ec45780bce2" +dependencies = [ + "darling", + "proc-macro2", + "quote", + "syn 2.0.60", +] + [[package]] name = "sha1" version = "0.10.6" @@ -2914,7 +3013,7 @@ dependencies = [ "futures-util", "hashlink", "hex", - "indexmap", + "indexmap 2.2.6", "log", "memchr", "once_cell", @@ -3112,6 +3211,12 @@ dependencies = [ "unicode-normalization", ] +[[package]] +name = "strsim" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" + [[package]] name = "subtle" version = "2.5.0" @@ -3381,7 +3486,7 @@ version = "0.22.12" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d3328d4f68a705b2a4498da1d580585d39a6510f98318a2cec3018a7ec61ddef" dependencies = [ - "indexmap", + "indexmap 2.2.6", "serde", "serde_spanned", "toml_datetime", @@ -3570,6 +3675,25 @@ version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1" +[[package]] +name = "ureq" +version = "2.9.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d11a831e3c0b56e438a28308e7c810799e3c118417f342d30ecec080105395cd" +dependencies = [ + "base64 0.22.0", + "flate2", + "log", + "once_cell", + "rustls", + "rustls-pki-types", + "rustls-webpki", + "serde", + "serde_json", + "url", + "webpki-roots", +] + [[package]] name = "url" version = "2.5.0" @@ -3635,9 +3759,12 @@ dependencies = [ "pretty_env_logger", "rocket", "serde", + "serde_with", "sha2", "sqlx", "tokio", + "ureq", + "url", "uuid", ] @@ -4073,6 +4200,6 @@ dependencies = [ "crc32fast", "crossbeam-utils", "displaydoc", - "indexmap", + "indexmap 2.2.6", "thiserror", ] diff --git a/Cargo.toml b/Cargo.toml index f946aba..7d99a57 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -26,3 +26,6 @@ blurhash = "0.2.1" candle-core = { git = "https://github.com/huggingface/candle.git", version = "0.5.1" } candle-nn = { git = "https://github.com/huggingface/candle.git", version = "0.5.1" } candle-transformers = { git = "https://github.com/huggingface/candle.git", version = "0.5.1" } +ureq = { version = "2.9.7", features = ["json"] } +url = "2.5.0" +serde_with = { version = "3.8.1", features = ["hex"] } diff --git a/config.toml b/config.toml index 40cf28d..d965c9e 100644 --- a/config.toml +++ b/config.toml @@ -16,5 +16,8 @@ public_url = "http://localhost:8000" # Whitelisted pubkeys, leave out to disable # whitelist = ["63fe6318dc58583cfe16810f86dd09e18bfd76aabc24a0081ce2856f330504ed"] -# Path for ViT image model (https://huggingface.co/google/vit-base-patch16-224) -# vit_model_path = "model.safetennsors" \ No newline at end of file +# Path for ViT(224) image model (https://huggingface.co/google/vit-base-patch16-224) +# vit_model_path = "model.safetennsors" + +# Webhook api endpoint +webhook_url = "https://api.snort.social/api/v1/media/webhook" \ No newline at end of file diff --git a/src/filesystem.rs b/src/filesystem.rs index cc69e75..112f2bf 100644 --- a/src/filesystem.rs +++ b/src/filesystem.rs @@ -2,11 +2,12 @@ use std::env::temp_dir; use std::fs; use std::io::SeekFrom; use std::path::{Path, PathBuf}; -use std::sync::{Arc, Mutex}; use std::time::SystemTime; use anyhow::Error; use log::info; +use serde::Serialize; +use serde_with::serde_as; use sha2::{Digest, Sha256}; use tokio::fs::File; use tokio::io::{AsyncRead, AsyncReadExt, AsyncSeekExt}; @@ -15,9 +16,11 @@ use crate::processing::{compress_file, FileProcessorResult}; use crate::processing::labeling::label_frame; use crate::settings::Settings; -#[derive(Clone, Default)] +#[serde_as] +#[derive(Clone, Default, Serialize)] pub struct FileSystemResult { pub path: PathBuf, + #[serde_as(as = "serde_with::hex::Hex")] pub sha256: Vec, pub size: u64, pub mime_type: String, @@ -51,8 +54,11 @@ impl FileStore { let result = self.store_compress_file(stream, mime_type, compress).await?; let dst_path = self.map_path(&result.sha256); if dst_path.exists() { - fs::remove_file(&result.path)?; - return Err(Error::msg("File already exists")); + fs::remove_file(result.path)?; + return Ok(FileSystemResult { + path: dst_path, + ..result + }); } fs::create_dir_all(dst_path.parent().unwrap())?; if let Err(e) = fs::copy(&result.path, &dst_path) { diff --git a/src/main.rs b/src/main.rs index 5f11dda..67db18b 100644 --- a/src/main.rs +++ b/src/main.rs @@ -13,6 +13,7 @@ use crate::db::Database; use crate::filesystem::FileStore; use crate::routes::{get_blob, head_blob, root}; use crate::settings::Settings; +use crate::webhook::Webhook; mod auth; mod blob; @@ -22,6 +23,7 @@ mod filesystem; mod routes; mod settings; mod processing; +mod webhook; #[rocket::main] async fn main() -> Result<(), Error> { @@ -58,6 +60,7 @@ async fn main() -> Result<(), Error> { .manage(FileStore::new(settings.clone())) .manage(settings.clone()) .manage(db.clone()) + .manage(settings.webhook_url.as_ref().map(|w| Webhook::new(w.clone()))) .attach(CORS) .attach(Shield::new()) // disable .mount("/", routes::blossom_routes()) diff --git a/src/routes/blossom.rs b/src/routes/blossom.rs index d8a6373..0587666 100644 --- a/src/routes/blossom.rs +++ b/src/routes/blossom.rs @@ -17,6 +17,7 @@ use crate::db::{Database, FileUpload}; use crate::filesystem::FileStore; use crate::routes::delete_file; use crate::settings::Settings; +use crate::webhook::Webhook; #[derive(Serialize, Deserialize)] struct BlossomError { @@ -82,6 +83,7 @@ async fn upload( fs: &State, db: &State, settings: &State, + webhook: &State>, data: Data<'_>, ) -> BlossomResponse { if !check_method(&auth.event, "upload") { @@ -122,6 +124,18 @@ async fn upload( { Ok(blob) => { let pubkey_vec = auth.event.pubkey.to_bytes().to_vec(); + if let Some(wh) = webhook.as_ref() { + match wh.store_file(&pubkey_vec, blob.clone()) { + Ok(store) => if !store { + let _ = fs::remove_file(blob.path); + return BlossomResponse::error("Upload rejected"); + } + Err(e) => { + let _ = fs::remove_file(blob.path); + return BlossomResponse::error(format!("Internal error, failed to call webhook: {}", e)); + } + } + } let user_id = match db.upsert_user(&pubkey_vec).await { Ok(u) => u, Err(e) => { diff --git a/src/routes/nip96.rs b/src/routes/nip96.rs index fa20561..6908764 100644 --- a/src/routes/nip96.rs +++ b/src/routes/nip96.rs @@ -14,6 +14,7 @@ use crate::db::{Database, FileUpload}; use crate::filesystem::FileStore; use crate::routes::delete_file; use crate::settings::Settings; +use crate::webhook::Webhook; #[derive(Serialize, Default)] #[serde(crate = "rocket::serde")] @@ -152,6 +153,7 @@ async fn upload( fs: &State, db: &State, settings: &State, + webhook: &State>, form: Form>, ) -> Nip96Response { if let Some(size) = auth.content_length { @@ -178,6 +180,18 @@ async fn upload( { Ok(blob) => { let pubkey_vec = auth.event.pubkey.to_bytes().to_vec(); + if let Some(wh) = webhook.as_ref() { + match wh.store_file(&pubkey_vec, blob.clone()) { + Ok(store) => if !store { + let _ = fs::remove_file(blob.path); + return Nip96Response::error("Upload rejected"); + } + Err(e) => { + let _ = fs::remove_file(blob.path); + return Nip96Response::error(&format!("Internal error, failed to call webhook: {}", e)); + } + } + } let user_id = match db.upsert_user(&pubkey_vec).await { Ok(u) => u, Err(e) => return Nip96Response::error(&format!("Could not save user: {}", e)), diff --git a/src/settings.rs b/src/settings.rs index 21af902..33cc67d 100644 --- a/src/settings.rs +++ b/src/settings.rs @@ -22,5 +22,8 @@ pub struct Settings { pub whitelist: Option>, /// Path for ViT image model - pub vit_model_path: Option + pub vit_model_path: Option, + + /// Webhook api endpoint + pub webhook_url: Option, } diff --git a/src/webhook.rs b/src/webhook.rs new file mode 100644 index 0000000..fde836f --- /dev/null +++ b/src/webhook.rs @@ -0,0 +1,41 @@ +use anyhow::Error; +use serde::{Deserialize, Serialize}; + +use crate::filesystem::FileSystemResult; + +pub(crate) struct Webhook { + url: String, +} + +#[derive(Serialize, Deserialize)] +struct WebhookRequest { + pub action: String, + pub subject: Option, + pub payload: T, +} + +impl Webhook { + pub fn new(url: String) -> Self { + Self { + url + } + } + + /// Ask webhook api if this file can be accepted + pub fn store_file(&self, pubkey: &Vec, fs: FileSystemResult) -> Result { + let body: WebhookRequest = WebhookRequest { + action: "store_file".to_string(), + subject: Some(hex::encode(pubkey)), + payload: fs, + }; + let req = ureq::post(&self.url) + .set("accept", "application/json") + .send_json(body)?; + + if req.status() == 200 { + Ok(true) + } else { + Ok(false) + } + } +} \ No newline at end of file