diff --git a/README.md b/README.md index 2d69a47..c7667a8 100644 --- a/README.md +++ b/README.md @@ -430,6 +430,21 @@ A secure API key can be generated by any password generator. "identifier": "/path/to/object" } ``` +- `POST /internal/set_not_found` Set the 404 image that is served from the original and process + endpoints. The image used must already be uploaded and have an alias. The request should look + like this: + ```json + { + "alias": "asdf.png" + } + ``` + + On success, the returned json should look like this: + ```json + { + "msg": "ok" + } + ``` Additionally, all endpoints support setting deadlines, after which the request will cease processing. To enable deadlines for your requests, you can set the `X-Request-Deadline` header to an diff --git a/dev.toml b/dev.toml index fe61c9f..ea58702 100644 --- a/dev.toml +++ b/dev.toml @@ -1,6 +1,8 @@ [server] address = '0.0.0.0:8080' worker_id = 'pict-rs-1' +api_key = 'api-key' + [tracing.logging] format = 'normal' targets = 'warn,tracing_actix_web=info,actix_server=info,actix_web=info' diff --git a/src/lib.rs b/src/lib.rs index b2fc17a..b6990cd 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -80,6 +80,8 @@ const MINUTES: u32 = 60; const HOURS: u32 = 60 * MINUTES; const DAYS: u32 = 24 * HOURS; +const NOT_FOUND_KEY: &str = "404-alias"; + static DO_CONFIG: OnceCell<(Configuration, Operation)> = OnceCell::new(); static CONFIG: Lazy = Lazy::new(|| { DO_CONFIG @@ -595,6 +597,21 @@ async fn process_details( Ok(HttpResponse::Ok().json(&details)) } +async fn not_found_hash(repo: &R) -> Result, Error> { + let Some(not_found) = repo.get(NOT_FOUND_KEY).await? else { + return Ok(None); + }; + + let alias = String::from_utf8_lossy(not_found.as_ref()) + .parse::() + .expect("Infallible"); + + repo.hash(&alias) + .await + .map(|opt| opt.map(|hash| (alias, hash))) + .map_err(Error::from) +} + /// Process files #[tracing::instrument(name = "Serving processed image", skip(repo, store))] async fn process( @@ -607,10 +624,15 @@ async fn process( let (format, alias, thumbnail_path, thumbnail_args) = prepare_process(query, ext.as_str())?; let path_string = thumbnail_path.to_string_lossy().to_string(); - let Some(hash) = repo.hash(&alias).await? else { - // Invalid alias - // TODO: placeholder 404 image - return Ok(HttpResponse::NotFound().finish()); + + let (hash, alias, not_found) = if let Some(hash) = repo.hash(&alias).await? { + (hash, alias, false) + } else { + let Some((alias, hash)) = not_found_hash(&repo).await? else { + return Ok(HttpResponse::NotFound().finish()); + }; + + (hash, alias, true) }; let identifier_opt = repo @@ -637,7 +659,7 @@ async fn process( new_details }; - return ranged_file_resp(&store, identifier, range, details).await; + return ranged_file_resp(&store, identifier, range, details, not_found).await; } let original_details = ensure_details(&repo, &store, &alias).await?; @@ -674,6 +696,11 @@ async fn process( } else { return Err(UploadError::Range.into()); } + } else if not_found { + ( + HttpResponse::NotFound(), + Either::right(once(ready(Ok(bytes)))), + ) } else { (HttpResponse::Ok(), Either::right(once(ready(Ok(bytes))))) }; @@ -785,15 +812,21 @@ async fn serve( ) -> Result { let alias = alias.into_inner(); - let Some(identifier) = repo.identifier_from_alias::(&alias).await? else { - // Invalid alias - // TODO: placeholder 404 image - return Ok(HttpResponse::NotFound().finish()); + let (hash, alias, not_found) = if let Some(hash) = repo.hash(&alias).await? { + (hash, Serde::into_inner(alias), false) + } else { + let Some((alias, hash)) = not_found_hash(&repo).await? else { + return Ok(HttpResponse::NotFound().finish()); + }; + + (hash, alias, true) }; + let identifier = repo.identifier(hash).await?; + let details = ensure_details(&repo, &store, &alias).await?; - ranged_file_resp(&store, identifier, range, details).await + ranged_file_resp(&store, identifier, range, details, not_found).await } #[tracing::instrument(name = "Serving file headers", skip(repo, store))] @@ -855,6 +888,7 @@ async fn ranged_file_resp( identifier: S::Identifier, range: Option>, details: Details, + not_found: bool, ) -> Result { let (builder, stream) = if let Some(web::Header(range_header)) = range { //Range header exists - return as ranged @@ -887,7 +921,12 @@ async fn ranged_file_resp( .to_stream(&identifier, None, None) .await? .map_err(Error::from); - (HttpResponse::Ok(), Either::right(stream)) + + if not_found { + (HttpResponse::NotFound(), Either::right(stream)) + } else { + (HttpResponse::Ok(), Either::right(stream)) + } }; Ok(srv_response( @@ -952,6 +991,21 @@ struct AliasQuery { alias: Serde, } +#[tracing::instrument(name = "Setting 404 Image", skip(repo))] +async fn set_not_found( + json: web::Json, + repo: web::Data, +) -> Result { + let alias = json.into_inner().alias; + + repo.set(NOT_FOUND_KEY, Vec::from(alias.to_string()).into()) + .await?; + + Ok(HttpResponse::Created().json(serde_json::json!({ + "msg": "ok", + }))) +} + #[tracing::instrument(name = "Purging file", skip(repo))] async fn purge( query: web::Query, @@ -1110,7 +1164,8 @@ fn configure_endpoints( .service(web::resource("/variants").route(web::delete().to(clean_variants::))) .service(web::resource("/purge").route(web::post().to(purge::))) .service(web::resource("/aliases").route(web::get().to(aliases::))) - .service(web::resource("/identifier").route(web::get().to(identifier::))), + .service(web::resource("/identifier").route(web::get().to(identifier::))) + .service(web::resource("/set_not_found").route(web::post().to(set_not_found::))), ); } diff --git a/src/processor.rs b/src/processor.rs index c649ca0..4dca9b7 100644 --- a/src/processor.rs +++ b/src/processor.rs @@ -295,7 +295,7 @@ impl Processor for Blur { } fn command(&self, mut args: Vec) -> Vec { - args.extend(["-gaussian-blur".to_string(), self.0.to_string()]); + args.extend(["-gaussian-blur".to_string(), format!("0x{}", self.0)]); args }