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