Skip to content
Snippets Groups Projects
Verified Commit 0aebdfbf authored by Janne Mareike Koschinski's avatar Janne Mareike Koschinski
Browse files

Cleanly handle URL endpoints

parent 48bf978f
No related branches found
No related tags found
No related merge requests found
...@@ -716,6 +716,7 @@ dependencies = [ ...@@ -716,6 +716,7 @@ dependencies = [
"rocket_cors", "rocket_cors",
"serde", "serde",
"serde_json", "serde_json",
"url 2.1.1",
"uuid", "uuid",
] ]
......
...@@ -13,6 +13,7 @@ itertools = "0.9.0" ...@@ -13,6 +13,7 @@ itertools = "0.9.0"
rocket = "0.4.5" rocket = "0.4.5"
rocket_cors = "0.5.2" rocket_cors = "0.5.2"
serde_json = "1.0.57" serde_json = "1.0.57"
url = "2.1.1"
[dependencies.chrono] [dependencies.chrono]
version = "0.4.15" version = "0.4.15"
......
use url::Url;
pub struct Config {
pub api_url: Url,
pub static_url: Url,
}
\ No newline at end of file
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use crate::config::Config;
use crate::models::*; use crate::models::*;
#[derive(Serialize, Deserialize, PartialEq, Debug)] #[derive(Serialize, Deserialize, PartialEq, Debug)]
...@@ -181,7 +182,7 @@ impl EpisodeDto { ...@@ -181,7 +182,7 @@ impl EpisodeDto {
season: episode.season_number, season: episode.season_number,
episode: episode.episode_number, episode: episode.episode_number,
air_date: episode.air_date, air_date: episode.air_date,
title title,
} }
} }
} }
...@@ -194,13 +195,13 @@ pub struct ImageDto { ...@@ -194,13 +195,13 @@ pub struct ImageDto {
pub src: String, pub src: String,
} }
impl From<TitleImage> for ImageDto { impl ImageDto {
fn from(src: TitleImage) -> Self { pub fn of(src: TitleImage, config: &Config) -> Result<Self, url::ParseError> {
ImageDto { Ok(ImageDto {
kind: src.kind, kind: src.kind,
mime: src.mime, mime: src.mime,
src: src.src, src: config.static_url.join(src.src.as_str())?.into_string(),
} })
} }
} }
...@@ -213,14 +214,14 @@ pub struct MediaDto { ...@@ -213,14 +214,14 @@ pub struct MediaDto {
pub src: String, pub src: String,
} }
impl From<TitleMedium> for MediaDto { impl MediaDto {
fn from(src: TitleMedium) -> Self { pub fn of(src: TitleMedium, config: &Config) -> Result<Self, url::ParseError> {
MediaDto { Ok(MediaDto {
mime: src.mime, mime: src.mime,
codecs: src.codecs, codecs: src.codecs,
languages: src.languages, languages: src.languages,
src: src.src, src: config.static_url.join(src.src.as_str())?.into_string(),
} })
} }
} }
...@@ -250,15 +251,15 @@ pub struct SubtitleDto { ...@@ -250,15 +251,15 @@ pub struct SubtitleDto {
pub src: String, pub src: String,
} }
impl From<TitleSubtitle> for SubtitleDto { impl SubtitleDto {
fn from(src: TitleSubtitle) -> Self { pub fn of(src: TitleSubtitle, config: &Config) -> Result<Self, url::ParseError> {
SubtitleDto { Ok(SubtitleDto {
format: src.format, format: src.format,
language: src.language, language: src.language,
region: src.region, region: src.region,
specifier: src.specifier, specifier: src.specifier,
src: src.src, src: config.static_url.join(src.src.as_str())?.into_string(),
} })
} }
} }
......
use diesel::*; use diesel::*;
use crate::config::Config;
use crate::dto::*; use crate::dto::*;
use crate::models::*; use crate::models::*;
use crate::schema::*; 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) let title_names: Vec<TitleName> = TitleName::belonging_to(&title)
.load::<TitleName>(db)?; .load::<TitleName>(db)?;
let title_descriptions: Vec<TitleDescription> = TitleDescription::belonging_to(&title) let title_descriptions: Vec<TitleDescription> = TitleDescription::belonging_to(&title)
...@@ -24,13 +25,14 @@ pub fn load_title(db: &PgConnection, title: Title) -> QueryResult<TitleDto> { ...@@ -24,13 +25,14 @@ pub fn load_title(db: &PgConnection, title: Title) -> QueryResult<TitleDto> {
let title_subtitles: Vec<TitleSubtitle> = TitleSubtitle::belonging_to(&title) let title_subtitles: Vec<TitleSubtitle> = TitleSubtitle::belonging_to(&title)
.load::<TitleSubtitle>(db)?; .load::<TitleSubtitle>(db)?;
Ok(process_title( Ok(process_title(
config,
title, title_names, title_descriptions, title, title_names, title_descriptions,
title_cast, title_genres, title_ratings, title_cast, title_genres, title_ratings,
title_images, title_media, title_subtitles, 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) let title_names: Vec<Vec<TitleName>> = TitleName::belonging_to(&titles)
.load::<TitleName>(db)? .load::<TitleName>(db)?
.grouped_by(&titles); .grouped_by(&titles);
...@@ -70,14 +72,20 @@ pub fn load_titles(db: &PgConnection, titles: Vec<Title>) -> QueryResult<Vec<Tit ...@@ -70,14 +72,20 @@ pub fn load_titles(db: &PgConnection, titles: Vec<Title>) -> QueryResult<Vec<Tit
let ((((((((title, akas), descriptions), let ((((((((title, akas), descriptions),
cast), genres), ratings), cast), genres), ratings),
images), media), subtitles) = tuple; 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>>()) }).collect::<Vec<TitleDto>>())
} }
fn process_title( fn process_title(
config: &Config,
title: Title, names: Vec<TitleName>, descriptions: Vec<TitleDescription>, title: Title, names: Vec<TitleName>, descriptions: Vec<TitleDescription>,
cast: Vec<(TitleCast, Person)>, genres: Vec<(TitleGenre, Genre)>, ratings: Vec<TitleRating>, 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 {
TitleDto::of( TitleDto::of(
title, title,
...@@ -97,14 +105,14 @@ fn process_title( ...@@ -97,14 +105,14 @@ fn process_title(
ratings.into_iter().map(|src| { ratings.into_iter().map(|src| {
RatingDto::from(src) RatingDto::from(src)
}).collect::<Vec<_>>(), }).collect::<Vec<_>>(),
images.into_iter().map(|src| { images.into_iter().filter_map(|src| {
ImageDto::from(src) ImageDto::of(src, &config).ok()
}).collect::<Vec<_>>(), }).collect::<Vec<_>>(),
media.into_iter().map(|src| { media.into_iter().filter_map(|src| {
MediaDto::from(src) MediaDto::of(src, &config).ok()
}).collect::<Vec<_>>(), }).collect::<Vec<_>>(),
subtitles.into_iter().map(|src| { subtitles.into_iter().filter_map(|src| {
SubtitleDto::from(src) SubtitleDto::of(src, &config).ok()
}).collect::<Vec<_>>(), }).collect::<Vec<_>>(),
) )
} }
\ No newline at end of file
...@@ -2,8 +2,9 @@ ...@@ -2,8 +2,9 @@
extern crate diesel; extern crate diesel;
extern crate dotenv; extern crate dotenv;
pub mod schema; pub mod config;
pub mod models;
pub mod dto; pub mod dto;
pub mod dto_helpers; pub mod dto_helpers;
pub mod models;
pub mod param_helpers; pub mod param_helpers;
pub mod schema;
\ No newline at end of file
...@@ -10,11 +10,14 @@ use std::env; ...@@ -10,11 +10,14 @@ use std::env;
use diesel::prelude::*; use diesel::prelude::*;
use dotenv::dotenv; use dotenv::dotenv;
use rocket::State;
use rocket_contrib::databases::diesel; use rocket_contrib::databases::diesel;
use rocket_contrib::json::Json; use rocket_contrib::json::Json;
use rocket_contrib::serve::StaticFiles; use rocket_contrib::serve::StaticFiles;
use rocket_cors::AllowedHeaders; use rocket_cors::AllowedHeaders;
use url::Url;
use media_backend::config::Config;
use media_backend::dto::*; use media_backend::dto::*;
use media_backend::dto_helpers::{load_title, load_titles}; use media_backend::dto_helpers::{load_title, load_titles};
use media_backend::models::*; use media_backend::models::*;
...@@ -31,7 +34,7 @@ fn list_genres(db: MediaflixConnection) -> QueryResult<Json<Vec<GenreDto>>> { ...@@ -31,7 +34,7 @@ fn list_genres(db: MediaflixConnection) -> QueryResult<Json<Vec<GenreDto>>> {
} }
#[get("/api/v1/genres/<genre_id>")] #[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::*; use media_backend::schema::*;
let genre: Genre = genres::table let genre: Genre = genres::table
.find(genre_id.uuid()) .find(genre_id.uuid())
...@@ -43,26 +46,27 @@ fn get_genre(db: MediaflixConnection, genre_id: ParamUuid) -> QueryResult<Json<G ...@@ -43,26 +46,27 @@ fn get_genre(db: MediaflixConnection, genre_id: ParamUuid) -> QueryResult<Json<G
.load::<Title>(&db.0)?; .load::<Title>(&db.0)?;
Ok(Json(GenreWithTitlesDto { Ok(Json(GenreWithTitlesDto {
genre: GenreDto::from(genre), genre: GenreDto::from(genre),
titles: load_titles(&db.0, titles)?, titles: load_titles(&db.0, &config, titles)?,
})) }))
} }
#[get("/api/v1/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::*; use media_backend::schema::*;
let titles = titles::table let titles = titles::table
.left_outer_join(title_episodes::table.on(title_episodes::episode_id.eq(titles::id))) .left_outer_join(title_episodes::table.on(title_episodes::episode_id.eq(titles::id)))
.filter(title_episodes::id.is_null()) .filter(title_episodes::id.is_null())
.select(titles::all_columns) .select(titles::all_columns)
.load::<Title>(&db.0)?; .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>")] #[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::*; use media_backend::schema::*;
let title = load_title( let title = load_title(
&db.0, &db.0,
&config,
titles::table titles::table
.find(title_id.uuid()) .find(title_id.uuid())
.first::<Title>(&db.0)?, .first::<Title>(&db.0)?,
...@@ -71,14 +75,14 @@ fn get_title(db: MediaflixConnection, title_id: ParamUuid) -> QueryResult<Json<T ...@@ -71,14 +75,14 @@ fn get_title(db: MediaflixConnection, title_id: ParamUuid) -> QueryResult<Json<T
} }
#[get("/api/v1/titles/<title_id>/episodes")] #[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::*; use media_backend::schema::*;
let titles: Vec<(TitleEpisode, Title)> = title_episodes::table let titles: Vec<(TitleEpisode, Title)> = title_episodes::table
.filter(title_episodes::show_id.eq(title_id.uuid())) .filter(title_episodes::show_id.eq(title_id.uuid()))
.inner_join(titles::table.on(titles::id.eq(title_episodes::episode_id))) .inner_join(titles::table.on(titles::id.eq(title_episodes::episode_id)))
.load::<(TitleEpisode, Title)>(&db.0)?; .load::<(TitleEpisode, Title)>(&db.0)?;
let (episodes, titles): (Vec<TitleEpisode>, Vec<Title>) = titles.into_iter().unzip(); 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) Ok(Json(episodes.into_iter().zip(titles)
.map(|(episode, title)| EpisodeDto::of(episode, title)) .map(|(episode, title)| EpisodeDto::of(episode, title))
.collect::<Vec<_>>())) .collect::<Vec<_>>()))
...@@ -88,6 +92,10 @@ fn main() -> Result<(), Box<dyn std::error::Error>> { ...@@ -88,6 +92,10 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
dotenv().ok(); dotenv().ok();
rocket::ignite() 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(MediaflixConnection::fairing())
.attach(rocket_cors::CorsOptions { .attach(rocket_cors::CorsOptions {
allowed_headers: AllowedHeaders::some(&["Authorization", "Accept", "Accept-Language"]), allowed_headers: AllowedHeaders::some(&["Authorization", "Accept", "Accept-Language"]),
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment