api/routes/modules/assignments/tickets/
common.rs

1//! Ticket utilities.
2//!
3//! This module provides helper functions and types for ticket-related endpoints.
4//!
5//! It includes:
6//! - `is_valid`: checks whether a user is authorized to access or modify a ticket.
7//! - `TicketResponse`: a serializable response type for ticket API endpoints.
8
9use db::models::{
10    tickets::Model as TicketModel,
11    user_module_role::{Column, Role},
12    UserModuleRole as Entity,
13};
14use db::models::user::Model as UserModel;
15use sea_orm::{ColumnTrait, DatabaseConnection, EntityTrait, QueryFilter};
16use serde::{Deserialize, Serialize};
17
18/// Returns whether `user_id` is allowed to view/post on a ticket in `module_id`.
19///
20/// Admins are always allowed.
21///
22/// Rules:
23/// - `is_admin == true` → allowed
24/// - Ticket **author** → allowed
25/// - Module **staff** (Lecturer, AssistantLecturer, Tutor) → allowed
26pub async fn is_valid(
27    user_id: i64,
28    ticket_id: i64,
29    module_id: i64,
30    is_admin: bool,
31    db: &DatabaseConnection,
32) -> bool {
33    // Admin override
34    if is_admin {
35        return true;
36    }
37
38    // Author of the ticket?
39    if TicketModel::is_author(ticket_id, user_id, db).await {
40        return true;
41    }
42
43    // Staff on this module?
44    let staff_roles = [Role::Lecturer, Role::AssistantLecturer, Role::Tutor];
45    Entity::find()
46        .filter(Column::UserId.eq(user_id))
47        .filter(Column::ModuleId.eq(module_id))
48        .filter(Column::Role.is_in(staff_roles))
49        .one(db)
50        .await
51        .unwrap_or(None)
52        .is_some()
53}
54
55#[derive(Debug, Serialize, Deserialize)]
56pub struct TicketResponse {
57    pub id: i64,
58    pub assignment_id: i64,
59    pub user_id: i64,
60    pub title: String,
61    pub description: String,
62    pub status: String,
63    pub created_at: String,
64    pub updated_at: String,
65}
66
67impl From<TicketModel> for TicketResponse {
68    fn from(ticket: TicketModel) -> Self {
69        Self {
70            id: ticket.id,
71            assignment_id: ticket.assignment_id,
72            user_id: ticket.user_id,
73            title: ticket.title,
74            description: ticket.description,
75            status: ticket.status.to_string(),
76            created_at: ticket.created_at.to_rfc3339(),
77            updated_at: ticket.updated_at.to_rfc3339(),
78        }
79    }
80}
81
82#[derive(Debug, Serialize)]
83pub struct UserResponse {
84    pub id: i64,
85    pub username: String,
86    pub email: String,
87}
88
89impl From<UserModel> for UserResponse {
90    fn from(user: UserModel) -> Self {
91        Self {
92            id: user.id,
93            username: user.username,
94            email: user.email,
95        }
96    }
97}
98
99#[derive(Debug, Serialize)]
100pub struct TicketWithUserResponse {
101    pub ticket: TicketResponse,
102    pub user: UserResponse,
103}