From 48bf978f2c52b87d8ec7dee59401e700b6268781 Mon Sep 17 00:00:00 2001
From: Janne Koschinski <janne@kuschku.de>
Date: Fri, 25 Sep 2020 19:49:35 +0200
Subject: [PATCH] Minor cleanup

---
 Cargo.lock         |  64 ++++++++++++++++++++--
 Cargo.toml         |   3 +-
 schema.graphql     | 131 ---------------------------------------------
 src/dto.rs         | 110 ++++++++++++++++++++++---------------
 src/dto_helpers.rs |  10 ++--
 src/main.rs        |  46 ++++++++++++----
 src/models.rs      |  25 +++++----
 7 files changed, 183 insertions(+), 206 deletions(-)
 delete mode 100644 schema.graphql

diff --git a/Cargo.lock b/Cargo.lock
index e7952f5..78cb32d 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -547,8 +547,8 @@ dependencies = [
  "time",
  "traitobject",
  "typeable",
- "unicase",
- "url",
+ "unicase 1.4.2",
+ "url 1.7.2",
 ]
 
 [[package]]
@@ -562,6 +562,17 @@ dependencies = [
  "unicode-normalization",
 ]
 
+[[package]]
+name = "idna"
+version = "0.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "02e2673c30ee86b5b96a9cb52ad15718aa1f966f5ab9ad54a8b95d5ca33120a9"
+dependencies = [
+ "matches",
+ "unicode-bidi",
+ "unicode-normalization",
+]
+
 [[package]]
 name = "indexmap"
 version = "1.5.2"
