use serde::{Deserialize, Serialize}; /// API response for a ticket with user information. /// /// Returned by ticket endpoints. Includes denormalized user data for easier frontend rendering. /// Created via [`TicketCreateScheme`]. /// /// # Fields /// - `id`: Unique ticket identifier /// - `category`: Ticket category/type /// - `betreff`: Ticket subject line /// - `description`: Detailed ticket description /// - `room`: Room number associated with the issue /// - `status`: Current ticket status (e.g., "open", "in_progress", "resolved") /// - `date`: When the ticket was created (UTC timestamp) /// - `user_id`: ID of the user who created the ticket (references [`User`]) /// - `user_first_name`, `user_last_name`: User's name (denormalized for convenience) /// /// # Example /// ```json /// { /// "id": 1, /// "category": "maintenance", /// "betreff": "Broken light in room 101", /// "description": "The ceiling light is not working", /// "room": 101, /// "status": "open", /// "date": "2024-01-15T10:30:00Z", /// "user_id": 5, /// "user_first_name": "John", /// "user_last_name": "Doe" /// } /// ``` #[derive(Deserialize, Serialize, Debug, PartialEq)] pub struct TicketResponse { pub id: i32, pub category: String, pub betreff: String, pub description: String, pub room: i16, pub status: String, pub date: chrono::DateTime, pub user_id: i16, pub user_first_name: String, pub user_last_name: String, } /// Complete user record from the database. /// /// Contains all user information including the password hash. /// This should NEVER be sent directly to clients - always use [`FilteredUser`] instead. /// /// # Fields /// - `id`: Unique user identifier /// - `first_name`, `last_name`: User's full name /// - `username`: Login username (must be unique) /// - `is_admin`: Whether user has admin privileges /// - `pwd`: Argon2 password hash (NEVER expose to clients) /// /// # Security Note /// The `pwd` field contains the password hash and should never be included in API responses. /// Use [`filter_user()`](`crate::handlers::auth::filter_user`) to convert to [`FilteredUser`] for responses. #[derive(Deserialize, Serialize, PartialEq, Debug, Clone, sqlx::FromRow)] pub struct User { pub id: i16, pub last_name: String, pub first_name: String, pub username: String, pub is_admin: bool, pub pwd: String, } /// Payload for creating a new ticket. /// /// Sent to `/api/tickets/create`. The backend automatically associates it with the /// authenticated user and sets the creation timestamp. Converted to [`TicketResponse`] for the response. /// /// # Fields /// - `category`: Ticket category/type /// - `betreff`: Subject line for the ticket /// - `description`: Detailed problem description /// - `room`: Room number where the issue is located #[derive(Deserialize, Serialize, Debug)] pub struct TicketCreateScheme { pub category: String, pub betreff: String, pub description: String, pub room: i16, } /// Payload for updating a ticket. /// /// Sent to `PATCH /api/tickets/{id}`. Allows updating the ticket [`TicketResponse::status`]. /// Only admins can update tickets. /// /// # Fields /// - `status`: New ticket status (e.g., "open", "in_progress", "resolved") #[derive(Deserialize, Serialize, Debug)] pub struct TicketUpdateScheme { pub status: String, } /// Payload for updating user information. /// /// Sent to `PATCH /api/users/{id}`. Allows updating profile and admin status. /// Only admins can update [`User`] records. Empty password field means no password change. /// /// # Fields /// - `id`: [`User`] ID to update /// - `first_name`, `last_name`: Updated user name /// - `username`: Updated login username /// - `make_admin`: New admin privilege status /// - `new_pwd`: New password (empty string = keep existing password) #[derive(Deserialize, Serialize, Debug)] pub struct UserUpdateScheme { pub id: i16, pub first_name: String, pub last_name: String, pub username: String, pub make_admin: bool, pub new_pwd: String, } /// Payload for creating a new user account. /// /// Used in both admin registration (`/api/register`) and initial setup (`/api/setup-admin`). /// The password is hashed server-side before storage using Argon2. Converted to [`User`] for storage. /// /// # Fields /// - `first_name`: User's first name /// - `last_name`: User's last name /// - `username`: Unique username for login /// - `is_admin`: Whether to grant admin privileges (setup endpoint always sets this to true) /// - `pwd`: Plain text password (hashed on server) #[derive(Deserialize, Serialize, Debug, sqlx::FromRow)] pub struct UserCreateScheme { pub first_name: String, pub last_name: String, pub username: String, pub is_admin: bool, pub pwd: String, } /// Payload for user login. /// /// Sent to `/api/login` endpoint with credentials. The backend verifies the password /// against the stored Argon2 hash. /// /// # Security /// The password is never stored in plain text - only the Argon2 hash is persisted. #[derive(Deserialize, Serialize, Debug)] pub struct LoginScheme { pub username: String, pub pwd: String, } /// User information sent to clients, excluding password hashes. /// /// This is the safe version of [`User`] data that gets returned in API responses. /// It never includes the password hash or JWT claims. Always use this for responses /// to prevent leaking sensitive data. #[derive(Debug, Clone, Serialize)] pub struct FilteredUser { pub id: i16, pub first_name: String, pub last_name: String, pub username: String, pub is_admin: bool, } /// JWT token claims embedded in the session token. /// /// Contains user identification and token validity information. /// Generated during login via `encode_token` and verified via `decode_token`. /// /// # Fields /// - `sub`: Subject - the user ID as a string (references [`User`]) /// - `issued`: Unix timestamp when token was created /// - `expires`: Unix timestamp when token expires (currently 1 hour from creation) /// /// # Token Lifetime /// Tokens are valid for 1 hour. After expiration, user must log in again. #[derive(Debug, Serialize, Deserialize, Clone)] pub struct Claims { /// Subject - typically the user ID #[serde(alias = "subject")] pub sub: String, /// Issued at time (Unix timestamp) #[serde(rename = "iat", alias = "issued", default)] pub issued: usize, /// Expiration time (Unix timestamp) #[serde(rename = "exp", alias = "expires", default)] pub expires: usize, }