api/routes/modules/assignments/files/
delete.rs

1use axum::{
2    extract::{State, Path},
3    http::StatusCode,
4    response::IntoResponse,
5    Json,
6};
7use sea_orm::{
8    ActiveModelTrait,
9    ColumnTrait,
10    EntityTrait,
11    QueryFilter,
12};
13use serde_json::json;
14use db::models::assignment_file;
15use util::state::AppState;
16
17/// DELETE /api/modules/{module_id}/assignments/{assignment_id}/files
18///
19/// Delete one or more files from a specific assignment. Only accessible by lecturers assigned to the module.
20///
21/// ### Path Parameters
22/// - `module_id` (i64): The ID of the module containing the assignment
23/// - `assignment_id` (i64): The ID of the assignment whose files are to be deleted
24///
25/// ### Request Body (JSON)
26/// - `file_ids` (array of i64): List of file IDs to delete. Must be a non-empty array.
27///
28/// ### Responses
29///
30/// - `200 OK`
31/// ```json
32/// {
33///   "success": true,
34///   "message": "Files removed successfully"
35/// }
36/// ```
37///
38/// - `400 Bad Request`
39/// ```json
40/// {
41///   "success": false,
42///   "message": "Request must include a non-empty list of file_ids"
43/// }
44/// ```
45///
46/// - `404 Not Found`
47/// ```json
48/// {
49///   "success": false,
50///   "message": "No assignment found with ID <assignment_id> in module <module_id>"
51/// }
52/// ```
53///
54/// - `500 Internal Server Error`
55/// ```json
56/// {
57///   "success": false,
58///   "message": "<database error details>"
59/// }
60/// ```
61///
62pub async fn delete_files(
63    State(app_state): State<AppState>,
64    Path((_, assignment_id)): Path<(i64, i64)>,
65    Json(req): Json<serde_json::Value>,
66) -> impl IntoResponse {
67    let db = app_state.db();
68
69    let file_ids: Vec<i64> = req
70        .get("file_ids")
71        .and_then(|v| v.as_array())
72        .map(|arr| arr.iter().filter_map(|v| v.as_i64()).collect())
73        .unwrap_or_default();
74
75    if file_ids.is_empty() {
76        return (
77            StatusCode::BAD_REQUEST,
78            Json(json!({
79                "success": false,
80                "message": "Request must include a non-empty list of file_ids"
81            })),
82        );
83    }
84
85    let found_models = match assignment_file::Entity::find()
86        .filter(assignment_file::Column::AssignmentId.eq(assignment_id as i32))
87        .filter(assignment_file::Column::Id.is_in(
88            file_ids.iter().copied().map(|id| id as i32).collect::<Vec<_>>(),
89        ))
90        .all(db)
91        .await
92    {
93        Ok(models) => models,
94        Err(_) => {
95            return (
96                StatusCode::NOT_FOUND,
97                Json(json!({
98                    "success": false,
99                    "message": format!("File(s) not found: {:?}", file_ids)
100                })),
101            );
102        }
103    };
104
105    let found_ids: Vec<i64> = found_models.iter().map(|m| m.id as i64).collect();
106    let missing: Vec<i64> = file_ids
107        .iter()
108        .filter(|id| !found_ids.contains(id))
109        .copied()
110        .collect();
111
112    if !missing.is_empty() {
113        return (
114            StatusCode::NOT_FOUND,
115            Json(json!({
116                "success": false,
117                "message": format!("File(s) not found: {:?}", missing)
118            })),
119        );
120    }
121
122    for file in found_models {
123        let _ = file.delete_file_only();
124        let am: assignment_file::ActiveModel = file.into();
125        let _ = am.delete(db).await;
126    }
127
128    (
129        StatusCode::OK,
130        Json(json!({
131            "success": true,
132            "message": "Files removed successfully"
133        })),
134    )
135}