@@ -702,6 +713,7 @@ dependencies = [
  "itertools",
  "rocket",
  "rocket_contrib",
+ "rocket_cors",
  "serde",
  "serde_json",
  "uuid",
@@ -1140,6 +1152,22 @@ dependencies = [
  "yansi",
 ]
 
+[[package]]
+name = "rocket_cors"
+version = "0.5.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ea20696dc46308d0ca06222905fe38e02b8e46c087af9c82ea85cdc386271076"
+dependencies = [
+ "log 0.4.11",
+ "regex",
+ "rocket",
+ "serde",
+ "serde_derive",
+ "unicase 2.6.0",
+ "unicase_serde",
+ "url 2.1.1",
+]
+
 [[package]]
 name = "rocket_http"
 version = "0.4.5"
@@ -1359,6 +1387,25 @@ dependencies = [
  "version_check 0.1.5",
 ]
 
+[[package]]
+name = "unicase"
+version = "2.6.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "50f37be617794602aabbeee0be4f259dc1778fabe05e2d67ee8f79326d5cb4f6"
+dependencies = [
+ "version_check 0.9.2",
+]
+
+[[package]]
+name = "unicase_serde"
+version = "0.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6ef53697679d874d69f3160af80bc28de12730a985d57bdf2b47456ccb8b11f1"
+dependencies = [
+ "serde",
+ "unicase 2.6.0",
+]
+
 [[package]]
 name = "unicode-bidi"
 version = "0.3.4"
@@ -1405,11 +1452,22 @@ version = "1.7.2"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "dd4e7c0d531266369519a4aa4f399d748bd37043b00bde1e4ff1f60a120b355a"
 dependencies = [
- "idna",
+ "idna 0.1.5",
  "matches",
  "percent-encoding 1.0.1",
 ]
 
+[[package]]
+name = "url"
+version = "2.1.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "829d4a8476c35c9bf0bbce5a3b23f4106f79728039b726d292bb93bc106787cb"
+dependencies = [
+ "idna 0.2.0",
+ "matches",
+ "percent-encoding 2.1.0",
+]
+
 [[package]]
 name = "uuid"
 version = "0.7.4"
diff --git a/Cargo.toml b/Cargo.toml
index ecb9665..960707e 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -11,6 +11,7 @@ env_logger = "0.7.1"
 futures = "0.3.5"
 itertools = "0.9.0"
 rocket = "0.4.5"
+rocket_cors = "0.5.2"
 serde_json = "1.0.57"
 
 [dependencies.chrono]
@@ -24,7 +25,7 @@ features = ["postgres", "chrono", "uuidv07"]
 [dependencies.rocket_contrib]
 version = "0.4.5"
 default-features = false
-features = ["json", "diesel_postgres_pool"]
+features = ["json", "diesel_postgres_pool", "serve"]
 
 [dependencies.serde]
 version = "1.0.115"
diff --git a/schema.graphql b/schema.graphql
deleted file mode 100644
index 1b2084a..0000000
--- a/schema.graphql
+++ /dev/null
@@ -1,131 +0,0 @@
-schema {
-    query: Query
-}
-
-type Query {
-    apiVersion: String!
-
-    genres(id: ID = null, limit: Int = null, offset: Int = null): [Genre!]!
-    people(id: ID = null, limit: Int = null, offset: Int = null): [Person!]!
-    titles(id: ID = null, limit: Int = null, offset: Int = null): [Title!]!
-}
-
-type Genre {
-    id: ID!
-    tmdbId: Int
-    name: String!
-
-    titles(limit: Int = null, offset: Int = null): [Title!]!
-}
-
-type Person {
-    id: ID!
-    imdbId: ID
-    name: String!
-
-    starring(limit: Int = null, offset: Int = null): [Title!]!
-}
-
-type Cast {
-    id: ID!
-    category: String
-    characters: [String!]!
-    credit: String
-
-    title: Title
-    person: Person
-}
-
-type Description {
-    id: ID!
-    region: String
-    languages: [String!]!
-    kind: LocalizedKind!
-    overview: String!
-    tagline: String
-}
-
-type ShowEpisode {
-    id: ID!
-    seasonNumber: String
-    episodeNumber: String
-    airDate: Date
-
-    show: Title
-    episode: Title
-}
-
-type Image {
-    id: ID!
-    kind: ImageKind!
-    mime: String!
-    src: String!
-}
-
-type Media {
-    id: ID!
-    mime: String
-    codecs: [String!]!
-    languages: [String!]!
-    src: String!
-}
-
-type Name {
-    id: ID!
-    region: String
-    languages: [String!]!
-    kind: LocalizedKind!
-    name: String!
-}
-
-type Rating {
-    id: ID!
-    region: String
-    certification: String!
-}
-
-type Subtitle {
-    id: ID!
-    format: String!
-    language: String
-    region: String
-    specifier: String
-    src: String!
-}
-
-type Title {
-    id: ID!
-    imdbId: String
-    tmdbId: Int
-    tvdbId: Int
-    originalLanguage: String
-    runtime: Int
-    yearStart: Int
-    yearEnd: Int
-
-    cast(limit: Int = null, offset: Int = null): [Cast!]!
-    descriptions(region: String = null, language: String = null, limit: Int = null, offset: Int = null): [Description!]!
-    genres: [Genre!]!
-    image: [Image!]!
-    media: [Media!]!
-    names(region: String = null, language: String = null, limit: Int = null, offset: Int = null): [Name!]!
-    ratings(region: String = null, limit: Int = null, offset: Int = null): [Rating!]!
-    subtitles: [Subtitle!]!
-    episodes(seasonNumber: Int = null, episodeNumber: Int = null, limit: Int = null, offset: Int = null): [ShowEpisode!]!
-    show: ShowEpisode
-}
-
-scalar Date
-
-enum ImageKind {
-    still
-    backdrop
-    poster
-    logo
-}
-
-enum LocalizedKind {
-    primary
-    original
-    localized
-}
\ No newline at end of file
diff --git a/src/dto.rs b/src/dto.rs
index 7fb4932..71eb572 100644
--- a/src/dto.rs
+++ b/src/dto.rs
@@ -1,21 +1,23 @@
 use serde::{Deserialize, Serialize};
-use crate::models::{Genre, Person, Title, TitleCast, TitleDescription, TitleName, TitleImage, TitleMedium, TitleRating, TitleSubtitle};
+
+use crate::models::*;
 
 #[derive(Serialize, Deserialize, PartialEq, Debug)]
+#[serde(rename_all = "camelCase")]
 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 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 cast: Vec<CastDto>,
+    pub genres: Vec<GenreDto>,
+    pub ratings: Vec<RatingDto>,
+    pub images: Vec<ImageDto>,
+    pub media: Vec<MediaDto>,
+    pub subtitles: Vec<SubtitleDto>,
     pub created_at: chrono::DateTime<chrono::Utc>,
     pub updated_at: chrono::DateTime<chrono::Utc>,
 }
