api/routes/modules/assignments/plagiarism/
patch.rs

1use axum::{
2    extract::{Path, State},
3    http::StatusCode,
4    response::IntoResponse,
5    Json,
6};
7use chrono::{DateTime, Utc};
8use db::models::plagiarism_case::{Entity as PlagiarismEntity, Status, Column as PlagiarismColumn};
9use sea_orm::{ColumnTrait, EntityTrait, IntoActiveModel, QueryFilter, ActiveModelTrait, Set};
10use serde::Serialize;
11use util::state::AppState;
12use crate::response::ApiResponse;
13
14#[derive(Debug, Serialize)]
15pub struct FlaggedCaseResponse {
16    id: i64,
17    status: String,
18    updated_at: DateTime<Utc>,
19}
20
21/// PATCH /api/modules/{module_id}/assignments/{assignment_id}/plagiarism/{case_id}/flag
22///
23/// Flags a plagiarism case after manual review, indicating confirmed plagiarism.
24/// Accessible only to lecturers and assistant lecturers assigned to the module.
25///
26/// # Path Parameters
27///
28/// - `module_id`: The ID of the parent module
29/// - `assignment_id`: The ID of the assignment containing the plagiarism case
30/// - `case_id`: The ID of the plagiarism case to flag
31///
32/// # Returns
33///
34/// Returns an HTTP response indicating the result:
35/// - `200 OK` with minimal case information on success
36/// - `403 FORBIDDEN` if user lacks required permissions
37/// - `404 NOT FOUND` if specified plagiarism case doesn't exist
38/// - `500 INTERNAL SERVER ERROR` for database errors or update failures
39///
40/// The response body includes only essential fields after the status change.
41///
42/// # Example Response (200 OK)
43///
44/// ```json
45/// {
46///   "success": true,
47///   "message": "Plagiarism case flagged",
48///   "data": {
49///     "id": 17,
50///     "status": "flagged",
51///     "updated_at": "2024-05-20T16:30:00Z"
52///   }
53/// }
54/// ```
55///
56/// # Example Responses
57///
58/// - `404 Not Found`  
59/// ```json
60/// {
61///   "success": false,
62///   "message": "Plagiarism case not found"
63/// }
64/// ```
65///
66/// - `500 Internal Server Error`  
67/// ```json
68/// {
69///   "success": false,
70///   "message": "Failed to update plagiarism case: [error details]"
71/// }
72/// ```
73///
74/// # Notes
75///
76/// - This operation updates the case status to "flagged" and sets the current timestamp to `updated_at`
77/// - Only users with lecturer or assistant lecturer roles assigned to the module can perform this action
78/// - Considered an irreversible action indicating confirmed plagiarism
79pub async fn patch_plagiarism_flag(
80    State(app_state): State<AppState>,
81    Path((_, assignment_id, case_id)): Path<(i64, i64, i64)>,
82) -> impl IntoResponse {
83    let case = match PlagiarismEntity::find()
84        .filter(PlagiarismColumn::Id.eq(case_id))
85        .filter(PlagiarismColumn::AssignmentId.eq(assignment_id))
86        .one(app_state.db())
87        .await
88    {
89        Ok(Some(case)) => case,
90        Ok(None) => {
91            return (
92                StatusCode::NOT_FOUND,
93                Json(ApiResponse::error("Plagiarism case not found")),
94            )
95        }
96        Err(e) => {
97            return (
98                StatusCode::INTERNAL_SERVER_ERROR,
99                Json(ApiResponse::error(format!("Database error: {}", e))),
100            )
101        }
102    };
103
104    let mut active_case = case.into_active_model();
105    active_case.status = Set(Status::Flagged);
106    active_case.updated_at = Set(Utc::now());
107
108    let updated_case = match active_case.update(app_state.db()).await {
109        Ok(case) => case,
110        Err(e) => {
111            return (
112                StatusCode::INTERNAL_SERVER_ERROR,
113                Json(ApiResponse::error(format!("Failed to update plagiarism case: {}", e))),
114            )
115        }
116    };
117
118    let response_data = FlaggedCaseResponse {
119        id: updated_case.id as i64,
120        status: updated_case.status.to_string(),
121        updated_at: updated_case.updated_at,
122    };
123
124    (
125        StatusCode::OK,
126        Json(ApiResponse::success(response_data, "Plagiarism case flagged")),
127    )
128}
129
130#[derive(Debug, Serialize)]
131pub struct ReviewedCaseResponse {
132    id: i64,
133    status: String,
134    updated_at: DateTime<Utc>,
135}
136
137/// PATCH /api/modules/{module_id}/assignments/{assignment_id}/plagiarism/{case_id}/review
138///
139/// Marks a plagiarism case as reviewed after manual inspection, indicating it's been cleared of plagiarism concerns.
140/// Accessible only to lecturers and assistant lecturers assigned to the module.
141///
142/// # Path Parameters
143///
144/// - `module_id`: The ID of the parent module
145/// - `assignment_id`: The ID of the assignment containing the plagiarism case
146/// - `case_id`: The ID of the plagiarism case to mark as reviewed
147///
148/// # Returns
149///
150/// Returns an HTTP response indicating the result:
151/// - `200 OK` with minimal case information on success
152/// - `403 FORBIDDEN` if user lacks required permissions
153/// - `404 NOT FOUND` if specified plagiarism case doesn't exist
154/// - `500 INTERNAL SERVER ERROR` for database errors or update failures
155///
156/// The response body includes only essential fields after the status change.
157///
158/// # Example Response (200 OK)
159///
160/// ```json
161/// {
162///   "success": true,
163///   "message": "Plagiarism case marked as reviewed",
164///   "data": {
165///     "id": 17,
166///     "status": "reviewed",
167///     "updated_at": "2024-05-20T17:45:00Z"
168///   }
169/// }
170/// ```
171///
172/// # Example Responses
173///
174/// - `404 Not Found`  
175/// ```json
176/// {
177///   "success": false,
178///   "message": "Plagiarism case not found"
179/// }
180/// ```
181///
182/// - `500 Internal Server Error`  
183/// ```json
184/// {
185///   "success": false,
186///   "message": "Failed to update plagiarism case: [error details]"
187/// }
188/// ```
189///
190/// # Notes
191///
192/// - This operation updates the case status to "reviewed" and sets the current timestamp to `updated_at`
193/// - Only users with lecturer or assistant lecturer roles assigned to the module can perform this action
194/// - Typically indicates the case was investigated and determined not to be plagiarism
195pub async fn patch_plagiarism_review(
196    State(app_state): State<AppState>,
197    Path((_, assignment_id, case_id)): Path<(i64, i64, i64)>,
198) -> impl IntoResponse {
199    let case = match PlagiarismEntity::find()
200        .filter(PlagiarismColumn::Id.eq(case_id))
201        .filter(PlagiarismColumn::AssignmentId.eq(assignment_id))
202        .one(app_state.db())
203        .await
204    {
205        Ok(Some(case)) => case,
206        Ok(None) => {
207            return (
208                StatusCode::NOT_FOUND,
209                Json(ApiResponse::error("Plagiarism case not found")),
210            )
211        }
212        Err(e) => {
213            return (
214                StatusCode::INTERNAL_SERVER_ERROR,
215                Json(ApiResponse::error(format!("Database error: {}", e))),
216            )
217        }
218    };
219
220    let mut active_case = case.into_active_model();
221    active_case.status = Set(Status::Reviewed);
222    active_case.updated_at = Set(Utc::now());
223
224    let updated_case = match active_case.update(app_state.db()).await {
225        Ok(case) => case,
226        Err(e) => {
227            return (
228                StatusCode::INTERNAL_SERVER_ERROR,
229                Json(ApiResponse::error(format!("Failed to update plagiarism case: {}", e))),
230            )
231        }
232    };
233
234    let response_data = ReviewedCaseResponse {
235        id: updated_case.id as i64,
236        status: updated_case.status.to_string(),
237        updated_at: updated_case.updated_at,
238    };
239
240    (
241        StatusCode::OK,
242        Json(ApiResponse::success(response_data, "Plagiarism case marked as reviewed")),
243    )
244}