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 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 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 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 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 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 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}