1use std::{borrow::Cow, path::PathBuf};
2
3use axum::{extract::multipart::MultipartError, http::StatusCode, response::IntoResponse, Json};
4use serde::Serialize;
5use tracing::error;
6
7#[derive(Debug, Clone)]
8pub struct EditorConfig {
9 pub(crate) enable_uploads: bool,
11 pub(crate) upload_max_size: usize,
13 pub(crate) allowed_file_types: Vec<Cow<'static, str>>,
15}
16
17impl Default for EditorConfig {
18 fn default() -> Self {
19 Self {
20 enable_uploads: true,
21 upload_max_size: 1024 * 1024 * 2,
22 allowed_file_types: vec!["image/png".into(), "image/jpeg".into()],
23 }
24 }
25}
26
27impl EditorConfig {
28 pub fn enable_uploads(mut self, enable: bool) -> Self {
30 self.enable_uploads = enable;
31 self
32 }
33
34 pub fn upload_max_size(mut self, max_size: usize) -> Self {
36 self.upload_max_size = max_size;
37 self
38 }
39
40 pub fn allow_file_type(mut self, file_type: impl Into<Cow<'static, str>>) -> Self {
42 self.allowed_file_types.push(file_type.into());
43 self
44 }
45
46 pub fn allowed_file_types(mut self, file_types: Vec<Cow<'static, str>>) -> Self {
48 self.allowed_file_types = file_types;
49 self
50 }
51}
52
53#[derive(Debug, Clone, Serialize)]
54#[serde(rename_all = "camelCase")]
55pub(crate) struct UploadedFileInfo {
56 file_path: PathBuf,
57}
58
59#[derive(Debug, Clone, Serialize)]
60pub(crate) struct UploadSuccess {
61 data: UploadedFileInfo,
62}
63
64impl UploadSuccess {
65 pub fn new(file_path: impl Into<PathBuf>) -> Self {
66 Self {
67 data: UploadedFileInfo {
68 file_path: file_path.into(),
69 },
70 }
71 }
72}
73
74#[derive(Debug, Clone, Serialize)]
75#[serde(rename_all = "camelCase", tag = "error")]
76pub(crate) enum UploadError {
77 NoFileGiven,
78 TypeNotAllowed,
79 FileTooLarge,
80 ImportError,
81}
82
83impl From<MultipartError> for UploadError {
84 fn from(err: MultipartError) -> Self {
85 error!("multipart error while uploading from editor: {err}");
86 match err.status() {
87 StatusCode::PAYLOAD_TOO_LARGE => UploadError::FileTooLarge,
88 _ => UploadError::ImportError,
89 }
90 }
91}
92
93impl IntoResponse for UploadError {
94 fn into_response(self) -> axum::response::Response {
95 let status_code = StatusCode::from_u16(match self {
96 UploadError::NoFileGiven | UploadError::ImportError => 400,
97 UploadError::TypeNotAllowed => 415,
98 UploadError::FileTooLarge => 413,
99 })
100 .unwrap();
101 let mut resp = Json::from(self).into_response();
102 *resp.status_mut() = status_code;
103
104 resp
105 }
106}