1use std::path::PathBuf;
2use axum::{
3 extract::{State, Query, Path},
4 http::{StatusCode, header, HeaderMap, HeaderValue},
5 response::IntoResponse,
6 Json,
7};
8use sea_orm::{ColumnTrait, EntityTrait, QueryFilter};
9use tokio::fs::File as FsFile;
10use serde::{Deserialize, Serialize};
11use tokio::io::AsyncReadExt;
12use util::state::AppState;
13use crate::{
14 auth::claims::AuthUser,
15 response::ApiResponse,
16};
17use db::models::{user, module, user_module_role, user_module_role::Role};
18use crate::routes::common::UserModule;
19
20#[derive(Debug, Serialize)]
21pub struct MeResponse {
22 pub id: i64,
23 pub email: String,
24 pub username: String,
25 pub admin: bool,
26 pub created_at: String,
27 pub updated_at: String,
28 pub modules: Vec<UserModule>,
29}
30
31#[derive(Deserialize)]
32pub struct HasRoleQuery {
33 pub module_id: i64,
34 pub role: String,
35}
36
37#[derive(serde::Serialize)]
38pub struct HasRoleResponse {
39 pub has_role: bool,
40}
41
42pub async fn get_me(
90 State(app_state): State<AppState>,
91 AuthUser(claims): AuthUser
92) -> impl IntoResponse {
93 let db = app_state.db();
94 let user_id = claims.sub;
95
96 let user = match user::Entity::find()
97 .filter(user::Column::Id.eq(user_id))
98 .one(db)
99 .await
100 {
101 Ok(Some(u)) => u,
102 Ok(None) => {
103 return (
104 StatusCode::NOT_FOUND,
105 Json(ApiResponse::<MeResponse>::error("User not found")),
106 );
107 }
108 Err(_) => {
109 return (
110 StatusCode::INTERNAL_SERVER_ERROR,
111 Json(ApiResponse::<MeResponse>::error("Database error")),
112 );
113 }
114 };
115
116 let roles = match user_module_role::Entity::find()
117 .filter(user_module_role::Column::UserId.eq(user.id))
118 .find_also_related(module::Entity)
119 .all(db)
120 .await
121 {
122 Ok(results) => results,
123 Err(_) => {
124 return (
125 StatusCode::INTERNAL_SERVER_ERROR,
126 Json(ApiResponse::<MeResponse>::error("Failed to load module roles")),
127 );
128 }
129 };
130
131 let modules: Vec<UserModule> = roles
132 .into_iter()
133 .filter_map(|(role, maybe_module)| {
134 maybe_module.map(|m| UserModule {
135 id: m.id,
136 code: m.code,
137 year: m.year,
138 description: m.description.unwrap_or_default(),
139 credits: m.credits,
140 created_at: m.created_at.to_rfc3339(),
141 updated_at: m.updated_at.to_rfc3339(),
142 role: role.role.to_string(),
143 })
144 })
145 .collect();
146
147 let response_data = MeResponse {
148 id: user.id,
149 email: user.email,
150 username: user.username,
151 admin: user.admin,
152 created_at: user.created_at.to_rfc3339(),
153 updated_at: user.updated_at.to_rfc3339(),
154 modules,
155 };
156
157 (
158 StatusCode::OK,
159 Json(ApiResponse::success(response_data, "User data retrieved successfully")),
160 )
161}
162
163pub async fn get_avatar(
208 State(app_state): State<AppState>,
209 Path(user_id): Path<i64>
210) -> impl IntoResponse {
211 let db = app_state.db();
212
213 let user = user::Entity::find_by_id(user_id).one(db).await.unwrap().unwrap();
214
215 let Some(path) = user.profile_picture_path else {
216 return (
217 StatusCode::NOT_FOUND,
218 Json(ApiResponse::<()>::error("No avatar set")),
219 )
220 .into_response();
221 };
222
223 let root = std::env::var("USER_PROFILE_STORAGE_ROOT")
224 .unwrap_or_else(|_| "data/user_profile_pictures".to_string());
225 let fs_path = PathBuf::from(root).join(path);
226
227 if tokio::fs::metadata(&fs_path).await.is_err() {
228 return (
229 StatusCode::NOT_FOUND,
230 Json(ApiResponse::<()>::error("Avatar file missing")),
231 )
232 .into_response();
233 }
234
235 let mut file = match FsFile::open(&fs_path).await {
236 Ok(f) => f,
237 Err(_) => {
238 return (
239 StatusCode::INTERNAL_SERVER_ERROR,
240 Json(ApiResponse::<()>::error("Could not open avatar")),
241 )
242 .into_response();
243 }
244 };
245
246 let mut buffer = Vec::new();
247 if let Err(_) = file.read_to_end(&mut buffer).await {
248 return (
249 StatusCode::INTERNAL_SERVER_ERROR,
250 Json(ApiResponse::<()>::error("Failed to read avatar")),
251 )
252 .into_response();
253 }
254
255 let mime = mime_guess::from_path(&fs_path)
256 .first_or_octet_stream()
257 .to_string();
258
259 let mut headers = HeaderMap::new();
260 headers.insert(
261 header::CONTENT_TYPE,
262 HeaderValue::from_str(&mime).unwrap_or(HeaderValue::from_static("application/octet-stream")),
263 );
264
265 (StatusCode::OK, headers, buffer).into_response()
266}
267
268pub async fn has_role_in_module(
294 State(app_state): State<AppState>,
295 AuthUser(claims): AuthUser,
296 Query(params): Query<HasRoleQuery>
297) -> impl IntoResponse {
298 let db = app_state.db();
299
300 let role = match params.role.to_lowercase().as_str() {
301 "lecturer" => Role::Lecturer,
302 "assistant_lecturer" => Role::AssistantLecturer,
303 "tutor" => Role::Tutor,
304 "student" => Role::Student,
305 _ => {
306 return (
307 StatusCode::BAD_REQUEST,
308 Json(ApiResponse::<HasRoleResponse>::error("Invalid role specified")),
309 )
310 }
311 };
312
313 let exists = match user_module_role::Entity::find()
314 .filter(user_module_role::Column::UserId.eq(claims.sub))
315 .filter(user_module_role::Column::ModuleId.eq(params.module_id))
316 .filter(user_module_role::Column::Role.eq(role))
317 .one(db)
318 .await
319 {
320 Ok(Some(_)) => true,
321 Ok(None) => false,
322 Err(_) => {
323 return (
324 StatusCode::INTERNAL_SERVER_ERROR,
325 Json(ApiResponse::<HasRoleResponse>::error("Database error")),
326 )
327 }
328 };
329
330 let response = HasRoleResponse { has_role: exists };
331
332 (
333 StatusCode::OK,
334 Json(ApiResponse::success(response, "Role check completed")),
335 )
336}
337
338#[derive(Debug, Deserialize)]
339pub struct ModuleRoleQuery {
340 pub module_id: i32,
341}
342
343#[derive(Debug, Serialize)]
344pub struct ModuleRoleResponse {
345 pub role: Option<String>,
346}
347
348pub async fn get_module_role(
370 State(app_state): State<AppState>,
371 AuthUser(claims): AuthUser,
372 Query(params): Query<ModuleRoleQuery>,
373) -> impl IntoResponse {
374 let db = app_state.db();
375
376 let role = match user_module_role::Entity::find()
377 .filter(user_module_role::Column::UserId.eq(claims.sub))
378 .filter(user_module_role::Column::ModuleId.eq(params.module_id))
379 .one(db)
380 .await
381 {
382 Ok(Some(model)) => Some(model.role.to_string().to_lowercase()),
383 Ok(None) => None,
384 Err(_) => {
385 return (
386 StatusCode::INTERNAL_SERVER_ERROR,
387 Json(ApiResponse::<ModuleRoleResponse>::error("Database error")),
388 );
389 }
390 };
391
392 (
393 StatusCode::OK,
394 Json(ApiResponse::success(
395 ModuleRoleResponse { role },
396 "Role fetched successfully",
397 )),
398 )
399}