2026-05-26 22:01:50 +02:00
2026-05-25 17:43:07 +02:00
2026-05-25 17:43:07 +02:00
2026-05-26 22:01:50 +02:00
2026-05-22 14:04:45 +02:00
2026-05-22 14:40:23 +02:00
2026-05-10 10:18:45 +02:00
2026-05-10 10:18:45 +02:00
2026-05-22 14:08:52 +02:00
2026-05-26 20:47:25 +02:00

Ticketsystem

A ticket system with backend and frontend components.

Components

  • [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: https://rust-lang.org/tools/install/

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: https://jwtsecrets.com/ 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 :

location /api/ {
  proxy_pass http://localhost:9000/api;
}

Diagrams

Class Diagramm

Backend

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

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 AdminCheckWrapperProps {
        +children: Children
    }
    SidebarState --> SidebarExpandState
    RoomTotalsProps --> TicketPartial
    UserTotalProps --> UserPartial
    UserTotalProps --> TicketPartial

Sequence Diagrams

1. System Initialization & Administrator Setup

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)

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

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

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 most of it and modified it to my needs and opinions

The sequence Diagrams above

Description
No description provided
Readme 1.1 MiB
Languages
Rust 97.1%
SCSS 2.8%
HTML 0.1%