api/routes/modules/
common.rs

1//! Module and role request/response models.
2//!
3//! Provides data structures for:
4//! - Module creation, validation, and response (`ModuleRequest`, `ModuleResponse`).
5//! - Assigning users to modules (`ModifyUsersModuleRequest`, `EditRoleRequest`).
6//! - User role responses and pagination (`RoleResponse`, `PaginatedRoleResponse`).
7//!
8//! Includes `From` implementations to convert database models into API-friendly responses.
9
10use chrono::{Datelike, Utc};
11use serde::{Serialize, Deserialize};
12use validator::Validate;
13
14#[derive(Debug, Deserialize)]
15pub struct ModifyUsersModuleRequest {
16    pub user_ids: Vec<i64>,
17}
18
19#[derive(Debug, Serialize)]
20pub struct RoleResponse {
21    pub id: i64,
22    pub username: String,
23    pub email: String,
24    pub admin: bool,
25    pub created_at: String,
26    pub updated_at: String,
27}
28
29impl From<db::models::user::Model> for RoleResponse {
30    fn from(user: db::models::user::Model) -> Self {
31        Self {
32            id: user.id,
33            username: user.username,
34            email: user.email,
35            admin: user.admin,
36            created_at: user.created_at.to_rfc3339(),
37            updated_at: user.updated_at.to_rfc3339(),
38        }
39    }
40}
41
42#[derive(Debug, Deserialize)]
43pub struct RoleQuery {
44    pub page: Option<u32>,
45    pub per_page: Option<u32>,
46    pub query: Option<String>,
47    pub email: Option<String>,
48    pub username: Option<String>,
49    pub sort: Option<String>,
50}
51
52#[derive(serde::Serialize)]
53pub struct PaginatedRoleResponse {
54    pub users: Vec<RoleResponse>,
55    pub page: u32,
56    pub per_page: u32,
57    pub total: u64,
58}
59
60#[derive(Debug, Deserialize, Validate)]
61pub struct EditRoleRequest {
62    #[validate(length(min = 1, message = "Request must include a non-empty list of user_ids"))]
63    pub user_ids: Vec<i64>,
64}
65
66lazy_static::lazy_static! {
67    static ref MODULE_CODE_REGEX: regex::Regex = regex::Regex::new("^[A-Z]{3}\\d{3}$").unwrap();
68}
69
70#[derive(Debug, Deserialize, Validate)]
71pub struct ModuleRequest {
72    #[validate(regex(
73        path = &*MODULE_CODE_REGEX,
74        message = "Module code must be in format ABC123"
75    ))]
76    pub code: String,
77
78    #[validate(range(min = Utc::now().year(), message = "Year must be current year or later"))]
79    pub year: i32,
80
81    #[validate(length(max = 1000, message = "Description must be at most 1000 characters"))]
82    pub description: Option<String>,
83
84    #[validate(range(min = 1, message = "Credits must be a positive number"))]
85    pub credits: i32,
86}
87
88#[derive(Debug, Serialize)]
89pub struct ModuleResponse {
90    pub id: i64,
91    pub code: String,
92    pub year: i32,
93    pub description: Option<String>,
94    pub credits: i32,
95    pub created_at: String,
96    pub updated_at: String,
97}
98
99impl From<db::models::module::Model> for ModuleResponse {
100    fn from(module: db::models::module::Model) -> Self {
101        Self {
102            id: module.id,
103            code: module.code,
104            year: module.year,
105            description: module.description,
106            credits: module.credits,
107            created_at: module.created_at.to_rfc3339(),
108            updated_at: module.updated_at.to_rfc3339(),
109        }
110    }
111}