1use chrono::{DateTime, Utc};
8use serde::{Deserialize, Serialize};
9use sqlx::{postgres::PgQueryResult, prelude::FromRow, types::Json};
10use uuid::Uuid;
11
12use super::{document_box::DocumentBoxScopeRaw, file::FileId, folder::FolderId, user::UserId};
13use crate::{DbErr, DbExecutor, DbResult};
14
15pub type PresignedUploadTaskId = Uuid;
16
17#[derive(Debug, Clone, FromRow, Serialize)]
19pub struct PresignedUploadTask {
20 pub id: PresignedUploadTaskId,
22 #[sqlx(json)]
24 pub status: PresignedTaskStatus,
25
26 pub name: String,
28 pub mime: String,
30 pub size: i32,
32
33 pub document_box: DocumentBoxScopeRaw,
35 pub folder_id: FolderId,
37 pub file_key: String,
39
40 pub created_at: DateTime<Utc>,
42 pub expires_at: DateTime<Utc>,
45 pub created_by: Option<UserId>,
47
48 pub parent_id: Option<FileId>,
50
51 pub processing_config: Option<Json<serde_json::Value>>,
54}
55
56impl Eq for PresignedUploadTask {}
57
58impl PartialEq for PresignedUploadTask {
59 fn eq(&self, other: &Self) -> bool {
60 self.id.eq(&other.id)
61 && self.status.eq(&other.status)
62 && self.name.eq(&other.name)
63 && self.mime.eq(&other.mime)
64 && self.size.eq(&other.size)
65 && self.document_box.eq(&other.document_box)
66 && self.folder_id.eq(&other.folder_id)
67 && self.file_key.eq(&other.file_key)
68 && self
71 .created_at
72 .timestamp_millis()
73 .eq(&other.created_at.timestamp_millis())
74 && self
77 .expires_at
78 .timestamp_millis()
79 .eq(&other.expires_at.timestamp_millis())
80 && self.created_by.eq(&self.created_by)
81 && self.parent_id.eq(&other.parent_id)
82 && self.processing_config.eq(&other.processing_config)
83 }
84}
85
86#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, Eq)]
87#[serde(tag = "status")]
88pub enum PresignedTaskStatus {
89 Pending,
90 Completed { file_id: FileId },
91 Failed { error: String },
92}
93
94#[derive(Default)]
96pub struct CreatePresignedUploadTask {
97 pub name: String,
98 pub mime: String,
99 pub document_box: DocumentBoxScopeRaw,
100 pub folder_id: FolderId,
101 pub size: i32,
102 pub file_key: String,
103 pub created_by: Option<UserId>,
104 pub expires_at: DateTime<Utc>,
105 pub parent_id: Option<FileId>,
106 pub processing_config: Option<serde_json::Value>,
107}
108
109impl PresignedUploadTask {
110 pub async fn create(
112 db: impl DbExecutor<'_>,
113 create: CreatePresignedUploadTask,
114 ) -> DbResult<PresignedUploadTask> {
115 let id = Uuid::new_v4();
116 let created_at = Utc::now();
117
118 let task = PresignedUploadTask {
119 id,
120 status: PresignedTaskStatus::Pending,
121 name: create.name,
123 mime: create.mime,
124 size: create.size,
125 document_box: create.document_box,
127 folder_id: create.folder_id,
128 file_key: create.file_key,
129 created_at,
131 expires_at: create.expires_at,
132 created_by: create.created_by,
133
134 parent_id: create.parent_id,
135 processing_config: create.processing_config.map(Json),
136 };
137
138 let status_json =
139 serde_json::to_value(&task.status).map_err(|err| DbErr::Encode(Box::new(err)))?;
140 let processing_config_json = task.processing_config.clone();
141
142 sqlx::query(
143 r#"
144 INSERT INTO "docbox_presigned_upload_tasks" (
145 "id",
146 "status",
147 "name",
148 "mime",
149 "size",
150 "document_box",
151 "folder_id",
152 "file_key",
153 "created_at",
154 "expires_at",
155 "created_by",
156 "parent_id",
157 "processing_config"
158 ) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13)
159 "#,
160 )
161 .bind(task.id)
162 .bind(status_json)
163 .bind(task.name.as_str())
164 .bind(task.mime.clone())
165 .bind(task.size)
166 .bind(task.document_box.as_str())
167 .bind(task.folder_id)
168 .bind(task.file_key.as_str())
169 .bind(task.created_at)
170 .bind(task.expires_at)
171 .bind(task.created_by.clone())
172 .bind(task.parent_id)
173 .bind(processing_config_json)
174 .execute(db)
175 .await?;
176
177 Ok(task)
178 }
179
180 pub async fn set_status(
181 &mut self,
182 db: impl DbExecutor<'_>,
183 status: PresignedTaskStatus,
184 ) -> DbResult<()> {
185 let status_json =
186 serde_json::to_value(&status).map_err(|err| DbErr::Encode(Box::new(err)))?;
187
188 sqlx::query(r#"UPDATE "docbox_presigned_upload_tasks" SET "status" = $1 WHERE "id" = $2"#)
189 .bind(status_json)
190 .bind(self.id)
191 .execute(db)
192 .await?;
193
194 self.status = status;
195 Ok(())
196 }
197
198 pub async fn find(
200 db: impl DbExecutor<'_>,
201 scope: &DocumentBoxScopeRaw,
202 task_id: PresignedUploadTaskId,
203 ) -> DbResult<Option<PresignedUploadTask>> {
204 sqlx::query_as(
205 r#"SELECT * FROM "docbox_presigned_upload_tasks"
206 WHERE "id" = $1 AND "document_box" = $2"#,
207 )
208 .bind(task_id)
209 .bind(scope)
210 .fetch_optional(db)
211 .await
212 }
213
214 pub async fn find_expired(
216 db: impl DbExecutor<'_>,
217 current_date: DateTime<Utc>,
218 ) -> DbResult<Vec<PresignedUploadTask>> {
219 sqlx::query_as(r#"SELECT * FROM "docbox_presigned_upload_tasks" WHERE "expires_at" < $1"#)
220 .bind(current_date)
221 .fetch_all(db)
222 .await
223 }
224
225 pub async fn find_by_file_key(
227 db: impl DbExecutor<'_>,
228 file_key: &str,
229 ) -> DbResult<Option<PresignedUploadTask>> {
230 sqlx::query_as(r#"SELECT * FROM "docbox_presigned_upload_tasks" WHERE "file_key" = $1"#)
231 .bind(file_key)
232 .fetch_optional(db)
233 .await
234 }
235
236 pub async fn delete(
238 db: impl DbExecutor<'_>,
239 task_id: PresignedUploadTaskId,
240 ) -> DbResult<PgQueryResult> {
241 sqlx::query(r#"DELETE FROM "docbox_presigned_upload_tasks" WHERE "id" = $1"#)
242 .bind(task_id)
243 .execute(db)
244 .await
245 }
246}