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}