@@ -25,12 +27,12 @@ impl TitleDto {
         src: Title,
         titles: Vec<TitleNameDto>,
         descriptions: Vec<TitleDescriptionDto>,
-        cast: Vec<TitleCastDto>,
+        cast: Vec<CastDto>,
         genres: Vec<GenreDto>,
-        ratings: Vec<TitleRatingDto>,
-        images: Vec<TitleImageDto>,
-        media: Vec<TitleMediaDto>,
-        subtitles: Vec<TitleSubtitleDto>
+        ratings: Vec<RatingDto>,
+        images: Vec<ImageDto>,
+        media: Vec<MediaDto>,
+        subtitles: Vec<SubtitleDto>,
     ) -> Self {
         TitleDto {
             ids: TitleIdDto {
@@ -58,6 +60,7 @@ impl TitleDto {
 }
 
 #[derive(Serialize, Deserialize, PartialEq, Debug)]
+#[serde(rename_all = "camelCase")]
 pub struct TitleIdDto {
     pub uuid: uuid::Uuid,
     pub imdb: Option<String>,
@@ -66,6 +69,7 @@ pub struct TitleIdDto {
 }
 
 #[derive(Serialize, Deserialize, PartialEq, Debug)]
+#[serde(rename_all = "camelCase")]
 pub struct TitleNameDto {
     pub region: Option<String>,
     pub languages: Vec<String>,
@@ -85,6 +89,7 @@ impl From<TitleName> for TitleNameDto {
 }
 
 #[derive(Serialize, Deserialize, PartialEq, Debug)]
+#[serde(rename_all = "camelCase")]
 pub struct TitleDescriptionDto {
     pub region: Option<String>,
     pub languages: Vec<String>,
@@ -106,6 +111,7 @@ impl From<TitleDescription> for TitleDescriptionDto {
 }
 
 #[derive(Serialize, Deserialize, PartialEq, Debug)]
+#[serde(rename_all = "camelCase")]
 pub struct GenreDto {
     pub id: uuid::Uuid,
     pub tmdb_id: Option<i32>,
@@ -123,22 +129,17 @@ impl From<Genre> for GenreDto {
 }
 
 #[derive(Serialize, Deserialize, PartialEq, Debug)]
-pub struct GenreWithTitlesDto {
-    pub genre: GenreDto,
-    pub titles: Vec<TitleDto>,
-}
-
-#[derive(Serialize, Deserialize, PartialEq, Debug)]
-pub struct TitleCastDto {
+#[serde(rename_all = "camelCase")]
+pub struct CastDto {
     pub category: Option<String>,
     pub characters: Vec<String>,
     pub credit: Option<String>,
     pub person: PersonDto,
 }
 
-impl TitleCastDto {
+impl CastDto {
     pub fn of(cast: TitleCast, person: Person) -> Self {
-        TitleCastDto {
+        CastDto {
             category: cast.category,
             characters: cast.characters,
             credit: cast.credit,
@@ -148,6 +149,7 @@ impl TitleCastDto {
 }
 
 #[derive(Serialize, Deserialize, PartialEq, Debug)]
+#[serde(rename_all = "camelCase")]
 pub struct PersonDto {
     pub id: uuid::Uuid,
     pub imdb_id: Option<String>,
@@ -165,67 +167,82 @@ impl From<Person> for PersonDto {
 }
 
 #[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>,
+#[serde(rename_all = "camelCase")]
+pub struct EpisodeDto {
+    pub season: Option<String>,
+    pub episode: Option<String>,
     pub air_date: Option<chrono::NaiveDate>,
+    pub title: TitleDto,
+}
+
+impl EpisodeDto {
+    pub fn of(episode: TitleEpisode, title: TitleDto) -> Self {
+        EpisodeDto {
+            season: episode.season_number,
+            episode: episode.episode_number,
+            air_date: episode.air_date,
+            title
+        }
+    }
 }
 
 #[derive(Serialize, Deserialize, PartialEq, Debug)]
-pub struct TitleImageDto {
+#[serde(rename_all = "camelCase")]
+pub struct ImageDto {
     pub kind: String,
     pub mime: String,
     pub src: String,
 }
 
-impl From<TitleImage> for TitleImageDto {
+impl From<TitleImage> for ImageDto {
     fn from(src: TitleImage) -> Self {
-        TitleImageDto {
+        ImageDto {
             kind: src.kind,
             mime: src.mime,
-            src: src.src
+            src: src.src,
         }
     }
 }
 
 #[derive(Serialize, Deserialize, PartialEq, Debug)]
-pub struct TitleMediaDto {
+#[serde(rename_all = "camelCase")]
+pub struct MediaDto {
     pub mime: String,
     pub codecs: Vec<String>,
     pub languages: Vec<String>,
     pub src: String,
 }
 
-impl From<TitleMedium> for TitleMediaDto {
+impl From<TitleMedium> for MediaDto {
     fn from(src: TitleMedium) -> Self {
-        TitleMediaDto {
+        MediaDto {
             mime: src.mime,
             codecs: src.codecs,
             languages: src.languages,
-            src: src.src
+            src: src.src,
         }
     }
 }
 
 #[derive(Serialize, Deserialize, PartialEq, Debug)]
-pub struct TitleRatingDto {
+#[serde(rename_all = "camelCase")]
+pub struct RatingDto {
     pub region: Option<String>,
     pub certification: String,
 }
 
-impl From<TitleRating> for TitleRatingDto {
+impl From<TitleRating> for RatingDto {
     fn from(src: TitleRating) -> Self {
-        TitleRatingDto {
+        RatingDto {
             region: src.region,
-            certification: src.certification
+            certification: src.certification,
         }
     }
 }
 
 #[derive(Serialize, Deserialize, PartialEq, Debug)]
-pub struct TitleSubtitleDto {
+#[serde(rename_all = "camelCase")]
+pub struct SubtitleDto {
     pub format: String,
     pub language: Option<String>,
     pub region: Option<String>,
@@ -233,14 +250,21 @@ pub struct TitleSubtitleDto {
     pub src: String,
 }
 
-impl From<TitleSubtitle> for TitleSubtitleDto {
+impl From<TitleSubtitle> for SubtitleDto {
     fn from(src: TitleSubtitle) -> Self {
-        TitleSubtitleDto {
+        SubtitleDto {
             format: src.format,
             language: src.language,
             region: src.region,
             specifier: src.specifier,
-            src: src.src
+            src: src.src,
         }
     }
+}
+
+#[derive(Serialize, Deserialize, PartialEq, Debug)]
+#[serde(rename_all = "camelCase")]
+pub struct GenreWithTitlesDto {
+    pub genre: GenreDto,
+    pub titles: Vec<TitleDto>,
 }
\ No newline at end of file
diff --git a/src/dto_helpers.rs b/src/dto_helpers.rs
index 915376c..2eae822 100644
--- a/src/dto_helpers.rs
+++ b/src/dto_helpers.rs
@@ -89,22 +89,22 @@ fn process_title(
         }).collect::<Vec<_>>(),
         cast.into_iter().map(|src| {
             let (cast, person) = src;
-            TitleCastDto::of(cast, person)
+            CastDto::of(cast, person)
         }).collect::<Vec<_>>(),
         genres.into_iter().map(|(_, src)| {
             GenreDto::from(src)
         }).collect::<Vec<_>>(),
         ratings.into_iter().map(|src| {
-            TitleRatingDto::from(src)
+            RatingDto::from(src)
         }).collect::<Vec<_>>(),
         images.into_iter().map(|src| {
-            TitleImageDto::from(src)
+            ImageDto::from(src)
         }).collect::<Vec<_>>(),
         media.into_iter().map(|src| {
-            TitleMediaDto::from(src)
+            MediaDto::from(src)
         }).collect::<Vec<_>>(),
         subtitles.into_iter().map(|src| {
-            TitleSubtitleDto::from(src)
+            SubtitleDto::from(src)
         }).collect::<Vec<_>>(),
     )
 }
\ No newline at end of file
diff --git a/src/main.rs b/src/main.rs
index d8b1616..adfe460 100644
--- a/src/main.rs
+++ b/src/main.rs
@@ -6,10 +6,14 @@ extern crate rocket;
 #[macro_use]
 extern crate rocket_contrib;
 
+use std::env;
+
 use diesel::prelude::*;
 use dotenv::dotenv;
 use rocket_contrib::databases::diesel;
 use rocket_contrib::json::Json;
+use rocket_contrib::serve::StaticFiles;
+use rocket_cors::AllowedHeaders;
 
 use media_backend::dto::*;
 use media_backend::dto_helpers::{load_title, load_titles};
@@ -20,17 +24,17 @@ use media_backend::param_helpers::ParamUuid;
 struct MediaflixConnection(diesel::PgConnection);
 
 #[get("/api/v1/genres")]
-fn list_genres(db: MediaflixConnection) -> QueryResult<Json<Vec<Genre>>> {
+fn list_genres(db: MediaflixConnection) -> QueryResult<Json<Vec<GenreDto>>> {
     use media_backend::schema::*;
-    let query = genres::table.into_boxed();
-    Ok(Json(query.load::<Genre>(&db.0)?))
+    let data = genres::table.load::<Genre>(&db.0)?;
+    Ok(Json(data.into_iter().map(GenreDto::from).collect::<Vec<_>>()))
 }
 
-#[get("/api/v1/genres/<id>")]
-fn get_genre(db: MediaflixConnection, id: ParamUuid) -> QueryResult<Json<GenreWithTitlesDto>> {
+#[get("/api/v1/genres/<genre_id>")]
+fn get_genre(db: MediaflixConnection, genre_id: ParamUuid) -> QueryResult<Json<GenreWithTitlesDto>> {
     use media_backend::schema::*;
     let genre: Genre = genres::table
-        .find(id.uuid())
+        .find(genre_id.uuid())
         .first::<Genre>(&db.0)?;
     let titles: Vec<Title> = title_genres::table
         .filter(title_genres::genre_id.eq(genre.id))
@@ -54,23 +58,42 @@ fn list_titles(db: MediaflixConnection) -> QueryResult<Json<Vec<TitleDto>>> {
     Ok(Json(load_titles(&db.0, titles)?))
 }
 
-#[get("/api/v1/titles/<title>")]
-fn get_title(db: MediaflixConnection, title: ParamUuid) -> QueryResult<Json<TitleDto>> {
+#[get("/api/v1/titles/<title_id>")]
+fn get_title(db: MediaflixConnection, title_id: ParamUuid) -> QueryResult<Json<TitleDto>> {
     use media_backend::schema::*;
     let title = load_title(
         &db.0,
         titles::table
-            .find(title.uuid())
+            .find(title_id.uuid())
             .first::<Title>(&db.0)?,
     )?;
     Ok(Json(title))
 }
 
-fn main() {
+#[get("/api/v1/titles/<title_id>/episodes")]
+fn list_episodes(db: MediaflixConnection, 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)?;
+    Ok(Json(episodes.into_iter().zip(titles)
+        .map(|(episode, title)| EpisodeDto::of(episode, title))
+        .collect::<Vec<_>>()))
+}
+
+fn main() -> Result<(), Box<dyn std::error::Error>> {
     dotenv().ok();
 
     rocket::ignite()
         .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![
@@ -80,5 +103,8 @@ fn main() {
                 get_title
             ],
         )
+        .mount("/static", StaticFiles::from(env::var("MEDIAFLIX_PATH")?))
         .launch();
+
+    Ok(())
 }
diff --git a/src/models.rs b/src/models.rs
index 865b0e1..3573ad9 100644
--- a/src/models.rs
+++ b/src/models.rs
@@ -1,11 +1,10 @@
 extern crate diesel;
 
 use diesel::*;
-use serde::{Deserialize, Serialize};
 
 use super::schema::*;
 
-#[derive(Identifiable, Queryable, Associations, Serialize, Deserialize, PartialEq, Debug)]
+#[derive(Identifiable, Queryable, Associations, PartialEq, Debug)]
 #[table_name = "genres"]
 pub struct Genre {
     pub id: uuid::Uuid,
@@ -15,7 +14,7 @@ pub struct Genre {
     pub updated_at: chrono::DateTime<chrono::Utc>,
 }
 
-#[derive(Identifiable, Queryable, Associations, Serialize, Deserialize, PartialEq, Debug)]
+#[derive(Identifiable, Queryable, Associations, PartialEq, Debug)]
 #[table_name = "people"]
 pub struct Person {
     pub id: uuid::Uuid,
@@ -25,7 +24,7 @@ pub struct Person {
     pub updated_at: chrono::DateTime<chrono::Utc>,
 }
 
-#[derive(Identifiable, Queryable, Associations, Serialize, Deserialize, PartialEq, Debug)]
+#[derive(Identifiable, Queryable, Associations, PartialEq, Debug)]
 #[belongs_to(Title, foreign_key = "title_id")]
 #[belongs_to(Person, foreign_key = "person_id")]
 #[table_name = "title_casts"]
@@ -40,7 +39,7 @@ pub struct TitleCast {
     pub person_id: uuid::Uuid,
 }
 
-#[derive(Identifiable, Queryable, Associations, Serialize, Deserialize, PartialEq, Debug)]
+#[derive(Identifiable, Queryable, Associations, PartialEq, Debug)]
 #[belongs_to(Title, foreign_key = "title_id")]
 #[table_name = "title_descriptions"]
 pub struct TitleDescription {
@@ -55,7 +54,7 @@ pub struct TitleDescription {
     pub title_id: uuid::Uuid,
 }
 
-#[derive(Identifiable, Queryable, Associations, Serialize, Deserialize, PartialEq, Debug)]
+#[derive(Identifiable, Queryable, Associations, PartialEq, Debug)]
 #[belongs_to(Title, foreign_key = "show_id")]
 #[table_name = "title_episodes"]
 pub struct TitleEpisode {
@@ -69,7 +68,7 @@ pub struct TitleEpisode {
     pub updated_at: chrono::DateTime<chrono::Utc>,
 }
 
-#[derive(Identifiable, Queryable, Associations, Serialize, Deserialize, PartialEq, Debug)]
+#[derive(Identifiable, Queryable, Associations, PartialEq, Debug)]
 #[belongs_to(Title, foreign_key = "title_id")]
 #[belongs_to(Genre, foreign_key = "genre_id")]
 #[table_name = "title_genres"]
@@ -81,7 +80,7 @@ pub struct TitleGenre {
     pub updated_at: chrono::DateTime<chrono::Utc>,
 }
 
-#[derive(Identifiable, Queryable, Associations, Serialize, Deserialize, PartialEq, Debug)]
+#[derive(Identifiable, Queryable, Associations, PartialEq, Debug)]
 #[belongs_to(Title, foreign_key = "title_id")]
 #[table_name = "title_images"]
 pub struct TitleImage {
@@ -94,7 +93,7 @@ pub struct TitleImage {
     pub title_id: uuid::Uuid,
 }
 
-#[derive(Identifiable, Queryable, Associations, Serialize, Deserialize, PartialEq, Debug)]
+#[derive(Identifiable, Queryable, Associations, PartialEq, Debug)]
 #[belongs_to(Title, foreign_key = "title_id")]
 #[table_name = "title_media"]
 pub struct TitleMedium {
@@ -108,7 +107,7 @@ pub struct TitleMedium {
     pub title_id: uuid::Uuid,
 }
 
-#[derive(Identifiable, Queryable, Associations, Serialize, Deserialize, PartialEq, Debug)]
+#[derive(Identifiable, Queryable, Associations, PartialEq, Debug)]
 #[belongs_to(Title, foreign_key = "title_id")]
 #[table_name = "title_names"]
 pub struct TitleName {
@@ -122,7 +121,7 @@ pub struct TitleName {
     pub title_id: uuid::Uuid,
 }
 
-#[derive(Identifiable, Queryable, Associations, Serialize, Deserialize, PartialEq, Debug)]
+#[derive(Identifiable, Queryable, Associations, PartialEq, Debug)]
 #[belongs_to(Title, foreign_key = "title_id")]
 #[table_name = "title_ratings"]
 pub struct TitleRating {
@@ -134,7 +133,7 @@ pub struct TitleRating {
     pub title_id: uuid::Uuid,
 }
 
-#[derive(Identifiable, Queryable, Associations, Serialize, Deserialize, PartialEq, Debug)]
+#[derive(Identifiable, Queryable, Associations, PartialEq, Debug)]
 #[belongs_to(Title, foreign_key = "title_id")]
 #[table_name = "title_subtitles"]
 pub struct TitleSubtitle {
@@ -149,7 +148,7 @@ pub struct TitleSubtitle {
     pub title_id: uuid::Uuid,
 }
 
-#[derive(Identifiable, Queryable, Serialize, Deserialize, PartialEq, Debug)]
+#[derive(Identifiable, Queryable, PartialEq, Debug)]
 #[table_name = "titles"]
 pub struct Title {
     pub id: uuid::Uuid,
-- 
GitLab