diff --git a/src/dto.rs b/src/dto.rs index fbdc64f546ba542fe08363a4b200c1f615d8d4d7..7947f0e3c509831a7f49d2fc5e76a380ab9d72ed 100644 --- a/src/dto.rs +++ b/src/dto.rs @@ -2,12 +2,13 @@ use serde::{Deserialize, Serialize}; use crate::config::Config; use crate::models::*; -use crate::dto_helpers::absolute_url; +use crate::dto_helpers::{absolute_url, UrlKind}; #[derive(Serialize, Deserialize, PartialEq, Debug)] #[serde(rename_all = "camelCase")] pub struct TitleDto { pub ids: TitleIdDto, + pub kind: String, pub original_language: Option<String>, pub runtime: Option<i32>, pub year_start: Option<i32>, @@ -45,6 +46,7 @@ impl TitleDto { tmdb: src.tmdb_id, tvdb: src.tvdb_id, }, + kind: src.kind, original_language: src.original_language, runtime: src.runtime, year_start: src.year_start, @@ -204,7 +206,7 @@ impl ImageDto { Ok(ImageDto { kind: src.kind, mime: src.mime, - src: absolute_url(config, src.src)?, + src: absolute_url(config, UrlKind::Static,src.src)?, }) } } @@ -224,7 +226,7 @@ impl MediaDto { mime: src.mime, codecs: src.codecs, languages: src.languages, - src: absolute_url(config, src.src)?, + src: absolute_url(config, UrlKind::Static, src.src)?, }) } } @@ -262,7 +264,7 @@ impl SubtitleDto { language: src.language, region: src.region, specifier: src.specifier, - src: absolute_url(config, src.src)?, + src: absolute_url(config, UrlKind::Static,src.src)?, }) } } @@ -272,4 +274,12 @@ impl SubtitleDto { pub struct GenreWithTitlesDto { pub genre: GenreDto, pub titles: Vec<TitleDto>, +} + +#[derive(Serialize, Deserialize, PartialEq, Debug)] +#[serde(rename_all = "camelCase")] +pub struct TitleMetaDto { + pub title: TitleDto, + pub show: Option<TitleDto>, + pub episodes: Vec<EpisodeDto>, } \ No newline at end of file diff --git a/src/dto_helpers.rs b/src/dto_helpers.rs index 43090bb15660d4d143c4d95473a772e43034c35d..bc51807a241475387d5161ae902e8b3bb5ef3788 100644 --- a/src/dto_helpers.rs +++ b/src/dto_helpers.rs @@ -1,10 +1,32 @@ use diesel::*; +use url::ParseError; +use uuid::Uuid; use crate::config::Config; use crate::dto::*; use crate::models::*; use crate::schema::*; -use url::ParseError; + +pub fn load_episodes(db: &PgConnection, config: &Config, title_id: Uuid) -> QueryResult<Vec<EpisodeDto>> { + let titles: Vec<(TitleEpisode, Title)> = title_episodes::table + .filter(title_episodes::show_id.eq(title_id)) + .inner_join(titles::table.on(titles::id.eq(title_episodes::episode_id))) + .load::<(TitleEpisode, Title)>(db)?; + let (episodes, titles): (Vec<TitleEpisode>, Vec<Title>) = titles.into_iter().unzip(); + let titles = load_titles(db, config, titles)?; + Ok(episodes.into_iter().zip(titles) + .map(|(episode, title)| EpisodeDto::of(episode, title)) + .collect::<Vec<_>>()) +} + +pub fn load_show(db: &PgConnection, config: &Config, title_id: Uuid) -> QueryResult<TitleDto> { + let show = title_episodes::table + .filter(title_episodes::episode_id.eq(title_id)) + .inner_join(titles::table.on(titles::id.eq(title_episodes::show_id))) + .select(titles::all_columns) + .first::<Title>(db)?; + load_title(db, config, show) +} pub fn load_title(db: &PgConnection, config: &Config, title: Title) -> QueryResult<TitleDto> { let title_names: Vec<TitleName> = TitleName::belonging_to(&title) @@ -129,10 +151,19 @@ fn process_title( }).collect::<Vec<_>>(), preview .and_then(|preview| preview.src) - .and_then(|preview| absolute_url(config, preview).ok()) + .and_then(|preview| absolute_url(config, UrlKind::Static, preview).ok()), ) } -pub fn absolute_url(config: &Config, url: impl Into<String>) -> Result<String, ParseError> { - config.static_url.join(url.into().as_str()).map(url::Url::into_string) +pub enum UrlKind { + Static, + Api, +} + +pub fn absolute_url(config: &Config, url_kind: UrlKind, url: impl Into<String>) -> Result<String, ParseError> { + let base_url = match url_kind { + UrlKind::Static => &config.static_url, + UrlKind::Api => &config.api_url + }; + base_url.join(url.into().as_str()).map(url::Url::into_string) } \ No newline at end of file diff --git a/src/main.rs b/src/main.rs index c99a27964bc67ab679b2de89285a9e8530a0423e..760a2fb0dc20a045530cb968b059729010d0190c 100644 --- a/src/main.rs +++ b/src/main.rs @@ -19,7 +19,7 @@ use url::Url; use media_backend::config::Config; use media_backend::dto::*; -use media_backend::dto_helpers::{load_title, load_titles}; +use media_backend::dto_helpers::{load_title, load_titles, load_episodes, load_show}; use media_backend::models::*; use media_backend::param_helpers::ParamUuid; @@ -62,7 +62,7 @@ fn list_titles(db: MediaflixConnection, config: State<Config>) -> QueryResult<Js } #[get("/api/v1/titles/<title_id>")] -fn get_title(db: MediaflixConnection, config: State<Config>, title_id: ParamUuid) -> QueryResult<Json<TitleDto>> { +fn get_title(db: MediaflixConnection, config: State<Config>, title_id: ParamUuid) -> QueryResult<Json<TitleMetaDto>> { use media_backend::schema::*; let title = load_title( &db.0, @@ -71,21 +71,25 @@ fn get_title(db: MediaflixConnection, config: State<Config>, title_id: ParamUuid .find(title_id.uuid()) .first::<Title>(&db.0)?, )?; - Ok(Json(title)) + match title.kind.as_str() { + "episode" => { + let show = load_show(&db.0, &config, title.ids.uuid)?; + let episodes = load_episodes(&db.0, &config, show.ids.uuid)?; + Ok(Json(TitleMetaDto { title, show: Some(show), episodes })) + } + "show" => { + let episodes = load_episodes(&db.0, &config, title.ids.uuid)?; + Ok(Json(TitleMetaDto { title, show: None, episodes })) + } + _ => { + Ok(Json(TitleMetaDto { title, show: None, episodes: vec![] })) + } + } } #[get("/api/v1/titles/<title_id>/episodes")] fn list_episodes(db: MediaflixConnection, config: State<Config>, title_id: ParamUuid) -> QueryResult<Json<Vec<EpisodeDto>>> { - use media_backend::schema::*; - let titles: Vec<(TitleEpisode, Title)> = title_episodes::table - .filter(title_episodes::show_id.eq(title_id.uuid())) - .inner_join(titles::table.on(titles::id.eq(title_episodes::episode_id))) - .load::<(TitleEpisode, Title)>(&db.0)?; - let (episodes, titles): (Vec<TitleEpisode>, Vec<Title>) = titles.into_iter().unzip(); - let titles = load_titles(&db.0, &config, titles)?; - Ok(Json(episodes.into_iter().zip(titles) - .map(|(episode, title)| EpisodeDto::of(episode, title)) - .collect::<Vec<_>>())) + Ok(Json(load_episodes(&db.0, &config, title_id.uuid())?)) } fn main() -> Result<(), Box<dyn std::error::Error>> { @@ -96,12 +100,6 @@ fn main() -> Result<(), Box<dyn std::error::Error>> { api_url: Url::parse(env::var("MEDIAFLIX_API_URL")?.as_str())?, static_url: Url::parse(env::var("MEDIAFLIX_STATIC_URL")?.as_str())?, }) - .attach(MediaflixConnection::fairing()) - .attach(rocket_cors::CorsOptions { - allowed_headers: AllowedHeaders::some(&["Authorization", "Accept", "Accept-Language"]), - allow_credentials: true, - ..Default::default() - }.to_cors()?) .mount( "/", rocket::routes![ @@ -112,6 +110,12 @@ fn main() -> Result<(), Box<dyn std::error::Error>> { ], ) .mount("/static", StaticFiles::from(env::var("MEDIAFLIX_PATH")?)) + .attach(MediaflixConnection::fairing()) + .attach(rocket_cors::CorsOptions { + allowed_headers: AllowedHeaders::some(&["Authorization", "Accept", "Accept-Language"]), + allow_credentials: true, + ..Default::default() + }.to_cors()?) .launch(); Ok(())