Skip to content

Backend: System Components

Goal and Context

The backend provides a REST API for the Rooms system and encapsulates the business logic around users, roles, rooms, tasks, events, displays, and check-ins. The API is implemented in Go and follows a controller model service structure where controllers handle HTTP, models encapsulate data access, and services contain domain specific helper logic such as renderer, QR, and mail. In production, Postgres is typically used while SQLite is available for local development and tests.

Architecture and Request Flow

The HTTP layer is strictly separated from data access and business logic. The entry point initializes configuration and infrastructure (DB/Redis/JWT) and starts the router. Requests pass through global middleware, then route-specific middleware (auth/permissions/validation), before a controller is invoked.

  1. cmd/rooms/main.go creates the main router (Gin Default) and sets CORS_adjust.
  2. All /v1/* requests are forwarded to router.NewRouter().
  3. router.NewRouter() registers logger/recovery and the DatabaseHealthCheck.
  4. Route groups define auth/permission/validation and invoke controllers.
  5. Controllers access models/services (DB/Redis/Python renderer).
  6. Responses are returned as JSON with clear status codes.

Go (API)

The API is based on Gin with a clear routing structure. Database access is handled via GORM and is automatically migrated on startup. JWT handling uses ES256 and reads keys from PEM files, so no symmetric keys need to be stored in plaintext.

  • Router: Gin with /v1 as the base path.
  • ORM: GORM, AutoMigrate for User, Role, Permission, Room, RoomStatus, Display, Task, TaskType, Event, EventType, QRCode, CheckIn.
  • JWT (ES256) via jwt_handling with PEM keys.
  • Config loader: config.Load() reads .env and validates required fields based on .env.example.
  • Audit logging via internal/pkg/logger (structured slog logs).

Postgres (and optional SQLite)

Postgres is the production database and is configured via environment variables. On startup, roles and permissions are synchronized so the API has a defined authorization baseline. SQLite is available as a fast, file-based mode and is primarily used in tests.

  • DB type via database_type (postgres or sqlite).
  • Postgres DSN is built from environment variables.
  • Roles and permissions are loaded on startup from app/backend/configs/roles.yaml (or ROLES_CONFIG_PATH) and synchronized into the DB.
  • A standard admin is created on startup if not present (standard_admin_*) (is SUPER admin).
  • A placeholder user deleted_user is automatically created and used for reassignments.

Redis

Redis is used as volatile storage for auth states. Crucial is the token blacklist so logout takes effect immediately even though JWTs are stateless. Additionally, a deactivation flag is used and checked in the auth middleware.

  • Required in production (server will not start if redis_addr is missing).
  • Logout blacklist: tokens are stored for the JWT lifetime.
  • Deactivation flag: user_deactivated_<uuid> prevents logins.

Python Renderer (E-Paper)

The renderer generates images for display devices and intentionally runs outside of Go to reuse existing Python logic and templates. Go passes JSON structures and receives a Base64-encoded image, which is delivered directly to the displays.

  • Implemented in app/backend/internal/epaper_rendering.
  • Go starts the module via python -m epaper_rendering.render.
  • Input is JSON for office or event rooms and the output is a Base64 image.
  • Format is png or 1bit with optional width and height.

Email (Verification)

User verification is sent via SMTP. The link points to a backend endpoint that, after successful verification, redirects to the frontend app or returns JSON depending on the Accept header.

  • services.SendVerificationEmail uses SMTP configuration from environment variables.
  • Verification link: /v1/user/verify?token=<token>.

Danger

Verification emails are not sent to these domains: example.com, example.net and example.org.

Please make sure to use one of these domains or any valid email address under your control for test purposes. Don’t use “fantasy” email addresses (not even for test purposes) that aren’t under your control (e.g., my-funny-test-address@gmail.com, room-test@test-it.com).

Logging and Auditing

Logging is based on slog and outputs structured entries. For security-relevant actions, there are audit logs that include the user, the action, and the target object.

  • Log level configurable via LOG_LEVEL.
  • Audit events via logger.Audit(...).

Configuration

Configuration is controlled via environment variables and loaded on startup by config.Load(). Required values are derived from app/backend/.env.example and if anything is missing startup fails so the API does not run in a partially configured state. If present a .env file is automatically loaded and can be used for local development. Changes to configuration only take effect after a server restart.

Warning

The file app/backend/.env.example must remain in the repository. The config.Load() function reads this file to determine which environment variables are required. Without it, the server cannot start.

Server and Logging

These values control where the HTTP server listens and how verbose logging is. Optionally, a separate host can be set for Swagger.

  • server_host, server_port: bind address and port of the HTTP server.
  • swagger_host: optional host for the Swagger UI (fallback to server_host:server_port).
  • LOG_LEVEL: log level (DEBUG, INFO, WARN, ERROR).

Admin Bootstrap

On first startup, a standard admin is created if it does not exist. These values must be set, otherwise startup fails.

  • standard_admin_username: username of the initial admin.
  • standard_admin_displayname: display name of the admin.
  • standard_admin_password: initial password (stored hashed). Must meet password requirements: minimum 12 characters, maximum 128 characters.

Database

The DB type is selected via database_type. For SQLite a local path is sufficient and for Postgres connection parameters are required.

  • database_type: sqlite or postgres.
  • database_path: path for SQLite (e.g. database.db).
  • postgres_host, postgres_port, postgres_database, postgres_username, postgres_password, postgres_sslmode.

Redis

Redis is used for logout blacklisting and deactivation flags. If redis_addr is missing, the server will not start.

  • redis_addr: host:port, e.g. localhost:6379.
  • redis_password: optional password.
  • redis_db: DB index (int).

JWT

JWTs are signed with ES256 and keys are provided as PEM files. Validity values control token lifetimes.

  • jwt_issuer: issuer claim for tokens.
  • jwt_private_key_path: path to the private PEM key.
  • jwt_public_key_path: path to the public PEM key.
  • jwt_valid_duration_in_h: access token validity in hours.
  • refresh_token_valid_duration_in_days: refresh token validity in days.

Roles and Permissions

The role definition can be overridden via a YAML file. Roles/permissions are synchronized on startup. The file app/backend/configs/roles.yaml can be used as a template for custom role configurations.

  • ROLES_CONFIG_PATH: path to the roles config (default: app/backend/configs/roles.yaml).

Frontend and QR Codes

These values are used to generate QR code links pointing to the frontend.

  • frontend_base_url: base URL of the frontend.
  • qr_checkin_path: path for check-in QRs.
  • qr_room_path: path for room QRs.

Display

  • display_refresh_interval_seconds: default = 20, the default refresh interval for the display (used for rendering logic, including QR code generation and clean-up). If you want to change the display refresh interval, you need to configure it in the Display Setup. After modifying the hardware, update this variable so that the backend knows the correct interval and can perform tasks such as calculating QR codes based on it. A shorter interval results in higher power consumption.

QR Code Cleanup

  • qr_code_cleanup_interval_hours: interval for periodic removal of expired, unused QRs.

Email

SMTP configuration is used for verification emails.

  • EMAIL_SERVER, EMAIL_SERVER_PORT, EMAIL_SERVER_USER, EMAIL_SERVER_PASSWORD.

Python Renderer

  • python_executable: optional Python binary for the renderer (default: python3, with venv auto-detection).

Data Model (Details)

The models are designed for a relational DB and use UUIDs as primary keys. Many-to-many relationships are represented via join tables, sometimes with additional fields (e.g. RoomUser.Description). Via Base, standard fields like UUID and timestamps are consistently provided.

Interactive Schema Map

The diagram below shows the live database structure. You can zoom, pan, and hover over relationships to see the foreign key constraints.

Base Type

Base contains UUID and timestamps and is embedded in multiple entities. This ensures consistent fields and reduces boilerplate in the models.

  • Base (UUID, CreatedAt, UpdatedAt, DeletedAt) for Room, Display, RoomStatus, Task, Event, QRCode, CheckIn.

Users/Roles/Permissions

The authorization model is role based. Roles have a Level that represents a hierarchy where users may only manage roles below their highest level and admins may manage everything. Permissions are centrally defined so controllers and middleware can check against stable names.

  • User has many Role, Role has many Permission.
  • Hierarchy via Role.Level: only roles with a lower level are allowed (except admin).
  • models.DefinedPermissions centrally defines all permission names.
  • Role configuration supports wildcards like resource:* and all.

Permissions List

All available permissions are defined in app/backend/internal/models/permission.go:

Category Permissions
System system:admin, system:audit
User user:read, user:create, user:update, user:delete, user:manage_roles, user:reset_password
Role role:read, role:create, role:update, role:delete, role:manage_permissions
Permission permission:read
Room room:read, room:create, room:update, room:delete, room:assign_users
Room Status room_status:read, room_status:update
Task task:read, task:create, task:update, task:delete, task:assign
Event event:read, event:create, event:update, event:delete, event:manage_participants
Display display:read, display:create, display:update, display:delete, display:regenerate_key
QR Code qr_code:read, qr_code:create, qr_code:update, qr_code:delete
Check-in checkin:read, checkin:perform, checkin:create, checkin:delete

Rooms

A room is the central entity and can have displays, status, and assigned users. User assignment is intentionally limited because e-paper layouts can only display a small number. Room status is updated via separate endpoints.

  • Room with RoomStatus (1:1), Display (1:n), RoomUser (n:m).
  • RoomUser contains additional fields such as Description.
  • Maximum of 4 users per room (limit in RoomUser).

Tasks/Events

Tasks and events share a common scheduling mechanism so recurrence and time windows are processed consistently. For events, it is additionally ensured that only bookable rooms are used.

  • Task and Event share SchedulableBase.
  • SchedulableBase encapsulates recurrence logic (recurrence_interval, recurrence_unit, recurrence_end_at).
  • Event can only be created in Room.IsBookable = true.

QRCode/CheckIn

QR codes are time-limited tokens that point to rooms or tasks. Check-ins connect a user with a task or a room at a specific time, providing a history for analysis or dashboard display.

  • QRCode belongs to a Room (required), optionally to a Task, and is time-valid.
  • CheckIn references a Room, optionally a Task, and optionally a QRCode.
  • Check-ins are used for history and analytics.

Directory Structure

Overview of the most important directories in app/backend:

app/backend/
├── cmd/
│   └── rooms/                    # Application entry point (main.go)
├── internal/
│   ├── api/
│   │   └── v1/                   # API Version 1
│   │       ├── controller/       # HTTP request handlers for resources
│   │       ├── middleware/       # Auth, validation, CORS middleware
│   │       └── router/           # Route definitions and setup
│   │
│   ├── config/                   # Configuration management (.env parsing, roles.yaml loading)
│   │
│   ├── database/                 # Database connections (SQLite, PostgreSQL, Redis)
│   │
│   ├── epaper_rendering/         # Python integration for E-Paper display rendering
│   │
│   ├── jwt_handling/             # JWT token generation and validation
│   │
│   ├── models/                   # GORM models (User, Room, Task, Event, QRCode, etc.)
│   │
│   ├── pkg/logger/               # Structured logging utility
│   │
│   ├── services/                 # Business logic (QRCode, Email, Renderer services)
│   │
│   ├── testutil/                 # Test utilities and database setup
│   │
│   └── validators/               # Input validation for API requests
├── api/                          # OpenAPI/Swagger specifications and Bruno collection
├── configs/
│   └── roles.yaml                # Role and permission definitions
├── deployments/                  # Docker and deployment configuration
└── .env.example                  # Configuration template (required variables)
Directory Purpose
internal/models/ Database models and DTOs
internal/api/v1/controller/ HTTP handlers for API endpoints
internal/config/ Environment variable and role configuration loading
internal/database/ DB connections and initialization
internal/services/ Business logic (QR code generation, email, rendering)
internal/validators/ Custom validators for API requests
configs/ roles.yaml with role hierarchy