docbox_core/files/
generated.rs1use crate::files::create_generated_file_key;
4use anyhow::Context;
5use bytes::Bytes;
6use docbox_storage::TenantStorageLayer;
7use futures::{
8 StreamExt,
9 stream::{FuturesOrdered, FuturesUnordered},
10};
11use mime::Mime;
12use tracing::{Instrument, debug, error};
13
14use docbox_database::models::{
15 file::FileId,
16 generated_file::{CreateGeneratedFile, GeneratedFile, GeneratedFileId, GeneratedFileType},
17};
18
19#[derive(Debug)]
20pub struct QueuedUpload {
21 pub mime: Mime,
22 pub ty: GeneratedFileType,
23 pub bytes: Bytes,
24}
25
26impl QueuedUpload {
27 pub fn new(mime: Mime, ty: GeneratedFileType, bytes: Bytes) -> Self {
28 Self { mime, ty, bytes }
29 }
30}
31
32pub enum GeneratedFileDeleteResult {
33 Ok,
35 Err(Vec<GeneratedFileId>, anyhow::Error),
38}
39
40pub async fn delete_generated_files(
41 storage: &TenantStorageLayer,
42 files: &[GeneratedFile],
43) -> GeneratedFileDeleteResult {
44 let files_count = files.len();
45
46 let mut futures = files
47 .iter()
48 .map(|file| {
49 async {
50 let id = file.id;
51 let file_id = file.file_id;
52 let file_key = file.file_key.to_string();
53
54 debug!(%id, %file_id, %file_key, "uploading file to s3",);
55
56 if let Err(cause) = storage.delete_file(&file_key).await {
58 error!(%id, %file_id, %file_key, ?cause, "failed to delete generated file");
59 }
60
61 debug!("deleted file from s3");
62
63 anyhow::Ok(id)
64 }
65 })
66 .collect::<FuturesUnordered<_>>();
67
68 let mut deleted: Vec<GeneratedFileId> = Vec::with_capacity(files_count);
69
70 while let Some(result) = futures.next().await {
71 match result {
72 Ok(id) => deleted.push(id),
73 Err(err) => return GeneratedFileDeleteResult::Err(deleted, err),
74 }
75 }
76
77 GeneratedFileDeleteResult::Ok
78}
79
80pub async fn upload_generated_files(
83 storage: &TenantStorageLayer,
84 base_file_key: &str,
85 file_id: &FileId,
86 file_hash: &str,
87 queued_uploads: Vec<QueuedUpload>,
88) -> Vec<anyhow::Result<CreateGeneratedFile>> {
89 queued_uploads
90 .into_iter()
91 .map(|queued_upload| {
92 let file_id = *file_id;
94 let file_hash = file_hash.to_string();
95 let file_mime = queued_upload.mime.to_string();
96 let file_key = create_generated_file_key(base_file_key, &queued_upload.mime);
97 let span = tracing::info_span!("upload_generated_files", %file_id, %file_hash, %file_key, %file_mime);
98
99 async move {
100 storage
102 .upload_file(&file_key, file_mime, queued_upload.bytes)
103 .await
104 .context("failed to upload generated file")?;
105
106 anyhow::Ok(CreateGeneratedFile {
107 file_id,
108 hash: file_hash,
109 mime: queued_upload.mime.to_string(),
110 ty: queued_upload.ty,
111 file_key,
112 })
113 }
114 .instrument(span)
115 })
116 .collect::<FuturesOrdered<_>>()
117 .collect()
118 .await
119}