api/routes/modules/
delete.rs

1//! Module deletion routes.
2//!
3//! Provides endpoints to delete modules:
4//! - `DELETE /api/modules/{id}` → Permanently delete a single module by ID.
5//! - `DELETE /api/modules/bulk` → Bulk delete multiple modules by their IDs.
6//!
7//! All responses follow the standard `ApiResponse` format.
8
9use axum::{
10    extract::{State, Path},
11    http::StatusCode,
12    response::IntoResponse,
13    Json,
14};
15use sea_orm::{ColumnTrait, EntityTrait, QueryFilter};
16use util::state::AppState;
17use crate::response::ApiResponse;
18use db::models::module;
19use serde::{Serialize, Deserialize};
20use validator::Validate;
21
22/// DELETE /api/modules/{module_id}
23///
24/// Permanently deletes a module by ID, including all its assignments and assignment files.  
25/// Only accessible by admin users.
26///
27/// ### Responses
28///
29/// - `200 OK`  
30/// ```json
31/// {
32///   "success": true,
33///   "data": null,
34///   "message": "Module deleted successfully"
35/// }
36/// ```
37///
38/// - `403 Forbidden`  
39/// ```json
40/// {
41///   "success": false,
42///   "data": null,
43///   "message": "You do not have permission to perform this action"
44/// }
45/// ```
46///
47/// - `404 Not Found`  
48/// ```json
49/// {
50///   "success": false,
51///   "data": null,
52///   "message": "Module not found"
53/// }
54/// ```
55pub async fn delete_module(
56    State(state): State<AppState>,
57    Path(module_id): Path<i64>
58) -> impl IntoResponse {
59    let db = state.db();
60
61    match module::Entity::find()
62        .filter(module::Column::Id.eq(module_id))
63        .one(db)
64        .await
65    {
66        Ok(Some(m)) => {
67            match m.delete(db).await {
68                Ok(_) => (
69                    StatusCode::OK,
70                    Json(ApiResponse::<()>::success((), "Module deleted successfully")),
71                ),
72                Err(e) => (
73                    StatusCode::INTERNAL_SERVER_ERROR,
74                    Json(ApiResponse::<()>::error(format!("Failed to delete module: {}", e))),
75                ),
76            }
77        }
78        Ok(None) => (
79            StatusCode::NOT_FOUND,
80            Json(ApiResponse::<()>::error("Module not found")),
81        ),
82        Err(e) => (
83            StatusCode::INTERNAL_SERVER_ERROR,
84            Json(ApiResponse::<()>::error(format!("Database error: {}", e))),
85        ),
86    }
87}
88
89#[derive(Debug, Deserialize, Validate)]
90pub struct BulkDeleteRequest {
91    #[validate(length(min = 1, message = "At least one module ID is required"))]
92    pub module_ids: Vec<i64>,
93}
94
95#[derive(Serialize)]
96pub struct BulkDeleteResult {
97    pub deleted: usize,
98    pub failed: Vec<FailedDelete>,
99}
100
101#[derive(Serialize)]
102pub struct FailedDelete {
103    pub id: i64,
104    pub error: String,
105}
106
107/// DELETE /api/modules/bulk
108///
109/// Bulk delete multiple modules by their IDs.
110/// Only accessible by admin users.
111///
112/// ### Request Body
113/// ```json
114/// {
115///   "module_ids": [1, 2, 3]
116/// }
117/// ```
118///
119/// ### Responses
120///
121/// - `200 OK`
122/// ```json
123/// {
124///   "success": true,
125///   "data": {
126///     "deleted": 2,
127///     "failed": [
128///       { "id": 3, "error": "Module not found" }
129///     ]
130///   },
131///   "message": "Deleted 2/3 modules"
132/// }
133/// ```
134///
135/// - `400 Bad Request`
136/// ```json
137/// {
138///   "success": false,
139///   "data": null,
140///   "message": "At least one module ID is required"
141/// }
142/// ```
143pub async fn bulk_delete_modules(
144    State(app_state): State<AppState>,
145    Json(req): Json<BulkDeleteRequest>,
146) -> impl IntoResponse {
147    let db = app_state.db();
148
149    // Validate the request
150    if let Err(validation_errors) = req.validate() {
151        let error_message = common::format_validation_errors(&validation_errors);
152        return (
153            StatusCode::BAD_REQUEST,
154            Json(ApiResponse::<BulkDeleteResult>::error(error_message)),
155        );
156    }
157
158    let mut deleted_count = 0;
159    let mut failed = Vec::new();
160
161    for &id in &req.module_ids {
162        match module::Entity::find()
163            .filter(module::Column::Id.eq(id))
164            .one(db)
165            .await
166        {
167            Ok(Some(module_model)) => {
168                match module_model.delete(db).await {
169                    Ok(_) => deleted_count += 1,
170                    Err(e) => {
171                        failed.push(FailedDelete {
172                            id,
173                            error: format!("Failed to delete module: {}", e),
174                        });
175                    }
176                }
177            }
178            Ok(None) => {
179                failed.push(FailedDelete {
180                    id,
181                    error: "Module not found".into(),
182                });
183            }
184            Err(e) => {
185                failed.push(FailedDelete {
186                    id,
187                    error: format!("Database error: {}", e),
188                });
189            }
190        }
191    }
192
193    let result = BulkDeleteResult {
194        deleted: deleted_count,
195        failed,
196    };
197
198    let message = format!(
199        "Deleted {}/{} modules",
200        deleted_count,
201        req.module_ids.len()
202    );
203
204    (
205        StatusCode::OK,
206        Json(ApiResponse::success(result, message)),
207    )
208}