From 2679198d91f9885f28b8381348c973cd99e9d994 Mon Sep 17 00:00:00 2001 From: Asone Date: Tue, 4 Oct 2022 21:35:34 +0200 Subject: [PATCH 1/4] chore(feat): create a file struct --- src/db/models/file.rs | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) create mode 100644 src/db/models/file.rs diff --git a/src/db/models/file.rs b/src/db/models/file.rs new file mode 100644 index 0000000..79e04f4 --- /dev/null +++ b/src/db/models/file.rs @@ -0,0 +1,20 @@ + +pub use crate::db::schema::file; + +use crate::graphql::types::input::file::FileInput; +use chrono::NaiveDateTime; +use diesel; +use diesel::prelude::*; +use diesel::PgConnection; +use std::path::PathBuf; + + +#[derive(Identifiable, Queryable, PartialEq, Debug)] +#[primary_key(uuid)] +#[table_name = "file"] +pub struct File{ + pub uuid: uuid::Uuid, + pub absolute_path: String, + pub created_at: NaiveDateTime, + pub updated_at: NaiveDateTime, +} \ No newline at end of file From 017aec1177e65661ef78c293e22bd6d400e45df1 Mon Sep 17 00:00:00 2001 From: Asone Date: Sun, 16 Oct 2022 08:59:58 +0200 Subject: [PATCH 2/4] feat(media): draft new media model implementation --- .../2022-04-16-153823_create_media/down.sql | 4 +- .../2022-04-16-153823_create_media/up.sql | 43 +++++++++- src/db/media_type_enum.rs | 12 +++ src/db/mod.rs | 1 + src/db/models/file.rs | 29 ++++++- src/db/models/media.rs | 79 +++++++++++++++++-- src/db/models/medias/audio.rs | 7 ++ src/db/models/medias/image.rs | 0 src/db/models/medias/video.rs | 7 ++ src/db/models/metadata.rs | 0 src/db/models/mod.rs | 1 + src/db/schema.rs | 63 ++++++++++++++- src/graphql/types/output/media.rs | 61 +++++++------- src/routes/file.rs | 10 +-- 14 files changed, 267 insertions(+), 50 deletions(-) create mode 100644 src/db/media_type_enum.rs create mode 100644 src/db/models/medias/audio.rs create mode 100644 src/db/models/medias/image.rs create mode 100644 src/db/models/medias/video.rs create mode 100644 src/db/models/metadata.rs 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..95b3572 --- /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 +} \ No newline at end of file diff --git a/src/db/mod.rs b/src/db/mod.rs index 21905c8..4b81fce 100644 --- a/src/db/mod.rs +++ b/src/db/mod.rs @@ -5,6 +5,7 @@ use self::models::api_payment::ApiPayment; pub mod igniter; pub mod models; pub mod schema; +pub mod media_type_enum; #[database("main_db")] pub struct PostgresConn(pub diesel::PgConnection); diff --git a/src/db/models/file.rs b/src/db/models/file.rs index 79e04f4..136e91f 100644 --- a/src/db/models/file.rs +++ b/src/db/models/file.rs @@ -1,12 +1,10 @@ pub use crate::db::schema::file; -use crate::graphql::types::input::file::FileInput; use chrono::NaiveDateTime; use diesel; use diesel::prelude::*; use diesel::PgConnection; -use std::path::PathBuf; #[derive(Identifiable, Queryable, PartialEq, Debug)] @@ -15,6 +13,33 @@ use std::path::PathBuf; pub struct File{ pub uuid: uuid::Uuid, pub absolute_path: String, + pub uploaded_by: uuid::Uuid, + pub checksum: String, + pub price: 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) -> () { + + } + } \ No newline at end of file diff --git a/src/db/models/media.rs b/src/db/models/media.rs index 8f44a4d..3ed89e1 100644 --- a/src/db/models/media.rs +++ b/src/db/models/media.rs @@ -1,12 +1,17 @@ +use crate::db::media_type_enum::MediaTypeEnum; +use crate::db::media_type_enum::MediaTypeEnumMapping; pub use crate::db::schema::media; - +use super::file::File; use crate::graphql::types::input::file::FileInput; use chrono::NaiveDateTime; use diesel; use diesel::prelude::*; use diesel::PgConnection; +use uuid::Uuid; use std::path::PathBuf; + + #[derive(Identifiable, Queryable, PartialEq, Debug)] #[primary_key(uuid)] #[table_name = "media"] @@ -14,38 +19,83 @@ 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, +} + + impl From<(&PathBuf, FileInput)> for NewMedia { fn from(file_data: (&PathBuf, FileInput)) -> Self { Self { 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 +104,29 @@ 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, @@ -70,4 +138,5 @@ impl Media { .first::(connection) .optional() } + } 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..8cdb1f8 100644 --- a/src/db/models/mod.rs +++ b/src/db/models/mod.rs @@ -4,3 +4,4 @@ pub mod media_payment; pub mod session; pub mod user; pub mod user_token; +pub mod file; 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/types/output/media.rs b/src/graphql/types/output/media.rs index 078a8f2..6947070 100644 --- a/src/graphql/types/output/media.rs +++ b/src/graphql/types/output/media.rs @@ -14,7 +14,6 @@ use juniper::{FieldError, FieldResult}; use juniper_relay::RelayConnectionNode; use std::fs::File; - /// To be deleted // #[derive(Clone, Serialize, Deserialize)] // pub struct MediaPreviewType { @@ -33,7 +32,6 @@ pub struct MediaType { pub price: i32, pub published: bool, pub created_at: NaiveDateTime, - absolute_path: String, } impl From for MediaType { @@ -44,8 +42,7 @@ impl From for MediaType { description: item.description, price: item.price, published: item.published, - created_at: item.created_at, - absolute_path: item.absolute_path, + created_at: item.created_at } } } @@ -60,14 +57,12 @@ impl From<(Media, String)> for MediaType { description: media.description, price: media.price, published: media.published, - created_at: media.created_at, - absolute_path: media.absolute_path, + created_at: media.created_at } } } impl MediaType { - // This method builds a json object with payment requirements details. // The json object will be provided as an error's extension of graphql response async fn _generate_invoiced_error(&self, context: &GQLContext, message: &str) -> FieldError { @@ -139,32 +134,32 @@ 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 { - Ok(result) => match result { - Some(t) => return Some(t.extension()), - None => return None, - }, - Err(_) => return 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, - } - } + // #[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 { + // Ok(result) => match result { + // Some(t) => return Some(t.extension()), + // None => return None, + // }, + // Err(_) => return 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, + // } + // } } /// Implements relay connection for Medias diff --git a/src/routes/file.rs b/src/routes/file.rs index 81e6b21..5515325 100644 --- a/src/routes/file.rs +++ b/src/routes/file.rs @@ -16,7 +16,7 @@ use crate::{ db::{ models::{ media::Media, - media_payment::{MediaPayment, NewMediaPayment}, + media_payment::{MediaPayment, NewMediaPayment}, file::File, }, PostgresConn, }, @@ -64,7 +64,7 @@ pub async fn get_file( // If the media 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(media.file).await; } // Otherwise we ensure try to retrieve an associated payment to the requested media. @@ -109,7 +109,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(media.file).await, InvoiceState::Accepted => Err(status::Custom(Status::NotFound, None)), InvoiceState::Canceled => { let invoice = request_new_media_payment(&media, lnd, db).await; @@ -237,9 +237,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 { From 6682b3d7b261133b8c56a70d7963f8371aea2656 Mon Sep 17 00:00:00 2001 From: Asone Date: Sun, 16 Oct 2022 14:04:11 +0200 Subject: [PATCH 3/4] feat(media): refactor file download to be handled with the new data structure --- src/db/models/file.rs | 28 +++++++++++-------- src/db/models/media.rs | 30 ++++++++------------ src/db/models/mod.rs | 2 +- src/graphql/types/output/file.rs | 45 ++++++++++++++++++++++++++++++ src/graphql/types/output/media.rs | 33 ++++++++++++++++++++-- src/graphql/types/output/mod.rs | 1 + src/routes/file.rs | 46 ++++++++++++++++++++++++++++--- 7 files changed, 148 insertions(+), 37 deletions(-) create mode 100644 src/graphql/types/output/file.rs diff --git a/src/db/models/file.rs b/src/db/models/file.rs index 136e91f..8a02585 100644 --- a/src/db/models/file.rs +++ b/src/db/models/file.rs @@ -1,36 +1,34 @@ - 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 struct File { pub uuid: uuid::Uuid, pub absolute_path: String, pub uploaded_by: uuid::Uuid, pub checksum: String, - pub price: i32, + pub size: i32, pub created_at: NaiveDateTime, pub updated_at: NaiveDateTime, } #[derive(Debug, Insertable)] #[table_name = "file"] -pub struct NewFile{ +pub struct NewFile { pub absolute_path: String, pub uploaded_by: uuid::Uuid, pub checksum: String, - pub size: i32 + pub size: i32, } -impl File{ - pub fn create(new_file: NewFile, connection: &PgConnection) -> QueryResult { +impl File { + pub fn create(new_file: NewFile, connection: &PgConnection) -> QueryResult { use crate::db::schema::file::dsl::*; diesel::insert_into::(file) @@ -38,8 +36,16 @@ impl File{ .get_result(connection) } - pub fn delete(uuid: uuid::Uuid) -> () { + 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::*; -} \ No newline at end of file + file.filter(uuid.eq(file_uuid)) + .first::(connection) + .optional() + } +} diff --git a/src/db/models/media.rs b/src/db/models/media.rs index 3ed89e1..aa013cf 100644 --- a/src/db/models/media.rs +++ b/src/db/models/media.rs @@ -1,16 +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 super::file::File; use crate::graphql::types::input::file::FileInput; use chrono::NaiveDateTime; use diesel; use diesel::prelude::*; use diesel::PgConnection; -use uuid::Uuid; use std::path::PathBuf; - - +use uuid::Uuid; #[derive(Identifiable, Queryable, PartialEq, Debug)] #[primary_key(uuid)] @@ -22,11 +20,10 @@ pub struct Media { pub price: i32, pub published: bool, pub file_uuid: uuid::Uuid, - pub type_ : MediaTypeEnum, + pub type_: MediaTypeEnum, pub metadata: uuid::Uuid, pub created_at: NaiveDateTime, pub updated_at: NaiveDateTime, - } #[derive(Identifiable, Queryable, PartialEq, Debug)] @@ -39,11 +36,10 @@ pub struct MediaDbModel { pub price: i32, pub published: bool, pub file_uuid: uuid::Uuid, - pub type_ : MediaTypeEnum, + pub type_: MediaTypeEnum, pub metadata: uuid::Uuid, pub created_at: NaiveDateTime, pub updated_at: NaiveDateTime, - } #[derive(Debug, Insertable, Associations)] @@ -61,7 +57,7 @@ pub struct NewMedia { pub metadata: uuid::Uuid, } -#[derive(Debug, Queryable,Identifiable, PartialEq, AsChangeset)] +#[derive(Debug, Queryable, Identifiable, PartialEq, AsChangeset)] #[primary_key(uuid)] #[table_name = "media"] pub struct EditMedia { @@ -72,7 +68,6 @@ pub struct EditMedia { pub price: i32, } - impl From<(&PathBuf, FileInput)> for NewMedia { fn from(file_data: (&PathBuf, FileInput)) -> Self { Self { @@ -83,7 +78,7 @@ impl From<(&PathBuf, FileInput)> for NewMedia { published: file_data.1.published, file_uuid: uuid::Uuid::new_v4(), type_: MediaTypeEnum::Default, - metadata: uuid::Uuid::new_v4() + metadata: uuid::Uuid::new_v4(), } } } @@ -95,7 +90,6 @@ impl From<(&PathBuf, FileInput)> for NewMedia { // } impl Media { - pub fn create(new_media: NewMedia, connection: &PgConnection) -> QueryResult { use crate::db::schema::media::dsl::*; @@ -104,14 +98,15 @@ 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 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::*; + use crate::db::schema::media::dsl::*; // diesel::delete(media.filter(id)) () @@ -138,5 +133,4 @@ impl Media { .first::(connection) .optional() } - } diff --git a/src/db/models/mod.rs b/src/db/models/mod.rs index 8cdb1f8..dd083d7 100644 --- a/src/db/models/mod.rs +++ b/src/db/models/mod.rs @@ -1,7 +1,7 @@ pub mod api_payment; +pub mod file; pub mod media; pub mod media_payment; pub mod session; pub mod user; pub mod user_token; -pub mod file; diff --git a/src/graphql/types/output/file.rs b/src/graphql/types/output/file.rs new file mode 100644 index 0000000..c138605 --- /dev/null +++ b/src/graphql/types/output/file.rs @@ -0,0 +1,45 @@ +use chrono::NaiveDateTime; + +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 + } +} + +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 139b684..86676ff 100644 --- a/src/graphql/types/output/media.rs +++ b/src/graphql/types/output/media.rs @@ -1,3 +1,5 @@ +use super::file::FileType; +use crate::db::models::file::File; use crate::{ db::models::{ media::Media, @@ -12,7 +14,6 @@ use infer::Infer; use juniper::Value; use juniper::{FieldError, FieldResult}; use juniper_relay_connection::RelayConnectionNode; -use std::fs::File; /// To be deleted // #[derive(Clone, Serialize, Deserialize)] @@ -29,6 +30,7 @@ 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, @@ -39,10 +41,11 @@ impl From for MediaType { 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 + created_at: item.created_at, } } } @@ -54,10 +57,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 + created_at: media.created_at, } } } @@ -134,6 +138,29 @@ impl MediaType { let uri = format!("/file/{}", &self.uuid); Ok(uri) } + + #[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(result) => Ok(FileType::from(result)), + None => Err(FieldError::new( + "No File object found for current media", + Value::Null, + )), + }, + 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(); 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 5515325..590dd90 100644 --- a/src/routes/file.rs +++ b/src/routes/file.rs @@ -15,8 +15,9 @@ use uuid::Uuid; use crate::{ db::{ models::{ + file::File, media::Media, - media_payment::{MediaPayment, NewMediaPayment}, file::File, + media_payment::{MediaPayment, NewMediaPayment}, }, PostgresConn, }, @@ -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.file).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.file).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, From 9789b0bec552db2c2eec478f21f01d3174d0c0c9 Mon Sep 17 00:00:00 2001 From: Asone Date: Tue, 18 Oct 2022 20:28:19 +0200 Subject: [PATCH 4/4] feat(media): wip of graphql union media handlers --- src/db/media_type_enum.rs | 4 +- src/db/mod.rs | 2 +- src/graphql/mutation.rs | 21 +++- src/graphql/queries/get_files_relay.rs | 11 +- src/graphql/query.rs | 10 +- src/graphql/types/input/file.rs | 13 ++- src/graphql/types/input/media.rs | 0 src/graphql/types/output/file.rs | 16 +++ src/graphql/types/output/media.rs | 142 +++++++++++++++++++++++-- 9 files changed, 197 insertions(+), 22 deletions(-) create mode 100644 src/graphql/types/input/media.rs diff --git a/src/db/media_type_enum.rs b/src/db/media_type_enum.rs index 95b3572..145b85e 100644 --- a/src/db/media_type_enum.rs +++ b/src/db/media_type_enum.rs @@ -8,5 +8,5 @@ pub enum MediaTypeEnum { Video, Pdf, Epub, - Image -} \ No newline at end of file + Image, +} diff --git a/src/db/mod.rs b/src/db/mod.rs index 4b81fce..3460917 100644 --- a/src/db/mod.rs +++ b/src/db/mod.rs @@ -3,9 +3,9 @@ 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; -pub mod media_type_enum; #[database("main_db")] pub struct PostgresConn(pub diesel::PgConnection); 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 index c138605..7d4d393 100644 --- a/src/graphql/types/output/file.rs +++ b/src/graphql/types/output/file.rs @@ -1,4 +1,5 @@ use chrono::NaiveDateTime; +use uuid::Uuid; use crate::{db::models::file::File, graphql::context::GQLContext}; @@ -28,6 +29,21 @@ impl FileType { 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 { diff --git a/src/graphql/types/output/media.rs b/src/graphql/types/output/media.rs index 86676ff..a23155c 100644 --- a/src/graphql/types/output/media.rs +++ b/src/graphql/types/output/media.rs @@ -1,4 +1,5 @@ use super::file::FileType; +use crate::db::media_type_enum::MediaTypeEnum; use crate::db::models::file::File; use crate::{ db::models::{ @@ -14,6 +15,7 @@ use infer::Infer; use juniper::Value; use juniper::{FieldError, FieldResult}; use juniper_relay_connection::RelayConnectionNode; +use uuid::Uuid; /// To be deleted // #[derive(Clone, Serialize, Deserialize)] @@ -25,6 +27,37 @@ use juniper_relay_connection::RelayConnectionNode; // 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, @@ -38,14 +71,21 @@ pub struct MediaType { impl From for MediaType { fn from(item: Media) -> Self { - 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, + 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, + }, } } } @@ -189,6 +229,67 @@ impl MediaType { // } } +#[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 /// It allows using obscure cursors for pagination impl RelayConnectionNode for MediaType { @@ -207,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" + } +}