backend/
models.rs

1use serde::{Deserialize, Serialize};
2
3/// API response for a ticket with user information.
4///
5/// Returned by ticket endpoints. Includes denormalized user data for easier frontend rendering.
6/// Created via [`TicketCreateScheme`].
7///
8/// # Fields
9/// - `id`: Unique ticket identifier
10/// - `category`: Ticket category/type
11/// - `betreff`: Ticket subject line
12/// - `description`: Detailed ticket description
13/// - `room`: Room number associated with the issue
14/// - `status`: Current ticket status (e.g., "open", "in_progress", "resolved")
15/// - `date`: When the ticket was created (UTC timestamp)
16/// - `user_id`: ID of the user who created the ticket (references [`User`])
17/// - `user_first_name`, `user_last_name`: User's name (denormalized for convenience)
18///
19/// # Example
20/// ```json
21/// {
22///   "id": 1,
23///   "category": "maintenance",
24///   "betreff": "Broken light in room 101",
25///   "description": "The ceiling light is not working",
26///   "room": 101,
27///   "status": "open",
28///   "date": "2024-01-15T10:30:00Z",
29///   "user_id": 5,
30///   "user_first_name": "John",
31///   "user_last_name": "Doe"
32/// }
33/// ```
34#[derive(Deserialize, Serialize, Debug, PartialEq)]
35pub struct TicketResponse {
36    /// Unique ticket identifier
37    pub id: i32,
38    /// Ticket category/type (e.g., "maintenance", "support")
39    pub category: String,
40    /// Ticket subject line
41    pub betreff: String,
42    /// Detailed ticket description
43    pub description: String,
44    /// Room number associated with the issue
45    pub room: i16,
46    /// Current ticket status (e.g., "open", "in_progress", "resolved", "archived")
47    pub status: String,
48    /// When the ticket was created (UTC timestamp)
49    pub date: chrono::DateTime<chrono::Utc>,
50    /// ID of the user who created the ticket
51    pub user_id: i16,
52    /// First name of the ticket creator (denormalized from `User`)
53    pub user_first_name: String,
54    /// Last name of the ticket creator (denormalized from `User`)
55    pub user_last_name: String,
56}
57
58/// Complete user record from the database.
59///
60/// Contains all user information including the password hash.
61/// This should NEVER be sent directly to clients - always use [`FilteredUser`] instead.
62///
63/// # Fields
64/// - `id`: Unique user identifier
65/// - `first_name`, `last_name`: User's full name
66/// - `username`: Login username (must be unique)
67/// - `is_admin`: Whether user has admin privileges
68/// - `pwd`: Argon2 password hash (NEVER expose to clients)
69///
70/// # Security Note
71/// The `pwd` field contains the password hash and should never be included in API responses.
72/// Use [`filter_user()`](`crate::handlers::auth::filter_user`) to convert to [`FilteredUser`] for responses.
73#[derive(Deserialize, Serialize, PartialEq, Debug, Clone, sqlx::FromRow)]
74pub struct User {
75    /// Unique user identifier
76    pub id: i16,
77    /// User's last name
78    pub last_name: String,
79    /// User's first name
80    pub first_name: String,
81    /// Unique login username (must be unique in the database)
82    pub username: String,
83    /// Whether this user has administrator privileges
84    pub is_admin: bool,
85    /// Argon2 password hash (NEVER expose to clients)
86    pub pwd: String,
87}
88
89/// Payload for creating a new ticket.
90///
91/// Sent to `/api/tickets/create`. The backend automatically associates it with the
92/// authenticated user and sets the creation timestamp. Converted to [`TicketResponse`] for the response.
93///
94/// # Fields
95/// - `category`: Ticket category/type
96/// - `betreff`: Subject line for the ticket
97/// - `description`: Detailed problem description
98/// - `room`: Room number where the issue is located
99#[derive(Deserialize, Serialize, Debug)]
100pub struct TicketCreateScheme {
101    /// Ticket category/type
102    pub category: String,
103    /// Subject line for the ticket
104    pub betreff: String,
105    /// Detailed problem description
106    pub description: String,
107    /// Room number where the issue is located
108    pub room: i16,
109}
110
111/// Payload for updating a ticket.
112///
113/// Sent to `PATCH /api/tickets/{id}`. Allows updating the ticket [`TicketResponse::status`].
114/// Only admins can update tickets.
115///
116/// # Fields
117/// - `status`: New ticket status (e.g., "open", "in_progress", "resolved")
118#[derive(Deserialize, Serialize, Debug)]
119pub struct TicketUpdateScheme {
120    /// New ticket status (e.g., "open", "in_progress", "resolved", "archived")
121    pub status: String,
122}
123
124/// Payload for updating user information.
125///
126/// Sent to `PATCH /api/users/{id}`. Allows updating profile and admin status.
127/// Only admins can update [`User`] records. Empty password field means no password change.
128///
129/// # Fields
130/// - `id`: [`User`] ID to update
131/// - `first_name`, `last_name`: Updated user name
132/// - `username`: Updated login username
133/// - `make_admin`: New admin privilege status
134/// - `new_pwd`: New password (empty string = keep existing password)
135#[derive(Deserialize, Serialize, Debug)]
136pub struct UserUpdateScheme {
137    /// User ID to update
138    pub id: i16,
139    /// Updated user first name
140    pub first_name: String,
141    /// Updated user last name
142    pub last_name: String,
143    /// Updated login username
144    pub username: String,
145    /// New admin privilege status
146    pub make_admin: bool,
147    /// New password (empty string = keep existing password)
148    pub new_pwd: String,
149}
150
151/// Payload for creating a new user account.
152///
153/// Used in both admin registration (`/api/register`) and initial setup (`/api/setup-admin`).
154/// The password is hashed server-side before storage using Argon2. Converted to [`User`] for storage.
155///
156/// # Fields
157/// - `first_name`: User's first name
158/// - `last_name`: User's last name
159/// - `username`: Unique username for login
160/// - `is_admin`: Whether to grant admin privileges (setup endpoint always sets this to true)
161/// - `pwd`: Plain text password (hashed on server)
162#[derive(Deserialize, Serialize, Debug, sqlx::FromRow)]
163pub struct UserCreateScheme {
164    /// User's first name
165    pub first_name: String,
166    /// User's last name
167    pub last_name: String,
168    /// Unique username for login
169    pub username: String,
170    /// Whether to grant admin privileges
171    pub is_admin: bool,
172    /// Plain text password (hashed on server before storage)
173    pub pwd: String,
174}
175
176/// Payload for user login.
177///
178/// Sent to `/api/login` endpoint with credentials. The backend verifies the password
179/// against the stored Argon2 hash.
180///
181/// # Security
182/// The password is never stored in plain text - only the Argon2 hash is persisted.
183#[derive(Deserialize, Serialize, Debug)]
184pub struct LoginScheme {
185    /// Username for login
186    pub username: String,
187    /// Plain text password (verified against stored Argon2 hash)
188    pub pwd: String,
189}
190
191/// User information sent to clients, excluding password hashes.
192///
193/// This is the safe version of [`User`] data that gets returned in API responses.
194/// It never includes the password hash or JWT claims. Always use this for responses
195/// to prevent leaking sensitive data.
196#[derive(Debug, Clone, Serialize)]
197pub struct FilteredUser {
198    /// Unique user identifier
199    pub id: i16,
200    /// User's first name
201    pub first_name: String,
202    /// User's last name
203    pub last_name: String,
204    /// Login username
205    pub username: String,
206    /// Whether user has admin privileges
207    pub is_admin: bool,
208}
209
210/// JWT token claims embedded in the session token.
211///
212/// Contains user identification and token validity information.
213/// Generated during login via `encode_token` and verified via `decode_token`.
214///
215/// # Fields
216/// - `sub`: Subject - the user ID as a string (references [`User`])
217/// - `issued`: Unix timestamp when token was created
218/// - `expires`: Unix timestamp when token expires (currently 1 hour from creation)
219///
220/// # Token Lifetime
221/// Tokens are valid for 1 hour. After expiration, user must log in again.
222#[derive(Debug, Serialize, Deserialize, Clone)]
223pub struct Claims {
224    /// Subject - typically the user ID
225    #[serde(alias = "subject")]
226    pub sub: String,
227    /// Issued at time (Unix timestamp)
228    #[serde(rename = "iat", alias = "issued", default)]
229    pub issued: usize,
230    /// Expiration time (Unix timestamp)
231    #[serde(rename = "exp", alias = "expires", default)]
232    pub expires: usize,
233}