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
67#[derive(Debug)]
68pub struct CreateGeneratedFile {
69    pub id: Uuid,
70    pub file_id: FileId,
71    pub mime: String,
72    pub ty: GeneratedFileType,
73    pub hash: String,
74    pub file_key: String,
75    pub created_at: DateTime<Utc>,
76}
77
78impl GeneratedFile {
79    pub async fn create(
80        db: impl DbExecutor<'_>,
81        CreateGeneratedFile {
82            id,
83            file_id,
84            ty,
85            hash,
86            file_key,
87            mime,
88            created_at,
89        }: CreateGeneratedFile,
90    ) -> DbResult<GeneratedFile> {
91        sqlx::query(
92            r#"
93            INSERT INTO "docbox_generated_files"
94            ("id", "file_id", "mime", "type", "hash", "file_key", "created_at")
95            VALUES ($1, $2, $3, $4, $5, $6, $7)
96        "#,
97        )
98        .bind(id)
99        .bind(file_id)
100        .bind(mime.as_str())
101        .bind(ty.to_string())
102        .bind(hash.as_str())
103        .bind(file_key.as_str())
104        .bind(created_at)
105        .execute(db)
106        .await?;
107
108        Ok(GeneratedFile {
109            id,
110            file_id,
111            mime,
112            ty,
113            hash,
114            file_key,
115            created_at,
116        })
117    }
118
119    /// Deletes the generated file
120    pub async fn delete(self, db: impl DbExecutor<'_>) -> DbResult<()> {
121        sqlx::query(r#"DELETE FROM "docbox_generated_files" WHERE "id" = $1"#)
122            .bind(self.id)
123            .execute(db)
124            .await?;
125
126        Ok(())
127    }
128
129    pub async fn find_all(
130        db: impl DbExecutor<'_>,
131        file_id: FileId,
132    ) -> DbResult<Vec<GeneratedFile>> {
133        sqlx::query_as(r#"SELECT * FROM "docbox_generated_files" WHERE "file_id" = $1"#)
134            .bind(file_id)
135            .fetch_all(db)
136            .await
137    }
138
139    /// Finds a specific file using its full path scope -> folder -> file
140    pub async fn find(
141        db: impl DbExecutor<'_>,
142        scope: &DocumentBoxScopeRaw,
143        file_id: FileId,
144        ty: GeneratedFileType,
145    ) -> DbResult<Option<GeneratedFile>> {
146        sqlx::query_as(
147            r#"
148            SELECT "gen".*
149            FROM "docbox_generated_files" "gen"
150            -- Join on the file itself
151            INNER JOIN "docbox_files" "file" ON "gen".file_id = "file"."id"
152            -- Join to the file parent folder
153            INNER JOIN "docbox_folders" "folder" ON "file"."folder_id" = "folder"."id"
154            -- Only find the matching type for the specified file
155            WHERE "file"."id" = $1 AND "folder"."document_box" = $2 AND "gen"."type" = $3
156        "#,
157        )
158        .bind(file_id)
159        .bind(scope)
160        .bind(ty.to_string())
161        .fetch_optional(db)
162        .await
163    }
164}