1
use askama::Template;
2
use axum::{
3
    Extension, Json,
4
    extract::{Multipart, Path, State},
5
    http::StatusCode,
6
    response::IntoResponse,
7
};
8
use serde::Deserialize;
9
use std::sync::Arc;
10
use uuid::Uuid;
11

            
12
use crate::{AppState, jwt_auth::JWTAuthMiddleware, pages::HtmlTemplate};
13

            
14
#[derive(Template)]
15
#[template(path = "pages/script/edit.html")]
16
struct ScriptEditPage {
17
    id: Uuid,
18
    name: Option<String>,
19
    size: usize,
20
    enabled: bool,
21
}
22

            
23
pub 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

            
46
fn deserialize_checkbox<'de, D>(deserializer: D) -> Result<Option<bool>, D::Error>
47
where
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)]
65
pub struct ScriptEditForm {
66
    id: Uuid,
67
    name: Option<String>,
68
    #[serde(default, deserialize_with = "deserialize_checkbox")]
69
    enabled: Option<bool>,
70
}
71

            
72
pub 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

            
108
pub 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

            
158
pub 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

            
178
pub 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")]
251
struct ScriptCreatePage;
252

            
253
pub async fn script_create_page() -> impl IntoResponse {
254
    HtmlTemplate(ScriptCreatePage {})
255
}