diff --git a/backend/src/cached_option.rs b/backend/src/cached_option.rs new file mode 100644 index 0000000..3df7af9 --- /dev/null +++ b/backend/src/cached_option.rs @@ -0,0 +1,65 @@ +use rocket::http::Status; +use rocket::response::Responder; +use rocket::{Request, Response, response}; +use std::ops::{FromResidual, Try}; + +#[derive(Debug)] +pub struct CachedOption(Option<(rocket::http::ContentType, Vec)>); + +impl CachedOption { + fn into_option(self) -> Option<(rocket::http::ContentType, Vec)> { + self.0 + } +} + +impl From)>> for CachedOption { + fn from(value: Option<(rocket::http::ContentType, Vec)>) -> Self { + Self(value) + } +} + +impl FromResidual> for CachedOption { + fn from_residual(_: std::option::Option) -> Self { + Self(None) + } +} + +impl FromResidual for CachedOption { + fn from_residual(residual: CachedOption) -> Self { + residual + } +} + +impl Try for CachedOption { + type Output = Option<(rocket::http::ContentType, Vec)>; + type Residual = Self; + + fn from_output(output: Self::Output) -> Self { + Self(output) + } + + fn branch(self) -> std::ops::ControlFlow { + match self.0 { + Some((ct, bytes)) => std::ops::ControlFlow::Break(Self(Some((ct, bytes)))), + None => std::ops::ControlFlow::Continue(None), + } + } +} + +impl<'r> Responder<'r, 'static> for CachedOption { + fn respond_to(self, _: &'r Request<'_>) -> response::Result<'static> { + let option = self.into_option(); + if let Some((ct, bytes)) = option { + Response::build() + .header(ct) + .header(rocket::http::Header::new( + "Cache-Control", + "max-age=31536000", + )) + .sized_body(bytes.len(), std::io::Cursor::new(bytes)) + .ok() + } else { + Err(Status::InternalServerError) + } + } +} diff --git a/backend/src/main.rs b/backend/src/main.rs index 790b19d..2e5dee6 100644 --- a/backend/src/main.rs +++ b/backend/src/main.rs @@ -1,3 +1,4 @@ +#![feature(try_trait_v2)] use rocket::fs::FileServer; use rocket::futures::lock::Mutex; @@ -62,12 +63,14 @@ async fn add_game( game: proto_utils::Proto, ) -> Option { let mut games = game_list.lock().await; - let game = game.into_inner(); + let mut game = game.into_inner(); if games.iter().any(|g| g.title == game.title) { return None; } + game.title = game.title.trim().to_string(); + games.push(game.clone()); let users = user_list.lock().await; @@ -118,11 +121,14 @@ async fn add_opinion( result } +mod cached_option; +use cached_option::CachedOption; + #[get("/game_thumbnail/")] async fn get_game_thumbnail( title: &str, game_list: &rocket::State<Mutex<Vec<Game>>>, -) -> Option<(rocket::http::ContentType, Vec<u8>)> { +) -> CachedOption { // 1. Sanitize title for filename let safe_title: String = title .chars() @@ -140,7 +146,7 @@ async fn get_game_thumbnail( std::fs::read_to_string(&cache_path_type), ) && let Some(ct) = rocket::http::ContentType::parse_flexible(&type_str) { - return Some((ct, bytes)); + return Some((ct, bytes)).into(); } let games = game_list.lock().await; @@ -177,10 +183,10 @@ async fn get_game_thumbnail( if let Ok(json) = resp.json::<serde_json::Value>().await { json["data"][0]["imageUrl"].as_str()?.to_string() } else { - return None; + return None.into(); } } - Err(_) => return None, + Err(_) => return None.into(), } } }; @@ -199,9 +205,9 @@ async fn get_game_thumbnail( let _ = std::fs::write(&cache_path_bin, &bytes); let _ = std::fs::write(&cache_path_type, content_type.to_string()); - Some((content_type, bytes)) + Some((content_type, bytes)).into() } - Err(_) => None, + Err(_) => None.into(), } } diff --git a/rust-toolchain.toml b/rust-toolchain.toml new file mode 100644 index 0000000..5d56faf --- /dev/null +++ b/rust-toolchain.toml @@ -0,0 +1,2 @@ +[toolchain] +channel = "nightly" diff --git a/state.json b/state.json index 47013d2..bedd6bf 100644 --- a/state.json +++ b/state.json @@ -9,7 +9,7 @@ "remote_id": 98626216952426 }, { - "title": " Asylum Life", + "title": "Asylum Life", "source": 1, "min_players": 1, "max_players": 40, @@ -43,7 +43,7 @@ "name": "Code002Lover", "opinion": [ { - "title": " Asylum Life", + "title": "Asylum Life", "would_play": true }, { @@ -63,7 +63,7 @@ "would_play": true }, { - "title": " Asylum Life", + "title": "Asylum Life", "would_play": false }, { @@ -75,4 +75,4 @@ "password_hash": "$2b$12$iezDgiYiMSEceyfDrriqVucQBx7v.U4TdKdc5qjEIJDbqsxiXHTBK" } ] -} \ No newline at end of file +}