Skip to main content

web/pages/script/
edit.rs

1use askama::Template;
2use axum::{
3    Extension, Json,
4    extract::{Multipart, Path, State},
5    http::StatusCode,
6    response::IntoResponse,
7};
8use serde::Deserialize;
9use std::sync::Arc;
10use uuid::Uuid;
11
12use crate::{AppState, jwt_auth::JWTAuthMiddleware, pages::HtmlTemplate};
13
14#[derive(Template)]
15#[template(path = "pages/script/edit.html")]
16struct ScriptEditPage {
17    id: Uuid,
18    name: Option<String>,
19    size: usize,
20    enabled: bool,
21}
22
23pub async fn script_edit_page(
24    State(_data): State<Arc<AppState>>,
25    Extension(jwt_auth): Extension<JWTAuthMiddleware>,
26    Path(id): Path<Uuid>,
27) -> Result<impl IntoResponse, (StatusCode, String)> {
28    let user = &jwt_auth.user;
29    let server_user = server::user::User { id: user.id };
30
31    let script = server_user
32        .get_script(id)
33        .await
34        .map_err(|e| (StatusCode::NOT_FOUND, format!("Script not found: {e:?}")))?;
35
36    let template = ScriptEditPage {
37        id: script.id,
38        name: script.name,
39        size: script.bytecode.len(),
40        enabled: script.enabled.as_deref() != Some("false"),
41    };
42
43    Ok(HtmlTemplate(template))
44}
45
46fn deserialize_checkbox<'de, D>(deserializer: D) -> Result<Option<bool>, D::Error>
47where
48    D: serde::Deserializer<'de>,
49{
50    use serde::de::Error;
51    let value: Option<serde_json::Value> = Option::deserialize(deserializer)?;
52    match value {
53        None => Ok(None),
54        Some(serde_json::Value::Bool(b)) => Ok(Some(b)),
55        Some(serde_json::Value::String(s)) => match s.as_str() {
56            "on" | "true" | "1" => Ok(Some(true)),
57            "off" | "false" | "0" | "" => Ok(Some(false)),
58            _ => Err(D::Error::custom(format!("invalid checkbox value: {s}"))),
59        },
60        Some(v) => Err(D::Error::custom(format!("invalid checkbox type: {v}"))),
61    }
62}
63
64#[derive(Deserialize)]
65pub struct ScriptEditForm {
66    id: Uuid,
67    name: Option<String>,
68    #[serde(default, deserialize_with = "deserialize_checkbox")]
69    enabled: Option<bool>,
70}
71
72pub async fn edit_script(
73    State(_data): State<Arc<AppState>>,
74    Extension(jwt_auth): Extension<JWTAuthMiddleware>,
75    Json(form): Json<ScriptEditForm>,
76) -> Result<impl IntoResponse, (StatusCode, Json<serde_json::Value>)> {
77    let user = &jwt_auth.user;
78    let server_user = server::user::User { id: user.id };
79
80    server_user
81        .update_script_name(form.id, form.name)
82        .await
83        .map_err(|e| {
84            let error_response = serde_json::json!({
85                "status": "fail",
86                "message": t!("Failed to update script name"),
87            });
88            log::error!("Failed to update script name: {e:?}");
89            (StatusCode::INTERNAL_SERVER_ERROR, Json(error_response))
90        })?;
91
92    let enabled = form.enabled.unwrap_or(false);
93    server_user
94        .set_script_enabled(form.id, enabled)
95        .await
96        .map_err(|e| {
97            let error_response = serde_json::json!({
98                "status": "fail",
99                "message": t!("Failed to update script enabled state"),
100            });
101            log::error!("Failed to update script enabled state: {e:?}");
102            (StatusCode::INTERNAL_SERVER_ERROR, Json(error_response))
103        })?;
104
105    Ok(format!("{}: {}", t!("Script updated"), form.id))
106}
107
108pub async fn upload_script_bytecode(
109    State(_data): State<Arc<AppState>>,
110    Extension(jwt_auth): Extension<JWTAuthMiddleware>,
111    Path(id): Path<Uuid>,
112    mut multipart: Multipart,
113) -> Result<impl IntoResponse, (StatusCode, Json<serde_json::Value>)> {
114    let user = &jwt_auth.user;
115    let server_user = server::user::User { id: user.id };
116
117    while let Some(field) = multipart.next_field().await.map_err(|e| {
118        let error_response = serde_json::json!({
119            "status": "fail",
120            "message": "Failed to read multipart data",
121        });
122        log::error!("Multipart error: {e:?}");
123        (StatusCode::BAD_REQUEST, Json(error_response))
124    })? {
125        if field.name() == Some("bytecode") {
126            let data = field.bytes().await.map_err(|e| {
127                let error_response = serde_json::json!({
128                    "status": "fail",
129                    "message": "Failed to read file data",
130                });
131                log::error!("File read error: {e:?}");
132                (StatusCode::BAD_REQUEST, Json(error_response))
133            })?;
134
135            server_user
136                .update_script_bytecode(id, data.to_vec())
137                .await
138                .map_err(|e| {
139                    let error_response = serde_json::json!({
140                        "status": "fail",
141                        "message": t!("Failed to update script bytecode"),
142                    });
143                    log::error!("Failed to update script bytecode: {e:?}");
144                    (StatusCode::INTERNAL_SERVER_ERROR, Json(error_response))
145                })?;
146
147            return Ok(format!("{}: {}", t!("Script bytecode updated"), id));
148        }
149    }
150
151    let error_response = serde_json::json!({
152        "status": "fail",
153        "message": "No bytecode field found in upload",
154    });
155    Err((StatusCode::BAD_REQUEST, Json(error_response)))
156}
157
158pub async fn delete_script(
159    State(_data): State<Arc<AppState>>,
160    Extension(jwt_auth): Extension<JWTAuthMiddleware>,
161    Path(id): Path<Uuid>,
162) -> Result<impl IntoResponse, (StatusCode, Json<serde_json::Value>)> {
163    let user = &jwt_auth.user;
164    let server_user = server::user::User { id: user.id };
165
166    server_user.delete_script(id).await.map_err(|e| {
167        let error_response = serde_json::json!({
168            "status": "fail",
169            "message": t!("Failed to delete script"),
170        });
171        log::error!("Failed to delete script: {e:?}");
172        (StatusCode::INTERNAL_SERVER_ERROR, Json(error_response))
173    })?;
174
175    Ok(t!("Script deleted"))
176}
177
178pub async fn create_script(
179    State(_data): State<Arc<AppState>>,
180    Extension(jwt_auth): Extension<JWTAuthMiddleware>,
181    mut multipart: Multipart,
182) -> Result<impl IntoResponse, (StatusCode, Json<serde_json::Value>)> {
183    let user = &jwt_auth.user;
184    let server_user = server::user::User { id: user.id };
185
186    let mut bytecode: Option<Vec<u8>> = None;
187    let mut name: Option<String> = None;
188
189    while let Some(field) = multipart.next_field().await.map_err(|e| {
190        let error_response = serde_json::json!({
191            "status": "fail",
192            "message": "Failed to read multipart data",
193        });
194        log::error!("Multipart error: {e:?}");
195        (StatusCode::BAD_REQUEST, Json(error_response))
196    })? {
197        match field.name() {
198            Some("bytecode") => {
199                let data = field.bytes().await.map_err(|e| {
200                    let error_response = serde_json::json!({
201                        "status": "fail",
202                        "message": "Failed to read file data",
203                    });
204                    log::error!("File read error: {e:?}");
205                    (StatusCode::BAD_REQUEST, Json(error_response))
206                })?;
207                bytecode = Some(data.to_vec());
208            }
209            Some("name") => {
210                let text = field.text().await.map_err(|e| {
211                    let error_response = serde_json::json!({
212                        "status": "fail",
213                        "message": "Failed to read name field",
214                    });
215                    log::error!("Field read error: {e:?}");
216                    (StatusCode::BAD_REQUEST, Json(error_response))
217                })?;
218                if !text.is_empty() {
219                    name = Some(text);
220                }
221            }
222            _ => {}
223        }
224    }
225
226    let bytecode = bytecode.ok_or_else(|| {
227        let error_response = serde_json::json!({
228            "status": "fail",
229            "message": "No bytecode file provided",
230        });
231        (StatusCode::BAD_REQUEST, Json(error_response))
232    })?;
233
234    let id = server_user
235        .create_script(bytecode, name)
236        .await
237        .map_err(|e| {
238            let error_response = serde_json::json!({
239                "status": "fail",
240                "message": t!("Failed to create script"),
241            });
242            log::error!("Failed to create script: {e:?}");
243            (StatusCode::INTERNAL_SERVER_ERROR, Json(error_response))
244        })?;
245
246    Ok(format!("{}: {}", t!("Script created"), id))
247}
248
249#[derive(Template)]
250#[template(path = "pages/script/create.html")]
251struct ScriptCreatePage;
252
253pub async fn script_create_page() -> impl IntoResponse {
254    HtmlTemplate(ScriptCreatePage {})
255}