diff --git a/Cargo.lock b/Cargo.lock index 78cb32db0d6699c3a0de00dde38c10c8abf018fa..bd50bb7ce8668eb03ae308319b32561d17b07649 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -716,6 +716,7 @@ dependencies = [ "rocket_cors", "serde", "serde_json", + "url 2.1.1", "uuid", ] diff --git a/Cargo.toml b/Cargo.toml index 960707e62296839dab0471bf49b8611d1318b267..8e889bc283a011c909e32a438380d69a72fe90c6 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -13,6 +13,7 @@ itertools = "0.9.0" rocket = "0.4.5" rocket_cors = "0.5.2" serde_json = "1.0.57" +url = "2.1.1" [dependencies.chrono] version = "0.4.15" diff --git a/src/config.rs b/src/config.rs new file mode 100644 index 0000000000000000000000000000000000000000..f33cbabd33d8efda4fcc05772f113ec1bfe86d56 --- /dev/null +++ b/src/config.rs @@ -0,0 +1,6 @@ +use url::Url; + +pub struct Config { + pub api_url: Url, + pub static_url: Url, +} \ No newline at end of file diff --git a/src/dto.rs b/src/dto.rs index 71eb572c54025cad0ac7ff356b67671ab573fc5f..aa69ad7a692b405e525a2879d8cd70ab03eba338 100644 --- a/src/dto.rs +++ b/src/dto.rs @@ -1,5 +1,6 @@ use serde::{Deserialize, Serialize}; +use crate::config::Config; use crate::models::*; #[derive(Serialize, Deserialize, PartialEq, Debug)] @@ -181,7 +182,7 @@ impl EpisodeDto { season: episode.season_number, episode: episode.episode_number, air_date: episode.air_date, - title + title, } } } @@ -194,13 +195,13 @@ pub struct ImageDto { pub src: String, } -impl From<TitleImage> for ImageDto { - fn from(src: TitleImage) -> Self { - ImageDto { +impl ImageDto { + pub fn of(src: TitleImage, config: &Config) -> Result<Self, url::ParseError> { + Ok(ImageDto { kind: src.kind, mime: src.mime, - src: src.src, - } + src: config.static_url.join(src.src.as_str())?.into_string(), + }) } } @@ -213,14 +214,14 @@ pub struct MediaDto { pub src: String, } -impl From<TitleMedium> for MediaDto { - fn from(src: TitleMedium) -> Self { - MediaDto { +impl MediaDto { + pub fn of(src: TitleMedium, config: &Config) -> Result<Self, url::ParseError> { + Ok(MediaDto { mime: src.mime, codecs: src.codecs, languages: src.languages, - src: src.src, - } + src: config.static_url.join(src.src.as_str())?.into_string(), + }) } } @@ -250,15 +251,15 @@ pub struct SubtitleDto { pub src: String, } -impl From<TitleSubtitle> for SubtitleDto { - fn from(src: TitleSubtitle) -> Self { - SubtitleDto { +impl SubtitleDto { + pub fn of(src: TitleSubtitle, config: &Config) -> Result<Self, url::ParseError> { + Ok(SubtitleDto { format: src.format, language: src.language, region: src.region, specifier: src.specifier, - src: src.src, - } + src: config.static_url.join(src.src.as_str())?.into_string(), + }) } } diff --git a/src/dto_helpers.rs b/src/dto_helpers.rs index 2eae822d3ec8b7b7a1f241ef93bcc1fb1f796d0d..c23cdd923119fcbf621fc735c6cb7d82fa5133ae 100644 --- a/src/dto_helpers.rs +++ b/src/dto_helpers.rs @@ -1,10 +1,11 @@ use diesel::*; +use crate::config::Config; use crate::dto::*; use crate::models::*; use crate::schema::*; -pub fn load_title(db: &PgConnection, title: Title) -> QueryResult<TitleDto> { +pub fn load_title(db: &PgConnection, config: &Config, title: Title) -> QueryResult<TitleDto> { let title_names: Vec<TitleName> = TitleName::belonging_to(&title) .load::<TitleName>(db)?; let title_descriptions: Vec<TitleDescription> = TitleDescription::belonging_to(&title) @@ -24,13 +25,14 @@ pub fn load_title(db: &PgConnection, title: Title) -> QueryResult<TitleDto> { let title_subtitles: Vec<TitleSubtitle> = TitleSubtitle::belonging_to(&title) .load::<TitleSubtitle>(db)?; Ok(process_title( + config, title, title_names, title_descriptions, title_cast, title_genres, title_ratings, title_images, title_media, title_subtitles, )) } -pub fn load_titles(db: &PgConnection, titles: Vec<Title>) -> QueryResult<Vec<TitleDto>> { +pub fn load_titles(db: &PgConnection, config: &Config, titles: Vec<Title>) -> QueryResult<Vec<TitleDto>> { let title_names: Vec<Vec<TitleName>> = TitleName::belonging_to(&titles) .load::<TitleName>(db)? .grouped_by(&titles); @@ -70,14 +72,20 @@ pub fn load_titles(db: &PgConnection, titles: Vec<Title>) -> QueryResult<Vec<Tit let ((((((((title, akas), descriptions), cast), genres), ratings), images), media), subtitles) = tuple; - process_title(title, akas, descriptions, cast, genres, ratings, images, media, subtitles) + process_title( + config, + title, akas, descriptions, + cast, genres, ratings, + images, media, subtitles, + ) }).collect::<Vec<TitleDto>>()) } fn process_title( + config: &Config, title: Title, names: Vec<TitleName>, descriptions: Vec<TitleDescription>, cast: Vec<(TitleCast, Person)>, genres: Vec<(TitleGenre, Genre)>, ratings: Vec<TitleRating>, - images: Vec<TitleImage>, media: Vec<TitleMedium>, subtitles: Vec<TitleSubtitle> + images: Vec<TitleImage>, media: Vec<TitleMedium>, subtitles: Vec<TitleSubtitle>, ) -> TitleDto { TitleDto::of( title, @@ -97,14 +105,14 @@ fn process_title( ratings.into_iter().map(|src| { RatingDto::from(src) }).collect::<Vec<_>>(), - images.into_iter().map(|src| { - ImageDto::from(src) + images.into_iter().filter_map(|src| { + ImageDto::of(src, &config).ok() }).collect::<Vec<_>>(), - media.into_iter().map(|src| { - MediaDto::from(src) + media.into_iter().filter_map(|src| { + MediaDto::of(src, &config).ok() }).collect::<Vec<_>>(), - subtitles.into_iter().map(|src| { - SubtitleDto::from(src) + subtitles.into_iter().filter_map(|src| { + SubtitleDto::of(src, &config).ok() }).collect::<Vec<_>>(), ) } \ No newline at end of file diff --git a/src/lib.rs b/src/lib.rs index 51672dc16a88d820a3a533d5ad0e93eab71171ec..a66ae8d0d07568b703087c69b8c0db447aced888 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -2,8 +2,9 @@ extern crate diesel; extern crate dotenv; -pub mod schema; -pub mod models; +pub mod config; pub mod dto; pub mod dto_helpers; -pub mod param_helpers; \ No newline at end of file +pub mod models; +pub mod param_helpers; +pub mod schema; \ No newline at end of file diff --git a/src/main.rs b/src/main.rs index adfe460f7b0e9eec3c3dbe49914c4ac2a5d500c8..c99a27964bc67ab679b2de89285a9e8530a0423e 100644 --- a/src/main.rs +++ b/src/main.rs @@ -10,11 +10,14 @@ use std::env; use diesel::prelude::*; use dotenv::dotenv; +use rocket::State; use rocket_contrib::databases::diesel; use rocket_contrib::json::Json; use rocket_contrib::serve::StaticFiles; use rocket_cors::AllowedHeaders; +use url::Url; +use media_backend::config::Config; use media_backend::dto::*; use media_backend::dto_helpers::{load_title, load_titles}; use media_backend::models::*; @@ -31,7 +34,7 @@ fn list_genres(db: MediaflixConnection) -> QueryResult<Json<Vec<GenreDto>>> { } #[get("/api/v1/genres/<genre_id>")] -fn get_genre(db: MediaflixConnection, genre_id: ParamUuid) -> QueryResult<Json<GenreWithTitlesDto>> { +fn get_genre(db: MediaflixConnection, config: State<Config>, genre_id: ParamUuid) -> QueryResult<Json<GenreWithTitlesDto>> { use media_backend::schema::*; let genre: Genre = genres::table .find(genre_id.uuid()) @@ -43,26 +46,27 @@ fn get_genre(db: MediaflixConnection, genre_id: ParamUuid) -> QueryResult<Json<G .load::<Title>(&db.0)?; Ok(Json(GenreWithTitlesDto { genre: GenreDto::from(genre), - titles: load_titles(&db.0, titles)?, + titles: load_titles(&db.0, &config, titles)?, })) } #[get("/api/v1/titles")] -fn list_titles(db: MediaflixConnection) -> QueryResult<Json<Vec<TitleDto>>> { +fn list_titles(db: MediaflixConnection, config: State<Config>) -> QueryResult<Json<Vec<TitleDto>>> { use media_backend::schema::*; let titles = titles::table .left_outer_join(title_episodes::table.on(title_episodes::episode_id.eq(titles::id))) .filter(title_episodes::id.is_null()) .select(titles::all_columns) .load::<Title>(&db.0)?; - Ok(Json(load_titles(&db.0, titles)?)) + Ok(Json(load_titles(&db.0, &config, titles)?)) } #[get("/api/v1/titles/<title_id>")] -fn get_title(db: MediaflixConnection, title_id: ParamUuid) -> QueryResult<Json<TitleDto>> { +fn get_title(db: MediaflixConnection, config: State<Config>, title_id: ParamUuid) -> QueryResult<Json<TitleDto>> { use media_backend::schema::*; let title = load_title( &db.0, + &config, titles::table .find(title_id.uuid()) .first::<Title>(&db.0)?, @@ -71,14 +75,14 @@ fn get_title(db: MediaflixConnection, title_id: ParamUuid) -> QueryResult<Json<T } #[get("/api/v1/titles/<title_id>/episodes")] -fn list_episodes(db: MediaflixConnection, title_id: ParamUuid) -> QueryResult<Json<Vec<EpisodeDto>>> { +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, titles)?; + let titles = load_titles(&db.0, &config, titles)?; Ok(Json(episodes.into_iter().zip(titles) .map(|(episode, title)| EpisodeDto::of(episode, title)) .collect::<Vec<_>>())) @@ -88,6 +92,10 @@ fn main() -> Result<(), Box<dyn std::error::Error>> { dotenv().ok(); rocket::ignite() + .manage(Config { + 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"]),