1use axum::{
10 Extension, Json,
11 extract::{Query, State},
12 http::StatusCode,
13 response::IntoResponse,
14};
15use db::models::{assignment, module, user_module_role};
16use migration::Expr;
17use sea_orm::{
18 ColumnTrait, Condition, EntityTrait, JoinType, PaginatorTrait, QueryFilter, QueryOrder, QuerySelect, RelationTrait
19};
20use serde::{Deserialize, Serialize};
21use util::state::AppState;
22use crate::{auth::AuthUser, response::ApiResponse};
23
24#[derive(Debug, Deserialize)]
26pub struct AssignmentFilterReq {
27 pub page: Option<i32>,
29 pub per_page: Option<i32>,
31 pub query: Option<String>,
33 pub role: Option<String>,
35 pub year: Option<i32>,
37 pub status: Option<String>,
39 pub sort: Option<String>,
41}
42
43#[derive(Serialize)]
45pub struct ModuleResponse {
46 pub id: i64,
47 pub code: String,
48}
49
50#[derive(Serialize)]
52pub struct AssignmentResponse {
53 pub id: i64,
54 pub title: String,
55 pub status: String,
56 pub available_from: String,
57 pub due_date: String,
58 pub created_at: String,
59 pub updated_at: String,
60 pub module: ModuleResponse,
61}
62
63#[derive(Serialize)]
65pub struct FilterAssignmentResponse {
66 pub assignments: Vec<AssignmentResponse>,
67 pub page: i32,
68 pub per_page: i32,
69 pub total: i32,
70}
71
72impl FilterAssignmentResponse {
73 fn new(assignments: Vec<AssignmentResponse>, page: i32, per_page: i32, total: i32) -> Self {
74 Self { assignments, page, per_page, total }
75 }
76}
77
78pub async fn get_my_assignments(
115 State(state): State<AppState>,
116 Extension(AuthUser(claims)): Extension<AuthUser>,
117 Query(params): Query<AssignmentFilterReq>,
118) -> impl IntoResponse {
119 let db = state.db();
120 let user_id = claims.sub;
121 let page = params.page.unwrap_or(1).max(1);
122 let per_page = params.per_page.unwrap_or(20).min(100);
123
124 let allowed_roles = vec!["lecturer", "assistant_lecturer", "tutor", "student"];
125 let requested_role = params.role.clone().filter(|r| allowed_roles.contains(&r.as_str()));
126
127 let memberships = user_module_role::Entity::find()
128 .filter(user_module_role::Column::UserId.eq(user_id))
129 .filter(user_module_role::Column::Role.is_in(allowed_roles.clone()))
130 .all(db)
131 .await
132 .unwrap_or_default();
133
134 if memberships.is_empty() {
135 let response = FilterAssignmentResponse::new(vec![], page, per_page, 0);
136 return (StatusCode::OK, Json(ApiResponse::success(response, "Assignments retrieved"))).into_response();
137 }
138
139 let module_ids: Vec<i64> = memberships.iter()
140 .filter(|m| requested_role.as_ref().map_or(true, |r| &m.role.to_string() == r))
141 .map(|m| m.module_id)
142 .collect();
143
144 if module_ids.is_empty() {
145 let response = FilterAssignmentResponse::new(vec![], page, per_page, 0);
146 return (StatusCode::OK, Json(ApiResponse::success(response, "Assignments retrieved"))).into_response();
147 }
148
149 let mut condition = Condition::all().add(assignment::Column::ModuleId.is_in(module_ids));
150
151 if let Some(year) = params.year {
152 condition = condition.add(Expr::col((module::Entity, module::Column::Year)).eq(year));
153 }
154
155 if let Some(ref status) = params.status {
156 condition = condition.add(assignment::Column::Status.eq(status));
157 }
158
159 if let Some(ref q) = params.query {
160 let pattern = format!("%{}%", q.to_lowercase());
161 condition = condition.add(
162 Condition::any()
163 .add(Expr::cust("LOWER(assignment.title)").like(&pattern))
164 .add(Expr::cust("LOWER(module.code)").like(&pattern))
165 );
166 }
167
168 let mut query = assignment::Entity::find()
169 .join(JoinType::InnerJoin, assignment::Relation::Module.def())
170 .filter(condition);
171
172 if let Some(sort_param) = ¶ms.sort {
173 for sort in sort_param.split(',') {
174 let (field, asc) = if sort.starts_with('-') { (&sort[1..], false) } else { (sort, true) };
175 query = match field {
176 "due_date" => if asc { query.order_by_asc(assignment::Column::DueDate) } else { query.order_by_desc(assignment::Column::DueDate) },
177 "available_from" => if asc { query.order_by_asc(assignment::Column::AvailableFrom) } else { query.order_by_desc(assignment::Column::AvailableFrom) },
178 _ => query,
179 };
180 }
181 } else {
182 query = query.order_by_asc(assignment::Column::DueDate).order_by_asc(assignment::Column::Id);
183 }
184
185 let paginator = query.clone().paginate(db, per_page as u64);
186 let total = match paginator.num_items().await {
187 Ok(n) => n as i32,
188 Err(_) => return (StatusCode::INTERNAL_SERVER_ERROR, Json(ApiResponse::<FilterAssignmentResponse>::error("Error counting assignments"))).into_response(),
189 };
190
191 match paginator.fetch_page((page - 1) as u64).await {
192 Ok(results) => {
193 let mut assignments_vec = Vec::new();
194 for a in results {
195 let m = module::Entity::find_by_id(a.module_id).one(db).await.unwrap_or(None);
196 if m.is_none() { continue; }
197 let m = m.unwrap();
198
199 assignments_vec.push(AssignmentResponse {
200 id: a.id,
201 title: a.name,
202 status: a.status.to_string(),
203 available_from: a.available_from.to_string(),
204 due_date: a.due_date.to_string(),
205 created_at: a.created_at.to_string(),
206 updated_at: a.updated_at.to_string(),
207 module: ModuleResponse { id: m.id, code: m.code },
208 });
209 }
210
211 let response = FilterAssignmentResponse::new(assignments_vec, page, per_page, total);
212 (StatusCode::OK, Json(ApiResponse::success(response, "Assignments retrieved"))).into_response()
213 }
214 Err(_) => (StatusCode::INTERNAL_SERVER_ERROR, Json(ApiResponse::<FilterAssignmentResponse>::error("Failed to retrieve assignments"))).into_response(),
215 }
216}