diff options
| author | metamuffin <metamuffin@disroot.org> | 2026-01-25 23:32:53 +0100 |
|---|---|---|
| committer | metamuffin <metamuffin@disroot.org> | 2026-01-25 23:32:53 +0100 |
| commit | 783d3598753bf84756296a2016e5dab30300519b (patch) | |
| tree | f7eaf276b65de8aab10db21d27e534f775d83167 /server | |
| parent | 5075aede44cb8ab2df10e6debba38483e8d11e96 (diff) | |
| download | jellything-783d3598753bf84756296a2016e5dab30300519b.tar jellything-783d3598753bf84756296a2016e5dab30300519b.tar.bz2 jellything-783d3598753bf84756296a2016e5dab30300519b.tar.zst | |
work on login
Diffstat (limited to 'server')
| -rw-r--r-- | server/Cargo.toml | 2 | ||||
| -rw-r--r-- | server/src/api.rs | 8 | ||||
| -rw-r--r-- | server/src/compat/youtube.rs | 74 | ||||
| -rw-r--r-- | server/src/logic/playersync.rs | 6 | ||||
| -rw-r--r-- | server/src/logic/stream.rs | 133 | ||||
| -rw-r--r-- | server/src/logic/userdata.rs | 101 | ||||
| -rw-r--r-- | server/src/main.rs | 2 | ||||
| -rw-r--r-- | server/src/request_info.rs | 9 | ||||
| -rw-r--r-- | server/src/routes.rs | 92 | ||||
| -rw-r--r-- | server/src/ui/account/mod.rs | 22 | ||||
| -rw-r--r-- | server/src/ui/assets.rs | 77 | ||||
| -rw-r--r-- | server/src/ui/error.rs | 36 | ||||
| -rw-r--r-- | server/src/ui/mod.rs | 79 | ||||
| -rw-r--r-- | server/src/ui/node.rs | 44 | ||||
| -rw-r--r-- | server/src/ui/search.rs | 41 | ||||
| -rw-r--r-- | server/src/ui/stats.rs | 20 |
16 files changed, 323 insertions, 423 deletions
diff --git a/server/Cargo.toml b/server/Cargo.toml index 534dce5..f12b8fe 100644 --- a/server/Cargo.toml +++ b/server/Cargo.toml @@ -25,7 +25,7 @@ env_logger = "0.11.8" futures = "0.3.31" log = { workspace = true } rand = "0.9.2" -rocket = { version = "0.5", features = ["secrets", "json"] } +rocket = { version = "0.5", features = ["json"] } rocket_ws = "0.1" serde = { version = "1.0.228", features = ["derive", "rc"] } serde_json = "1.0.145" diff --git a/server/src/api.rs b/server/src/api.rs index 2b3d016..9d16433 100644 --- a/server/src/api.rs +++ b/server/src/api.rs @@ -4,6 +4,7 @@ Copyright (C) 2026 metamuffin <metamuffin.org> */ use super::ui::error::MyResult; +use crate::request_info::RequestInfo; use rocket::{get, response::Redirect, serde::json::Json}; #[get("/api")] @@ -38,7 +39,8 @@ pub fn r_version() -> &'static str { // } #[get("/nodes_modified?<since>")] -pub fn r_nodes_modified_since(session: A<Session>, since: u64) -> MyResult<Json<Vec<NodeID>>> { - let nodes = get_nodes_modified_since(&session.0, since)?; - Ok(Json(nodes)) +pub fn r_nodes_modified_since(ri: RequestInfo<'_>, since: u64) -> MyResult<Json<Vec<String>>> { + // let nodes = get_nodes_modified_since(&session.0, since)?; + // Ok(Json(nodes)) + todo!() } diff --git a/server/src/compat/youtube.rs b/server/src/compat/youtube.rs index 5e86014..e511d9b 100644 --- a/server/src/compat/youtube.rs +++ b/server/src/compat/youtube.rs @@ -3,53 +3,47 @@ which is licensed under the GNU Affero General Public License (version 3); see /COPYING. Copyright (C) 2026 metamuffin <metamuffin.org> */ -use crate::{request_info::A, ui::error::MyResult}; -use anyhow::anyhow; -use jellycommon::{ - routes::{u_node_slug, u_node_slug_player}, - IdentifierType, -}; -use jellylogic::{ - node::{get_node_by_eid, node_id_to_slug}, - session::Session, -}; +use crate::{request_info::RequestInfo, ui::error::MyResult}; use rocket::{get, response::Redirect}; #[get("/watch?<v>")] -pub fn r_youtube_watch(session: A<Session>, v: &str) -> MyResult<Redirect> { - if v.len() != 11 { - Err(anyhow!("video id length incorrect"))? - } - let Some(id) = get_node_by_eid(&session.0, IdentifierType::YoutubeVideo, v)? else { - Err(anyhow!("element not found"))? - }; - let slug = node_id_to_slug(&session.0, id)?; - Ok(Redirect::to(u_node_slug_player(&slug))) +pub fn r_youtube_watch(ri: RequestInfo<'_>, v: &str) -> MyResult<Redirect> { + // if v.len() != 11 { + // Err(anyhow!("video id length incorrect"))? + // } + // let Some(id) = get_node_by_eid(&session.0, IdentifierType::YoutubeVideo, v)? else { + // Err(anyhow!("element not found"))? + // }; + // let slug = node_id_to_slug(&session.0, id)?; + // Ok(Redirect::to(u_node_slug_player(&slug))) + todo!() } #[get("/channel/<id>")] -pub fn r_youtube_channel(session: A<Session>, id: &str) -> MyResult<Redirect> { - let Some(id) = (if id.starts_with("UC") { - get_node_by_eid(&session.0, IdentifierType::YoutubeChannel, id)? - } else if id.starts_with("@") { - get_node_by_eid(&session.0, IdentifierType::YoutubeChannelHandle, id)? - } else { - Err(anyhow!("unknown channel id format"))? - }) else { - Err(anyhow!("channel not found"))? - }; - let slug = node_id_to_slug(&session.0, id)?; - Ok(Redirect::to(u_node_slug(&slug))) +pub fn r_youtube_channel(ri: RequestInfo<'_>, id: &str) -> MyResult<Redirect> { + // let Some(id) = (if id.starts_with("UC") { + // get_node_by_eid(&session.0, IdentifierType::YoutubeChannel, id)? + // } else if id.starts_with("@") { + // get_node_by_eid(&session.0, IdentifierType::YoutubeChannelHandle, id)? + // } else { + // Err(anyhow!("unknown channel id format"))? + // }) else { + // Err(anyhow!("channel not found"))? + // }; + // let slug = node_id_to_slug(&session.0, id)?; + // Ok(Redirect::to(u_node_slug(&slug))) + todo!() } #[get("/embed/<v>")] -pub fn r_youtube_embed(session: A<Session>, v: &str) -> MyResult<Redirect> { - if v.len() != 11 { - Err(anyhow!("video id length incorrect"))? - } - let Some(id) = get_node_by_eid(&session.0, IdentifierType::YoutubeVideo, v)? else { - Err(anyhow!("element not found"))? - }; - let slug = node_id_to_slug(&session.0, id)?; - Ok(Redirect::to(u_node_slug_player(&slug))) +pub fn r_youtube_embed(ri: RequestInfo<'_>, v: &str) -> MyResult<Redirect> { + // if v.len() != 11 { + // Err(anyhow!("video id length incorrect"))? + // } + // let Some(id) = get_node_by_eid(&session.0, IdentifierType::YoutubeVideo, v)? else { + // Err(anyhow!("element not found"))? + // }; + // let slug = node_id_to_slug(&session.0, id)?; + // Ok(Redirect::to(u_node_slug_player(&slug))) + todo!() } diff --git a/server/src/logic/playersync.rs b/server/src/logic/playersync.rs index 6c1f9f4..71e2809 100644 --- a/server/src/logic/playersync.rs +++ b/server/src/logic/playersync.rs @@ -2,12 +2,12 @@ use anyhow::bail; use chashmap::CHashMap; use futures::{SinkExt, StreamExt}; use log::warn; -use rocket::{get, State}; -use rocket_ws::{stream::DuplexStream, Channel, Message, WebSocket}; +use rocket::{State, get}; +use rocket_ws::{Channel, Message, WebSocket, stream::DuplexStream}; use serde::{Deserialize, Serialize}; use tokio::sync::broadcast::{self, Sender}; -use crate::request_info::cors::Cors; +use crate::responders::cors::Cors; #[derive(Default)] pub struct PlayersyncChannels { diff --git a/server/src/logic/stream.rs b/server/src/logic/stream.rs index 55d6850..430c10c 100644 --- a/server/src/logic/stream.rs +++ b/server/src/logic/stream.rs @@ -3,18 +3,16 @@ which is licensed under the GNU Affero General Public License (version 3); see /COPYING. Copyright (C) 2026 metamuffin <metamuffin.org> */ -use crate::{request_info::A, ui::error::MyError}; -use anyhow::{anyhow, Result}; -use jellycommon::{api::NodeFilterSort, stream::StreamSpec, NodeID, TrackSource}; -use jellylogic::{node::get_node, session::Session}; +use crate::{request_info::RequestInfo, ui::error::MyError}; +use anyhow::{Result, anyhow}; +use jellycommon::stream::StreamSpec; use jellystream::SMediaInfo; use log::{info, warn}; use rocket::{ - get, head, + Either, Request, Response, get, head, http::{Header, Status}, request::{self, FromRequest}, response::{self, Redirect, Responder}, - Either, Request, Response, }; use std::{ collections::{BTreeMap, BTreeSet}, @@ -22,14 +20,14 @@ use std::{ sync::Arc, }; use tokio::{ - io::{duplex, DuplexStream}, + io::{DuplexStream, duplex}, task::spawn_blocking, }; use tokio_util::io::SyncIoBridge; #[head("/n/<_id>/stream?<spec..>")] pub async fn r_stream_head( - _sess: A<Session>, + _sess: RequestInfo<'_>, _id: &str, spec: BTreeMap<String, String>, ) -> Result<Either<StreamResponse, Redirect>, MyError> { @@ -45,27 +43,27 @@ pub async fn r_stream_head( #[get("/n/<id>/stream?<spec..>")] pub async fn r_stream( - session: A<Session>, + session: RequestInfo<'_>, id: &str, range: Option<RequestRange>, spec: BTreeMap<String, String>, ) -> Result<Either<StreamResponse, RedirectResponse>, MyError> { let spec = StreamSpec::from_query_kv(&spec).map_err(|x| anyhow!("spec invalid: {x}"))?; - // TODO perm - let node = get_node( - &session.0, - NodeID::from_slug(id), - false, - false, - NodeFilterSort::default(), - )? - .node; + // // TODO perm + // let node = get_node( + // &session.0, + // NodeID::from_slug(id), + // false, + // false, + // NodeFilterSort::default(), + // )? + // .node; - let media = Arc::new( - node.media - .clone() - .ok_or(anyhow!("item does not contain media"))?, - ); + // let media = Arc::new( + // node.media + // .clone() + // .ok_or(anyhow!("item does not contain media"))?, + // ); // TODO its unclear how requests with multiple tracks should be handled. // if spec.track.len() == 1 { @@ -115,55 +113,56 @@ pub async fn r_stream( // } // } - info!( - "stream request (range={})", - range - .as_ref() - .map(|r| r.to_cr_hv()) - .unwrap_or("none".to_string()) - ); + // info!( + // "stream request (range={})", + // range + // .as_ref() + // .map(|r| r.to_cr_hv()) + // .unwrap_or("none".to_string()) + // ); - let urange = match &range { - Some(r) => { - let r = r.0.first().unwrap_or(&(None..None)); - r.start.unwrap_or(0)..r.end.unwrap_or(u64::MAX) - } - None => 0..u64::MAX, - }; + // let urange = match &range { + // Some(r) => { + // let r = r.0.first().unwrap_or(&(None..None)); + // r.start.unwrap_or(0)..r.end.unwrap_or(u64::MAX) + // } + // None => 0..u64::MAX, + // }; - let head = jellystream::stream_head(&spec); + // let head = jellystream::stream_head(&spec); - let mut sources = BTreeSet::new(); - for t in &media.tracks { - if let TrackSource::Local(path, _) = &t.source { - sources.insert(path.to_owned()); - } - } - let media = Arc::new(SMediaInfo { - files: sources, - title: node.title.clone(), - }); + // let mut sources = BTreeSet::new(); + // for t in &media.tracks { + // if let TrackSource::Local(path, _) = &t.source { + // sources.insert(path.to_owned()); + // } + // } + // let media = Arc::new(SMediaInfo { + // files: sources, + // title: node.title.clone(), + // }); - // TODO cleaner solution needed - let mut reader = match spawn_blocking(move || jellystream::stream(media, spec, urange)) - .await - .unwrap() - { - Ok(o) => o, - Err(e) => { - warn!("stream error: {e:?}"); - Err(e)? - } - }; - let (stream_write, stream_read) = duplex(4096); - spawn_blocking(move || std::io::copy(&mut reader, &mut SyncIoBridge::new(stream_write))); + // // TODO cleaner solution needed + // let mut reader = match spawn_blocking(move || jellystream::stream(media, spec, urange)) + // .await + // .unwrap() + // { + // Ok(o) => o, + // Err(e) => { + // warn!("stream error: {e:?}"); + // Err(e)? + // } + // }; + // let (stream_write, stream_read) = duplex(4096); + // spawn_blocking(move || std::io::copy(&mut reader, &mut SyncIoBridge::new(stream_write))); - Ok(Either::Left(StreamResponse { - stream: stream_read, - range, - advertise_range: head.range_supported, - content_type: head.content_type, - })) + // Ok(Either::Left(StreamResponse { + // stream: stream_read, + // range, + // advertise_range: head.range_supported, + // content_type: head.content_type, + // })) + todo!() } pub struct RedirectResponse(String); diff --git a/server/src/logic/userdata.rs b/server/src/logic/userdata.rs index 104de4a..ea96507 100644 --- a/server/src/logic/userdata.rs +++ b/server/src/logic/userdata.rs @@ -3,23 +3,10 @@ which is licensed under the GNU Affero General Public License (version 3); see /COPYING. Copyright (C) 2026 metamuffin <metamuffin.org> */ -use crate::{request_info::A, ui::error::MyResult}; -use jellycommon::{ - api::NodeFilterSort, - routes::u_node_id, - user::{NodeUserData, WatchedState}, - NodeID, -}; -use jellylogic::{ - node::{ - get_node, update_node_userdata_rating, update_node_userdata_watched, - update_node_userdata_watched_progress, - }, - session::Session, -}; +use crate::ui::error::MyResult; use rocket::{ - form::Form, get, post, response::Redirect, serde::json::Json, FromForm, FromFormField, - UriDisplayQuery, + FromForm, FromFormField, UriDisplayQuery, form::Form, get, post, response::Redirect, + serde::json::Json, }; #[derive(Debug, FromFormField, UriDisplayQuery)] @@ -29,48 +16,48 @@ pub enum UrlWatchedState { Pending, } -#[get("/n/<id>/userdata")] -pub fn r_node_userdata(session: A<Session>, id: A<NodeID>) -> MyResult<Json<NodeUserData>> { - let u = get_node(&session.0, id.0, false, false, NodeFilterSort::default())?.userdata; - Ok(Json(u)) -} +// #[get("/n/<id>/userdata")] +// pub fn r_node_userdata(session: A<Session>, id: A<NodeID>) -> MyResult<Json<NodeUserData>> { +// let u = get_node(&session.0, id.0, false, false, NodeFilterSort::default())?.userdata; +// Ok(Json(u)) +// } -#[post("/n/<id>/watched?<state>")] -pub async fn r_node_userdata_watched( - session: A<Session>, - id: A<NodeID>, - state: UrlWatchedState, -) -> MyResult<Redirect> { - update_node_userdata_watched( - &session.0, - id.0, - match state { - UrlWatchedState::None => WatchedState::None, - UrlWatchedState::Watched => WatchedState::Watched, - UrlWatchedState::Pending => WatchedState::Pending, - }, - )?; - Ok(Redirect::found(u_node_id(id.0))) -} +// #[post("/n/<id>/watched?<state>")] +// pub async fn r_node_userdata_watched( +// session: A<Session>, +// id: A<NodeID>, +// state: UrlWatchedState, +// ) -> MyResult<Redirect> { +// update_node_userdata_watched( +// &session.0, +// id.0, +// match state { +// UrlWatchedState::None => WatchedState::None, +// UrlWatchedState::Watched => WatchedState::Watched, +// UrlWatchedState::Pending => WatchedState::Pending, +// }, +// )?; +// Ok(Redirect::found(u_node_id(id.0))) +// } -#[derive(FromForm)] -pub struct UpdateRating { - #[field(validate = range(-10..=10))] - rating: i32, -} +// #[derive(FromForm)] +// pub struct UpdateRating { +// #[field(validate = range(-10..=10))] +// rating: i32, +// } -#[post("/n/<id>/update_rating", data = "<form>")] -pub async fn r_node_userdata_rating( - session: A<Session>, - id: A<NodeID>, - form: Form<UpdateRating>, -) -> MyResult<Redirect> { - update_node_userdata_rating(&session.0, id.0, form.rating)?; - Ok(Redirect::found(u_node_id(id.0))) -} +// #[post("/n/<id>/update_rating", data = "<form>")] +// pub async fn r_node_userdata_rating( +// session: A<Session>, +// id: A<NodeID>, +// form: Form<UpdateRating>, +// ) -> MyResult<Redirect> { +// update_node_userdata_rating(&session.0, id.0, form.rating)?; +// Ok(Redirect::found(u_node_id(id.0))) +// } -#[post("/n/<id>/progress?<t>")] -pub async fn r_node_userdata_progress(session: A<Session>, id: A<NodeID>, t: f64) -> MyResult<()> { - update_node_userdata_watched_progress(&session.0, id.0, t)?; - Ok(()) -} +// #[post("/n/<id>/progress?<t>")] +// pub async fn r_node_userdata_progress(session: A<Session>, id: A<NodeID>, t: f64) -> MyResult<()> { +// update_node_userdata_watched_progress(&session.0, id.0, t)?; +// Ok(()) +// } diff --git a/server/src/main.rs b/server/src/main.rs index bd9901a..aab335a 100644 --- a/server/src/main.rs +++ b/server/src/main.rs @@ -48,7 +48,7 @@ async fn main() { } } -pub(crate) struct State { +pub struct State { pub config: Config, pub cache: Cache, diff --git a/server/src/request_info.rs b/server/src/request_info.rs index 3468c58..0f2fd3a 100644 --- a/server/src/request_info.rs +++ b/server/src/request_info.rs @@ -9,7 +9,8 @@ use crate::{ auth::token_to_user, ui::error::{MyError, MyResult}, }; -use jellycommon::jellyobject::ObjectBuffer; +use anyhow::anyhow; +use jellycommon::jellyobject::{Object, ObjectBuffer}; use jellyui::RenderInfo; use rocket::{ Request, async_trait, @@ -46,6 +47,12 @@ impl<'a> RequestInfo<'a> { state: state.clone(), }) } + pub fn require_user(&'a self) -> MyResult<Object<'a>> { + self.user + .as_ref() + .map(|u| u.as_object()) + .ok_or(MyError(anyhow!("user required"))) + } pub fn render_info(&'a self) -> RenderInfo<'a> { RenderInfo { lang: self.lang, diff --git a/server/src/routes.rs b/server/src/routes.rs index 19c76b2..01d0081 100644 --- a/server/src/routes.rs +++ b/server/src/routes.rs @@ -10,32 +10,12 @@ use crate::{ logic::{ playersync::{PlayersyncChannels, r_playersync}, stream::r_stream, - userdata::{ - r_node_userdata, r_node_userdata_progress, r_node_userdata_rating, - r_node_userdata_watched, - }, }, ui::{ - account::{ - r_account_login, r_account_login_post, r_account_logout, r_account_logout_post, - r_account_register, r_account_register_post, - settings::{r_account_settings, r_account_settings_post}, - }, - admin::{ - import::{r_admin_import, r_admin_import_post, r_admin_import_stream}, - log::{r_admin_log, r_admin_log_stream}, - r_admin_dashboard, r_admin_invite, r_admin_remove_invite, r_admin_update_search, - user::{r_admin_remove_user, r_admin_user, r_admin_user_permission, r_admin_users}, - }, - assets::{r_image, r_item_poster, r_node_thumbnail}, + assets::r_image, error::{r_api_catch, r_catch}, - home::r_home, - items::r_items, node::r_node, - player::r_player, - r_favicon, r_index, - search::r_search, - stats::r_stats, + r_favicon, style::{r_assets_font, r_assets_js, r_assets_js_map, r_assets_style}, }, }; @@ -52,7 +32,7 @@ macro_rules! uri { }; } -pub fn build_rocket(state: Arc<State>) -> Rocket<Build> { +pub(super) fn build_rocket(state: Arc<State>) -> Rocket<Build> { rocket::build() .configure(Config { address: std::env::var("BIND_ADDR") @@ -84,47 +64,47 @@ pub fn build_rocket(state: Arc<State>) -> Rocket<Build> { "/", routes![ // Frontend - r_account_login_post, - r_account_login, - r_account_logout_post, - r_account_logout, - r_account_register_post, - r_account_register, - r_account_settings_post, - r_account_settings, - r_admin_dashboard, - r_admin_import, - r_admin_import_post, - r_admin_import_stream, - r_admin_invite, - r_admin_log_stream, - r_admin_log, - r_admin_remove_invite, - r_admin_remove_user, - r_admin_update_search, - r_admin_user_permission, - r_admin_user, - r_admin_users, - r_items, + // r_account_login_post, + // r_account_login, + // r_account_logout_post, + // r_account_logout, + // r_account_register_post, + // r_account_register, + // r_account_settings_post, + // r_account_settings, + // r_admin_dashboard, + // r_admin_import, + // r_admin_import_post, + // r_admin_import_stream, + // r_admin_invite, + // r_admin_log_stream, + // r_admin_log, + // r_admin_remove_invite, + // r_admin_remove_user, + // r_admin_update_search, + // r_admin_user_permission, + // r_admin_user, + // r_admin_users, + // r_items, r_image, r_assets_font, r_assets_js_map, r_assets_js, r_assets_style, r_favicon, - r_home, - r_index, - r_item_poster, + // r_home, + // r_index, + // r_item_poster, r_node, - r_node_thumbnail, - r_node_userdata_progress, - r_node_userdata_rating, - r_node_userdata_watched, - r_node_userdata, - r_player, + // r_node_thumbnail, + // r_node_userdata_progress, + // r_node_userdata_rating, + // r_node_userdata_watched, + // r_node_userdata, + // r_player, r_playersync, - r_search, - r_stats, + // r_search, + // r_stats, r_stream, // API r_nodes_modified_since, diff --git a/server/src/ui/account/mod.rs b/server/src/ui/account/mod.rs index 429b70a..ec8bd49 100644 --- a/server/src/ui/account/mod.rs +++ b/server/src/ui/account/mod.rs @@ -3,12 +3,17 @@ which is licensed under the GNU Affero General Public License (version 3); see /COPYING. Copyright (C) 2026 metamuffin <metamuffin.org> */ -pub mod settings; +// pub mod settings; use super::error::MyError; -use crate::ui::{error::MyResult, home::rocket_uri_macro_r_home}; +use crate::{ + request_info::RequestInfo, + ui::{error::MyResult, home::rocket_uri_macro_r_home}, +}; use anyhow::anyhow; +use jellycommon::{VIEW_ACCOUNT_LOGIN, jellyobject::Object}; use jellyimport::is_importing; +use jellyui::render_view; use rocket::{ FromForm, form::{Contextual, Form}, @@ -30,16 +35,9 @@ pub struct RegisterForm { } #[get("/account/register")] -pub async fn r_account_register(lang: AcceptLanguage) -> RawHtml<String> { - let AcceptLanguage(lang) = lang; - RawHtml(render_page( - &AccountRegister { lang: &lang }, - RenderInfo { - importing: false, - session: None, - lang, - }, - )) +pub async fn r_account_register(ri: RequestInfo<'_>) -> RawHtml<String> { + let ob = Object::EMPTY.insert(VIEW_ACCOUNT_LOGIN, ()); + RawHtml(render_view(ri.render_info(), ob.as_object())) } #[derive(FromForm, Serialize, Deserialize)] diff --git a/server/src/ui/assets.rs b/server/src/ui/assets.rs index 8f3fb4a..c956672 100644 --- a/server/src/ui/assets.rs +++ b/server/src/ui/assets.rs @@ -4,58 +4,57 @@ Copyright (C) 2026 metamuffin <metamuffin.org> */ use super::error::MyResult; -use anyhow::{Context, anyhow}; -use rocket::{get, http::ContentType, response::Redirect}; -use std::str::FromStr; +use crate::{request_info::RequestInfo, responders::cache::CacheControlImage}; +use anyhow::Context; +use rocket::{get, http::ContentType}; +use std::path::PathBuf; pub const AVIF_QUALITY: u32 = 70; pub const AVIF_SPEED: u8 = 5; -#[get("/image/<key..>?<size>")] +#[get("/image/<path..>?<size>")] pub async fn r_image( - _session: A<Session>, - key: A<Asset>, + ri: RequestInfo<'_>, + path: PathBuf, size: Option<usize>, ) -> MyResult<(ContentType, CacheControlImage)> { let size = size.unwrap_or(2048); - - if !key.0.0.ends_with(".image") { - Err(anyhow!("request to non-image"))? - } + let path = path.to_string_lossy().to_string(); // fit the resolution into a finite set so the maximum cache is finite too. let width = 2usize.pow(size.clamp(128, 2048).ilog2()); - let encoded = jellytranscoder::image::transcode(&key.0.0, AVIF_QUALITY, AVIF_SPEED, width) - .context("transcoding asset")?; + let encoded = + jellytranscoder::image::transcode(&ri.state.cache, &path, AVIF_QUALITY, AVIF_SPEED, width) + .context("transcoding asset")?; Ok((ContentType::AVIF, CacheControlImage(encoded))) } -#[get("/n/<id>/image/<slot>?<size>")] -pub async fn r_item_poster( - session: A<Session>, - id: A<NodeID>, - slot: &str, - size: Option<usize>, -) -> MyResult<Redirect> { - let slot = PictureSlot::from_str(slot).map_err(|_| anyhow!("slot invalid"))?; - let node = get_node(&session.0, id.0, false, false, NodeFilterSort::default())?; - let picture = node - .node - .pictures - .get(&slot) - .cloned() - .ok_or(anyhow!("no pic todo"))?; - Ok(Redirect::permanent(rocket::uri!(r_image(picture, size)))) -} +// #[get("/n/<id>/image/<slot>?<size>")] +// pub async fn r_item_poster( +// session: A<Session>, +// id: A<NodeID>, +// slot: &str, +// size: Option<usize>, +// ) -> MyResult<Redirect> { +// let slot = PictureSlot::from_str(slot).map_err(|_| anyhow!("slot invalid"))?; +// let node = get_node(&session.0, id.0, false, false, NodeFilterSort::default())?; +// let picture = node +// .node +// .pictures +// .get(&slot) +// .cloned() +// .ok_or(anyhow!("no pic todo"))?; +// Ok(Redirect::permanent(rocket::uri!(r_image(picture, size)))) +// } -#[get("/n/<id>/thumbnail?<t>&<size>")] -pub async fn r_node_thumbnail( - session: A<Session>, - id: A<NodeID>, - t: f64, - size: Option<usize>, -) -> MyResult<Redirect> { - let picture = get_node_thumbnail(&session.0, id.0, t).await?; - Ok(Redirect::permanent(rocket::uri!(r_image(picture, size)))) -} +// #[get("/n/<id>/thumbnail?<t>&<size>")] +// pub async fn r_node_thumbnail( +// session: A<Session>, +// id: A<NodeID>, +// t: f64, +// size: Option<usize>, +// ) -> MyResult<Redirect> { +// let picture = get_node_thumbnail(&session.0, id.0, t).await?; +// Ok(Redirect::permanent(rocket::uri!(r_image(picture, size)))) +// } diff --git a/server/src/ui/error.rs b/server/src/ui/error.rs index 0f279fc..578d841 100644 --- a/server/src/ui/error.rs +++ b/server/src/ui/error.rs @@ -3,38 +3,20 @@ which is licensed under the GNU Affero General Public License (version 3); see /COPYING. Copyright (C) 2026 metamuffin <metamuffin.org> */ -use log::info; use rocket::{ - catch, - http::{ContentType, Status}, - response::{self, content::RawHtml, Responder}, - Request, + Request, catch, + http::Status, + response::{self, Responder, content::RawHtml}, }; -use serde_json::{json, Value}; -use std::{fmt::Display, fs::File, io::Read, sync::LazyLock}; - -static ERROR_IMAGE: LazyLock<Vec<u8>> = LazyLock::new(|| { - info!("loading error image"); - let mut f = File::open(CONF.asset_path.join("error.avif")) - .expect("please create error.avif in the asset dir"); - let mut o = Vec::new(); - f.read_to_end(&mut o).unwrap(); - o -}); +use serde_json::{Value, json}; +use std::fmt::Display; #[catch(default)] pub fn r_catch(status: Status, _request: &Request) -> RawHtml<String> { catch_with_message(format!("{status}")) } fn catch_with_message(message: String) -> RawHtml<String> { - RawHtml(render_page( - &ErrorPage { status: message }, - RenderInfo { - importing: false, - session: None, - lang: Language::English, - }, - )) + RawHtml(message) // TODO } #[catch(default)] @@ -52,9 +34,9 @@ impl<'r> Responder<'r, 'static> for MyError { fn respond_to(self, req: &'r Request<'_>) -> response::Result<'static> { match req.accept().map(|a| a.preferred()) { Some(x) if x.is_json() => json!({ "error": format!("{}", self.0) }).respond_to(req), - Some(x) if x.is_avif() || x.is_png() || x.is_jpeg() => { - (ContentType::AVIF, ERROR_IMAGE.as_slice()).respond_to(req) - } + // Some(x) if x.is_avif() || x.is_png() || x.is_jpeg() => { + // (ContentType::AVIF, ERROR_IMAGE.as_slice()).respond_to(req) + // } _ => catch_with_message(format!("{:#}", self.0)).respond_to(req), } } diff --git a/server/src/ui/mod.rs b/server/src/ui/mod.rs index 92b93fe..55fad6a 100644 --- a/server/src/ui/mod.rs +++ b/server/src/ui/mod.rs @@ -4,58 +4,51 @@ Copyright (C) 2026 metamuffin <metamuffin.org> */ use error::MyResult; -use home::rocket_uri_macro_r_home; -use rocket::{ - Either, - futures::FutureExt, - get, - response::{Redirect, content::RawHtml}, -}; -use std::{future::Future, pin::Pin}; -use tokio::{ - fs::{File, read_to_string}, - io::AsyncRead, -}; +use rocket::{futures::FutureExt, get}; +use std::{future::Future, pin::Pin, sync::Arc}; +use tokio::{fs::File, io::AsyncRead}; + +use crate::State; pub mod account; -pub mod admin; +// pub mod admin; pub mod assets; pub mod error; -pub mod home; -pub mod items; +// pub mod home; +// pub mod items; pub mod node; -pub mod player; -pub mod search; -pub mod stats; +// pub mod player; +// pub mod search; +// pub mod stats; pub mod style; -#[get("/")] -pub async fn r_index( - lang: AcceptLanguage, - sess: Option<A<Session>>, -) -> MyResult<Either<Redirect, RawHtml<String>>> { - let AcceptLanguage(lang) = lang; - if sess.is_some() { - Ok(Either::Left(Redirect::temporary(rocket::uri!(r_home())))) - } else { - let front = read_to_string(CONF.asset_path.join("front.htm")).await?; - Ok(Either::Right(RawHtml(render_page( - &CustomPage { - title: "Jellything".to_string(), - body: front, - }, - RenderInfo { - importing: false, - session: None, - lang, - }, - )))) - } -} +// #[get("/")] +// pub async fn r_index( +// lang: AcceptLanguage, +// sess: Option<A<Session>>, +// ) -> MyResult<Either<Redirect, RawHtml<String>>> { +// let AcceptLanguage(lang) = lang; +// if sess.is_some() { +// Ok(Either::Left(Redirect::temporary(rocket::uri!(r_home())))) +// } else { +// let front = read_to_string(CONF.asset_path.join("front.htm")).await?; +// Ok(Either::Right(RawHtml(render_page( +// &CustomPage { +// title: "Jellything".to_string(), +// body: front, +// }, +// RenderInfo { +// importing: false, +// session: None, +// lang, +// }, +// )))) +// } +// } #[get("/favicon.ico")] -pub async fn r_favicon() -> MyResult<File> { - Ok(File::open(CONF.asset_path.join("favicon.ico")).await?) +pub async fn r_favicon(s: &rocket::State<Arc<State>>) -> MyResult<File> { + Ok(File::open(s.config.asset_path.join("favicon.ico")).await?) } pub struct Defer(Pin<Box<dyn Future<Output = String> + Send>>); diff --git a/server/src/ui/node.rs b/server/src/ui/node.rs index 85beac6..cf5c793 100644 --- a/server/src/ui/node.rs +++ b/server/src/ui/node.rs @@ -5,42 +5,12 @@ */ use super::error::MyResult; use crate::request_info::RequestInfo; -use rocket::{Either, get, response::content::RawHtml, serde::json::Json}; +use jellycommon::jellyobject::Object; +use jellyui::render_view; +use rocket::{get, response::content::RawHtml}; -#[get("/n/<id>?<parents>&<children>&<filter..>")] -pub async fn r_node( - ri: RequestInfo<'_>, - id: A<NodeID>, - filter: Option<ANodeFilterSort>, - parents: bool, - children: bool, -) -> MyResult<Either<RawHtml<String>, Json<ApiNodeResponse>>> { - let filter: Option<NodeFilterSort> = filter.map(Into::into); - let filter = filter.unwrap_or_default(); - - let r = get_node( - &ri.session, - id.0, - !ri.accept.is_json() || children, - !ri.accept.is_json() || parents, - filter.clone(), - )?; - - Ok(if ri.accept.is_json() { - Either::Right(Json(r)) - } else { - Either::Left(RawHtml(render_page( - &NodePage { - node: &r.node, - udata: &r.userdata, - children: &r.children, - parents: &r.parents, - similar: &[], - filter: &filter, - lang: &ri.lang, - player: false, - }, - ri.render_info(), - ))) - }) +#[get("/n/<slug>")] +pub fn r_node(ri: RequestInfo<'_>, slug: &str) -> MyResult<RawHtml<String>> { + ri.require_user()?; + Ok(RawHtml(render_view(ri.render_info(), Object::EMPTY))) } diff --git a/server/src/ui/search.rs b/server/src/ui/search.rs index fce79e3..8ec2697 100644 --- a/server/src/ui/search.rs +++ b/server/src/ui/search.rs @@ -10,27 +10,28 @@ use rocket::{Either, get, response::content::RawHtml, serde::json::Json}; #[get("/search?<query>&<page>")] pub async fn r_search( - ri: RequestInfo, + ri: RequestInfo<'_>, query: Option<&str>, page: Option<usize>, -) -> MyResult<Either<RawHtml<String>, Json<ApiSearchResponse>>> { - let r = query - .map(|query| search(&ri.session, query, page)) - .transpose()?; +) -> MyResult<RawHtml<String>> { + // let r = query + // .map(|query| search(&ri.session, query, page)) + // .transpose()?; - Ok(if ri.accept.is_json() { - let Some(r) = r else { - Err(anyhow!("no query"))? - }; - Either::Right(Json(r)) - } else { - Either::Left(RawHtml(render_page( - &SearchPage { - lang: &ri.lang, - query: &query.map(|s| s.to_string()), - r, - }, - ri.render_info(), - ))) - }) + // Ok(if ri.accept.is_json() { + // let Some(r) = r else { + // Err(anyhow!("no query"))? + // }; + // Either::Right(Json(r)) + // } else { + // Either::Left(RawHtml(render_page( + // &SearchPage { + // lang: &ri.lang, + // query: &query.map(|s| s.to_string()), + // r, + // }, + // ri.render_info(), + // ))) + // }) + todo!() } diff --git a/server/src/ui/stats.rs b/server/src/ui/stats.rs index fc4ae64..387ca63 100644 --- a/server/src/ui/stats.rs +++ b/server/src/ui/stats.rs @@ -3,22 +3,10 @@ which is licensed under the GNU Affero General Public License (version 3); see /COPYING. Copyright (C) 2026 metamuffin <metamuffin.org> */ -use super::error::MyError; -use crate::request_info::RequestInfo; -use rocket::{Either, get, response::content::RawHtml, serde::json::Json}; +use crate::{request_info::RequestInfo, ui::error::MyResult}; +use rocket::{get, response::content::RawHtml}; #[get("/stats")] -pub fn r_stats( - ri: RequestInfo, -) -> Result<Either<RawHtml<String>, Json<ApiStatsResponse>>, MyError> { - let r = stats(&ri.session)?; - - Ok(if ri.accept.is_json() { - Either::Right(Json(r)) - } else { - Either::Left(RawHtml(render_page( - &StatsPage { lang: &ri.lang, r }, - ri.render_info(), - ))) - }) +pub fn r_stats(ri: RequestInfo) -> MyResult<RawHtml<String>> { + todo!() } |