1use axum::{
10 Extension, Json,
11 extract::{Query, State},
12 http::StatusCode,
13 response::IntoResponse,
14};
15use db::models::{announcements, module, user, 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;
22
23use crate::{auth::AuthUser, response::ApiResponse};
24
25#[derive(Debug, Deserialize)]
27pub struct FilterReq {
28 pub page: Option<i32>,
30 pub per_page: Option<i32>,
32 pub query: Option<String>,
34 pub role: Option<String>,
36 pub year: Option<i32>,
38 pub pinned: Option<bool>,
40 pub sort: Option<String>,
42}
43
44#[derive(Serialize)]
46pub struct UserResponse {
47 pub id: i64,
48 pub username: String,
49}
50
51#[derive(Serialize)]
53pub struct ModuleResponse {
54 pub id: i64,
55 pub code: String,
56}
57
58#[derive(Serialize)]
60pub struct AnnouncementResponse {
61 pub id: i64,
62 pub title: String,
63 pub content: String,
64 pub pinned: bool,
65 pub created_at: String,
66 pub updated_at: String,
67 pub module: ModuleResponse,
68 pub user: UserResponse,
69}
70
71#[derive(Serialize)]
73pub struct FilterResponse {
74 pub announcements: Vec<AnnouncementResponse>,
75 pub page: i32,
76 pub per_page: i32,
77 pub total: i32,
78}
79
80impl FilterResponse {
81 fn new(announcements: Vec<AnnouncementResponse>, page: i32, per_page: i32, total: i32) -> Self {
82 Self { announcements, page, per_page, total }
83 }
84}
85
86pub async fn get_my_announcements(
123 State(state): State<AppState>,
124 Extension(AuthUser(claims)): Extension<AuthUser>,
125 Query(params): Query<FilterReq>,
126) -> impl IntoResponse {
127 let db = state.db();
128 let user_id = claims.sub;
129 let page = params.page.unwrap_or(1).max(1);
130 let per_page = params.per_page.unwrap_or(20).min(100);
131
132 let allowed_roles = vec!["lecturer", "assistant_lecturer", "tutor", "student"];
133 let requested_role = params.role.clone().filter(|r| allowed_roles.contains(&r.as_str()));
134
135 let memberships = user_module_role::Entity::find()
136 .filter(user_module_role::Column::UserId.eq(user_id))
137 .filter(user_module_role::Column::Role.is_in(allowed_roles.clone()))
138 .all(db)
139 .await
140 .unwrap_or_default();
141
142 if memberships.is_empty() {
143 let response = FilterResponse::new(vec![], page, per_page, 0);
144 return (StatusCode::OK, Json(ApiResponse::success(response, "Announcements retrieved"))).into_response();
145 }
146
147 let module_ids: Vec<i64> = memberships.iter()
148 .filter(|m| requested_role.as_ref().map_or(true, |r| &m.role.to_string() == r))
149 .map(|m| m.module_id)
150 .collect();
151
152 if module_ids.is_empty() {
153 let response = FilterResponse::new(vec![], page, per_page, 0);
154 return (StatusCode::OK, Json(ApiResponse::success(response, "Announcements retrieved"))).into_response();
155 }
156
157 let mut condition = Condition::all().add(announcements::Column::ModuleId.is_in(module_ids));
158
159 if let Some(year) = params.year {
160 condition = condition.add(Expr::col((module::Entity, module::Column::Year)).eq(year));
161 }
162
163 if let Some(pinned) = params.pinned {
164 condition = condition.add(announcements::Column::Pinned.eq(pinned));
165 }
166
167 if let Some(ref q) = params.query {
168 let pattern = format!("%{}%", q.to_lowercase());
169 condition = condition.add(
170 Condition::any()
171 .add(Expr::cust("LOWER(announcements.title)").like(&pattern))
172 .add(Expr::cust("LOWER(module.code)").like(&pattern))
173 .add(Expr::cust("LOWER(user.username)").like(&pattern))
174 );
175 }
176
177 let mut query = announcements::Entity::find()
178 .join(JoinType::InnerJoin, announcements::Relation::Module.def())
179 .join(JoinType::InnerJoin, announcements::Relation::User.def())
180 .filter(condition);
181
182 if let Some(sort_param) = ¶ms.sort {
183 for sort in sort_param.split(',') {
184 let (field, asc) = if sort.starts_with('-') { (&sort[1..], false) } else { (sort, true) };
185 query = match field {
186 "created_at" => if asc { query.order_by_asc(announcements::Column::CreatedAt) } else { query.order_by_desc(announcements::Column::CreatedAt) },
187 "updated_at" => if asc { query.order_by_asc(announcements::Column::UpdatedAt) } else { query.order_by_desc(announcements::Column::UpdatedAt) },
188 _ => query,
189 };
190 }
191 } else {
192 query = query.order_by_desc(announcements::Column::CreatedAt).order_by_asc(announcements::Column::Id);
193 }
194
195 let paginator = query.clone().paginate(db, per_page as u64);
196 let total = match paginator.num_items().await {
197 Ok(n) => n as i32,
198 Err(_) => return (StatusCode::INTERNAL_SERVER_ERROR, Json(ApiResponse::<FilterResponse>::error("Error counting announcements"))).into_response(),
199 };
200
201 match paginator.fetch_page((page - 1) as u64).await {
202 Ok(results) => {
203 let mut announcements_vec = Vec::new();
204 for a in results {
205 let m = module::Entity::find_by_id(a.module_id).one(db).await.unwrap_or(None);
206 if m.is_none() { continue; }
207 let m = m.unwrap();
208
209 let u = user::Entity::find_by_id(a.user_id).one(db).await.unwrap_or(None);
210
211 announcements_vec.push(AnnouncementResponse {
212 id: a.id,
213 title: a.title,
214 content: a.body,
215 pinned: a.pinned,
216 created_at: a.created_at.to_string(),
217 updated_at: a.updated_at.to_string(),
218 module: ModuleResponse { id: m.id, code: m.code },
219 user: UserResponse { id: a.user_id, username: u.map(|uu| uu.username).unwrap_or_default() },
220 });
221 }
222
223 let response = FilterResponse::new(announcements_vec, page, per_page, total);
224 (StatusCode::OK, Json(ApiResponse::success(response, "Announcements retrieved"))).into_response()
225 }
226 Err(_) => (StatusCode::INTERNAL_SERVER_ERROR, Json(ApiResponse::<FilterResponse>::error("Failed to retrieve announcements"))).into_response(),
227 }
228}