Skip to main content

buckets_core/
model.rs

1use pathbufd::PathBufD;
2use serde::{Deserialize, Serialize};
3use std::{
4    collections::HashMap,
5    fs::{exists, remove_file, write},
6};
7use tetratto_shared::{snow::Snowflake, unix_epoch_timestamp};
8
9#[derive(Serialize, Deserialize, PartialEq, Eq)]
10pub enum MediaType {
11    #[serde(alias = "image/webp")]
12    Webp,
13    #[serde(alias = "image/avif")]
14    Avif,
15    #[serde(alias = "image/png")]
16    Png,
17    #[serde(alias = "image/jpg")]
18    Jpg,
19    #[serde(alias = "image/gif")]
20    Gif,
21    #[serde(alias = "image/carpgraph")]
22    Carpgraph,
23}
24
25impl MediaType {
26    pub fn extension(&self) -> &str {
27        match self {
28            Self::Webp => "webp",
29            Self::Avif => "avif",
30            Self::Png => "png",
31            Self::Jpg => "jpg",
32            Self::Gif => "gif",
33            Self::Carpgraph => "carpgraph",
34        }
35    }
36
37    pub fn mime(&self) -> String {
38        format!("image/{}", self.extension())
39    }
40}
41
42#[derive(Serialize, Deserialize)]
43pub struct UploadMetadata {
44    pub what: MediaType,
45    #[serde(default)]
46    pub alt: String,
47    #[serde(default)]
48    pub kv: HashMap<String, String>,
49}
50
51impl UploadMetadata {
52    pub fn validate_kv(&self) -> result::Result<()> {
53        for x in &self.kv {
54            if x.0.len() > 32 {
55                return Err(result::Error::DataTooLong("key".to_string()));
56            }
57
58            if x.1.len() > 128 {
59                return Err(result::Error::DataTooLong("value".to_string()));
60            }
61        }
62
63        Ok(())
64    }
65}
66
67#[derive(Serialize, Deserialize)]
68pub struct MediaUpload {
69    pub id: usize,
70    pub created: usize,
71    pub owner: usize,
72    pub bucket: String,
73    pub metadata: UploadMetadata,
74}
75
76impl MediaUpload {
77    /// Create a new [`MediaUpload`].
78    pub fn new(what: MediaType, owner: usize, bucket: String) -> Self {
79        Self {
80            id: Snowflake::new().to_string().parse::<usize>().unwrap(),
81            created: unix_epoch_timestamp(),
82            owner,
83            bucket,
84            metadata: UploadMetadata {
85                alt: String::new(),
86                what,
87                kv: HashMap::new(),
88            },
89        }
90    }
91
92    /// Get the path to the fs file for this upload (without bucket).
93    pub(crate) fn legacy_path(&self, directory: &str) -> PathBufD {
94        PathBufD::current().extend(&[
95            directory,
96            &format!("{}.{}", self.id, self.metadata.what.extension()),
97        ])
98    }
99
100    /// Get the path to the fs file for this upload (with bucket).
101    pub fn full_path(&self, directory: &str) -> PathBufD {
102        PathBufD::current().extend(&[
103            directory,
104            &format!(
105                "{}{}.{}",
106                if self.bucket != "" {
107                    format!("{}.", self.bucket)
108                } else {
109                    String::new()
110                },
111                self.id,
112                self.metadata.what.extension()
113            ),
114        ])
115    }
116
117    /// Get the path to the fs file for this upload.
118    ///
119    /// Uses path with bucket unless legacy path exists.
120    pub fn path(&self, directory: &str) -> PathBufD {
121        let legacy = self.legacy_path(directory);
122
123        if std::fs::exists(&legacy).unwrap() {
124            return legacy;
125        }
126
127        self.full_path(directory)
128    }
129
130    /// Write to this upload in the file system.
131    pub fn write(&self, directory: &str, bytes: &[u8]) -> result::Result<()> {
132        match write(self.path(directory), bytes) {
133            Ok(_) => Ok(()),
134            Err(e) => Err(result::Error::MiscError(e.to_string())),
135        }
136    }
137
138    /// Delete this upload in the file system.
139    pub fn remove(&self, directory: &str) -> result::Result<()> {
140        let path = self.path(directory);
141
142        if !exists(&path).unwrap() {
143            return Ok(());
144        }
145
146        match remove_file(path) {
147            Ok(_) => Ok(()),
148            Err(e) => Err(result::Error::MiscError(e.to_string())),
149        }
150    }
151}
152
153pub mod result {
154    use serde::{Deserialize, Serialize};
155    use std::fmt::Display;
156
157    #[derive(Serialize, Deserialize)]
158    pub struct ApiReturn<T>
159    where
160        T: Serialize,
161    {
162        pub ok: bool,
163        pub message: String,
164        pub payload: T,
165    }
166
167    #[derive(Debug)]
168    pub enum Error {
169        MiscError(String),
170        DatabaseConnection(String),
171        GeneralNotFound(String),
172        DatabaseError(String),
173        NotAllowed,
174        DataTooLong(String),
175        DataTooShort(String),
176        FileTooLarge,
177        FileTooSmall,
178        Unknown,
179    }
180
181    impl Display for Error {
182        fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
183            f.write_str(&match self {
184                Self::MiscError(msg) => msg.to_owned(),
185                Self::DatabaseConnection(msg) => msg.to_owned(),
186                Self::DatabaseError(msg) => format!("Database error: {msg}"),
187                Self::GeneralNotFound(name) => format!("Unable to find requested {name}"),
188                Self::NotAllowed => "You are not allowed to do this".to_string(),
189                Self::DataTooLong(name) => format!("Given {name} is too long!"),
190                Self::DataTooShort(name) => format!("Given {name} is too short!"),
191                Self::FileTooLarge => "Given file is too large".to_string(),
192                Self::FileTooSmall => "Given file is too small".to_string(),
193                _ => format!("An unknown error as occurred: ({:?})", self),
194            })
195        }
196    }
197
198    impl<T> From<Error> for ApiReturn<T>
199    where
200        T: Default + Serialize,
201    {
202        fn from(val: Error) -> Self {
203            ApiReturn {
204                ok: false,
205                message: val.to_string(),
206                payload: T::default(),
207            }
208        }
209    }
210
211    pub type Result<T> = std::result::Result<T, Error>;
212}