derived_cms/
easymde.rs

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    /// enable drag-and-drop upload functionality in the default markdown editor
10    pub(crate) enable_uploads: bool,
11    /// max upload size in bytes
12    pub(crate) upload_max_size: usize,
13    /// Allowed file types to upload. Default: image/png, image/jpeg
14    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    /// Enable uploads directly in the editor.
29    pub fn enable_uploads(mut self, enable: bool) -> Self {
30        self.enable_uploads = enable;
31        self
32    }
33
34    /// Set the max size for uploads.
35    pub fn upload_max_size(mut self, max_size: usize) -> Self {
36        self.upload_max_size = max_size;
37        self
38    }
39
40    /// Add an allowed file type to the currently allowed file types.
41    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    /// Reset the allowed file types to the given list.
47    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}