From 09643888d0bef53f4b0bbb8ed213b5c49f97963c Mon Sep 17 00:00:00 2001 From: Janne Koschinski <janne@kuschku.de> Date: Fri, 25 Sep 2020 12:40:46 +0200 Subject: [PATCH] Implement basic data retrieval and querying --- Cargo.lock | 16 ++++ Cargo.toml | 1 + src/dto.rs | 246 ++++++++++++++++++++++++++++++++++++++++++++++++++ src/lib.rs | 1 + src/main.rs | 86 +++++++++++++++--- src/models.rs | 5 +- src/schema.rs | 3 +- 7 files changed, 344 insertions(+), 14 deletions(-) create mode 100644 src/dto.rs diff --git a/Cargo.lock b/Cargo.lock index 3880d50..e7952f5 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -277,6 +277,12 @@ version = "0.15.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "77c90badedccf4105eca100756a0b1289e191f6fcbdadd3cee1d2f614f97da8f" +[[package]] +name = "either" +version = "1.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e78d4f1cc4ae33bbfc157ed5d5a5ef3bc29227303d595861deb238fcec4e9457" + [[package]] name = "env_logger" version = "0.7.1" @@ -601,6 +607,15 @@ dependencies = [ "libc", ] +[[package]] +name = "itertools" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "284f18f85651fe11e8a991b2adb42cb078325c996ed026d994719efcfca1d54b" +dependencies = [ + "either", +] + [[package]] name = "itoa" version = "0.4.6" @@ -684,6 +699,7 @@ dependencies = [ "dotenv", "env_logger", "futures", + "itertools", "rocket", "rocket_contrib", "serde", diff --git a/Cargo.toml b/Cargo.toml index 3e91561..ecb9665 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -9,6 +9,7 @@ anyhow = "1.0.32" dotenv = "0.15.0" env_logger = "0.7.1" futures = "0.3.5" +itertools = "0.9.0" rocket = "0.4.5" serde_json = "1.0.57" diff --git a/src/dto.rs b/src/dto.rs new file mode 100644 index 0000000..7fb4932 --- /dev/null +++ b/src/dto.rs @@ -0,0 +1,246 @@ +use serde::{Deserialize, Serialize}; +use crate::models::{Genre, Person, Title, TitleCast, TitleDescription, TitleName, TitleImage, TitleMedium, TitleRating, TitleSubtitle}; + +#[derive(Serialize, Deserialize, PartialEq, Debug)] +pub struct TitleDto { + pub ids: TitleIdDto, + pub original_language: Option<String>, + pub runtime: Option<i32>, + pub year_start: Option<i32>, + pub year_end: Option<i32>, + pub titles: Vec<TitleNameDto>, // + pub descriptions: Vec<TitleDescriptionDto>, + pub cast: Vec<TitleCastDto>, + pub genres: Vec<GenreDto>, // + pub ratings: Vec<TitleRatingDto>, // + pub images: Vec<TitleImageDto>, + pub media: Vec<TitleMediaDto>, + pub subtitles: Vec<TitleSubtitleDto>, + pub created_at: chrono::DateTime<chrono::Utc>, + pub updated_at: chrono::DateTime<chrono::Utc>, +} + +impl TitleDto { + pub fn of( + src: Title, + titles: Vec<TitleNameDto>, + descriptions: Vec<TitleDescriptionDto>, + cast: Vec<TitleCastDto>, + genres: Vec<GenreDto>, + ratings: Vec<TitleRatingDto>, + images: Vec<TitleImageDto>, + media: Vec<TitleMediaDto>, + subtitles: Vec<TitleSubtitleDto> + ) -> Self { + TitleDto { + ids: TitleIdDto { + uuid: src.id, + imdb: src.imdb_id, + tmdb: src.tmdb_id, + tvdb: src.tvdb_id, + }, + original_language: src.original_language, + runtime: src.runtime, + year_start: src.year_start, + year_end: src.year_end, + titles, + descriptions, + cast, + genres, + ratings, + images, + media, + subtitles, + created_at: src.created_at, + updated_at: src.updated_at, + } + } +} + +#[derive(Serialize, Deserialize, PartialEq, Debug)] +pub struct TitleIdDto { + pub uuid: uuid::Uuid, + pub imdb: Option<String>, + pub tmdb: Option<i32>, + pub tvdb: Option<i32>, +} + +#[derive(Serialize, Deserialize, PartialEq, Debug)] +pub struct TitleNameDto { + pub region: Option<String>, + pub languages: Vec<String>, + pub kind: String, + pub name: String, +} + +impl From<TitleName> for TitleNameDto { + fn from(src: TitleName) -> Self { + TitleNameDto { + region: src.region, + languages: src.languages, + kind: src.kind, + name: src.name, + } + } +} + +#[derive(Serialize, Deserialize, PartialEq, Debug)] +pub struct TitleDescriptionDto { + pub region: Option<String>, + pub languages: Vec<String>, + pub kind: String, + pub overview: String, + pub tagline: Option<String>, +} + +impl From<TitleDescription> for TitleDescriptionDto { + fn from(src: TitleDescription) -> Self { + TitleDescriptionDto { + region: src.region, + languages: src.languages, + kind: src.kind, + overview: src.overview, + tagline: src.tagline, + } + } +} + +#[derive(Serialize, Deserialize, PartialEq, Debug)] +pub struct GenreDto { + pub id: uuid::Uuid, + pub tmdb_id: Option<i32>, + pub name: String, +} + +impl From<Genre> for GenreDto { + fn from(src: Genre) -> Self { + GenreDto { + id: src.id, + tmdb_id: src.tmdb_id, + name: src.name, + } + } +} + +#[derive(Serialize, Deserialize, PartialEq, Debug)] +pub struct GenreWithTitlesDto { + pub genre: GenreDto, + pub titles: Vec<TitleDto>, +} + +#[derive(Serialize, Deserialize, PartialEq, Debug)] +pub struct TitleCastDto { + pub category: Option<String>, + pub characters: Vec<String>, + pub credit: Option<String>, + pub person: PersonDto, +} + +impl TitleCastDto { + pub fn of(cast: TitleCast, person: Person) -> Self { + TitleCastDto { + category: cast.category, + characters: cast.characters, + credit: cast.credit, + person: PersonDto::from(person), + } + } +} + +#[derive(Serialize, Deserialize, PartialEq, Debug)] +pub struct PersonDto { + pub id: uuid::Uuid, + pub imdb_id: Option<String>, + pub name: String, +} + +impl From<Person> for PersonDto { + fn from(src: Person) -> Self { + PersonDto { + id: src.id, + imdb_id: src.imdb_id, + name: src.name, + } + } +} + +#[derive(Serialize, Deserialize, PartialEq, Debug)] +pub struct TitleEpisodeDto { + pub show_id: uuid::Uuid, + pub episode_id: uuid::Uuid, + pub season_number: Option<String>, + pub episode_number: Option<String>, + pub air_date: Option<chrono::NaiveDate>, +} + +#[derive(Serialize, Deserialize, PartialEq, Debug)] +pub struct TitleImageDto { + pub kind: String, + pub mime: String, + pub src: String, +} + +impl From<TitleImage> for TitleImageDto { + fn from(src: TitleImage) -> Self { + TitleImageDto { + kind: src.kind, + mime: src.mime, + src: src.src + } + } +} + +#[derive(Serialize, Deserialize, PartialEq, Debug)] +pub struct TitleMediaDto { + pub mime: String, + pub codecs: Vec<String>, + pub languages: Vec<String>, + pub src: String, +} + +impl From<TitleMedium> for TitleMediaDto { + fn from(src: TitleMedium) -> Self { + TitleMediaDto { + mime: src.mime, + codecs: src.codecs, + languages: src.languages, + src: src.src + } + } +} + +#[derive(Serialize, Deserialize, PartialEq, Debug)] +pub struct TitleRatingDto { + pub region: Option<String>, + pub certification: String, +} + +impl From<TitleRating> for TitleRatingDto { + fn from(src: TitleRating) -> Self { + TitleRatingDto { + region: src.region, + certification: src.certification + } + } +} + +#[derive(Serialize, Deserialize, PartialEq, Debug)] +pub struct TitleSubtitleDto { + pub format: String, + pub language: Option<String>, + pub region: Option<String>, + pub specifier: Option<String>, + pub src: String, +} + +impl From<TitleSubtitle> for TitleSubtitleDto { + fn from(src: TitleSubtitle) -> Self { + TitleSubtitleDto { + format: src.format, + language: src.language, + region: src.region, + specifier: src.specifier, + src: src.src + } + } +} \ No newline at end of file diff --git a/src/lib.rs b/src/lib.rs index bcac8b8..aab3a82 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -4,3 +4,4 @@ extern crate dotenv; pub mod schema; pub mod models; +pub mod dto; \ No newline at end of file diff --git a/src/main.rs b/src/main.rs index cf94f40..4440c19 100644 --- a/src/main.rs +++ b/src/main.rs @@ -14,6 +14,7 @@ use rocket_contrib::databases::diesel; use rocket_contrib::json::Json; use uuid::Uuid; +use media_backend::dto::{GenreDto, GenreWithTitlesDto, TitleCastDto, TitleDescriptionDto, TitleDto, TitleImageDto, TitleMediaDto, TitleNameDto, TitleRatingDto, TitleSubtitleDto}; use media_backend::models::*; #[database("mediaflix")] @@ -38,25 +39,88 @@ impl FromParam<'_> for ParamUuid { } -#[get("/api/v1/genres/<genre>")] -fn get_genre(db: MediaflixConnection, genre: ParamUuid) -> QueryResult<Json<Vec<((Title, Vec<TitleName>), Vec<TitleRating>)>>> { +#[get("/api/v1/genres/<id>")] +fn get_genre(db: MediaflixConnection, id: ParamUuid) -> QueryResult<Json<GenreWithTitlesDto>> { use media_backend::schema::*; - let titles = title_genres::table - .filter(title_genres::genre_id.eq(genre.0)) + let genre: Genre = genres::table + .find(id.0) + .first::<Genre>(&db.0)?; + let titles: Vec<Title> = title_genres::table + .filter(title_genres::genre_id.eq(genre.id)) .inner_join(titles::table) .select(titles::all_columns) .load::<Title>(&db.0)?; - let title_akas = TitleName::belonging_to(&titles) + let title_names: Vec<Vec<TitleName>> = TitleName::belonging_to(&titles) .load::<TitleName>(&db.0)? .grouped_by(&titles); - let title_ratings = TitleRating::belonging_to(&titles) + let title_descriptions: Vec<Vec<TitleDescription>> = TitleDescription::belonging_to(&titles) + .load::<TitleDescription>(&db.0)? + .grouped_by(&titles); + let title_cast: Vec<Vec<(TitleCast, Person)>> = TitleCast::belonging_to(&titles) + .inner_join(people::table) + .load::<(TitleCast, Person)>(&db.0)? + .grouped_by(&titles); + let title_genres: Vec<Vec<(TitleGenre, Genre)>> = TitleGenre::belonging_to(&titles) + .inner_join(genres::table) + .load::<(TitleGenre, Genre)>(&db.0)? + .grouped_by(&titles); + let title_ratings: Vec<Vec<TitleRating>> = TitleRating::belonging_to(&titles) .load::<TitleRating>(&db.0)? .grouped_by(&titles); - let data = titles.into_iter() - .zip(title_akas) - .zip(title_ratings) - .collect::<Vec<_>>(); - Ok(Json(data)) + let title_images: Vec<Vec<TitleImage>> = TitleImage::belonging_to(&titles) + .load::<TitleImage>(&db.0)? + .grouped_by(&titles); + let title_media: Vec<Vec<TitleMedium>> = TitleMedium::belonging_to(&titles) + .load::<TitleMedium>(&db.0)? + .grouped_by(&titles); + let title_subtitles: Vec<Vec<TitleSubtitle>> = TitleSubtitle::belonging_to(&titles) + .load::<TitleSubtitle>(&db.0)? + .grouped_by(&titles); + Ok(Json(GenreWithTitlesDto { + genre: GenreDto::from(genre), + titles: titles.into_iter() + .zip(title_names) + .zip(title_descriptions) + .zip(title_cast) + .zip(title_genres) + .zip(title_ratings) + .zip(title_images) + .zip(title_media) + .zip(title_subtitles) + .map(|tuple| { + let ((((((((title, akas), descriptions), + cast), genres), ratings), + images), media), subtitles) = tuple; + TitleDto::of( + title, + akas.into_iter().map(|src| { + TitleNameDto::from(src) + }).collect::<Vec<_>>(), + descriptions.into_iter().map(|src| { + TitleDescriptionDto::from(src) + }).collect::<Vec<_>>(), + cast.into_iter().map(|src| { + let (cast, person) = src; + TitleCastDto::of(cast, person) + }).collect::<Vec<_>>(), + genres.into_iter().map(|(_, src)| { + GenreDto::from(src) + }).collect::<Vec<_>>(), + ratings.into_iter().map(|src| { + TitleRatingDto::from(src) + }).collect::<Vec<_>>(), + images.into_iter().map(|src| { + TitleImageDto::from(src) + }).collect::<Vec<_>>(), + media.into_iter().map(|src| { + TitleMediaDto::from(src) + }).collect::<Vec<_>>(), + subtitles.into_iter().map(|src| { + TitleSubtitleDto::from(src) + }).collect::<Vec<_>>(), + ) + }).collect::<Vec<TitleDto>>(), + })) } diff --git a/src/models.rs b/src/models.rs index 6e28154..865b0e1 100644 --- a/src/models.rs +++ b/src/models.rs @@ -56,16 +56,17 @@ pub struct TitleDescription { } #[derive(Identifiable, Queryable, Associations, Serialize, Deserialize, PartialEq, Debug)] -#[belongs_to(Title, foreign_key = "parent_id")] +#[belongs_to(Title, foreign_key = "show_id")] #[table_name = "title_episodes"] pub struct TitleEpisode { pub id: uuid::Uuid, + pub show_id: uuid::Uuid, + pub episode_id: uuid::Uuid, pub season_number: Option<String>, pub episode_number: Option<String>, pub air_date: Option<chrono::NaiveDate>, pub created_at: chrono::DateTime<chrono::Utc>, pub updated_at: chrono::DateTime<chrono::Utc>, - pub parent_id: uuid::Uuid, } #[derive(Identifiable, Queryable, Associations, Serialize, Deserialize, PartialEq, Debug)] diff --git a/src/schema.rs b/src/schema.rs index 3a91dc2..f20a6d7 100644 --- a/src/schema.rs +++ b/src/schema.rs @@ -48,12 +48,13 @@ table! { table! { title_episodes (id) { id -> Uuid, + show_id -> Uuid, + episode_id -> Uuid, season_number -> Nullable<Text>, episode_number -> Nullable<Text>, air_date -> Nullable<Date>, created_at -> Timestamptz, updated_at -> Timestamptz, - parent_id -> Uuid, } } -- GitLab