aboutsummaryrefslogtreecommitdiff
path: root/server/src
diff options
context:
space:
mode:
Diffstat (limited to 'server/src')
-rw-r--r--server/src/api.rs1
-rw-r--r--server/src/compat/jellyfin/mod.rs2
-rw-r--r--server/src/compat/mod.rs2
-rw-r--r--server/src/compat/youtube.rs2
-rw-r--r--server/src/config.rs53
-rw-r--r--server/src/helper/accept.rs72
-rw-r--r--server/src/helper/asset.rs37
-rw-r--r--server/src/helper/filter_sort.rs162
-rw-r--r--server/src/helper/language.rs60
-rw-r--r--server/src/helper/mod.rs69
-rw-r--r--server/src/helper/node_id.rs17
-rw-r--r--server/src/helper/session.rs67
-rw-r--r--server/src/logic/playersync.rs2
-rw-r--r--server/src/logic/stream.rs2
-rw-r--r--server/src/logic/userdata.rs2
-rw-r--r--server/src/main.rs28
-rw-r--r--server/src/request_info.rs128
-rw-r--r--server/src/request_info/session.rs15
-rw-r--r--server/src/responders/cache.rs (renamed from server/src/helper/cache.rs)0
-rw-r--r--server/src/responders/cors.rs (renamed from server/src/helper/cors.rs)0
-rw-r--r--server/src/responders/mod.rs8
-rw-r--r--server/src/routes.rs100
-rw-r--r--server/src/ui/account/mod.rs2
-rw-r--r--server/src/ui/account/settings.rs2
-rw-r--r--server/src/ui/admin/import.rs2
-rw-r--r--server/src/ui/admin/log.rs2
-rw-r--r--server/src/ui/admin/mod.rs2
-rw-r--r--server/src/ui/admin/user.rs2
-rw-r--r--server/src/ui/assets.rs2
-rw-r--r--server/src/ui/home.rs2
-rw-r--r--server/src/ui/items.rs2
-rw-r--r--server/src/ui/mod.rs2
-rw-r--r--server/src/ui/node.rs2
-rw-r--r--server/src/ui/player.rs2
-rw-r--r--server/src/ui/search.rs2
-rw-r--r--server/src/ui/stats.rs2
36 files changed, 256 insertions, 601 deletions
diff --git a/server/src/api.rs b/server/src/api.rs
index fe68b1a..45bcd90 100644
--- a/server/src/api.rs
+++ b/server/src/api.rs
@@ -4,7 +4,6 @@
Copyright (C) 2026 metamuffin <metamuffin.org>
*/
use super::ui::error::MyResult;
-use crate::helper::A;
use rocket::{get, post, response::Redirect, serde::json::Json};
use serde_json::{Value, json};
diff --git a/server/src/compat/jellyfin/mod.rs b/server/src/compat/jellyfin/mod.rs
index db60530..8fa44cb 100644
--- a/server/src/compat/jellyfin/mod.rs
+++ b/server/src/compat/jellyfin/mod.rs
@@ -5,7 +5,7 @@
*/
pub mod models;
-use crate::{helper::A, ui::error::MyResult};
+use crate::{request_helpers::A, ui::error::MyResult};
use anyhow::anyhow;
use jellycommon::{
api::{NodeFilterSort, SortOrder, SortProperty},
diff --git a/server/src/compat/mod.rs b/server/src/compat/mod.rs
index 85fb566..859b60a 100644
--- a/server/src/compat/mod.rs
+++ b/server/src/compat/mod.rs
@@ -3,5 +3,5 @@
which is licensed under the GNU Affero General Public License (version 3); see /COPYING.
Copyright (C) 2026 metamuffin <metamuffin.org>
*/
-pub mod jellyfin;
+// pub mod jellyfin;
pub mod youtube;
diff --git a/server/src/compat/youtube.rs b/server/src/compat/youtube.rs
index ef9e09d..5e86014 100644
--- a/server/src/compat/youtube.rs
+++ b/server/src/compat/youtube.rs
@@ -3,7 +3,7 @@
which is licensed under the GNU Affero General Public License (version 3); see /COPYING.
Copyright (C) 2026 metamuffin <metamuffin.org>
*/
-use crate::{helper::A, ui::error::MyResult};
+use crate::{request_info::A, ui::error::MyResult};
use anyhow::anyhow;
use jellycommon::{
routes::{u_node_slug, u_node_slug_player},
diff --git a/server/src/config.rs b/server/src/config.rs
index f552306..73d6b73 100644
--- a/server/src/config.rs
+++ b/server/src/config.rs
@@ -6,20 +6,33 @@
use anyhow::{Context, Result, anyhow};
use jellycache::init_cache;
-use jellylogic::init_database;
use serde::Deserialize;
-use std::env::{args, var};
+use std::{
+ env::{args, var},
+ path::PathBuf,
+ sync::{LazyLock, Mutex},
+};
use tokio::fs::read_to_string;
+static CONF_PRELOAD: Mutex<Option<AllConfigs>> = Mutex::new(None);
+pub static CONF: LazyLock<AllConfigs> =
+ LazyLock::new(|| CONF_PRELOAD.lock().unwrap().take().unwrap());
+
+pub struct AllConfigs {
+ pub ui: jellyui::Config,
+ pub transcoder: jellytranscoder::Config,
+ pub stream: jellystream::Config,
+ pub cache: jellycache::Config,
+ pub import: jellyimport::Config,
+ pub server: Config,
+}
+
#[derive(Debug, Deserialize)]
-struct Config {
- transcoder: jellytranscoder::Config,
- ui: jellyui::Config,
- stream: jellystream::Config,
- cache: jellycache::Config,
- server: crate::Config,
- logic: jellylogic::Config,
- import: jellyimport::Config,
+pub struct Config {
+ pub asset_path: PathBuf,
+ pub cookie_key: Option<String>,
+ pub tls: bool,
+ pub hostname: String,
}
pub async fn load_config() -> Result<()> {
@@ -30,19 +43,17 @@ pub async fn load_config() -> Result<()> {
"No config supplied. Use first argument or JELLYTHING_CONFIG environment variable."
))?;
- let config_raw = read_to_string(path).await.context("reading main config")?;
- let config: Config = serde_yaml_ng::from_str(&config_raw).context("parsing main config")?;
-
- *jellystream::CONF_PRELOAD.lock().unwrap() = Some(config.stream);
- *jellytranscoder::CONF_PRELOAD.lock().unwrap() = Some(config.transcoder);
- *jellycache::CONF_PRELOAD.lock().unwrap() = Some(config.cache);
- *jellylogic::CONF_PRELOAD.lock().unwrap() = Some(config.logic);
- *jellyimport::CONF_PRELOAD.lock().unwrap() = Some(config.import);
- *crate::CONF_PRELOAD.lock().unwrap() = Some(config.server);
- *jellyui::CONF_PRELOAD.lock().unwrap() = Some(config.ui);
+ let config = read_to_string(path).await.context("reading main config")?;
+ *CONF_PRELOAD.lock().unwrap() = Some(AllConfigs {
+ ui: serde_yaml_ng::from_str(&config).context("ui config")?,
+ transcoder: serde_yaml_ng::from_str(&config).context("transcoder config")?,
+ stream: serde_yaml_ng::from_str(&config).context("stream config")?,
+ cache: serde_yaml_ng::from_str(&config).context("cache config")?,
+ import: serde_yaml_ng::from_str(&config).context("import config")?,
+ server: serde_yaml_ng::from_str(&config).context("server config")?,
+ });
init_cache()?;
- init_database()?;
Ok(())
}
diff --git a/server/src/helper/accept.rs b/server/src/helper/accept.rs
deleted file mode 100644
index cbbc843..0000000
--- a/server/src/helper/accept.rs
+++ /dev/null
@@ -1,72 +0,0 @@
-/*
- This file is part of jellything (https://codeberg.org/metamuffin/jellything)
- which is licensed under the GNU Affero General Public License (version 3); see /COPYING.
- Copyright (C) 2026 metamuffin <metamuffin.org>
-*/
-use rocket::{
- http::MediaType,
- outcome::Outcome,
- request::{self, FromRequest},
- Request,
-};
-use std::ops::Deref;
-
-#[derive(Debug, Default)]
-pub enum Accept {
- #[default]
- Other,
- Json,
- Image,
- Media,
- Html,
-}
-impl Accept {
- pub fn from_request_ut(request: &Request) -> Self {
- if let Some(a) = request.accept() {
- if a.preferred().exact_eq(&MediaType::JSON) {
- Accept::Json
- } else {
- Accept::Other
- }
- } else {
- Accept::Other
- }
- }
-
- pub fn is_json(&self) -> bool {
- matches!(self, Self::Json)
- }
-}
-
-pub struct AcceptJson(bool);
-impl Deref for AcceptJson {
- type Target = bool;
- fn deref(&self) -> &Self::Target {
- &self.0
- }
-}
-impl<'r> FromRequest<'r> for AcceptJson {
- type Error = ();
-
- fn from_request<'life0, 'async_trait>(
- request: &'r Request<'life0>,
- ) -> ::core::pin::Pin<
- Box<
- dyn ::core::future::Future<Output = request::Outcome<Self, Self::Error>>
- + ::core::marker::Send
- + 'async_trait,
- >,
- >
- where
- 'r: 'async_trait,
- 'life0: 'async_trait,
- Self: 'async_trait,
- {
- Box::pin(async move {
- Outcome::Success(AcceptJson(matches!(
- Accept::from_request_ut(request),
- Accept::Json
- )))
- })
- }
-}
diff --git a/server/src/helper/asset.rs b/server/src/helper/asset.rs
deleted file mode 100644
index ac58687..0000000
--- a/server/src/helper/asset.rs
+++ /dev/null
@@ -1,37 +0,0 @@
-/*
- This file is part of jellything (https://codeberg.org/metamuffin/jellything)
- which is licensed under the GNU Affero General Public License (version 3); see /COPYING.
- Copyright (C) 2026 metamuffin <metamuffin.org>
-*/
-
-use crate::helper::A;
-use jellycommon::Asset;
-use rocket::{
- http::uri::fmt::{FromUriParam, Path, UriDisplay},
- request::{FromParam, FromSegments},
-};
-use std::fmt::Write;
-
-impl<'a> FromParam<'a> for A<Asset> {
- type Error = ();
- fn from_param(param: &'a str) -> Result<Self, Self::Error> {
- Ok(A(Asset(param.to_owned())))
- }
-}
-impl UriDisplay<Path> for A<Asset> {
- fn fmt(&self, f: &mut rocket::http::uri::fmt::Formatter<'_, Path>) -> std::fmt::Result {
- write!(f, "{}", self.0 .0)
- }
-}
-impl FromUriParam<Path, Asset> for A<Asset> {
- type Target = A<Asset>;
- fn from_uri_param(param: Asset) -> Self::Target {
- A(param)
- }
-}
-impl<'r> FromSegments<'r> for A<Asset> {
- type Error = ();
- fn from_segments(segments: rocket::http::uri::Segments<'r, Path>) -> Result<Self, Self::Error> {
- Ok(A(Asset(segments.collect::<Vec<_>>().join("/"))))
- }
-}
diff --git a/server/src/helper/filter_sort.rs b/server/src/helper/filter_sort.rs
deleted file mode 100644
index 7d66b38..0000000
--- a/server/src/helper/filter_sort.rs
+++ /dev/null
@@ -1,162 +0,0 @@
-/*
- This file is part of jellything (https://codeberg.org/metamuffin/jellything)
- which is licensed under the GNU Affero General Public License (version 3); see /COPYING.
- Copyright (C) 2026 metamuffin <metamuffin.org>
-*/
-
-use super::A;
-use jellycommon::{
- api::{FilterProperty, NodeFilterSort, SortOrder, SortProperty},
- user::{PlayerKind, Theme},
-};
-use rocket::{
- async_trait,
- form::{DataField, FromForm, FromFormField, Result, ValueField},
- UriDisplayQuery,
-};
-
-impl From<ANodeFilterSort> for NodeFilterSort {
- fn from(val: ANodeFilterSort) -> Self {
- NodeFilterSort {
- sort_by: val.sort_by.map(|e| match e {
- ASortProperty::ReleaseDate => SortProperty::ReleaseDate,
- ASortProperty::Title => SortProperty::Title,
- ASortProperty::Index => SortProperty::Index,
- ASortProperty::Duration => SortProperty::Duration,
- ASortProperty::RatingRottenTomatoes => SortProperty::RatingRottenTomatoes,
- ASortProperty::RatingMetacritic => SortProperty::RatingMetacritic,
- ASortProperty::RatingImdb => SortProperty::RatingImdb,
- ASortProperty::RatingTmdb => SortProperty::RatingTmdb,
- ASortProperty::RatingYoutubeViews => SortProperty::RatingYoutubeViews,
- ASortProperty::RatingYoutubeLikes => SortProperty::RatingYoutubeLikes,
- ASortProperty::RatingYoutubeFollowers => SortProperty::RatingYoutubeFollowers,
- ASortProperty::RatingUser => SortProperty::RatingUser,
- ASortProperty::RatingLikesDivViews => SortProperty::RatingLikesDivViews,
- }),
- filter_kind: val.filter_kind.map(|l| {
- l.into_iter()
- .map(|e| match e {
- AFilterProperty::FederationLocal => FilterProperty::FederationLocal,
- AFilterProperty::FederationRemote => FilterProperty::FederationRemote,
- AFilterProperty::Watched => FilterProperty::Watched,
- AFilterProperty::Unwatched => FilterProperty::Unwatched,
- AFilterProperty::WatchProgress => FilterProperty::WatchProgress,
- AFilterProperty::KindMovie => FilterProperty::KindMovie,
- AFilterProperty::KindVideo => FilterProperty::KindVideo,
- AFilterProperty::KindShortFormVideo => FilterProperty::KindShortFormVideo,
- AFilterProperty::KindMusic => FilterProperty::KindMusic,
- AFilterProperty::KindCollection => FilterProperty::KindCollection,
- AFilterProperty::KindChannel => FilterProperty::KindChannel,
- AFilterProperty::KindShow => FilterProperty::KindShow,
- AFilterProperty::KindSeries => FilterProperty::KindSeries,
- AFilterProperty::KindSeason => FilterProperty::KindSeason,
- AFilterProperty::KindEpisode => FilterProperty::KindEpisode,
- })
- .collect()
- }),
- sort_order: val.sort_order.map(|e| match e {
- ASortOrder::Ascending => SortOrder::Ascending,
- ASortOrder::Descending => SortOrder::Descending,
- }),
- }
- }
-}
-
-#[async_trait]
-impl<'v> FromFormField<'v> for A<Theme> {
- fn from_value(field: ValueField<'v>) -> Result<'v, Self> {
- Err(field.unexpected())?
- }
- async fn from_data(field: DataField<'v, '_>) -> Result<'v, Self> {
- Err(field.unexpected())?
- }
-}
-
-#[async_trait]
-impl<'v> FromFormField<'v> for A<PlayerKind> {
- fn from_value(field: ValueField<'v>) -> Result<'v, Self> {
- Err(field.unexpected())?
- }
- async fn from_data(field: DataField<'v, '_>) -> Result<'v, Self> {
- Err(field.unexpected())?
- }
-}
-
-#[derive(FromForm, UriDisplayQuery, Clone)]
-pub struct ANodeFilterSort {
- sort_by: Option<ASortProperty>,
- filter_kind: Option<Vec<AFilterProperty>>,
- sort_order: Option<ASortOrder>,
-}
-
-#[derive(FromFormField, UriDisplayQuery, Clone)]
-enum AFilterProperty {
- #[field(value = "fed_local")]
- FederationLocal,
- #[field(value = "fed_remote")]
- FederationRemote,
- #[field(value = "watched")]
- Watched,
- #[field(value = "unwatched")]
- Unwatched,
- #[field(value = "watch_progress")]
- WatchProgress,
- #[field(value = "kind_movie")]
- KindMovie,
- #[field(value = "kind_video")]
- KindVideo,
- #[field(value = "kind_short_form_video")]
- KindShortFormVideo,
- #[field(value = "kind_music")]
- KindMusic,
- #[field(value = "kind_collection")]
- KindCollection,
- #[field(value = "kind_channel")]
- KindChannel,
- #[field(value = "kind_show")]
- KindShow,
- #[field(value = "kind_series")]
- KindSeries,
- #[field(value = "kind_season")]
- KindSeason,
- #[field(value = "kind_episode")]
- KindEpisode,
-}
-
-#[derive(FromFormField, UriDisplayQuery, Clone)]
-enum ASortProperty {
- #[field(value = "release_date")]
- ReleaseDate,
- #[field(value = "title")]
- Title,
- #[field(value = "index")]
- Index,
- #[field(value = "duration")]
- Duration,
- #[field(value = "rating_rt")]
- RatingRottenTomatoes,
- #[field(value = "rating_mc")]
- RatingMetacritic,
- #[field(value = "rating_imdb")]
- RatingImdb,
- #[field(value = "rating_tmdb")]
- RatingTmdb,
- #[field(value = "rating_yt_views")]
- RatingYoutubeViews,
- #[field(value = "rating_yt_likes")]
- RatingYoutubeLikes,
- #[field(value = "rating_yt_followers")]
- RatingYoutubeFollowers,
- #[field(value = "rating_user")]
- RatingUser,
- #[field(value = "rating_loved")]
- RatingLikesDivViews,
-}
-
-#[derive(FromFormField, UriDisplayQuery, Clone)]
-enum ASortOrder {
- #[field(value = "ascending")]
- Ascending,
- #[field(value = "descending")]
- Descending,
-}
diff --git a/server/src/helper/language.rs b/server/src/helper/language.rs
deleted file mode 100644
index e106e12..0000000
--- a/server/src/helper/language.rs
+++ /dev/null
@@ -1,60 +0,0 @@
-/*
- This file is part of jellything (https://codeberg.org/metamuffin/jellything)
- which is licensed under the GNU Affero General Public License (version 3); see /COPYING.
- Copyright (C) 2026 metamuffin <metamuffin.org>
-*/
-use jellyui::locale::Language;
-use rocket::{
- outcome::Outcome,
- request::{self, FromRequest},
- Request,
-};
-use std::ops::Deref;
-
-pub struct AcceptLanguage(pub Language);
-impl Deref for AcceptLanguage {
- type Target = Language;
- fn deref(&self) -> &Self::Target {
- &self.0
- }
-}
-impl<'r> FromRequest<'r> for AcceptLanguage {
- type Error = ();
-
- fn from_request<'life0, 'async_trait>(
- request: &'r Request<'life0>,
- ) -> ::core::pin::Pin<
- Box<
- dyn ::core::future::Future<Output = request::Outcome<Self, Self::Error>>
- + ::core::marker::Send
- + 'async_trait,
- >,
- >
- where
- 'r: 'async_trait,
- 'life0: 'async_trait,
- Self: 'async_trait,
- {
- Box::pin(async move { Outcome::Success(AcceptLanguage(lang_from_request(request))) })
- }
-}
-
-pub(crate) fn lang_from_request(request: &Request) -> Language {
- request
- .headers()
- .get_one("accept-language")
- .and_then(|h| {
- h.split(",")
- .filter_map(|e| {
- let code = e.split(";").next()?;
- let code = code.split_once("-").unwrap_or((code, "")).0;
- match code {
- "en" => Some(Language::English),
- "de" => Some(Language::German),
- _ => None,
- }
- })
- .next()
- })
- .unwrap_or(Language::English)
-}
diff --git a/server/src/helper/mod.rs b/server/src/helper/mod.rs
deleted file mode 100644
index f52fcac..0000000
--- a/server/src/helper/mod.rs
+++ /dev/null
@@ -1,69 +0,0 @@
-/*
- This file is part of jellything (https://codeberg.org/metamuffin/jellything)
- which is licensed under the GNU Affero General Public License (version 3); see /COPYING.
- Copyright (C) 2026 metamuffin <metamuffin.org>
-*/
-pub mod accept;
-pub mod cache;
-pub mod cors;
-pub mod filter_sort;
-pub mod language;
-pub mod node_id;
-pub mod session;
-pub mod asset;
-
-use crate::ui::error::{MyError, MyResult};
-use accept::Accept;
-use jellyimport::is_importing;
-use jellylogic::session::Session;
-use jellyui::{
- locale::Language,
- scaffold::{RenderInfo, SessionInfo},
-};
-use language::lang_from_request;
-use rocket::{
- async_trait,
- http::Status,
- request::{FromRequest, Outcome},
- Request,
-};
-use session::session_from_request;
-
-#[derive(Debug, Clone, Copy, Default)]
-pub struct A<T>(pub T);
-
-pub struct RequestInfo {
- pub lang: Language,
- pub accept: Accept,
- pub session: Session,
-}
-
-impl RequestInfo {
- pub async fn from_request_ut(request: &Request<'_>) -> MyResult<Self> {
- Ok(Self {
- lang: lang_from_request(request),
- accept: Accept::from_request_ut(request),
- session: session_from_request(request).await?,
- })
- }
- pub fn render_info(&self) -> RenderInfo {
- RenderInfo {
- importing: is_importing(),
- session: Some(SessionInfo {
- user: self.session.user.clone(), // TODO no clone?
- }),
- lang: self.lang,
- }
- }
-}
-
-#[async_trait]
-impl<'r> FromRequest<'r> for RequestInfo {
- type Error = MyError;
- async fn from_request(request: &'r Request<'_>) -> Outcome<Self, Self::Error> {
- match Self::from_request_ut(request).await {
- Ok(a) => Outcome::Success(a),
- Err(a) => Outcome::Error((Status::BadRequest, a)),
- }
- }
-}
diff --git a/server/src/helper/node_id.rs b/server/src/helper/node_id.rs
deleted file mode 100644
index 5c2e52a..0000000
--- a/server/src/helper/node_id.rs
+++ /dev/null
@@ -1,17 +0,0 @@
-/*
- This file is part of jellything (https://codeberg.org/metamuffin/jellything)
- which is licensed under the GNU Affero General Public License (version 3); see /COPYING.
- Copyright (C) 2026 metamuffin <metamuffin.org>
-*/
-
-use super::A;
-use jellycommon::NodeID;
-use rocket::request::FromParam;
-use std::str::FromStr;
-
-impl<'a> FromParam<'a> for A<NodeID> {
- type Error = ();
- fn from_param(param: &'a str) -> Result<Self, Self::Error> {
- NodeID::from_str(param).map_err(|_| ()).map(A)
- }
-}
diff --git a/server/src/helper/session.rs b/server/src/helper/session.rs
deleted file mode 100644
index 61f6d66..0000000
--- a/server/src/helper/session.rs
+++ /dev/null
@@ -1,67 +0,0 @@
-/*
- This file is part of jellything (https://codeberg.org/metamuffin/jellything)
- which is licensed under the GNU Affero General Public License (version 3); see /COPYING.
- Copyright (C) 2026 metamuffin <metamuffin.org>
-*/
-use super::A;
-use crate::ui::error::MyError;
-use anyhow::anyhow;
-use jellylogic::session::{bypass_auth_session, token_to_session, Session};
-use log::warn;
-use rocket::{
- async_trait,
- http::Status,
- outcome::Outcome,
- request::{self, FromRequest},
- Request,
-};
-
-pub(super) async fn session_from_request(req: &Request<'_>) -> Result<Session, MyError> {
- if cfg!(feature = "bypass-auth") {
- Ok(bypass_auth_session()?)
- } else {
- let token = req
- .query_value("session")
- .map(|e| e.unwrap())
- .or_else(|| req.query_value("api_key").map(|e| e.unwrap()))
- .or_else(|| req.headers().get_one("X-MediaBrowser-Token"))
- .or_else(|| {
- req.headers()
- .get_one("Authorization")
- .and_then(parse_jellyfin_auth)
- }) // for jellyfin compat
- .or(req.cookies().get("session").map(|cookie| cookie.value()))
- .ok_or(anyhow!("not logged in"))?;
-
- // jellyfin urlescapes the token for *some* requests
- let token = token.replace("%3D", "=");
- Ok(token_to_session(&token)?)
- }
-}
-
-fn parse_jellyfin_auth(h: &str) -> Option<&str> {
- for tok in h.split(" ") {
- if let Some(tok) = tok.strip_prefix("Token=\"") {
- if let Some(tok) = tok.strip_suffix("\"") {
- return Some(tok);
- }
- }
- }
- None
-}
-
-#[async_trait]
-impl<'r> FromRequest<'r> for A<Session> {
- type Error = MyError;
- async fn from_request<'life0>(
- request: &'r Request<'life0>,
- ) -> request::Outcome<Self, Self::Error> {
- match session_from_request(request).await {
- Ok(x) => Outcome::Success(A(x)),
- Err(e) => {
- warn!("authentificated route rejected: {e:?}");
- Outcome::Forward(Status::Unauthorized)
- }
- }
- }
-}
diff --git a/server/src/logic/playersync.rs b/server/src/logic/playersync.rs
index b4cc51b..6c1f9f4 100644
--- a/server/src/logic/playersync.rs
+++ b/server/src/logic/playersync.rs
@@ -7,7 +7,7 @@ use rocket_ws::{stream::DuplexStream, Channel, Message, WebSocket};
use serde::{Deserialize, Serialize};
use tokio::sync::broadcast::{self, Sender};
-use crate::helper::cors::Cors;
+use crate::request_info::cors::Cors;
#[derive(Default)]
pub struct PlayersyncChannels {
diff --git a/server/src/logic/stream.rs b/server/src/logic/stream.rs
index 36d2ec1..55d6850 100644
--- a/server/src/logic/stream.rs
+++ b/server/src/logic/stream.rs
@@ -3,7 +3,7 @@
which is licensed under the GNU Affero General Public License (version 3); see /COPYING.
Copyright (C) 2026 metamuffin <metamuffin.org>
*/
-use crate::{helper::A, ui::error::MyError};
+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};
diff --git a/server/src/logic/userdata.rs b/server/src/logic/userdata.rs
index 2dd3a85..104de4a 100644
--- a/server/src/logic/userdata.rs
+++ b/server/src/logic/userdata.rs
@@ -3,7 +3,7 @@
which is licensed under the GNU Affero General Public License (version 3); see /COPYING.
Copyright (C) 2026 metamuffin <metamuffin.org>
*/
-use crate::{helper::A, ui::error::MyResult};
+use crate::{request_info::A, ui::error::MyResult};
use jellycommon::{
api::NodeFilterSort,
routes::u_node_id,
diff --git a/server/src/main.rs b/server/src/main.rs
index e17083b..be1aba4 100644
--- a/server/src/main.rs
+++ b/server/src/main.rs
@@ -11,36 +11,18 @@ use crate::logger::setup_logger;
use config::load_config;
use log::{error, info, warn};
use routes::build_rocket;
-use serde::{Deserialize, Serialize};
-use std::sync::Mutex;
-use std::{path::PathBuf, process::exit, sync::LazyLock};
+use std::process::exit;
pub mod api;
pub mod compat;
pub mod config;
-pub mod helper;
pub mod logger;
pub mod logic;
+pub mod request_info;
+pub mod responders;
pub mod routes;
pub mod ui;
-#[derive(Debug, Deserialize, Serialize, Default)]
-pub struct Config {
- asset_path: PathBuf,
- cookie_key: Option<String>,
- tls: bool,
- hostname: String,
-}
-
-pub static CONF_PRELOAD: Mutex<Option<Config>> = Mutex::new(None);
-static CONF: LazyLock<Config> = LazyLock::new(|| {
- CONF_PRELOAD
- .lock()
- .unwrap()
- .take()
- .expect("cache config not preloaded. logic error")
-});
-
#[rocket::main]
async fn main() {
setup_logger();
@@ -53,10 +35,6 @@ async fn main() {
#[cfg(feature = "bypass-auth")]
logger::warn!("authentification bypass enabled");
- if let Err(e) = create_admin_account() {
- error!("failed to create admin account: {e:?}");
- }
-
let r = build_rocket().launch().await;
match r {
Ok(_) => warn!("server shutdown"),
diff --git a/server/src/request_info.rs b/server/src/request_info.rs
new file mode 100644
index 0000000..779b4e1
--- /dev/null
+++ b/server/src/request_info.rs
@@ -0,0 +1,128 @@
+/*
+ This file is part of jellything (https://codeberg.org/metamuffin/jellything)
+ which is licensed under the GNU Affero General Public License (version 3); see /COPYING.
+ Copyright (C) 2026 metamuffin <metamuffin.org>
+*/
+
+use crate::ui::error::{MyError, MyResult};
+use anyhow::anyhow;
+use jellycommon::jellyobject::ObjectBuffer;
+use jellyui::RenderInfo;
+use rocket::{
+ Request, async_trait,
+ http::{MediaType, Status},
+ request::{FromRequest, Outcome},
+};
+
+pub struct RequestInfo<'a> {
+ pub lang: &'a str,
+ pub accept: Accept,
+ pub user: Option<ObjectBuffer>,
+}
+
+#[async_trait]
+impl<'r> FromRequest<'r> for RequestInfo<'r> {
+ type Error = MyError;
+ async fn from_request(request: &'r Request<'_>) -> Outcome<Self, Self::Error> {
+ match Self::from_request_ut(request).await {
+ Ok(a) => Outcome::Success(a),
+ Err(a) => Outcome::Error((Status::BadRequest, a)),
+ }
+ }
+}
+
+impl<'a> RequestInfo<'a> {
+ pub async fn from_request_ut(request: &'a Request<'_>) -> MyResult<Self> {
+ Ok(Self {
+ lang: accept_language(request),
+ accept: Accept::from_request_ut(request),
+ user: None,
+ // session: session_from_request(request).await?,
+ })
+ }
+ pub fn render_info(&self) -> RenderInfo<'a> {
+ RenderInfo {
+ lang: self.lang,
+ status_message: None,
+ user: self.user.as_ref().map(|u| u.as_object()),
+ config: CONF.ui,
+ }
+ }
+}
+
+#[derive(Debug, Default)]
+pub enum Accept {
+ #[default]
+ Other,
+ Json,
+ Image,
+ Media,
+ Html,
+}
+impl Accept {
+ pub fn from_request_ut(request: &Request) -> Self {
+ if let Some(a) = request.accept() {
+ if a.preferred().exact_eq(&MediaType::JSON) {
+ Accept::Json
+ } else {
+ Accept::Other
+ }
+ } else {
+ Accept::Other
+ }
+ }
+
+ pub fn is_json(&self) -> bool {
+ matches!(self, Self::Json)
+ }
+}
+
+pub(super) fn accept_language<'a>(request: &'a Request<'_>) -> &'a str {
+ request
+ .headers()
+ .get_one("accept-language")
+ .and_then(|h| {
+ h.split(",")
+ .filter_map(|e| {
+ let code = e.split(";").next()?;
+ let code = code.split_once("-").unwrap_or((code, "")).0;
+ Some(code)
+ })
+ .next()
+ })
+ .unwrap_or("en")
+}
+
+pub(super) async fn session_from_request(req: &Request<'_>) -> Result<Session, MyError> {
+ if cfg!(feature = "bypass-auth") {
+ Ok(bypass_auth_session()?)
+ } else {
+ let token = req
+ .query_value("session")
+ .map(|e| e.unwrap())
+ .or_else(|| req.query_value("api_key").map(|e| e.unwrap()))
+ .or_else(|| req.headers().get_one("X-MediaBrowser-Token"))
+ .or_else(|| {
+ req.headers()
+ .get_one("Authorization")
+ .and_then(parse_jellyfin_auth)
+ }) // for jellyfin compat
+ .or(req.cookies().get("session").map(|cookie| cookie.value()))
+ .ok_or(anyhow!("not logged in"))?;
+
+ // jellyfin urlescapes the token for *some* requests
+ let token = token.replace("%3D", "=");
+ Ok(token_to_session(&token)?)
+ }
+}
+
+fn parse_jellyfin_auth(h: &str) -> Option<&str> {
+ for tok in h.split(" ") {
+ if let Some(tok) = tok.strip_prefix("Token=\"")
+ && let Some(tok) = tok.strip_suffix("\"")
+ {
+ return Some(tok);
+ }
+ }
+ None
+}
diff --git a/server/src/request_info/session.rs b/server/src/request_info/session.rs
new file mode 100644
index 0000000..d032659
--- /dev/null
+++ b/server/src/request_info/session.rs
@@ -0,0 +1,15 @@
+/*
+ This file is part of jellything (https://codeberg.org/metamuffin/jellything)
+ which is licensed under the GNU Affero General Public License (version 3); see /COPYING.
+ Copyright (C) 2026 metamuffin <metamuffin.org>
+*/
+use super::A;
+use crate::ui::error::MyError;
+use anyhow::anyhow;
+use log::warn;
+use rocket::{
+ Request, async_trait,
+ http::Status,
+ outcome::Outcome,
+ request::{self, FromRequest},
+};
diff --git a/server/src/helper/cache.rs b/server/src/responders/cache.rs
index a943de8..a943de8 100644
--- a/server/src/helper/cache.rs
+++ b/server/src/responders/cache.rs
diff --git a/server/src/helper/cors.rs b/server/src/responders/cors.rs
index 875b1e5..875b1e5 100644
--- a/server/src/helper/cors.rs
+++ b/server/src/responders/cors.rs
diff --git a/server/src/responders/mod.rs b/server/src/responders/mod.rs
new file mode 100644
index 0000000..b62fe40
--- /dev/null
+++ b/server/src/responders/mod.rs
@@ -0,0 +1,8 @@
+/*
+ This file is part of jellything (https://codeberg.org/metamuffin/jellything)
+ which is licensed under the GNU Affero General Public License (version 3); see /COPYING.
+ Copyright (C) 2026 metamuffin <metamuffin.org>
+*/
+
+pub mod cache;
+pub mod cors;
diff --git a/server/src/routes.rs b/server/src/routes.rs
index 2d3e790..3b410f7 100644
--- a/server/src/routes.rs
+++ b/server/src/routes.rs
@@ -1,9 +1,9 @@
+use crate::config::CONF;
/*
This file is part of jellything (https://codeberg.org/metamuffin/jellything)
which is licensed under the GNU Affero General Public License (version 3); see /COPYING.
Copyright (C) 2026 metamuffin <metamuffin.org>
*/
-use crate::CONF;
use crate::logic::playersync::{PlayersyncChannels, r_playersync};
use crate::ui::account::{r_account_login, r_account_logout, r_account_register};
use crate::ui::admin::import::{r_admin_import, r_admin_import_post, r_admin_import_stream};
@@ -31,22 +31,22 @@ use crate::ui::{
use crate::{
api::{r_api_account_login, r_api_root, r_nodes_modified_since, r_version},
compat::{
- jellyfin::{
- r_jellyfin_artists, r_jellyfin_branding_configuration, r_jellyfin_branding_css,
- r_jellyfin_displaypreferences_usersettings,
- r_jellyfin_displaypreferences_usersettings_post, r_jellyfin_items,
- r_jellyfin_items_image_primary, r_jellyfin_items_images_backdrop,
- r_jellyfin_items_intros, r_jellyfin_items_item, r_jellyfin_items_playbackinfo,
- r_jellyfin_items_similar, r_jellyfin_livetv_programs_recommended, r_jellyfin_persons,
- r_jellyfin_playback_bitratetest, r_jellyfin_quickconnect_enabled,
- r_jellyfin_sessions_capabilities_full, r_jellyfin_sessions_playing,
- r_jellyfin_sessions_playing_progress, r_jellyfin_shows_nextup, r_jellyfin_socket,
- r_jellyfin_system_endpoint, r_jellyfin_system_info, r_jellyfin_system_info_public,
- r_jellyfin_system_info_public_case, r_jellyfin_users_authenticatebyname,
- r_jellyfin_users_authenticatebyname_case, r_jellyfin_users_id, r_jellyfin_users_items,
- r_jellyfin_users_items_item, r_jellyfin_users_public, r_jellyfin_users_views,
- r_jellyfin_video_stream,
- },
+ // jellyfin::{
+ // r_jellyfin_artists, r_jellyfin_branding_configuration, r_jellyfin_branding_css,
+ // r_jellyfin_displaypreferences_usersettings,
+ // r_jellyfin_displaypreferences_usersettings_post, r_jellyfin_items,
+ // r_jellyfin_items_image_primary, r_jellyfin_items_images_backdrop,
+ // r_jellyfin_items_intros, r_jellyfin_items_item, r_jellyfin_items_playbackinfo,
+ // r_jellyfin_items_similar, r_jellyfin_livetv_programs_recommended, r_jellyfin_persons,
+ // r_jellyfin_playback_bitratetest, r_jellyfin_quickconnect_enabled,
+ // r_jellyfin_sessions_capabilities_full, r_jellyfin_sessions_playing,
+ // r_jellyfin_sessions_playing_progress, r_jellyfin_shows_nextup, r_jellyfin_socket,
+ // r_jellyfin_system_endpoint, r_jellyfin_system_info, r_jellyfin_system_info_public,
+ // r_jellyfin_system_info_public_case, r_jellyfin_users_authenticatebyname,
+ // r_jellyfin_users_authenticatebyname_case, r_jellyfin_users_id, r_jellyfin_users_items,
+ // r_jellyfin_users_items_item, r_jellyfin_users_public, r_jellyfin_users_views,
+ // r_jellyfin_video_stream,
+ // },
youtube::{r_youtube_channel, r_youtube_embed, r_youtube_watch},
},
logic::{
@@ -161,39 +161,39 @@ pub fn build_rocket() -> Rocket<Build> {
r_api_root,
r_version,
// Compat
- r_jellyfin_artists,
- r_jellyfin_branding_configuration,
- r_jellyfin_branding_css,
- r_jellyfin_displaypreferences_usersettings_post,
- r_jellyfin_displaypreferences_usersettings,
- r_jellyfin_items_image_primary,
- r_jellyfin_items_images_backdrop,
- r_jellyfin_items_intros,
- r_jellyfin_items_item,
- r_jellyfin_items_playbackinfo,
- r_jellyfin_items_similar,
- r_jellyfin_items,
- r_jellyfin_livetv_programs_recommended,
- r_jellyfin_persons,
- r_jellyfin_playback_bitratetest,
- r_jellyfin_quickconnect_enabled,
- r_jellyfin_sessions_capabilities_full,
- r_jellyfin_sessions_playing_progress,
- r_jellyfin_sessions_playing,
- r_jellyfin_shows_nextup,
- r_jellyfin_socket,
- r_jellyfin_system_endpoint,
- r_jellyfin_system_info_public_case,
- r_jellyfin_system_info_public,
- r_jellyfin_system_info,
- r_jellyfin_users_authenticatebyname,
- r_jellyfin_users_authenticatebyname_case,
- r_jellyfin_users_id,
- r_jellyfin_users_items_item,
- r_jellyfin_users_items,
- r_jellyfin_users_public,
- r_jellyfin_users_views,
- r_jellyfin_video_stream,
+ // r_jellyfin_artists,
+ // r_jellyfin_branding_configuration,
+ // r_jellyfin_branding_css,
+ // r_jellyfin_displaypreferences_usersettings_post,
+ // r_jellyfin_displaypreferences_usersettings,
+ // r_jellyfin_items_image_primary,
+ // r_jellyfin_items_images_backdrop,
+ // r_jellyfin_items_intros,
+ // r_jellyfin_items_item,
+ // r_jellyfin_items_playbackinfo,
+ // r_jellyfin_items_similar,
+ // r_jellyfin_items,
+ // r_jellyfin_livetv_programs_recommended,
+ // r_jellyfin_persons,
+ // r_jellyfin_playback_bitratetest,
+ // r_jellyfin_quickconnect_enabled,
+ // r_jellyfin_sessions_capabilities_full,
+ // r_jellyfin_sessions_playing_progress,
+ // r_jellyfin_sessions_playing,
+ // r_jellyfin_shows_nextup,
+ // r_jellyfin_socket,
+ // r_jellyfin_system_endpoint,
+ // r_jellyfin_system_info_public_case,
+ // r_jellyfin_system_info_public,
+ // r_jellyfin_system_info,
+ // r_jellyfin_users_authenticatebyname,
+ // r_jellyfin_users_authenticatebyname_case,
+ // r_jellyfin_users_id,
+ // r_jellyfin_users_items_item,
+ // r_jellyfin_users_items,
+ // r_jellyfin_users_public,
+ // r_jellyfin_users_views,
+ // r_jellyfin_video_stream,
r_youtube_channel,
r_youtube_embed,
r_youtube_watch,
diff --git a/server/src/ui/account/mod.rs b/server/src/ui/account/mod.rs
index d731f0f..35cf52e 100644
--- a/server/src/ui/account/mod.rs
+++ b/server/src/ui/account/mod.rs
@@ -7,7 +7,7 @@ pub mod settings;
use super::error::MyError;
use crate::{
- helper::{language::AcceptLanguage, A},
+ request_info::{language::AcceptLanguage, A},
ui::{error::MyResult, home::rocket_uri_macro_r_home},
};
use anyhow::anyhow;
diff --git a/server/src/ui/account/settings.rs b/server/src/ui/account/settings.rs
index 491e82e..167731f 100644
--- a/server/src/ui/account/settings.rs
+++ b/server/src/ui/account/settings.rs
@@ -5,7 +5,7 @@
*/
use super::format_form_error;
use crate::{
- helper::{RequestInfo, A},
+ request_info::{RequestInfo, A},
ui::error::MyResult,
};
use jellycommon::{
diff --git a/server/src/ui/admin/import.rs b/server/src/ui/admin/import.rs
index 52add7f..fb33c67 100644
--- a/server/src/ui/admin/import.rs
+++ b/server/src/ui/admin/import.rs
@@ -7,7 +7,7 @@
use std::time::Duration;
use crate::{
- helper::{A, RequestInfo},
+ request_info::{A, RequestInfo},
ui::error::MyResult,
};
use jellycommon::routes::u_admin_import;
diff --git a/server/src/ui/admin/log.rs b/server/src/ui/admin/log.rs
index ef84704..e948daa 100644
--- a/server/src/ui/admin/log.rs
+++ b/server/src/ui/admin/log.rs
@@ -4,7 +4,7 @@
Copyright (C) 2026 metamuffin <metamuffin.org>
*/
use crate::{
- helper::{A, RequestInfo},
+ request_info::{A, RequestInfo},
ui::error::MyResult,
};
use jellylogic::{
diff --git a/server/src/ui/admin/mod.rs b/server/src/ui/admin/mod.rs
index 0cc226b..9759627 100644
--- a/server/src/ui/admin/mod.rs
+++ b/server/src/ui/admin/mod.rs
@@ -8,7 +8,7 @@ pub mod log;
pub mod user;
use super::error::MyResult;
-use crate::helper::RequestInfo;
+use crate::request_info::RequestInfo;
use jellycommon::routes::u_admin_dashboard;
use jellyimport::is_importing;
use jellylogic::admin::{create_invite, delete_invite, list_invites, update_search_index};
diff --git a/server/src/ui/admin/user.rs b/server/src/ui/admin/user.rs
index 3d56f46..03950df 100644
--- a/server/src/ui/admin/user.rs
+++ b/server/src/ui/admin/user.rs
@@ -3,7 +3,7 @@
which is licensed under the GNU Affero General Public License (version 3); see /COPYING.
Copyright (C) 2026 metamuffin <metamuffin.org>
*/
-use crate::{helper::RequestInfo, ui::error::MyResult};
+use crate::{request_info::RequestInfo, ui::error::MyResult};
use anyhow::Context;
use jellycommon::{
routes::{u_admin_user, u_admin_users},
diff --git a/server/src/ui/assets.rs b/server/src/ui/assets.rs
index 738d1de..6dc6731 100644
--- a/server/src/ui/assets.rs
+++ b/server/src/ui/assets.rs
@@ -4,7 +4,7 @@
Copyright (C) 2026 metamuffin <metamuffin.org>
*/
use super::error::MyResult;
-use crate::helper::{A, cache::CacheControlImage};
+use crate::request_info::{A, cache::CacheControlImage};
use anyhow::{Context, anyhow};
use jellycommon::{Asset, NodeID, PictureSlot, api::NodeFilterSort};
use jellylogic::{assets::get_node_thumbnail, node::get_node, session::Session};
diff --git a/server/src/ui/home.rs b/server/src/ui/home.rs
index db57880..499c3cd 100644
--- a/server/src/ui/home.rs
+++ b/server/src/ui/home.rs
@@ -5,7 +5,7 @@
*/
use super::error::MyResult;
-use crate::helper::{accept::Accept, RequestInfo};
+use crate::request_info::{accept::Accept, RequestInfo};
use jellycommon::api::ApiHomeResponse;
use jellyui::{home::HomePage, render_page};
use rocket::{get, response::content::RawHtml, serde::json::Json, Either};
diff --git a/server/src/ui/items.rs b/server/src/ui/items.rs
index bf99ef2..86aaf12 100644
--- a/server/src/ui/items.rs
+++ b/server/src/ui/items.rs
@@ -4,7 +4,7 @@
Copyright (C) 2026 metamuffin <metamuffin.org>
*/
use super::error::MyError;
-use crate::helper::{accept::Accept, filter_sort::ANodeFilterSort, RequestInfo};
+use crate::request_info::{accept::Accept, filter_sort::ANodeFilterSort, RequestInfo};
use jellycommon::api::ApiItemsResponse;
use jellylogic::items::all_items;
use jellyui::{items::ItemsPage, render_page};
diff --git a/server/src/ui/mod.rs b/server/src/ui/mod.rs
index aca6c33..946401e 100644
--- a/server/src/ui/mod.rs
+++ b/server/src/ui/mod.rs
@@ -4,7 +4,7 @@
Copyright (C) 2026 metamuffin <metamuffin.org>
*/
use crate::{
- helper::{language::AcceptLanguage, A},
+ request_info::{language::AcceptLanguage, A},
CONF,
};
use error::MyResult;
diff --git a/server/src/ui/node.rs b/server/src/ui/node.rs
index 4b452f3..fac1909 100644
--- a/server/src/ui/node.rs
+++ b/server/src/ui/node.rs
@@ -4,7 +4,7 @@
Copyright (C) 2026 metamuffin <metamuffin.org>
*/
use super::error::MyResult;
-use crate::helper::{filter_sort::ANodeFilterSort, RequestInfo, A};
+use crate::request_info::{filter_sort::ANodeFilterSort, RequestInfo, A};
use jellycommon::{
api::{ApiNodeResponse, NodeFilterSort},
NodeID,
diff --git a/server/src/ui/player.rs b/server/src/ui/player.rs
index 7b69aab..d43f3ea 100644
--- a/server/src/ui/player.rs
+++ b/server/src/ui/player.rs
@@ -5,7 +5,7 @@
*/
use super::error::MyResult;
use crate::{
- helper::{RequestInfo, A},
+ request_info::{RequestInfo, A},
CONF,
};
use jellycommon::{
diff --git a/server/src/ui/search.rs b/server/src/ui/search.rs
index 47c39c8..8a67672 100644
--- a/server/src/ui/search.rs
+++ b/server/src/ui/search.rs
@@ -4,7 +4,7 @@
Copyright (C) 2026 metamuffin <metamuffin.org>
*/
use super::error::MyResult;
-use crate::helper::RequestInfo;
+use crate::request_info::RequestInfo;
use anyhow::anyhow;
use jellycommon::api::ApiSearchResponse;
use jellylogic::search::search;
diff --git a/server/src/ui/stats.rs b/server/src/ui/stats.rs
index 0c5d2cd..b0225e6 100644
--- a/server/src/ui/stats.rs
+++ b/server/src/ui/stats.rs
@@ -4,7 +4,7 @@
Copyright (C) 2026 metamuffin <metamuffin.org>
*/
use super::error::MyError;
-use crate::helper::RequestInfo;
+use crate::request_info::RequestInfo;
use jellycommon::api::ApiStatsResponse;
use jellylogic::stats::stats;
use jellyui::{render_page, stats::StatsPage};