use std::str::FromStr;
use chrono::{DateTime, Utc};
use serde::{Deserialize, Serialize};
use sqlx::{postgres::PgQueryResult, prelude::FromRow};
use utoipa::ToSchema;
use uuid::Uuid;
use super::{document_box::DocumentBoxScopeRaw, file::FileId};
use crate::{DbExecutor, DbResult};
pub type GeneratedFileId = Uuid;
#[derive(
Debug,
Clone,
Copy,
strum::EnumString,
strum::Display,
Deserialize,
Serialize,
ToSchema,
PartialEq,
Eq,
)]
pub enum GeneratedFileType {
Pdf,
CoverPage,
SmallThumbnail,
LargeThumbnail,
TextContent,
HtmlContent,
Metadata,
}
impl TryFrom<String> for GeneratedFileType {
type Error = strum::ParseError;
fn try_from(value: String) -> Result<Self, Self::Error> {
GeneratedFileType::from_str(&value)
}
}
#[derive(Debug, Clone, FromRow, Serialize, ToSchema)]
pub struct GeneratedFile {
#[schema(value_type = Uuid)]
pub id: GeneratedFileId,
#[schema(value_type = Uuid)]
pub file_id: FileId,
pub mime: String,
#[sqlx(rename = "type")]
#[serde(rename = "type")]
#[sqlx(try_from = "String")]
pub ty: GeneratedFileType,
pub hash: String,
#[serde(skip)]
pub file_key: String,
pub created_at: DateTime<Utc>,
}
impl Eq for GeneratedFile {}
impl PartialEq for GeneratedFile {
fn eq(&self, other: &Self) -> bool {
self.id.eq(&other.id)
&& self.file_id.eq(&other.file_id)
&& self.mime.eq(&other.mime)
&& self.ty.eq(&other.ty)
&& self.hash.eq(&other.hash)
&& self.file_key.eq(&other.file_key)
&& self
.created_at
.timestamp_millis()
.eq(&other.created_at.timestamp_millis())
}
}
#[derive(Debug)]
pub struct CreateGeneratedFile {
pub id: Uuid,
pub file_id: FileId,
pub mime: String,
pub ty: GeneratedFileType,
pub hash: String,
pub file_key: String,
pub created_at: DateTime<Utc>,
}
impl GeneratedFile {
pub async fn create(
db: impl DbExecutor<'_>,
CreateGeneratedFile {
id,
file_id,
ty,
hash,
file_key,
mime,
created_at,
}: CreateGeneratedFile,
) -> DbResult<GeneratedFile> {
sqlx::query(
r#"
INSERT INTO "docbox_generated_files"
("id", "file_id", "mime", "type", "hash", "file_key", "created_at")
VALUES ($1, $2, $3, $4, $5, $6, $7)
"#,
)
.bind(id)
.bind(file_id)
.bind(mime.as_str())
.bind(ty.to_string())
.bind(hash.as_str())
.bind(file_key.as_str())
.bind(created_at)
.execute(db)
.await?;
Ok(GeneratedFile {
id,
file_id,
mime,
ty,
hash,
file_key,
created_at,
})
}
pub async fn delete(self, db: impl DbExecutor<'_>) -> DbResult<PgQueryResult> {
sqlx::query(r#"DELETE FROM "docbox_generated_files" WHERE "id" = $1"#)
.bind(self.id)
.execute(db)
.await
}
pub async fn find_all(
db: impl DbExecutor<'_>,
file_id: FileId,
) -> DbResult<Vec<GeneratedFile>> {
sqlx::query_as(r#"SELECT * FROM "docbox_generated_files" WHERE "file_id" = $1"#)
.bind(file_id)
.fetch_all(db)
.await
}
pub async fn find(
db: impl DbExecutor<'_>,
scope: &DocumentBoxScopeRaw,
file_id: FileId,
ty: GeneratedFileType,
) -> DbResult<Option<GeneratedFile>> {
sqlx::query_as(
r#"
SELECT "gen".*
FROM "docbox_generated_files" "gen"
-- Join on the file itself
INNER JOIN "docbox_files" "file" ON "gen".file_id = "file"."id"
-- Join to the file parent folder
INNER JOIN "docbox_folders" "folder" ON "file"."folder_id" = "folder"."id"
-- Only find the matching type for the specified file
WHERE "file"."id" = $1 AND "folder"."document_box" = $2 AND "gen"."type" = $3
"#,
)
.bind(file_id)
.bind(scope)
.bind(ty.to_string())
.fetch_optional(db)
.await
}
}