diff --git a/README.cargo.md b/README.cargo.md
new file mode 100644
index 0000000..c0f06d7
--- /dev/null
+++ b/README.cargo.md
@@ -0,0 +1,63 @@
+# Ticketsystem
+
+A ticket system with backend and frontend components.
+
+## Components
+
+- **[Backend](../backend/index.html)** - The server-side API and business logic
+- **[Frontend](../frontend/index.html)** - The client-side user interface
+
+## Usage
+### Prerequisite
+#### IMPORTANT
+Before compiling the programm you have to install the rust toolchain.
+For a guide to do this, visit:
+
+A instance of a postgresql has to be accessible to the backend. Place the connection details in a .env file into the variable `DATABASE_URL`
+To setup the tables you either can create them manually by following the sheme specified in `backend/migrations` or apply them with sqlx
+To install sqlx run `cargo install sqlx` and then in the `backend` directory run `sqlx migrate run` to create the tables
+
+### Environment
+The .env file has to be in the root directory of the project or in the same directory as the executable
+
+Keys:
+`DATABASE_URL`: Specifying the url and connection details for the database
+`TOKEN_SECRET`: The JWT token secret, can theoretically be anything but is more secure when generated with a tool, e.g:
+`ORIGIN`: The origin of the frontend, used for CORS rules
+`BACKEND_PORT`: The port which the backend should use to run on
+
+### Backend
+The backend can either be run via `cargo run --release` or `cargo build --release` using the correct target architecture, e.g. 'x86_64-unknown-linux-gnu',
+the executable will be placed in the `target/release` directory and can then be run via any method
+
+### Frontend
+The HTML code for the frontend can be generated by using `trunk build`. The resulting files will end up in the `frontend/dist` directory and can be served over any webserver supporting wasm
+#### NOTE
+To install trunk run `cargo install trunk`
+
+#### IMPORTANT
+Requests from the frontend to /api/* have to be proxied to the Backend
+Example with nginx and frontend running at localhost:8000 and backend at localhost:9000 :
+```nginx
+location /api/ {
+ proxy_pass http://localhost:9000/api;
+}
+```
+
+
+## Usage of AI
+
+Github Copilot CLI was used with the model Claude Haiku 4.5 to generate most of the documentation
+
+Google Antigravity generated the Sequence Diagrams
+
+### Prompt
+Generate comments for cargo doc describing the indivilual components and create links to relevant structs, functions etc.
+
+Generate a sequence diagramm in @[README.md] behind the class diagramms
+
+### Output
+The comments with `///` or `//!`
+I've gone over it and modified it to my needs and opinions
+
+The general structure sequence diagrams above. I've modified and fixed any errors and discrepancys
diff --git a/README.md b/README.md
index 525c212..81507ae 100644
--- a/README.md
+++ b/README.md
@@ -4,5 +4,382 @@ A ticket system with backend and frontend components.
## Components
-- **[Backend](../backend/index.html)** - The server-side API and business logic
-- **[Frontend](../frontend/index.html)** - The client-side user interface
+- **[Backend]** - The server-side API and business logic
+- **[Frontend]** - The client-side user interface
+
+## Usage
+### Prerequisite
+> [!IMPORTANT]
+> Before compiling the programm you have to install the rust toolchain.
+> For a guide to do this, visit:
+
+A instance of a postgresql has to be accessible to the backend. Place the connection details in a .env file into the variable `DATABASE_URL`
+To setup the tables you either can create them manually by following the sheme specified in `backend/migrations` or apply them with sqlx
+To install sqlx run `cargo install sqlx` and then in the `backend` directory run `sqlx migrate run` to create the tables
+
+### Environment
+The .env file has to be in the root directory of the project or in the same directory as the executable
+
+Keys:
+`DATABASE_URL`: Specifying the url and connection details for the database
+`TOKEN_SECRET`: The JWT token secret, can theoretically be anything but is more secure when generated with a tool, e.g:
+`ORIGIN`: The origin of the frontend, used for CORS rules
+`BACKEND_PORT`: The port which the backend should use to run on
+
+### Backend
+The backend can either be run via `cargo run --release` or `cargo build --release` using the correct target architecture, e.g. 'x86_64-unknown-linux-gnu',
+the executable will be placed in the `target/release` directory and can then be run via any method
+
+### Frontend
+The HTML code for the frontend can be generated by using `trunk build`. The resulting files will end up in the `frontend/dist` directory and can be served over any webserver supporting wasm
+> [!NOTE]
+> To install trunk run `cargo install trunk`
+
+> [!IMPORTANT]
+> Requests from the frontend to /api/* have to be proxied to the Backend
+> Example with nginx and frontend running at localhost:8000 and backend at localhost:9000 :
+> ```nginx
+> location /api/ {
+> proxy_pass http://localhost:9000/api;
+> }
+> ```
+
+## Diagrams
+### Class Diagramm
+#### Backend
+```mermaid
+classDiagram
+ class Error {
+ +status: &'static str
+ +message: String
+ }
+ class TicketResponse {
+ +id: i32
+ +category: String
+ +betreff: String
+ +description: String
+ +room: i16
+ +status: String
+ +date: chrono::DateTime~chrono::Utc~
+ +user_id: i16
+ +user_first_name: String
+ +user_last_name: String
+ }
+ class User {
+ +id: i16
+ +last_name: String
+ +first_name: String
+ +username: String
+ +is_admin: bool
+ +pwd: String
+ }
+ class TicketCreateScheme {
+ +category: String
+ +betreff: String
+ +description: String
+ +room: i16
+ }
+ class TicketUpdateScheme {
+ +status: String
+ }
+ class UserUpdateScheme {
+ +id: i16
+ +first_name: String
+ +last_name: String
+ +username: String
+ +make_admin: bool
+ +new_pwd: String
+ }
+ class UserCreateScheme {
+ +first_name: String
+ +last_name: String
+ +username: String
+ +is_admin: bool
+ +pwd: String
+ }
+ class LoginScheme {
+ +username: String
+ +pwd: String
+ }
+ class FilteredUser {
+ +id: i16
+ +first_name: String
+ +last_name: String
+ +username: String
+ +is_admin: bool
+ }
+ class Claims {
+ +sub: String
+ +issued: usize
+ +expires: usize
+ }
+ class AppState {
+ -db: PgPool
+ -env: Env
+ }
+ class Env {
+ +db_url: String
+ +token_secret: String
+ +origin: String
+ +backend_port: String
+ +load() Env
+ }
+ AppState --> Env
+ Env ..> Env
+```
+
+#### Frontend
+```mermaid
+classDiagram
+ class TicketCreateScheme {
+ +category: String
+ +betreff: String
+ +description: String
+ +room: i16
+ }
+ class TicketUpdateScheme {
+ +status: String
+ }
+ class Ticket {
+ +id: i32
+ +category: String
+ +betreff: String
+ +description: String
+ +room: i16
+ +status: String
+ +date: chrono::DateTime~chrono::Utc~
+ +user_id: i16
+ +user_first_name: String
+ +user_last_name: String
+ }
+ class TicketProps {
+ +id: i32
+ }
+ class ActiveUser {
+ +id: Option~i16~
+ +is_admin: bool
+ }
+ class ApiError {
+ -message: String
+ -_status: String
+ }
+ class SidebarExpandState {
+ +ticket_open: bool
+ +users_open: bool
+ }
+ class Default {
+ +default() Self
+ }
+ class SidebarState {
+ +expand: SidebarExpandState
+ +set_tickets_open: Callback~bool~
+ +toggle_tickets: Callback~()~
+ +set_users_open: Callback~bool~
+ +toggle_users: Callback~()~
+ +new(expand:SidebarExpandState, set_tickets_open:Callback~bool~, toggle_tickets:Callback~()~, set_users_open:Callback~bool~, toggle_users:Callback~()~) Self
+ }
+ class SidebarProps {
+ +children: Children
+ }
+ class TicketPartial {
+ -date: DateTime~Utc~
+ -room: i16
+ -user_id: i16
+ }
+ class UserPartial {
+ -id: i16
+ -first_name: String
+ -last_name: String
+ }
+ class RoomTotalsProps {
+ -tickets: Vec~TicketPartial~
+ }
+ class UserTotalProps {
+ -users: Vec~UserPartial~
+ -tickets: Vec~TicketPartial~
+ }
+ class AdminSetupScheme {
+ +first_name: String
+ +last_name: String
+ +username: String
+ +pwd: String
+ }
+ class UserCreateScheme {
+ +first_name: String
+ +last_name: String
+ +username: String
+ +is_admin: bool
+ +pwd: String
+ }
+ class LoginScheme {
+ +username: String
+ +pwd: String
+ }
+ class UserUpdateScheme {
+ +id: i16
+ +first_name: String
+ +last_name: String
+ +username: String
+ +make_admin: bool
+ +new_pwd: String
+ }
+ class FilteredUser {
+ +id: i16
+ +first_name: String
+ +last_name: String
+ +username: String
+ +is_admin: bool
+ }
+ class UserProps {
+ +id: i16
+ }
+ class ApiError {
+ -message: String
+ -_status: String
+ }
+ class AuthState {
+ +is_authenticated: Option~bool~
+ +is_admin: Option~bool~
+ }
+ class ProtectedRouteProps {
+ +children: Children
+ +admin_page: bool
+ }
+ class SidebarShellProps {
+ +children: Children
+ }
+ class SidebarComponentProps {
+ +is_open: bool
+ +on_close: Callback~()~
+ }
+ class AdminCheckWrapperProps {
+ +children: Children
+ }
+ SidebarState --> SidebarExpandState
+ RoomTotalsProps --> TicketPartial
+ UserTotalProps --> UserPartial
+ UserTotalProps --> TicketPartial
+ SidebarShellProps ..> SidebarComponentProps
+
+```
+
+### Sequence Diagrams
+#### 1. System Initialization & Administrator Setup
+
+```mermaid
+sequenceDiagram
+ autonumber
+ actor Admin as Initial Administrator
+ participant FE as Frontend (Yew)
+ participant BE as Backend (Axum)
+ participant DB@{"type": "database", "alias": "Database"}
+
+ Note over Admin, DB: System Initialization Flow
+ FE->>BE: GET /api/check-admin
+ BE->>DB: SELECT COUNT(*) FROM users WHERE is_admin = true
+ DB-->>BE: 0 (No admin found)
+ BE-->>FE: HTTP 200 OK {"exists": false}
+ FE-->>Admin: Render Admin Setup Page
+ Admin->>FE: Input username, password, first/last name
+ FE->>BE: POST /api/setup-admin {username, pwd, ...}
+ Note over BE: Hash password using Argon2
+ BE->>DB: INSERT INTO users (username, pwd, is_admin, ...)
+ DB-->>BE: Success
+ BE-->>FE: HTTP 200 OK {"status": "success"}
+ FE-->>Admin: Redirect to Login Page
+```
+
+#### 2. User Authentication Flow (Login)
+
+```mermaid
+sequenceDiagram
+ autonumber
+ actor User
+ participant FE as Frontend (Yew)
+ participant BE as Backend (Axum)
+ participant DB as Database@{"type": "database"}
+
+ Note over User, DB: Authentication & Cookie Session Setup
+ User->>FE: Enter username & password
+ FE->>BE: POST /api/login {username, pwd}
+ BE->>DB: SELECT * FROM users WHERE username = $1
+ DB-->>BE: Return user record with password hash
+ Note over BE: Verify password using Argon2
+ alt Password Valid
+ Note over BE: Generate JWT token containing claims (sub: user_id)
+ Note over BE: Build HttpOnly, Secure, Lax cookie 'token'
+
+ BE-->>FE: HTTP 200 OK {"status": "success", "token": "...", "user": {...}}
+ Note over BE,FE: Header: Set-Cookie: token=...#59; Path=/#59; HttpOnly#59; SameSite=Lax
+
+ Note over FE: Save auth state to global context
+ FE-->>User: Redirect to Dashboard / Home
+ else Password Invalid
+ BE-->>FE: HTTP 400 Bad Request {"status": "error", "message": "Invalid password"}
+ FE-->>User: Display error message
+ end
+```
+
+#### 3. Ticket Lifecycle Flow
+
+```mermaid
+sequenceDiagram
+ autonumber
+ actor User as Authenticated User
+ actor Admin as Administrator
+ participant FE as Frontend (Yew)
+ participant BE as Backend (Axum)
+ participant DB as Database@{"type": "database"}
+
+ Note over User, DB: Ticket Creation Flow (Protected Route)
+ User->>FE: Fill out ticket form & submit
+ FE->>BE: POST /api/tickets/create {category, betreff, description, room} (Includes 'token' cookie)
+ Note over BE: validate_token middleware decodes & verifies JWT
+ BE->>DB: INSERT INTO tickets (category, description, betreff, room, user_id)
+ DB-->>BE: Success
+ BE-->>FE: HTTP 200 OK {"status": "success"}
+ FE-->>User: Clear form & display success notification
+
+ Note over Admin, DB: Ticket Review & Resolution (Admin Only Route)
+ Admin->>FE: View Ticket Board
+ FE->>BE: GET /api/tickets (Includes 'token' cookie)
+ Note over BE: validate_token middleware checks JWT
+ BE->>DB: SELECT tickets JOIN users ...
+ DB-->>BE: Return list of tickets
+ BE-->>FE: HTTP 200 OK [tickets]
+ FE-->>Admin: Render Ticket List
+
+ Admin->>FE: Click "Resolve" on ticket
+ FE->>BE: PATCH /api/tickets/{id} {"status": "Resolved"} (Includes 'token' cookie)
+ Note over BE: validate_admin middleware verifies token & checks is_admin = true
+ BE->>DB: UPDATE tickets SET status = $1 WHERE id = $2
+ DB-->>BE: Success
+ BE-->>FE: HTTP 200 OK {"status": "success"}
+ FE-->>Admin: Update ticket status in UI
+
+ Note over Admin, DB: Ticket Archiving Flow (Admin Only Route)
+ Admin->>FE: Open Archived Tickets page
+ FE->>BE: GET /api/tickets/archive (Includes 'token' cookie)
+ Note over BE: validate_admin middleware verifies token & admin role
+ BE->>DB: SELECT * FROM tickets WHERE status = 'Archived'
+ DB-->>BE: Return archived tickets
+ BE-->>FE: HTTP 200 OK [Archived Tickets]
+ FE-->>Admin: Render historical archive board
+```
+
+## Usage of AI
+
+Github Copilot CLI was used with the model Claude Haiku 4.5 to generate most of the documentation
+
+Google Antigravity generated the Sequence Diagrams
+
+### Prompt
+Generate comments for cargo doc describing the indivilual components and create links to relevant structs, functions etc.
+
+Generate a sequence diagramm in @[README.md] behind the class diagramms
+
+### Output
+The comments with `///` or `//!`
+I've gone over it and modified it to my needs and opinions
+
+The general structure sequence diagrams above. I've modified and fixed any errors and discrepancys
diff --git a/backend/src/cookie/jwt.rs b/backend/src/cookie/jwt.rs
index 8f37315..b386fff 100644
--- a/backend/src/cookie/jwt.rs
+++ b/backend/src/cookie/jwt.rs
@@ -6,8 +6,8 @@ use crate::models::Claims;
/// Error response for JWT token operations.
///
-/// Returned when token encoding or decoding fails. Used in error responses
-/// for invalid or expired tokens.
+/// Returned when token encoding or decoding fails via `encode_token` or `decode_token`.
+/// Used in error responses for invalid or expired [`Claims`] tokens.
///
/// # Fields
/// - `status`: HTTP status text (e.g., "error")
@@ -22,7 +22,7 @@ pub struct Error {
///
/// This function creates a new JWT with the provided user ID as the subject,
/// sets the issued-at and expiration times (60 minutes from now), and signs it
-/// using the given encoding key.
+/// using the given encoding key. The resulting token is a serialized [`Claims`].
///
/// # Arguments
/// - `header`: The JWT header, specifying the algorithm (e.g., HS256).
@@ -30,7 +30,7 @@ pub struct Error {
/// - `key`: The `EncodingKey` used to sign the JWT.
///
/// # Returns
-/// A `String` representing the encoded JWT.
+/// A `String` representing the encoded JWT containing [`Claims`].
///
/// # Panics
/// Panics if the token encoding fails for any reason (e.g., invalid key).
@@ -50,14 +50,15 @@ pub fn encode_token(header: &Header, id: String, key: &EncodingKey) -> String {
///
/// This function attempts to decode a JWT string, validate its signature and claims
/// using the provided decoding key. It specifically ignores expiration (`validate_exp`)
-/// and "not before" (`validate_nbf`) claims during validation.
+/// and "not before" (`validate_nbf`) claims during validation. Returns the extracted [`Claims`]
+/// on success.
///
/// # Arguments
/// - `token`: The JWT string to decode.
/// - `key`: The `DecodingKey` used to verify the JWT's signature.
///
/// # Returns
-/// - `Ok(Claims)`: If the token is successfully decoded and verified, returns the extracted `Claims`.
+/// - `Ok(Claims)`: If the token is successfully decoded and verified, returns the extracted [`Claims`].
/// - `Err((StatusCode, Json))`: If the token is invalid, expired, or cannot be decoded,
/// returns an `UNAUTHORIZED` status code along with a JSON error message.
pub fn decode_token(token: String, key: &DecodingKey) -> Result)> {
diff --git a/backend/src/cookie/validation.rs b/backend/src/cookie/validation.rs
index e1d238c..bfaecd9 100644
--- a/backend/src/cookie/validation.rs
+++ b/backend/src/cookie/validation.rs
@@ -17,9 +17,10 @@ use crate::{AppState, cookie::jwt::decode_token, handlers::auth::filter_user, mo
/// Axum middleware to validate a JWT token present in cookies or Authorization header.
///
/// This function extracts a JWT from the request (either from the `token` cookie or
-/// the `Authorization: Bearer` header), decodes and validates it. If valid, it fetches
-/// the corresponding user from the database and inserts a `FilteredUser` into the
-/// request extensions for subsequent handlers to use.
+/// the `Authorization: Bearer` header), decodes and validates it using [`decode_token`](`crate::cookie::jwt::decode_token`)).
+/// If valid, it fetches the corresponding [`User`] from the database and inserts a
+/// [`FilteredUser`](crate::models::FilteredUser)
+/// (converted via [`filter_user`](`crate::handlers::auth::filter_user`)) into the request extensions for subsequent handlers to use.
///
/// If the token is missing, invalid, or the user is not found, it returns an
/// appropriate error response (401 Unauthorized).
@@ -110,9 +111,10 @@ pub async fn validate_token(
/// Axum middleware to validate JWT token and ensure the authenticated user has admin privileges.
///
-/// This middleware first performs all checks of `validate_token`: extracting, decoding,
-/// and validating the JWT, and fetching the associated user from the database.
-/// Additionally, it verifies that the fetched user has `is_admin` set to `true`.
+/// This middleware first performs all checks of [`validate_token`]: extracting, decoding,
+/// and validating the JWT via [`decode_token`](`crate::cookie::jwt::decode_token`), and fetching the associated [`User`] from the database.
+/// Additionally, it verifies that the fetched user has `is_admin` set to `true`. Returns a [`FilteredUser`](crate::models::FilteredUser)
+/// (converted via [`filter_user`](`crate::handlers::auth::filter_user`)) in the request extensions if both authentication and admin status are valid.
///
/// If the user is not authenticated or not an administrator, it returns an
/// appropriate error response (401 Unauthorized or 403 Forbidden).
diff --git a/backend/src/env.rs b/backend/src/env.rs
index f385bf8..ed4a760 100644
--- a/backend/src/env.rs
+++ b/backend/src/env.rs
@@ -1,12 +1,13 @@
/// Environment configuration for the application.
///
/// Loads required configuration from environment variables at startup.
-/// All variables must be present or the application will panic.
+/// All variables must be present or the application will panic during [`Env::load`].
+/// Used by [`AppState`](crate::AppState) for configuring JWT signing and CORS.
///
/// # Fields
-/// - `db_url`: PostgreSQL database connection URL.
-/// - `token_secret`: Secret key used to sign and verify JWT tokens.
-/// - `origin`: Frontend origin URL for CORS policy.
+/// - `db_url`: PostgreSQL database connection URL
+/// - `token_secret`: Secret key used to sign and verify [`Claims`](crate::models::Claims) in JWT tokens
+/// - `origin`: Frontend origin URL for CORS policy configuration
///
/// # Required Environment Variables
/// - `DATABASE_URL`: PostgreSQL connection string (e.g., `postgresql://user:pass@localhost/dbname`)
@@ -25,12 +26,20 @@ pub struct Env {
impl Env {
/// Loads environment configuration from system environment variables.
///
- /// Panics if any required variable is missing.
+ /// Reads `DATABASE_URL`, `TOKEN_SECRET`, and `ORIGIN` from the environment and returns
+ /// a configured [`Env`] instance. Used during server initialization in the `main` function.
+ ///
+ /// # Panics
+ /// If any required variable is missing (DATABASE_URL, TOKEN_SECRET, or ORIGIN).
///
/// # Example
/// ```ignore
/// let env = Env::load();
/// // Environment must have DATABASE_URL, TOKEN_SECRET, and ORIGIN set
+ /// let app_state = AppState {
+ /// db: pool,
+ /// env,
+ /// };
/// ```
pub fn load() -> Env {
let db_url = std::env::var("DATABASE_URL").expect("DATABASE_URL must be set");
diff --git a/backend/src/handlers/auth.rs b/backend/src/handlers/auth.rs
index 282ee96..36ad6f8 100644
--- a/backend/src/handlers/auth.rs
+++ b/backend/src/handlers/auth.rs
@@ -22,11 +22,12 @@ use crate::{
/// Registers a new user in the system.
///
-/// Creates a new user account with the provided credentials. The password is hashed using Argon2
-/// before being stored. Only administrators can create new users.
+/// Creates a new [`User`] account with the provided [`UserCreateScheme`] credentials.
+/// The password is hashed using Argon2 before being stored. Only administrators can create new users.
///
/// # Arguments
-/// - `request`: User creation details including first/last name, username, admin flag, and password
+/// - `State(data)`: Application state containing [`AppState`] for database access
+/// - `request`: [`UserCreateScheme`] containing user details including first/last name, username, admin flag, and password
///
/// # Returns
/// - `200 OK` on successful user creation
@@ -34,12 +35,7 @@ use crate::{
/// - `500 Internal Server Error` if database insertion fails
///
/// # Password Hashing
-/// Uses Argon2 with a cryptographically secure random salt:
-/// ```ignore
-/// let argon = Argon2::default();
-/// let salt = SaltString::generate(&mut OsRng);
-/// let hashed_pwd = argon.hash_password(password.as_bytes(), &salt)?;
-/// ```
+/// Uses Argon2 with a cryptographically secure random salt.
pub async fn create_user(
State(data): State>,
Json(request): Json,
@@ -106,27 +102,23 @@ pub async fn create_user(
/// Authenticates a user and creates a JWT token for session management.
///
/// Verifies the provided username and password against stored credentials using Argon2 verification.
-/// On successful authentication, generates a JWT token and sets it as an HTTP-only cookie.
+/// On successful authentication, generates and encodes a [`Claims`](crate::models::Claims) token via [`encode_token`](`crate::cookie::jwt::encode_token`) and sets it as an HTTP-only cookie.
/// The token is valid for 1 hour.
///
/// # Arguments
-/// - `request`: Login credentials (username, password)
+/// - `State(data)`: Application state containing [`AppState`] for database access
+/// - `request`: [`LoginScheme`] containing login credentials (username, password)
///
/// # Returns
-/// - `200 OK` with JSON containing token and filtered user info
+/// - `200 OK` with JSON containing token and filtered [`FilteredUser`] info
/// - `400 Bad Request` if username not found or password invalid
/// - `500 Internal Server Error` if database query fails
///
/// # Security Features
/// - HTTP-only cookie prevents JavaScript access
/// - SameSite=Lax protects against CSRF attacks
-/// - Password verification uses Argon2:
-/// ```ignore
-/// let valid_pwd = Argon2::default()
-/// .verify_password(&request.pwd.as_bytes(), &pwd_hash.unwrap())
-/// .is_ok();
-/// ```
-/// - JWT token includes user ID and expiration timestamp
+/// - Password verification uses Argon2 with stored [`User`] hash
+/// - JWT token includes user ID and expiration timestamp via [`Claims`](crate::models::Claims) encoded by [`encode_token`](`crate::cookie::jwt::encode_token`)
///
/// # Example Response
/// ```json
@@ -195,7 +187,7 @@ pub async fn login(
///
/// Sets the authentication cookie to expire immediately (max_age = -1 hour) which causes
/// the browser to discard it. This effectively logs the user out without requiring server-side
-/// session invalidation.
+/// session invalidation. The cookie no longer contains a valid [`Claims`](crate::models::Claims) token.
///
/// # Returns
/// Always returns `200 OK` with success message and an expired cookie header
@@ -227,11 +219,11 @@ pub async fn logout() -> Result,
State(data): State>,
@@ -482,7 +479,10 @@ pub async fn update_user(
/// Checks if any administrator user exists in the system.
///
/// This endpoint is used during initialization to determine if the setup page should be displayed.
-/// It counts all users with `is_admin = true` in the database.
+/// It counts all [`User`] records with `is_admin = true` in the database.
+///
+/// # Arguments
+/// - `State(data)`: Application state containing [`AppState`] for database access
///
/// # Returns
/// - `200 OK` with JSON: `{"has_admin": bool}` - Whether at least one admin exists
@@ -512,28 +512,24 @@ pub async fn check_admin_exists(
/// Creates the initial administrator account for a fresh system.
///
-/// This function handles the one-time setup of the first admin user. It checks that no admin exists
-/// before allowing creation. This endpoint is only functional when the system has no administrators.
+/// This function handles the one-time setup of the first admin [`User`]. It checks that no admin exists
+/// before allowing creation via database count. This endpoint is only functional when the system has no administrators.
/// Once created, subsequent admin registrations must go through the normal `create_user` endpoint
/// with proper authorization.
///
/// # Arguments
-/// - `request`: User creation details (first_name, last_name, username, password)
+/// - `State(data)`: Application state containing [`AppState`] for database access
+/// - `request`: [`UserCreateScheme`] containing user creation details (first_name, last_name, username, password)
///
/// # Returns
/// - `200 OK` with success message if admin account created
/// - `400 Bad Request` if:
-/// - Admin already exists
+/// - Admin already exists (checked via admin count)
/// - Username or password is empty
/// - `500 Internal Server Error` if database insertion fails
///
/// # Security Note
-/// The password is hashed using Argon2 with a random salt before storage:
-/// ```ignore
-/// let argon = Argon2::default();
-/// let salt = SaltString::generate(&mut OsRng);
-/// let hashed_pwd = argon.hash_password(request.pwd.as_bytes(), &salt)?;
-/// ```
+/// The password is hashed using Argon2 with a random salt before storage.
pub async fn setup_initial_admin(
State(data): State>,
Json(request): Json,
@@ -598,17 +594,18 @@ pub async fn setup_initial_admin(
}
}
-/// Converts a User with sensitive data into a FilteredUser safe for API responses.
+/// Converts a [`User`] with sensitive data into a [`FilteredUser`] safe for API responses.
///
/// This function removes password hashes and other sensitive information before
-/// returning user data to clients. Always use this helper instead of directly
-/// serializing User objects.
+/// returning [`User`] data to clients. Always use this helper instead of directly
+/// serializing [`User`] objects.
+/// Used by all authentication endpoints to ensure passwords are never exposed.
///
/// # Arguments
-/// - `user`: Reference to the internal User struct containing password hash
+/// - `user`: Reference to the internal [`User`] struct containing password hash
///
/// # Returns
-/// FilteredUser with only safe-to-share information:
+/// [`FilteredUser`] with only safe-to-share information:
/// - `id`: User ID
/// - `first_name`, `last_name`: User name
/// - `username`: Login username
@@ -621,7 +618,7 @@ pub async fn setup_initial_admin(
/// # Example
/// ```ignore
/// let user = get_user_from_db(1).await?;
-/// let safe_user = filter_user(&user);
+/// let safe_user = filter_user(&user); // Convert User to FilteredUser
/// // safe_user can be safely serialized and sent to client
/// ```
pub fn filter_user(user: &User) -> FilteredUser {
diff --git a/backend/src/handlers/ticket.rs b/backend/src/handlers/ticket.rs
index 8f05cfc..7f2e131 100644
--- a/backend/src/handlers/ticket.rs
+++ b/backend/src/handlers/ticket.rs
@@ -16,12 +16,14 @@ use crate::{
/// Creates a new support ticket.
///
-/// Associates the ticket with the authenticated user and sets the current timestamp.
+/// Associates the ticket with the authenticated [`FilteredUser`] and sets the current timestamp.
+/// Converts the [`TicketCreateScheme`] request into a database record.
/// Tickets are automatically created with "open" status.
///
/// # Arguments
-/// - `user`: Authenticated user (extracted from JWT token)
-/// - `body`: Ticket details (category, subject, description, room)
+/// - `Extension(user)`: Authenticated [`FilteredUser`] (extracted from JWT token via middleware)
+/// - `State(data)`: Application state containing [`AppState`] for database access
+/// - `Json(body)`: [`TicketCreateScheme`] containing ticket details (category, subject, description, room)
///
/// # Returns
/// - `200 OK` on successful creation
@@ -60,10 +62,11 @@ pub async fn create_ticket(
/// Deletes a ticket by ID.
///
-/// Only admins can delete tickets. Marks the ticket as deleted or removes from database.
+/// Only admins can delete tickets (enforced by middleware). Removes the [`TicketResponse`] and associated data from the database.
///
/// # Arguments
-/// - `id`: Ticket ID to delete
+/// - `Path(id)`: Ticket ID to delete, extracted from URL path
+/// - `State(data)`: Application state containing [`AppState`] for database access
///
/// # Returns
/// - `204 No Content` on successful deletion
@@ -97,15 +100,18 @@ pub async fn delete_ticket(
/// Retrieves all non-archived tickets.
///
-/// Returns a list of all active tickets with user information denormalized for easier rendering.
-/// Tickets are ordered by creation date (newest first).
+/// Returns a list of all active [`TicketResponse`] objects with user information denormalized for easier rendering.
+/// Tickets are ordered by creation date (newest first). Joins with [`User`](crate::models::User) table to include creator information.
+///
+/// # Arguments
+/// - `State(data)`: Application state containing [`AppState`] for database access
///
/// # Filtering
/// - Excludes tickets with status "Archived"
-/// - Uses LEFT JOIN to include creator information
+/// - Uses LEFT JOIN to include creator information from [`User`](crate::models::User)
///
/// # Returns
-/// - `200 OK` with array of TicketResponse objects
+/// - `200 OK` with array of [`TicketResponse`] objects
/// - `500 Internal Server Error` if database query fails
///
/// # Example Response
@@ -169,12 +175,14 @@ pub async fn get_tickets(
/// Retrieves a specific ticket by ID.
///
/// Includes full ticket details and denormalized user information (creator name).
+/// Returns a [`TicketResponse`] with all metadata by joining with [`User`](crate::models::User) table.
///
/// # Arguments
-/// - `id`: Ticket ID to retrieve
+/// - `Path(id)`: Ticket ID to retrieve, extracted from URL path
+/// - `State(data)`: Application state containing [`AppState`] for database access
///
/// # Returns
-/// - `200 OK` with TicketResponse object
+/// - `200 OK` with [`TicketResponse`] object
/// - `404 Not Found` if ticket doesn't exist
/// - `500 Internal Server Error` if database error occurs
///
@@ -242,15 +250,16 @@ pub async fn get_ticket_by_id(
/// Updates a ticket's status.
///
-/// Only admins can update ticket status. This is typically used to transition tickets
-/// through their lifecycle (open → in_progress → resolved → archived).
+/// Only admins can update ticket status (enforced by middleware). Applies [`TicketUpdateScheme`] to modify the [`TicketResponse`].
+/// This is typically used to transition tickets through their lifecycle (open → in_progress → resolved → archived).
///
/// # Arguments
-/// - `id`: Ticket ID to update
-/// - `body`: Update payload containing new status
+/// - `Path(id)`: Ticket ID to update, extracted from URL path
+/// - `State(data)`: Application state containing [`AppState`] for database access
+/// - `Json(body)`: [`TicketUpdateScheme`] update payload containing new status
///
/// # Returns
-/// - `200 OK` with updated TicketResponse
+/// - `200 OK` with updated [`TicketResponse`]
/// - `500 Internal Server Error` if ticket not found or database error
///
/// # Typical Status Flow
diff --git a/backend/src/main.rs b/backend/src/main.rs
index 04c0dd9..e4d5326 100644
--- a/backend/src/main.rs
+++ b/backend/src/main.rs
@@ -25,11 +25,12 @@ use crate::env::Env;
/// Shared application state passed to all route handlers.
///
/// Contains the database connection pool and environment configuration.
-/// This is wrapped in Arc for thread-safe sharing across async tasks.
+/// This is wrapped in Arc for thread-safe sharing across async tasks and cloned into each route
+/// via `with_state`.
///
/// # Fields
-/// - `db`: PostgreSQL connection pool for database access
-/// - `env`: Configuration loaded from environment variables
+/// - `db`: PostgreSQL connection pool for database access (via `sqlx::PgPool`)
+/// - `env`: [`Env`] configuration loaded from environment variables
pub struct AppState {
db: PgPool,
env: Env,
@@ -39,15 +40,19 @@ pub struct AppState {
///
/// Initializes the server by:
/// 1. Loading environment variables from `.env` file
-/// 2. Establishing database connection pool
+/// 2. Establishing database connection pool to PostgreSQL
/// 3. Configuring CORS policy for cross-origin requests
-/// 4. Starting HTTP server on port 8001
+/// 4. Creating the router with [`create_router`] containing all endpoints
+/// 5. Starting HTTP server on port 8001
///
/// # Server Configuration
/// - Binds to `0.0.0.0:8001` (all network interfaces)
/// - Allows: GET, POST, PATCH, DELETE methods
/// - Allows credentials and custom headers
-/// - CORS origin configured from environment
+/// - CORS origin configured from [`Env`]
+///
+/// # State Setup
+/// Creates shared [`AppState`] wrapped in `Arc` and passes to all routes
///
/// # Panics
/// - If environment loading fails
diff --git a/backend/src/models.rs b/backend/src/models.rs
index 184670d..a6d5111 100644
--- a/backend/src/models.rs
+++ b/backend/src/models.rs
@@ -3,6 +3,7 @@ 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
@@ -12,7 +13,7 @@ use serde::{Deserialize, Serialize};
/// - `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
+/// - `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
@@ -47,7 +48,7 @@ pub struct TicketResponse {
/// 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.
+/// This should NEVER be sent directly to clients - always use [`FilteredUser`] instead.
///
/// # Fields
/// - `id`: Unique user identifier
@@ -58,7 +59,7 @@ pub struct TicketResponse {
///
/// # Security Note
/// The `pwd` field contains the password hash and should never be included in API responses.
-/// Use `filter_user()` to convert to `FilteredUser` for 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,
@@ -72,7 +73,7 @@ pub struct User {
/// 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.
+/// authenticated user and sets the creation timestamp. Converted to [`TicketResponse`] for the response.
///
/// # Fields
/// - `category`: Ticket category/type
@@ -89,7 +90,7 @@ pub struct TicketCreateScheme {
/// Payload for updating a ticket.
///
-/// Sent to `PATCH /api/tickets/{id}`. Currently only allows status updates.
+/// Sent to `PATCH /api/tickets/{id}`. Allows updating the ticket [`TicketResponse::status`].
/// Only admins can update tickets.
///
/// # Fields
@@ -102,10 +103,10 @@ pub struct TicketUpdateScheme {
/// Payload for updating user information.
///
/// Sent to `PATCH /api/users/{id}`. Allows updating profile and admin status.
-/// Only admins can update users. Empty password field means no password change.
+/// Only admins can update [`User`] records. Empty password field means no password change.
///
/// # Fields
-/// - `id`: User ID to update
+/// - `id`: [`User`] ID to update
/// - `first_name`, `last_name`: Updated user name
/// - `username`: Updated login username
/// - `make_admin`: New admin privilege status
@@ -123,7 +124,7 @@ pub struct UserUpdateScheme {
/// 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.
+/// The password is hashed server-side before storage using Argon2. Converted to [`User`] for storage.
///
/// # Fields
/// - `first_name`: User's first name
@@ -155,7 +156,7 @@ pub struct LoginScheme {
/// User information sent to clients, excluding password hashes.
///
-/// This is the safe version of User data that gets returned in API responses.
+/// 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)]
@@ -170,10 +171,10 @@ pub struct FilteredUser {
/// JWT token claims embedded in the session token.
///
/// Contains user identification and token validity information.
-/// Generated during login and verified by middleware on protected routes.
+/// Generated during login via `encode_token` and verified via `decode_token`.
///
/// # Fields
-/// - `sub`: Subject - the user ID as a string
+/// - `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)
///
diff --git a/backend/src/router.rs b/backend/src/router.rs
index bc41ba5..0c37299 100644
--- a/backend/src/router.rs
+++ b/backend/src/router.rs
@@ -19,30 +19,31 @@ use crate::{
/// Creates the complete router with all API endpoints.
///
-/// The router is organized in layers for proper middleware application:
+/// The router is organized in layers for proper middleware application. Uses [`AppState`]
+/// for shared application context across all routes.
///
/// ## Route Layers (from most to least restricted):
///
/// ### Admin-Only Routes (requires admin privilege + valid token)
-/// - `GET /api/tickets/{id}` - Get specific ticket details
-/// - `DELETE /api/tickets/{id}` - Delete a ticket
-/// - `PATCH /api/tickets/{id}` - Update ticket status
-/// - `POST /api/register` - Create a new user
-/// - `GET /api/users` - List all users
-/// - `GET /api/users/{id}` - Get user details
-/// - `DELETE /api/users/{id}` - Delete a user
-/// - `PATCH /api/users/{id}` - Update user details
+/// - `GET /api/tickets/{id}` - Get specific ticket details (via `get_ticket_by_id`)
+/// - `DELETE /api/tickets/{id}` - Delete a ticket (via `delete_ticket`)
+/// - `PATCH /api/tickets/{id}` - Update ticket status (via `edit_ticket`)
+/// - `POST /api/register` - Create a new user (via `create_user`)
+/// - `GET /api/users` - List all users (via `get_users`)
+/// - `GET /api/users/{id}` - Get user details (via `get_user_by_id`)
+/// - `DELETE /api/users/{id}` - Delete a user (via `delete_user`)
+/// - `PATCH /api/users/{id}` - Update user details (via `update_user`)
///
/// ### Protected Routes (requires valid token)
-/// - `GET /api/tickets` - List all tickets
-/// - `POST /api/tickets/create` - Create a new ticket
-/// - `GET /api/logout` - Logout user
-/// - `GET /api/users/current` - Get current authenticated user
+/// - `GET /api/tickets` - List all tickets (via `get_tickets`)
+/// - `POST /api/tickets/create` - Create a new ticket (via `create_ticket`)
+/// - `GET /api/logout` - Logout user (via `logout`)
+/// - `GET /api/users/current` - Get current authenticated user (via `get_current_user`)
///
/// ### Public Routes (no authentication required)
-/// - `POST /api/login` - User login
-/// - `GET /api/check-admin` - Check if admin exists (for setup detection)
-/// - `POST /api/setup-admin` - Create initial admin account (only if no admin exists)
+/// - `POST /api/login` - User login (via `login`)
+/// - `GET /api/check-admin` - Check if admin exists (via `check_admin_exists`)
+/// - `POST /api/setup-admin` - Create initial admin account (via `setup_initial_admin`)
///
/// # Middleware Stack
/// - Admin routes have `validate_admin` middleware
diff --git a/frontend/src/auth.rs b/frontend/src/auth.rs
index 18720fa..77fc817 100644
--- a/frontend/src/auth.rs
+++ b/frontend/src/auth.rs
@@ -33,14 +33,14 @@ pub struct ProtectedRouteProps {
/// A component that protects routes by enforcing authentication and optional administrator privileges.
///
-/// This component fetches the current user's authentication and admin status from the
-/// `/api/users/current` endpoint upon mounting. Based on the `AuthState` and the
-/// `admin_page` property, it either renders its children or redirects the user.
+/// This component uses the backend's validation middleware by fetching the current user's authentication
+/// and admin status from the `/api/users/current` endpoint (which requires a valid JWT token).
+/// Based on the [`AuthState`] and the `admin_page` property, it either renders its children or redirects the user.
///
/// # Behavior
-/// - **Initial Load**: Displays "Loading..." while checking authentication status.
+/// - **Initial Load**: Displays "Loading..." while checking authentication status via the backend.
/// - **Not Authenticated**: Redirects to the login page (`crate::Route::Login`).
-/// - **Authenticated**:
+/// - **Authenticated** (valid JWT token from backend):
/// - If `admin_page` is `true`:
/// - If the user is an administrator (`is_admin: Some(true)`), it renders `children`.
/// - If the user is not an administrator (`is_admin: Some(false)`), it redirects to
diff --git a/frontend/src/lib.rs b/frontend/src/lib.rs
index f1a4bcb..9300489 100644
--- a/frontend/src/lib.rs
+++ b/frontend/src/lib.rs
@@ -10,7 +10,9 @@ use yew_router::prelude::*;
/// Defines the application's various routes and their corresponding paths.
///
/// This enum is used by `yew-router` to map URLs to specific components,
-/// enabling navigation within the single-page application.
+/// enabling navigation within the single-page application. Each route is protected
+/// by [`ProtectedRoute`] middleware where appropriate to enforce authentication and authorization.
+/// See [`switch`] for the routing logic.
#[derive(Clone, PartialEq, Routable)]
enum Route {
/// The application's home page.
@@ -65,10 +67,17 @@ pub struct SidebarShellProps {
/// A shell component that provides a consistent layout with a sidebar and a main content area.
///
/// This component is designed to wrap page-specific content, ensuring that the sidebar
-/// is always present for navigation.
+/// is always present for navigation. Integrates with [`crate::pages::sidebar::Sidebar`] for navigation.
+///
+/// # Mobile Support
+/// On mobile displays, the sidebar is hidden by default and can be toggled:
+/// - **Menu Toggle Button**: Renders a floating menu button to slide the sidebar open.
+/// - **Dark Mode Toggle**: Floating button to change theme.
+/// - **Overlay Backdrop**: Dims the screen when the sidebar is open. Clicking it closes the sidebar.
+/// - **Auto-Close on Navigation**: Automatically closes the sidebar when navigating to a new route.
///
/// # Components
-/// - [`sidebar::Sidebar`]: The navigation sidebar component.
+/// - [`crate::pages::sidebar::Sidebar`]: The navigation sidebar component, accepting mobile open state.
/// - Main content area: Renders the `children` passed to this component.
///
/// # Example
@@ -101,11 +110,11 @@ pub struct AdminCheckWrapperProps {
///
/// This component is used to gate access to pages that should only be accessible before
/// system initialization (e.g., login page). It performs an asynchronous check to the
-/// `/api/check-admin` endpoint to determine system state.
+/// backend's `/api/check-admin` endpoint (via `crate::handlers::auth::check_admin_exists`) to determine system state.
///
/// # Behavior
-/// - **Loading**: Displays "Loading..." while checking admin status
-/// - **No Admin**: Automatically redirects to `/setup` page to initialize
+/// - **Loading**: Displays "Loading..." while checking admin status from the backend
+/// - **No Admin**: Automatically redirects to [`Route::Setup`] page for initialization
/// - **Admin Exists**: Renders the wrapped children (e.g., login page)
///
/// # Example Usage
@@ -115,19 +124,9 @@ pub struct AdminCheckWrapperProps {
///
/// ```
///
-/// # Implementation Detail
-/// The check happens in `use_effect_with` on component mount:
-/// ```ignore
-/// spawn_local(async move {
-/// match Request::get("/api/check-admin").send().await {
-/// Ok(resp) if resp.status() == 200 => {
-/// let has_admin = data["has_admin"].as_bool().unwrap_or(false);
-/// admin_exists.set(Some(has_admin));
-/// }
-/// _ => admin_exists.set(Some(false))
-/// }
-/// });
-/// ```
+/// # Backend Integration
+/// The check queries the backend's `/api/check-admin` endpoint which returns `{"has_admin": bool}`.
+/// This allows the frontend to determine if initial admin setup is required.
#[component(AdminCheckWrapper)]
fn admin_check_wrapper(props: &AdminCheckWrapperProps) -> Html {
let admin_exists = use_state(|| None::);
@@ -166,18 +165,24 @@ fn admin_check_wrapper(props: &AdminCheckWrapperProps) -> Html {
/// The main routing logic for the application.
///
-/// This function takes a `Route` enum variant and returns the corresponding HTML
+/// This function takes a [`Route`] enum variant and returns the corresponding HTML
/// content to be rendered. It acts as a central dispatcher for the application's
-/// navigation.
+/// navigation and authentication flow.
///
-/// Many routes are wrapped in a [`ProtectedRoute`] to enforce authentication
-/// and authorization, and in a [`SidebarShell`] to maintain consistent layout.
+/// Most routes are wrapped in [`ProtectedRoute`] to enforce authentication
+/// and authorization based on the `admin_page` flag, and in [`SidebarShell`] to maintain consistent layout.
+/// Login and Setup routes use [`AdminCheckWrapper`] instead to handle pre-authentication states.
///
/// # Arguments
/// - `route`: The [`Route`] enum variant representing the current URL path.
///
/// # Returns
/// An `Html` component that should be rendered for the given route.
+///
+/// # Route Protection
+/// - **Admin-required routes** (`admin_page={true}`): Require both authentication and admin privileges
+/// - **Public routes** (`admin_page={false}`): Require only authentication
+/// - **Pre-auth routes** (`AdminCheckWrapper`): Used before admin creation or login
fn switch(route: Route) -> Html {
match route {
Route::Home => html! {
@@ -259,12 +264,16 @@ fn switch(route: Route) -> Html {
/// The root component of the Yew application.
///
/// This component sets up the application's routing using `yew-router`'s
-/// [`BrowserRouter`] and [`Switch`] components. All other application content
-/// is rendered based on the current route.
+/// `BrowserRouter` and `Switch` components. All other application content
+/// is rendered based on the current [`Route`] matched by the `switch` function.
+///
+/// Uses [`switch`] as the routing dispatcher to handle all route-specific rendering,
+/// which applies appropriate middleware like [`ProtectedRoute`] and [`AdminCheckWrapper`].
///
/// # Structure
-/// - [`BrowserRouter`]: Enables client-side routing.
-/// - [`Switch`]: Renders components based on the matched [`Route`].
+/// - `BrowserRouter`: Enables client-side routing.
+/// - `Switch`: Renders components based on the matched [`Route`].
+/// - `switch` function: Determines which component to render for each route.
#[component(App)]
pub fn app() -> Html {
html! {
diff --git a/frontend/src/pages/basic_pages.rs b/frontend/src/pages/basic_pages.rs
index 064f03c..9fb91bb 100644
--- a/frontend/src/pages/basic_pages.rs
+++ b/frontend/src/pages/basic_pages.rs
@@ -3,6 +3,29 @@ use wasm_bindgen_futures::spawn_local;
use yew::prelude::*;
use yew_router::prelude::*;
+/// Removes surrounding double quotes from a string.
+///
+/// This macro takes an expression that evaluates to a string and returns a new `String`
+/// with any leading or trailing double quotes removed. It's useful for cleaning up
+/// string data that might be inadvertently wrapped in quotes, such as JSON string values.
+///
+/// # Arguments
+///
+/// * `$str`: An expression that can be converted into a string slice (`&str`).
+///
+/// # Examples
+///
+/// ```rust
+/// use your_crate::dequote; // Assuming `dequote` is re-exported or in scope
+///
+/// let quoted_string = "\"hello world\"";
+/// let dequoted_string = dequote!(quoted_string);
+/// assert_eq!(dequoted_string, "hello world");
+///
+/// let already_clean = "no quotes";
+/// let dequoted_clean = dequote!(already_clean);
+/// assert_eq!(dequoted_clean, "no quotes");
+/// ```
macro_rules! dequote {
($str:expr) => {
$str.trim_matches('"').to_string()
diff --git a/frontend/src/pages/setup.rs b/frontend/src/pages/setup.rs
index 2f8c532..127c2d5 100644
--- a/frontend/src/pages/setup.rs
+++ b/frontend/src/pages/setup.rs
@@ -29,7 +29,7 @@ pub struct AdminSetupScheme {
/// a form to create the first admin user. Key functionality:
///
/// - **Admin Check**: On mount, verifies if an admin already exists by calling `/api/check-admin`.
-/// If an admin is found, the user is redirected to the login page (`crate::Route::Login`).
+/// If an admin is found, the user is redirected to the login page ([`crate::Route::Login`]).
/// - **Form Fields**: Collects `first_name`, `last_name`, `username`, `password`, and `confirm_password`.
/// - **Form Validation**:
/// - Ensures password fields are not empty.
diff --git a/frontend/src/pages/sidebar.rs b/frontend/src/pages/sidebar.rs
index ade0e6c..f37035f 100644
--- a/frontend/src/pages/sidebar.rs
+++ b/frontend/src/pages/sidebar.rs
@@ -7,6 +7,11 @@ use wasm_bindgen_futures::spawn_local;
use yew::prelude::*;
use yew_router::prelude::*;
+/// The key used to store and retrieve the sidebar's expansion state in `LocalStorage`.
+///
+/// This constant ensures consistency when accessing the stored state across different
+/// parts of the application. The value associated with this key in `LocalStorage`
+/// will be a serialized [`SidebarExpandState`] object.
const STORAGE_KEY: &str = "sidebar_state";
/// Represents the expansion state of collapsible menus within the sidebar.
diff --git a/frontend/src/pages/user.rs b/frontend/src/pages/user.rs
index 642e156..f696154 100644
--- a/frontend/src/pages/user.rs
+++ b/frontend/src/pages/user.rs
@@ -102,6 +102,14 @@ pub struct UserProps {
pub id: i16,
}
+/// Represents an error response from the API.
+///
+/// This struct is used to deserialize error messages received from the backend API.
+/// It typically contains a human-readable message and an internal status code.
+///
+/// # Fields
+/// - `message`: A `String` containing a description of the error.
+/// - `_status`: An internal status code or message, often ignored in frontend display.
#[derive(Deserialize, Debug)]
struct ApiError {
message: String,
diff --git a/frontend/src/pages/utilities.rs b/frontend/src/pages/utilities.rs
index 861cb99..8444ab5 100644
--- a/frontend/src/pages/utilities.rs
+++ b/frontend/src/pages/utilities.rs
@@ -28,7 +28,7 @@ struct TicketPartial {
/// for calculating and visualizing ticket distribution per room.
///
/// # Fields
-/// - `tickets`: A vector of `TicketPartial` containing ticket data relevant for room totals.
+/// - `tickets`: A vector of [`TicketPartial`] containing ticket data relevant for room totals.
#[derive(Properties, PartialEq)]
struct RoomTotalsProps {
tickets: Vec,
@@ -64,7 +64,7 @@ fn weekday_index(dt: &DateTime) -> usize {
/// the total count of tickets submitted on that day.
///
/// # Arguments
-/// - `tickets`: A slice of `TicketPartial` items to count.
+/// - `tickets`: A slice of [`TicketPartial`] items to count.
///
/// # Returns
/// An array `[usize; 7]` with ticket counts for each weekday.
@@ -78,12 +78,12 @@ fn count_by_weekday(tickets: &[TicketPartial]) -> [usize; 7] {
/// Calculates the occurrences of each weekday within the date range covered by the tickets.
///
-/// This function determines the minimum and maximum dates from the provided `TicketPartial`
+/// This function determines the minimum and maximum dates from the provided [`TicketPartial`]
/// slice and then counts how many times each weekday occurs within that inclusive date range.
/// This is useful for normalizing ticket counts against the number of available days for each weekday.
///
/// # Arguments
-/// - `partials`: A slice of `TicketPartial` items defining the date range.
+/// - `partials`: A slice of [`TicketPartial`] items defining the date range.
///
/// # Returns
/// An array `[usize; 7]` where each element represents the number of times a
@@ -132,7 +132,8 @@ fn day_counts(partials: &[TicketPartial]) -> [usize; 7] {
/// Converts a numerical room representation back into a human-readable string format.
///
-/// This function is the inverse of the room parsing logic in `SubmitTicket` component.
+/// This function is the inverse of the room parsing logic in
+/// [`SubmitTicket`](`crate::pages::ticket::SubmitTicket`) component.
/// It converts negative numbers back to "K" prefixed rooms, numbers >= 1000 back to "D" prefixed rooms,
/// and other numbers to their string representation.
///
@@ -303,12 +304,12 @@ pub fn ticket_count_component() -> Html {
///
/// # State
/// Uses `use_state` hooks to manage:
-/// - `tickets`: A vector of `TicketPartial` for statistical analysis.
+/// - `tickets`: A vector of [`TicketPartial`] for statistical analysis.
/// - `error`: Any error message from API calls.
/// - `loading`: A boolean indicating if data is being fetched.
///
/// # Functionality
-/// - Fetches all tickets (as `TicketPartial`) from `/api/tickets`.
+/// - Fetches all tickets (as [`TicketPartial`]) from `/api/tickets`.
/// - Calculates:
/// - `counts`: Number of tickets submitted on each weekday.
/// - `occ`: Number of occurrences of each weekday in the ticket date range.
@@ -421,7 +422,7 @@ pub fn submit_stats_component() -> Html {
/// A component that displays the total number of tickets per room.
///
-/// This component takes a list of `TicketPartial` items and calculates the
+/// This component takes a list of [`TicketPartial`] items and calculates the
/// total number of tickets for each room. It then displays these totals
/// in a sorted list with a bar chart visualization.
///
@@ -433,7 +434,7 @@ pub fn submit_stats_component() -> Html {
/// - **Sorts Results**: Displays rooms sorted by ticket count in descending order.
/// - **Visualizes Data**: Renders a bar chart where the width of each bar is
/// proportional to the ticket count for that room, relative to the room with the maximum tickets.
-/// - **Room Formatting**: Uses the `parse_room` function to display room numbers
+/// - **Room Formatting**: Uses the [`parse_room`] function to display room numbers
/// in a human-readable format.
///
/// # Example
diff --git a/src/lib.rs b/src/lib.rs
index 7913c79..a23a1d3 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -1 +1 @@
-#![doc = include_str!("../README.md")]
+#![doc = include_str!("../README.cargo.md")]