Initial commit

Signed-off-by: Louis Vallat <louis@louis-vallat.xyz>
This commit is contained in:
Louis Vallat 2021-06-09 11:53:04 +02:00
commit a5649447ff
12 changed files with 1727 additions and 0 deletions

2
.gitignore vendored Normal file
View File

@ -0,0 +1,2 @@
/target
.idea/

1395
Cargo.lock generated Normal file

File diff suppressed because it is too large Load Diff

12
Cargo.toml Normal file
View File

@ -0,0 +1,12 @@
[package]
name = "moviequotebot_api"
version = "0.1.0"
authors = ["louis"]
edition = "2018"
[dependencies]
actix-web = "4.0.0-beta.6"
serde = "1.0.126"
serde_json = "1.0.64"
diesel = { version = "1.4.7", features = ["postgres", "chrono"] }
chrono = { version = "0.4.19", features = ["serde"]}

5
diesel.toml Normal file
View File

@ -0,0 +1,5 @@
# For documentation on how to configure this file,
# see diesel.rs/guides/configuring-diesel-cli
[print_schema]
file = "src/schema.rs"

0
migrations/.gitkeep Normal file
View File

View File

@ -0,0 +1,2 @@
-- This file should undo anything in `up.sql`
DROP TABLE IF EXISTS properties, films, languages, subtitle_lines, subtitles, user_config;

View File

@ -0,0 +1,93 @@
-- Your SQL goes here
/**
Store some information on the application.
*/
CREATE TABLE IF NOT EXISTS properties
(
id int GENERATED ALWAYS AS IDENTITY,
app_key text NOT NULL,
app_value text NOT NULL,
PRIMARY KEY (id),
UNIQUE (app_key)
);
/**
Film
*/
CREATE TABLE IF NOT EXISTS films
(
id int GENERATED ALWAYS AS IDENTITY,
imdb_id varchar(10) NOT NULL,
title text NOT NULL,
year int,
film_type text NOT NULL,
season int,
episode int,
poster_link text,
PRIMARY KEY (id),
UNIQUE (imdb_id)
);
/**
Available languages
*/
CREATE TABLE IF NOT EXISTS languages
(
id int GENERATED ALWAYS AS IDENTITY,
alpha3_b char(3) NOT NULL,
alpha3_t char(3),
alpha2 char(2),
english text NOT NULL,
french text NOT NULL,
PRIMARY KEY (id),
UNIQUE (alpha3_b)
);
/**
Subtitles
*/
CREATE TABLE IF NOT EXISTS subtitles
(
id int GENERATED ALWAYS AS IDENTITY,
film_id int NOT NULL,
language_id int NOT NULL,
importer_id bigint,
importer_guild_id bigint,
imported_date timestamptz NOT NULL DEFAULT now(),
UNIQUE (film_id, language_id),
PRIMARY KEY (id),
FOREIGN KEY (film_id)
REFERENCES films (id),
FOREIGN KEY (language_id)
REFERENCES languages (id)
);
/**
Subtitle lines
*/
CREATE TABLE IF NOT EXISTS subtitle_lines
(
id int GENERATED ALWAYS AS IDENTITY,
subtitle_id int NOT NULL,
dialog_line text NOT NULL,
time_code text NOT NULL,
PRIMARY KEY (id),
FOREIGN KEY (subtitle_id)
REFERENCES subtitles (id)
);
/**
User configurations
*/
CREATE TABLE IF NOT EXISTS user_config
(
id int GENERATED ALWAYS AS IDENTITY,
user_id bigint NOT NULL,
guild_id bigint DEFAULT NULL,
default_language_id int DEFAULT NULL,
PRIMARY KEY (id),
UNIQUE (user_id, guild_id),
FOREIGN KEY (default_language_id)
REFERENCES languages (id)
);

8
src/lib.rs Normal file
View File

@ -0,0 +1,8 @@
use diesel::pg::PgConnection;
use std::env;
use diesel::Connection;
pub async fn establish_connection() -> PgConnection {
let database_url = env::var("DATABASE_URL").expect("DATABASE_URL must be set");
PgConnection::establish(&database_url).expect(&format!("Error connecting to {}", database_url))
}

32
src/main.rs Normal file
View File

@ -0,0 +1,32 @@
#[macro_use]
extern crate diesel;
extern crate actix_web;
mod schema;
mod model;
mod route;
mod lib;
use actix_web::{App, HttpServer, Responder, web};
use std::env;
async fn health() -> impl Responder {
"Healthy"
}
#[actix_web::main]
async fn main() -> std::io::Result<()> {
HttpServer::new(|| {
App::new().service(web::scope("/v1")
.configure(route::init_routes)
)
.route("/health", web::get().to(health))
})
.bind(format!("{}:{}",
env::var("HOST_IP").unwrap_or("0.0.0.0".to_string()),
env::var("HOST_PORT").unwrap_or("8080".to_string())
))?
.run()
.await
}

