docbox_database/models/
generated_file.rs

1use std::str::FromStr;
2
3use chrono::{DateTime, Utc};
4use serde::{Deserialize, Serialize};
5use sqlx::prelude::FromRow;
6use utoipa::ToSchema;
7use uuid::Uuid;
8
9use super::{document_box::DocumentBoxScopeRaw, file::FileId};
10use crate::{DbExecutor, DbResult};
11
12pub type GeneratedFileId = Uuid;
13
14#[derive(
15    Debug, Clone, Copy, strum::EnumString, strum::Display, Deserialize, Serialize, ToSchema,
16)]
17pub enum GeneratedFileType {
18    /// Conversion to PDF file
19    Pdf,
20    /// Full sized cover page render
21    CoverPage,
22    /// Small file sized thumbnail image
23    SmallThumbnail,
24    /// Larger thumbnail image, for a small preview tooltip
25    LargeThumbnail,
26    /// Text content extracted from the file
27    TextContent,
28    /// HTML content extracted from things like emails
29    HtmlContent,
30    /// JSON encoded metadata for the file
31    /// (Used by emails to store the email metadata in an accessible ways)
32    Metadata,
33}
34
35impl TryFrom<String> for GeneratedFileType {
36    type Error = strum::ParseError;
37    fn try_from(value: String) -> Result<Self, Self::Error> {
38        GeneratedFileType::from_str(&value)
39    }
40}
41
42/// File generated as an artifact of an uploaded file
43#[derive(Debug, FromRow, Serialize, ToSchema)]
44pub struct GeneratedFile {
45    /// Unique identifier for the file
46    #[schema(value_type = Uuid)]
47    pub id: GeneratedFileId,
48    /// File this generated file belongs  to
49    #[schema(value_type = Uuid)]
50    pub file_id: FileId,
51    /// Mime type of the generated file content
52    pub mime: String,
53    /// Type of the generated file
54    #[sqlx(rename = "type")]
55    #[serde(rename = "type")]
56    #[sqlx(try_from = "String")]
57    pub ty: GeneratedFileType,
58    /// Hash of the file this was generated from
59    pub hash: String,
60    /// S3 key pointing to the file
61    #[serde(skip)]
62    pub file_key: String,
63    /// When the file was created
64    pub created_at: DateTime<Utc>,
65}
66
67pub struct CreateGeneratedFile {
68    pub file_id: FileId,
69    pub mime: String,
70    pub ty: GeneratedFileType,
71    pub hash: String,
72    pub file_key: String,
73}
74
75impl GeneratedFile {
76    pub async fn create(
77        db: impl DbExecutor<'_>,
78        CreateGeneratedFile {
79            file_id,
80            ty,
81            hash,
82            file_key,
83            mime,
84        }: CreateGeneratedFile,
85    ) -> DbResult<GeneratedFile> {
86        let id = Uuid::new_v4();
87        let created_at = Utc::now();
88
89        sqlx::query(
90            r#"
91            INSERT INTO "docbox_generated_files" 
92            ("id", "file_id", "mime", "type", "hash", "file_key", "created_at")
93            VALUES ($1, $2, $3, $4, $5, $6, $7)
94        "#,
95        )
96        .bind(id)
97        .bind(file_id)
98        .bind(mime.as_str())
99        .bind(ty.to_string())
100        .bind(hash.as_str())
101        .bind(file_key.as_str())
102        .bind(created_at)
103        .execute(db)
104        .await?;
105
106        Ok(GeneratedFile {
107            id,
108            file_id,
109            mime,
110            ty,
111            hash,
112            file_key,
113            created_at,
114        })
115    }
116
117    /// Deletes the generated file
118    pub async fn delete(self, db: impl DbExecutor<'_>) -> DbResult<()> {
119        sqlx::query(r#"DELETE FROM "docbox_generated_files" WHERE "id" = $1"#)
120            .bind(self.id)
121            .execute(db)
122            .await?;
123
124        Ok(())
125    }
126
127    pub async fn find_all(
128        db: impl DbExecutor<'_>,
129        file_id: FileId,
130    ) -> DbResult<Vec<GeneratedFile>> {
131        sqlx::query_as(r#"SELECT * FROM "docbox_generated_files" WHERE "file_id" = $1"#)
132            .bind(file_id)
133            .fetch_all(db)
134            .await
135    }
136
137    /// Finds a specific file using its full path scope -> folder -> file
138    pub async fn find(
139        db: impl DbExecutor<'_>,
140        scope: &DocumentBoxScopeRaw,
141        file_id: FileId,
142        ty: GeneratedFileType,
143    ) -> DbResult<Option<GeneratedFile>> {
144        sqlx::query_as(
145            r#"
146            SELECT "gen".*
147            FROM "docbox_generated_files" "gen"
148            -- Join on the file itself
149            INNER JOIN "docbox_files" "file" ON "gen".file_id = "file"."id"
150            -- Join to the file parent folder
151            INNER JOIN "docbox_folders" "folder" ON "file"."folder_id" = "folder"."id"
152            -- Only find the matching type for the specified file
153            WHERE "file"."id" = $1 AND "folder"."document_box" = $2 AND "gen"."type" = $3
154        "#,
155        )
156        .bind(file_id)
157        .bind(scope)
158        .bind(ty.to_string())
159        .fetch_optional(db)
160        .await
161    }
162}