diff --git a/migrations/2022-04-16-153823_create_media/down.sql b/migrations/2022-04-16-153823_create_media/down.sql index 8333bd2..b3e428f 100644 --- a/migrations/2022-04-16-153823_create_media/down.sql +++ b/migrations/2022-04-16-153823_create_media/down.sql @@ -1,2 +1,4 @@ -- This file should undo anything in `up.sql` -DROP TABLE "media"; \ No newline at end of file +DROP TABLE "media"; +DROP TABLE "file"; +DROP TYPE "media_type"; \ No newline at end of file diff --git a/migrations/2022-04-16-153823_create_media/up.sql b/migrations/2022-04-16-153823_create_media/up.sql index 3acd7e4..8eaed52 100644 --- a/migrations/2022-04-16-153823_create_media/up.sql +++ b/migrations/2022-04-16-153823_create_media/up.sql @@ -1,11 +1,50 @@ -- Your SQL goes here-- Your SQL goes here +CREATE TYPE media_type AS ENUM ('Default', 'Audio', 'Video','Pdf','Epub','Image'); + +CREATE TABLE IF NOT EXISTS "file" ( + "uuid" uuid UNIQUE PRIMARY KEY NOT NULL, + "absolute_path" TEXT NOT NULL, + "uploaded_by" uuid NOT NULL, + "checksum" TEXT NOT NULL, + "size" INT NOT NULL DEFAULT 0, + "created_at" TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT CURRENT_TIMESTAMP, + "updated_at" TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT CURRENT_TIMESTAMP +); + CREATE TABLE IF NOT EXISTS "media" ( "uuid" uuid UNIQUE PRIMARY KEY NOT NULL, "title" TEXT NOT NULL, "description" TEXT, - "absolute_path" TEXT NOT NULL, "price" INT NOT NULL DEFAULT 0, "published" boolean NOT NULL DEFAULT false, + "file_uuid" uuid UNIQUE NOT NULL, + "type" media_type NOT NULL DEFAULT 'Default', + "metadata" uuid UNIQUE NOT NULL, "created_at" TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT CURRENT_TIMESTAMP, - "updated_at" TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT CURRENT_TIMESTAMP + "updated_at" TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT CURRENT_TIMESTAMP, + CONSTRAINT media FOREIGN KEY("file_uuid") REFERENCES "file"(uuid) +); + +CREATE TABLE IF NOT EXISTS "audio_metadata" ( + "uuid" uuid UNIQUE PRIMARY KEY NOT NULL, + "codec" TEXT DEFAULT NULL, + "length" TEXT DEFAULT NULL, + "artist" TEXT DEFAULT NULL +); + +CREATE TABLE IF NOT EXISTS "video_metadata" ( + "uuid" uuid UNIQUE PRIMARY KEY NOT NULL, + "codec" TEXT DEFAULT NULL, + "length" TEXT DEFAULT NULL +); + +CREATE TABLE IF NOT EXISTS "image_metadata" ( + "uuid" uuid UNIQUE PRIMARY KEY NOT NULL +); + +CREATE TABLE IF NOT EXISTS "epub_metadata" ( + "uuid" uuid UNIQUE PRIMARY KEY NOT NULL ); + + + diff --git a/src/db/media_type_enum.rs b/src/db/media_type_enum.rs new file mode 100644 index 0000000..145b85e --- /dev/null +++ b/src/db/media_type_enum.rs @@ -0,0 +1,12 @@ +use diesel_derive_enum::DbEnum; + +// define your enum +#[derive(Debug, DbEnum, PartialEq)] +pub enum MediaTypeEnum { + Default, + Audio, + Video, + Pdf, + Epub, + Image, +} diff --git a/src/db/mod.rs b/src/db/mod.rs index 21905c8..3460917 100644 --- a/src/db/mod.rs +++ b/src/db/mod.rs @@ -3,6 +3,7 @@ use rocket_sync_db_pools::{database, diesel}; use self::models::api_payment::ApiPayment; pub mod igniter; +pub mod media_type_enum; pub mod models; pub mod schema; diff --git a/src/db/models/file.rs b/src/db/models/file.rs new file mode 100644 index 0000000..8a02585 --- /dev/null +++ b/src/db/models/file.rs @@ -0,0 +1,51 @@ +pub use crate::db::schema::file; + +use chrono::NaiveDateTime; +use diesel; +use diesel::prelude::*; +use diesel::PgConnection; + +#[derive(Identifiable, Queryable, PartialEq, Debug)] +#[primary_key(uuid)] +#[table_name = "file"] +pub struct File { + pub uuid: uuid::Uuid, + pub absolute_path: String, + pub uploaded_by: uuid::Uuid, + pub checksum: String, + pub size: i32, + pub created_at: NaiveDateTime, + pub updated_at: NaiveDateTime, +} + +#[derive(Debug, Insertable)] +#[table_name = "file"] +pub struct NewFile { + pub absolute_path: String, + pub uploaded_by: uuid::Uuid, + pub checksum: String, + pub size: i32, +} + +impl File { + pub fn create(new_file: NewFile, connection: &PgConnection) -> QueryResult { + use crate::db::schema::file::dsl::*; + + diesel::insert_into::(file) + .values(&new_file) + .get_result(connection) + } + + pub fn delete(uuid: uuid::Uuid) -> () {} + + pub fn find_one_by_uuid( + file_uuid: uuid::Uuid, + connection: &PgConnection, + ) -> QueryResult> { + use crate::db::schema::file::dsl::*; + + file.filter(uuid.eq(file_uuid)) + .first::(connection) + .optional() + } +} diff --git a/src/db/models/media.rs b/src/db/models/media.rs index 8f44a4d..aa013cf 100644 --- a/src/db/models/media.rs +++ b/src/db/models/media.rs @@ -1,11 +1,14 @@ +use super::file::File; +use crate::db::media_type_enum::MediaTypeEnum; +use crate::db::media_type_enum::MediaTypeEnumMapping; pub use crate::db::schema::media; - use crate::graphql::types::input::file::FileInput; use chrono::NaiveDateTime; use diesel; use diesel::prelude::*; use diesel::PgConnection; use std::path::PathBuf; +use uuid::Uuid; #[derive(Identifiable, Queryable, PartialEq, Debug)] #[primary_key(uuid)] @@ -14,20 +17,53 @@ pub struct Media { pub uuid: uuid::Uuid, pub title: String, pub description: Option, - pub absolute_path: String, pub price: i32, pub published: bool, + pub file_uuid: uuid::Uuid, + pub type_: MediaTypeEnum, + pub metadata: uuid::Uuid, + pub created_at: NaiveDateTime, + pub updated_at: NaiveDateTime, +} + +#[derive(Identifiable, Queryable, PartialEq, Debug)] +#[primary_key(uuid)] +#[table_name = "media"] +pub struct MediaDbModel { + pub uuid: uuid::Uuid, + pub title: String, + pub description: Option, + pub price: i32, + pub published: bool, + pub file_uuid: uuid::Uuid, + pub type_: MediaTypeEnum, + pub metadata: uuid::Uuid, pub created_at: NaiveDateTime, pub updated_at: NaiveDateTime, } -#[derive(Debug, Insertable)] +#[derive(Debug, Insertable, Associations)] #[table_name = "media"] +#[belongs_to(parent = File, foreign_key = "file_uuid")] +// #[belongs_to(parent = Metadata, foreign_key = "file_uuid")] pub struct NewMedia { pub uuid: uuid::Uuid, pub title: String, pub description: Option, - pub absolute_path: String, + pub published: bool, + pub price: i32, + pub file_uuid: uuid::Uuid, + pub type_: MediaTypeEnum, + pub metadata: uuid::Uuid, +} + +#[derive(Debug, Queryable, Identifiable, PartialEq, AsChangeset)] +#[primary_key(uuid)] +#[table_name = "media"] +pub struct EditMedia { + pub uuid: uuid::Uuid, + pub title: String, + pub description: Option, pub published: bool, pub price: i32, } @@ -38,13 +74,21 @@ impl From<(&PathBuf, FileInput)> for NewMedia { uuid: uuid::Uuid::new_v4(), title: file_data.1.title, description: file_data.1.description, - absolute_path: file_data.0.to_string_lossy().to_owned().to_string(), price: file_data.1.price, published: file_data.1.published, + file_uuid: uuid::Uuid::new_v4(), + type_: MediaTypeEnum::Default, + metadata: uuid::Uuid::new_v4(), } } } +// impl From for Media { +// fn from(media: MediaDbModel) -> Self { +// Self { uuid: (), title: (), description: (), price: (), published: (), file_uuid: (), type_: (), metadata: (), created_at: (), updated_at: () } +// } +// } + impl Media { pub fn create(new_media: NewMedia, connection: &PgConnection) -> QueryResult { use crate::db::schema::media::dsl::*; @@ -54,11 +98,30 @@ impl Media { .get_result(connection) } + pub fn edit(edit_media: EditMedia, connection: &PgConnection) -> QueryResult { + use crate::db::schema::media::dsl::*; + diesel::update(media) + .set(&edit_media) + .get_result(connection) + } + + pub fn delete() -> () { + use crate::db::schema::media::dsl::*; + + // diesel::delete(media.filter(id)) + () + } + pub fn find_all_published(connection: &PgConnection) -> Vec { use crate::db::schema::media::dsl::*; media.filter(published.eq(true)).load(connection).unwrap() } + pub fn find_all(connection: &PgConnection) -> Vec { + use crate::db::schema::media::dsl::*; + media.filter(published.eq(true)).load(connection).unwrap() + } + pub fn find_one_by_uuid( media_uuid: uuid::Uuid, connection: &PgConnection, diff --git a/src/db/models/medias/audio.rs b/src/db/models/medias/audio.rs new file mode 100644 index 0000000..daa393c --- /dev/null +++ b/src/db/models/medias/audio.rs @@ -0,0 +1,7 @@ +#[derive(Debug, Clone)] +pub struct Audio{ + length: i64, + codec: String, + cover: String, + +} \ No newline at end of file diff --git a/src/db/models/medias/image.rs b/src/db/models/medias/image.rs new file mode 100644 index 0000000..e69de29 diff --git a/src/db/models/medias/video.rs b/src/db/models/medias/video.rs new file mode 100644 index 0000000..6a50e5c --- /dev/null +++ b/src/db/models/medias/video.rs @@ -0,0 +1,7 @@ +#[derive(Debug, Clone)] +pub struct Video{ + length: i64, + codec: String, + cover: String, + +} \ No newline at end of file diff --git a/src/db/models/metadata.rs b/src/db/models/metadata.rs new file mode 100644 index 0000000..e69de29 diff --git a/src/db/models/mod.rs b/src/db/models/mod.rs index 00a61da..dd083d7 100644 --- a/src/db/models/mod.rs +++ b/src/db/models/mod.rs @@ -1,4 +1,5 @@ pub mod api_payment; +pub mod file; pub mod media; pub mod media_payment; pub mod session; diff --git a/src/db/schema.rs b/src/db/schema.rs index ebc9d45..0b0591d 100644 --- a/src/db/schema.rs +++ b/src/db/schema.rs @@ -9,13 +9,52 @@ table! { } table! { + audio_metadata (uuid) { + uuid -> Uuid, + codec -> Nullable, + length -> Nullable, + artist -> Nullable, + } +} + +table! { + epub_metadata (uuid) { + uuid -> Uuid, + } +} + +table! { + file (uuid) { + uuid -> Uuid, + absolute_path -> Text, + uploaded_by -> Uuid, + checksum -> Text, + size -> Int4, + created_at -> Timestamptz, + updated_at -> Timestamptz, + } +} + +table! { + image_metadata (uuid) { + uuid -> Uuid, + } +} + +table! { + use diesel::sql_types::*; + use crate::db::media_type_enum::MediaTypeEnumMapping; + media (uuid) { uuid -> Uuid, title -> Text, description -> Nullable, - absolute_path -> Text, price -> Int4, published -> Bool, + file_uuid -> Uuid, + #[sql_name = "type"] + type_ -> MediaTypeEnumMapping, + metadata -> Uuid, created_at -> Timestamptz, updated_at -> Timestamptz, } @@ -53,7 +92,27 @@ table! { } } +table! { + video_metadata (uuid) { + uuid -> Uuid, + codec -> Nullable, + length -> Nullable, + } +} + +joinable!(media -> file (file_uuid)); joinable!(media_payment -> media (media_uuid)); joinable!(session -> user (user_uuid)); -allow_tables_to_appear_in_same_query!(api_payment, media, media_payment, session, user,); +allow_tables_to_appear_in_same_query!( + api_payment, + audio_metadata, + epub_metadata, + file, + image_metadata, + media, + media_payment, + session, + user, + video_metadata, +); diff --git a/src/graphql/mutation.rs b/src/graphql/mutation.rs index a506833..e0b419e 100644 --- a/src/graphql/mutation.rs +++ b/src/graphql/mutation.rs @@ -7,7 +7,10 @@ use crate::db::models::{ use super::{ context::GQLContext, - types::{input::file::FileInput, output::media::MediaType}, + types::{ + input::file::FileInput, + output::media::{MediaType, MediaUnion}, + }, }; use crate::graphql::mutations::update_password; @@ -24,6 +27,22 @@ impl Mutation { #[juniper::graphql_object(context = GQLContext)] impl Mutation { + // // Allows upload + // async fn upload<'a>( + // context: &'a GQLContext, + // file_input: FileInput + // ) -> FieldResult { + + // // Restrict mutation to authenticated users + // if Self::is_authenticated(&context.user) == false { + // return Err(FieldError::new( + // "You need to be authenticated to use this mutation", + // graphql_value!(""), + // )); + // } + + // } + #[graphql(description = "Upload and stores a payable media onto the server")] async fn upload_file<'a>( context: &'a GQLContext, diff --git a/src/graphql/queries/get_files_relay.rs b/src/graphql/queries/get_files_relay.rs index 357d934..ecd73c7 100644 --- a/src/graphql/queries/get_files_relay.rs +++ b/src/graphql/queries/get_files_relay.rs @@ -3,7 +3,10 @@ use juniper_relay_connection::RelayConnection; use crate::{ db::models::media::Media, - graphql::{context::GQLContext, types::output::media::MediaType}, + graphql::{ + context::GQLContext, + types::output::media::{MediaType, MediaUnion}, + }, }; pub async fn get_files_list_relay<'a>( @@ -12,7 +15,7 @@ pub async fn get_files_list_relay<'a>( after: Option, last: Option, before: Option, -) -> FieldResult> { +) -> FieldResult> { let connection = context.get_db_connection(); let db_results = connection.run(move |c| Media::find_all_published(c)).await; @@ -21,8 +24,8 @@ pub async fn get_files_list_relay<'a>( let result = RelayConnection::new(first, after, last, before, |first, after, last| { Ok(db_results .into_iter() - .map(|p| MediaType::from(p)) - .collect::>()) + .map(|p| MediaUnion::from(p)) + .collect::>()) }); result diff --git a/src/graphql/query.rs b/src/graphql/query.rs index 754964a..2c856b1 100644 --- a/src/graphql/query.rs +++ b/src/graphql/query.rs @@ -3,9 +3,9 @@ use super::queries::get_files_relay::get_files_list_relay; use super::queries::get_media::get_media; use super::queries::request_invoice_for_media::request_invoice_for_media; use super::types::output::media::MediaType; -use crate::db::models::media::Media; use crate::graphql::context::GQLContext; use crate::graphql::types::output::invoices::MediaInvoice; +use crate::{db::models::media::Media, graphql::types::output::media::MediaUnion}; use juniper::{FieldError, FieldResult}; use juniper_relay_connection::RelayConnection; use uuid::Uuid; @@ -42,18 +42,18 @@ impl Query { } #[graphql(description = "Gets the list of available medias")] - async fn get_medias_list(context: &'a GQLContext) -> Result, FieldError> { + async fn get_medias_list(context: &'a GQLContext) -> Result, FieldError> { let connection = context.get_db_connection(); let db_results = connection.run(move |c| Media::find_all_published(c)).await; Ok(db_results .into_iter() - .map(|media| MediaType::from(media)) - .collect::>()) + .map(|media| MediaUnion::from(media)) + .collect::>()) } #[graphql(description = "Gets available files with relay pagination")] - async fn get_files_relay(context: &'a GQLContext) -> FieldResult> { + async fn get_files_relay(context: &'a GQLContext) -> FieldResult> { get_files_list_relay(context, None, None, None, None).await } } diff --git a/src/graphql/types/input/file.rs b/src/graphql/types/input/file.rs index 81748b9..6034e06 100644 --- a/src/graphql/types/input/file.rs +++ b/src/graphql/types/input/file.rs @@ -1,10 +1,21 @@ +#[derive(Clone, GraphQLEnum)] +pub enum MediaType { + Default, + Audio, + Video, + Epub, + Pdf, + Image, +} + #[derive(Clone, GraphQLInputObject)] pub struct FileInput { pub filename: String, pub title: String, - pub description: Option, pub price: i32, + pub description: Option, pub published: bool, + pub kind: MediaType, // We expect this to be always `null` as per the spec // see : https://github.com/jaydenseric/graphql-multipart-request-spec pub file: Option, diff --git a/src/graphql/types/input/media.rs b/src/graphql/types/input/media.rs new file mode 100644 index 0000000..e69de29 diff --git a/src/graphql/types/output/file.rs b/src/graphql/types/output/file.rs new file mode 100644 index 0000000..7d4d393 --- /dev/null +++ b/src/graphql/types/output/file.rs @@ -0,0 +1,61 @@ +use chrono::NaiveDateTime; +use uuid::Uuid; + +use crate::{db::models::file::File, graphql::context::GQLContext}; + +#[derive(Debug)] +pub struct FileType { + pub uuid: uuid::Uuid, + pub absolute_path: String, + pub uploaded_by: uuid::Uuid, + pub checksum: String, + pub size: i32, + pub created_at: NaiveDateTime, + pub updated_at: NaiveDateTime, +} + +#[graphql_object( + name = "File", + description = "File output type" + context = GQLContext +)] +impl FileType { + #[graphql(description = "The file internal id")] + pub fn uuid(&self) -> uuid::Uuid { + self.uuid + } + + #[graphql(description = "The file checksum")] + pub fn checksum(&self) -> &String { + &self.checksum + } + + #[graphql(description = "The file size")] + pub fn size(&self) -> i32 { + self.size + } + + #[graphql(description = "The creation date on server")] + pub fn created_at(&self) -> NaiveDateTime { + self.created_at + } + + #[graphql(description = "The creation date on server")] + pub fn uploaded_by(&self) -> Uuid { + self.uploaded_by + } +} + +impl From for FileType { + fn from(item: File) -> Self { + Self { + uuid: item.uuid, + absolute_path: item.absolute_path, + uploaded_by: item.uploaded_by, + checksum: item.checksum, + size: item.size, + created_at: item.created_at, + updated_at: item.updated_at, + } + } +} diff --git a/src/graphql/types/output/media.rs b/src/graphql/types/output/media.rs index ef699ec..a23155c 100644 --- a/src/graphql/types/output/media.rs +++ b/src/graphql/types/output/media.rs @@ -1,3 +1,6 @@ +use super::file::FileType; +use crate::db::media_type_enum::MediaTypeEnum; +use crate::db::models::file::File; use crate::{ db::models::{ media::Media, @@ -12,7 +15,7 @@ use infer::Infer; use juniper::Value; use juniper::{FieldError, FieldResult}; use juniper_relay_connection::RelayConnectionNode; -use std::fs::File; +use uuid::Uuid; /// To be deleted // #[derive(Clone, Serialize, Deserialize)] @@ -24,27 +27,65 @@ use std::fs::File; // pub created_at: NaiveDateTime, // } +#[derive(GraphQLUnion)] +#[graphql(Context = GQLContext)] +pub enum MediaUnion { + Default(DefaultMedia), + Audio(AudioMedia), + Video(VideoMedia), + Pdf(PdfMedia), + Epub(EpubMedia), +} + +impl From for MediaUnion { + fn from(item: Media) -> MediaUnion { + match item.type_ { + // MediaTypeEnum::Audio => todo!(), + // MediaTypeEnum::Video => todo!(), + // MediaTypeEnum::Pdf => todo!(), + // MediaTypeEnum::Epub => todo!(), + // MediaTypeEnum::Image => todo!(), + _ => MediaUnion::Default(DefaultMedia { + uuid: item.uuid, + title: item.title, + file_uuid: item.file_uuid, + description: item.description, + price: item.price, + published: item.published, + created_at: item.created_at, + }), + } + } +} + #[derive(Clone)] pub struct MediaType { pub uuid: uuid::Uuid, pub title: String, pub description: Option, + pub file_uuid: uuid::Uuid, pub price: i32, pub published: bool, pub created_at: NaiveDateTime, - absolute_path: String, } impl From for MediaType { fn from(item: Media) -> Self { - Self { - uuid: item.uuid, - title: item.title, - description: item.description, - price: item.price, - published: item.published, - created_at: item.created_at, - absolute_path: item.absolute_path, + match item.type_ { + MediaTypeEnum::Audio => todo!(), + MediaTypeEnum::Video => todo!(), + MediaTypeEnum::Pdf => todo!(), + MediaTypeEnum::Epub => todo!(), + MediaTypeEnum::Image => todo!(), + _ => Self { + uuid: item.uuid, + title: item.title, + file_uuid: item.file_uuid, + description: item.description, + price: item.price, + published: item.published, + created_at: item.created_at, + }, } } } @@ -56,11 +97,11 @@ impl From<(Media, String)> for MediaType { Self { uuid: media.uuid, title: media.title, + file_uuid: media.file_uuid, description: media.description, price: media.price, published: media.published, created_at: media.created_at, - absolute_path: media.absolute_path, } } } @@ -137,32 +178,116 @@ impl MediaType { let uri = format!("/file/{}", &self.uuid); Ok(uri) } - #[graphql(description = "The file type")] - fn file_type(&self) -> Option<&str> { - let info = Infer::new(); - let kind = info.get_from_path(&self.absolute_path); - match kind { + #[graphql(description = "")] + async fn file<'a>(&self, context: &'a GQLContext) -> FieldResult { + let file_uuid = self.file_uuid; + let object = context + .get_db_connection() + .run(move |c| File::find_one_by_uuid(file_uuid, c)) + .await; + + match object { Ok(result) => match result { - Some(t) => return Some(t.extension()), - None => return None, + Some(result) => Ok(FileType::from(result)), + None => Err(FieldError::new( + "No File object found for current media", + Value::Null, + )), }, - Err(_) => return None, + Err(_) => Err(FieldError::new( + "Could not retrieve object from database", + Value::Null, + )), } } + // #[graphql(description = "The file type")] + // fn file_type(&self) -> Option<&str> { + // let info = Infer::new(); + // let kind = info.get_from_path(&self.absolute_path); - #[graphql(description = "The file size")] - fn file_size(&self) -> Option { - let file = File::open(&self.absolute_path); + // match kind { + // Ok(result) => match result { + // Some(t) => return Some(t.extension()), + // None => return None, + // }, + // Err(_) => return None, + // } + // } - match file { - Ok(file) => { - let size = file.metadata().unwrap().len().to_string(); - Some(size.parse::().unwrap_or(0)) - } - Err(_) => None, - } - } + // #[graphql(description = "The file size")] + // fn file_size(&self) -> Option { + // let file = File::open(&self.absolute_path); + + // match file { + // Ok(file) => { + // let size = file.metadata().unwrap().len().to_string(); + // Some(size.parse::().unwrap_or(0)) + // } + // Err(_) => None, + // } + // } +} + +#[derive(GraphQLObject)] +pub struct DefaultMedia { + pub uuid: uuid::Uuid, + pub title: String, + pub description: Option, + pub file_uuid: uuid::Uuid, + pub price: i32, + pub published: bool, + pub created_at: NaiveDateTime, +} +#[derive(GraphQLObject)] +pub struct AudioMedia { + pub uuid: uuid::Uuid, + pub title: String, + pub description: Option, + pub file_uuid: uuid::Uuid, + pub price: i32, + pub published: bool, + pub created_at: NaiveDateTime, +} +#[derive(GraphQLObject)] +pub struct VideoMedia { + pub uuid: uuid::Uuid, + pub title: String, + pub description: Option, + pub file_uuid: uuid::Uuid, + pub price: i32, + pub published: bool, + pub created_at: NaiveDateTime, +} +#[derive(GraphQLObject)] +pub struct EpubMedia { + pub uuid: uuid::Uuid, + pub title: String, + pub description: Option, + pub file_uuid: uuid::Uuid, + pub price: i32, + pub published: bool, + pub created_at: NaiveDateTime, +} +#[derive(GraphQLObject)] +pub struct PdfMedia { + pub uuid: uuid::Uuid, + pub title: String, + pub description: Option, + pub file_uuid: uuid::Uuid, + pub price: i32, + pub published: bool, + pub created_at: NaiveDateTime, +} +#[derive(GraphQLObject)] +pub struct ImageMedia { + pub uuid: uuid::Uuid, + pub title: String, + pub description: Option, + pub file_uuid: uuid::Uuid, + pub price: i32, + pub published: bool, + pub created_at: NaiveDateTime, } /// Implements relay connection for Medias @@ -183,3 +308,28 @@ impl RelayConnectionNode for MediaType { "MediaConnectionEdge" } } + +impl RelayConnectionNode for MediaUnion { + type Cursor = String; + + fn cursor(&self) -> Self::Cursor { + let uuid = match self { + MediaUnion::Default(instance) => instance.uuid, + MediaUnion::Audio(instance) => instance.uuid, + MediaUnion::Video(instance) => instance.uuid, + MediaUnion::Pdf(instance) => instance.uuid, + MediaUnion::Epub(instance) => instance.uuid, + }; + + let cursor = format!("media:{}", uuid); + base64::encode(cursor) + } + + fn connection_type_name() -> &'static str { + "MediaUnionConnection" + } + + fn edge_type_name() -> &'static str { + "MediaUnionConnectionEdge" + } +} diff --git a/src/graphql/types/output/mod.rs b/src/graphql/types/output/mod.rs index a57402d..535dbed 100644 --- a/src/graphql/types/output/mod.rs +++ b/src/graphql/types/output/mod.rs @@ -1,3 +1,4 @@ +pub mod file; pub mod invoices; pub mod media; pub mod payment; diff --git a/src/routes/file.rs b/src/routes/file.rs index 81e6b21..590dd90 100644 --- a/src/routes/file.rs +++ b/src/routes/file.rs @@ -15,6 +15,7 @@ use uuid::Uuid; use crate::{ db::{ models::{ + file::File, media::Media, media_payment::{MediaPayment, NewMediaPayment}, }, @@ -35,6 +36,7 @@ pub enum FileHandlingError { LNFailure, UuidParsingError, PaymentRequired, + FileNotFound, } /// A route to retrieve files behind the paywall. @@ -62,9 +64,26 @@ pub async fn get_file( // We can so consider the unwrap as safe. let media = media.unwrap(); - // If the media exists and is free we should deliver it to the user without performing any further operation + // Calls the get_file_entry to try to retrieve the requested file from database + let file = get_file_entry(&(media.file_uuid.to_string()), &db).await; + + // Builds the error response if the file could not be retrieved + if file.is_err() { + return match file.unwrap_err() { + FileHandlingError::DbFailure => Err(status::Custom(Status::InternalServerError, None)), + FileHandlingError::FileNotFound => Err(status::Custom(Status::NotFound, None)), + FileHandlingError::UuidParsingError => Err(status::Custom(Status::BadRequest, None)), + _ => Err(status::Custom(Status::ImATeapot, None)), + }; + } + + // There's no reason we could not unwrap the media as the match above should ensure to handle all the error cases. + // We can so consider the unwrap as safe. + let file = file.unwrap(); + + // If the file exists and is free we should deliver it to the user without performing any further operation if media.price == 0 { - return set_download_responder(media).await; + return set_download_responder(file).await; } // Otherwise we ensure try to retrieve an associated payment to the requested media. @@ -109,7 +128,7 @@ pub async fn get_file( let invoice = invoice.unwrap(); match invoice.state() { - InvoiceState::Settled => set_download_responder(media).await, + InvoiceState::Settled => set_download_responder(file).await, InvoiceState::Accepted => Err(status::Custom(Status::NotFound, None)), InvoiceState::Canceled => { let invoice = request_new_media_payment(&media, lnd, db).await; @@ -190,6 +209,25 @@ async fn get_media(uuid: &String, db: &PostgresConn) -> Result Result { + let uuid = Uuid::parse_str(uuid.as_str()); + + match uuid { + Ok(uuid) => { + let file = db.run(move |c| File::find_one_by_uuid(uuid, c)).await; + match file { + Ok(file) => match file { + Some(file) => Ok(file), + None => Err(FileHandlingError::FileNotFound), + }, + Err(_) => Err(FileHandlingError::DbFailure), + } + } + Err(_) => Err(FileHandlingError::UuidParsingError), + } +} + // Retrieves a media payment based on async fn get_media_payment( payment_request: Option, @@ -237,9 +275,9 @@ async fn get_invoice( } async fn set_download_responder( - media: Media, + file: File, ) -> Result>>> { - let path = Path::new(&media.absolute_path); + let path = Path::new(&file.absolute_path); let filename = path.file_name(); match filename {