60
src/model.rs Normal file
View File

@ -0,0 +1,60 @@
use serde::{Deserialize, Serialize};
use chrono::{DateTime, Utc};
#[derive(Deserialize, Serialize, Queryable)]
pub struct Subtitle {
pub id: i32,
pub film_id: i32,
pub language_id: i32,
pub importer_id: Option<i64>,
pub importer_guild_id: Option<i64>,
pub imported_date: DateTime<Utc>,
}
#[derive(Deserialize, Serialize, Queryable)]
pub struct Language {
pub id: i32,
pub alpha3_b: String,
pub alpha3_t: Option<String>,
pub alpha2_b: Option<String>,
pub english: String,
pub french: String,
}
#[derive(Deserialize, Serialize, Queryable)]
pub struct Film {
pub id: i32,
pub imdb_id: String,
pub title: String,
pub year: Option<i32>,
pub film_type: String,
pub season: Option<i32>,
pub episode: Option<i32>,
pub poster_link: Option<String>,
}
#[derive(Deserialize, Serialize)]
pub struct SubtitleTrimmed {
pub id: i32,
pub film_id: i32,
pub language_id: i32,
pub imported_date: DateTime<Utc>,
}
impl SubtitleTrimmed {
pub fn from(s: Subtitle) -> SubtitleTrimmed {
SubtitleTrimmed {
id: s.id,
film_id: s.film_id,
language_id: s.language_id,
imported_date: s.imported_date
}
}
}
#[derive(Deserialize, Serialize, Queryable)]
pub struct SubtitleOutV1 {
pub subtitles: Vec<SubtitleTrimmed>,
pub films: Vec<Film>,
pub languages: Vec<Language>,
}

44
src/route.rs Normal file
View File

@ -0,0 +1,44 @@
extern crate diesel;
use actix_web::{get, web, HttpResponse, Responder};
use crate::diesel::prelude::*;
use crate::lib::establish_connection;
use crate::model::*;
#[get("/subtitles/list")]
async fn list_all() -> impl Responder {
use crate::schema::films::dsl::*;
use crate::schema::subtitles::dsl::*;
use crate::schema::languages::dsl::*;
let connection = establish_connection().await;
let results = subtitles
.inner_join(films)
.inner_join(languages)
.load::<(Subtitle, Film, Language)>(&connection).expect("Error loading subtitles");
let mut s: Vec<SubtitleTrimmed> = vec![];
let mut f: Vec<Film> = vec![];
let mut l: Vec<Language> = vec![];
for r in results {
s.push(SubtitleTrimmed::from(r.0));
let f_id = r.1.id;
let l_id = r.2.id;
if !f.iter().any(|film| film.id == f_id) {
f.push(r.1);
}
if !l.iter().any(|language| language.id == l_id) {
l.push(r.2);
}
}
HttpResponse::Ok().json(SubtitleOutV1{
subtitles: s,
films: f,
languages: l
})
}
pub fn init_routes(config: &mut web::ServiceConfig) {
config.service(list_all);
}

74
src/schema.rs Normal file
View File

@ -0,0 +1,74 @@
table! {
films (id) {
id -> Int4,
imdb_id -> Varchar,
title -> Text,
year -> Nullable<Int4>,
film_type -> Text,
season -> Nullable<Int4>,
episode -> Nullable<Int4>,
poster_link -> Nullable<Text>,
}
}
table! {
languages (id) {
id -> Int4,
alpha3_b -> Bpchar,
alpha3_t -> Nullable<Bpchar>,
alpha2 -> Nullable<Bpchar>,
english -> Text,
french -> Text,
}
}
table! {
properties (id) {
id -> Int4,
app_key -> Text,
app_value -> Text,
}
}
table! {
subtitle_lines (id) {
id -> Int4,
subtitle_id -> Int4,
dialog_line -> Text,
time_code -> Text,
}
}
table! {
subtitles (id) {
id -> Int4,
film_id -> Int4,
language_id -> Int4,
importer_id -> Nullable<Int8>,
importer_guild_id -> Nullable<Int8>,
imported_date -> Timestamptz,
}
}
table! {
user_config (id) {
id -> Int4,
user_id -> Int8,
guild_id -> Nullable<Int8>,
default_language_id -> Nullable<Int4>,
}
}
joinable!(subtitle_lines -> subtitles (subtitle_id));
joinable!(subtitles -> films (film_id));
joinable!(subtitles -> languages (language_id));
joinable!(user_config -> languages (default_language_id));
allow_tables_to_appear_in_same_query!(
films,
languages,
properties,
subtitle_lines,
subtitles,
user_config,
);