# 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: 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 ```mermaid classDiagram direction RL class backend_src_cookie_Error { +status: &'static str +message: String } class backend_src_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 backend_src_User { +id: i16 +last_name: String +first_name: String +username: String +is_admin: bool +pwd: String } class backend_src_TicketCreateScheme { +category: String +betreff: String +description: String +room: i16 } class backend_src_TicketUpdateScheme { +status: String } class backend_src_UserUpdateScheme { +id: i16 +first_name: String +last_name: String +username: String +make_admin: bool +new_pwd: String } class backend_src_UserCreateScheme { +first_name: String +last_name: String +username: String +is_admin: bool +pwd: String } class backend_src_LoginScheme { +username: String +pwd: String } class backend_src_FilteredUser { +id: i16 +first_name: String +last_name: String +username: String +is_admin: bool } class backend_src_Claims { +sub: String +issued: usize +expires: usize } class backend_src_AppState { -db: PgPool -env: backend_src_Env } class backend_src_Env { +db_url: String +token_secret: String +origin: String +backend_port: String +load() backend_src_Env } class backend_target_debug_build_chrono-tz-56cec396bfb3cea1_out_FromStr { +from_str(s:&str) Result~Self, Self::Err~ } class backend_target_debug_build_chrono-tz-56cec396bfb3cea1_out_Tz { +name() &'static str } class backend_target_debug_build_chrono-tz-56cec396bfb3cea1_out_Debug { +fmt(f:&mut Formatter) fmt::Result } class backend_target_debug_build_chrono-tz-56cec396bfb3cea1_out_Display { +fmt(f:&mut Formatter) fmt::Result } class backend_target_debug_build_chrono-tz-56cec396bfb3cea1_out_TimeSpans { +timespans() FixedTimespanSet } class backend_target_debug_build_chrono-tz-261584f9cc573a32_out_FromStr { +from_str(s:&str) Result~Self, Self::Err~ } class backend_target_debug_build_chrono-tz-261584f9cc573a32_out_Tz { +name() &'static str } class backend_target_debug_build_chrono-tz-261584f9cc573a32_out_Debug { +fmt(f:&mut Formatter) fmt::Result } class backend_target_debug_build_chrono-tz-261584f9cc573a32_out_Display { +fmt(f:&mut Formatter) fmt::Result } class backend_target_debug_build_chrono-tz-261584f9cc573a32_out_TimeSpans { +timespans() FixedTimespanSet } class frontend_src_pages_TicketCreateScheme { +category: String +betreff: String +description: String +room: i16 } class frontend_src_pages_TicketUpdateScheme { +status: String } class frontend_src_pages_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 frontend_src_pages_TicketProps { +id: i32 } class frontend_src_pages_ActiveUser { +id: Option~i16~ +is_admin: bool } class frontend_src_pages_ApiError { -message: String -_status: String } class frontend_src_pages_SidebarExpandState { +ticket_open: bool +users_open: bool } class frontend_src_pages_Default { +default() Self } class frontend_src_pages_SidebarState { +expand: frontend_src_pages_SidebarExpandState +set_tickets_open: Callback~bool~ +toggle_tickets: Callback~()~ +set_users_open: Callback~bool~ +toggle_users: Callback~()~ +new(expand:frontend_src_pages_SidebarExpandState, set_tickets_open:Callback~bool~, toggle_tickets:Callback~()~, set_users_open:Callback~bool~, toggle_users:Callback~()~) Self } class frontend_src_pages_SidebarProps { +children: Children } class frontend_src_pages_TicketPartial { -date: DateTime~Utc~ -room: i16 -user_id: i16 } class frontend_src_pages_UserPartial { -id: i16 -first_name: String -last_name: String } class frontend_src_pages_RoomTotalsProps { -tickets: Vec~frontend_src_pages_TicketPartial~ } class frontend_src_pages_UserTotalProps { -users: Vec~frontend_src_pages_UserPartial~ -tickets: Vec~frontend_src_pages_TicketPartial~ } class frontend_src_pages_AdminSetupScheme { +first_name: String +last_name: String +username: String +pwd: String } class frontend_src_pages_UserCreateScheme { +first_name: String +last_name: String +username: String +is_admin: bool +pwd: String } class frontend_src_pages_LoginScheme { +username: String +pwd: String } class frontend_src_pages_UserUpdateScheme { +id: i16 +first_name: String +last_name: String +username: String +make_admin: bool +new_pwd: String } class frontend_src_pages_FilteredUser { +id: i16 +first_name: String +last_name: String +username: String +is_admin: bool } class frontend_src_pages_UserProps { +id: i16 } class frontend_src_pages_ApiError { -message: String -_status: String } class frontend_src_AuthState { +is_authenticated: Option~bool~ +is_admin: Option~bool~ } class frontend_src_ProtectedRouteProps { +children: Children +admin_page: bool } class frontend_src_SidebarShellProps { +children: Children } class frontend_src_AdminCheckWrapperProps { +children: Children } backend_src_AppState --> backend_src_Env backend_src_Env ..> backend_src_Env frontend_src_pages_SidebarState --> frontend_src_pages_SidebarExpandState frontend_src_pages_RoomTotalsProps --> frontend_src_pages_TicketPartial frontend_src_pages_UserTotalProps --> frontend_src_pages_UserPartial frontend_src_pages_UserTotalProps --> frontend_src_pages_TicketPartial ``` ### 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) database DB as Database (Postgres) 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 as User / Admin participant FE as Frontend (Yew) participant BE as Backend (Axum) database DB as Database (Postgres) 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": {...}}
Header: Set-Cookie: token=...; Path=/; HttpOnly; 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) database DB as Database (Postgres) 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