042a1d4773
* introduced central package registry by package init function * dynamic registration of routes, model, migrations and initializers. * setting a docker compose project name to prevent shutdown of other containers with the same (composer)name * ai documentation * app template * Create tests for ALL API entpoints in ALL packages Based on current data. Ensure that all API endpoints used in frontend are tested. These tests are crucial for the next refactoring tasks. * adopting agent instructions for a more consistent coding style * added desired module layout and debugging information * Fix All Failing tests All failing tests are fixed now that makes the refactoring more easy since all tests must pass * restored routes for maintenance * added common translations * added new tests for API Endpoint * Merge branch 'separate_business_logic' * added lern and skill improvement cost editing * Set Docker image tag when building to prevent rebuild when nothing has changed * add and remove PP for Weaponskill fixed * add and remove PP for same named skills fixed * add new task
1088 lines
55 KiB
Markdown
1088 lines
55 KiB
Markdown
# Bamort — Detailed Project Documentation
|
||
|
||
> Version: 0.2.4 (backend) / 0.2.3 (frontend)
|
||
> Generated: 2026-03-13
|
||
> Purpose: Comprehensive architecture, component, and business-logic reference
|
||
|
||
---
|
||
|
||
## Table of Contents
|
||
|
||
1. [Project Purpose and Domain](#1-project-purpose-and-domain)
|
||
2. [High-Level Architecture](#2-high-level-architecture)
|
||
3. [Technology Stack](#3-technology-stack)
|
||
4. [Backend Architecture](#4-backend-architecture)
|
||
- [Entry Point and Startup Sequence](#41-entry-point-and-startup-sequence)
|
||
- [Router and Middleware](#42-router-and-middleware)
|
||
- [Authentication System](#43-authentication-system)
|
||
- [Module Breakdown (Functional View)](#44-module-breakdown-functional-view)
|
||
5. [Frontend Architecture](#5-frontend-architecture)
|
||
- [Application Bootstrap](#51-application-bootstrap)
|
||
- [Router and Navigation Guards](#52-router-and-navigation-guards)
|
||
- [State Management (Pinia Stores)](#53-state-management-pinia-stores)
|
||
- [Views and Components (Functional View)](#54-views-and-components-functional-view)
|
||
- [API Communication Layer](#55-api-communication-layer)
|
||
6. [Core Business Logic](#6-core-business-logic)
|
||
- [Character Data Model](#61-character-data-model)
|
||
- [Derived Values Calculation](#62-derived-values-calculation)
|
||
- [Skill and Spell Learning System](#63-skill-and-spell-learning-system)
|
||
- [Character Creation Wizard](#64-character-creation-wizard)
|
||
- [PDF Export Pipeline](#65-pdf-export-pipeline)
|
||
- [Audit Log](#66-audit-log)
|
||
7. [Data Access and Persistence](#7-data-access-and-persistence)
|
||
8. [Module Interaction Map](#8-module-interaction-map)
|
||
9. [Complete API Route Reference](#9-complete-api-route-reference)
|
||
10. [Infrastructure and Deployment](#10-infrastructure-and-deployment)
|
||
11. [User Roles and Access Control](#11-user-roles-and-access-control)
|
||
12. [Internationalisation (i18n)](#12-internationalisation-i18n)
|
||
13. [Known Limitations and Security Notes](#13-known-limitations-and-security-notes)
|
||
|
||
---
|
||
|
||
## 1. Project Purpose and Domain
|
||
|
||
**Bamort** (BaMoRT = *Ba*sic Mo*R*phing *T*ool) is a web-based character management system for the **Midgard** tabletop role-playing game (M5 system). It is designed as a replacement for the older MOAM desktop application.
|
||
|
||
**Core user-facing features:**
|
||
- Create, manage, and view RPG characters (stats, skills, spells, equipment)
|
||
- Guide new users through a multi-step character creation wizard following Midgard M5 rules
|
||
- Manage skill and spell learning progression with rule-enforced costs (EP, TE, LE, gold)
|
||
- Track experience points (EP), practice points (PP), and wealth (gold/silver/copper)
|
||
- Export characters as PDF character sheets via HTML templates
|
||
- Import/export characters as JSON or CSV for interoperability with other tools
|
||
- Share characters between users (read-only or read/write)
|
||
- Maintain game-system master data (canonical skill, spell, and equipment tables)
|
||
|
||
**Business domain vocabulary:**
|
||
| Term | Meaning |
|
||
|------|---------|
|
||
| Char / Character | A player or NPC character in the game |
|
||
| Grad | Character level/grade |
|
||
| EP | Experience points — spent to improve skills and spells |
|
||
| TE | Training entries — attempts needed to improve a skill |
|
||
| LE | Learning entries — prerequisite step counts for spells |
|
||
| PP | Practice points — earned in combat/use, reduce learning costs |
|
||
| Fertigkeit | Skill (e.g., Schwimmen, Klettern) |
|
||
| Waffenfertigkeit | Weapon skill (subtype of Fertigkeit) |
|
||
| Zauber | Spell (magical ability) |
|
||
| Eigenschaft | Attribute (St=Strength, Gs=Dexterity, Gw=Agility, Ko=Constitution, In=Intelligence, Zt=Magical Talent, Au=Appearance, Wk=Willpower, Pa=Parry) |
|
||
| Rasse | Race (Mensch, Zwerg, Elf, etc.) |
|
||
| Typ | Character class code (Kr=Knight, Ma=Mage, Hx=Witch, etc.) |
|
||
| Lp / Ap / B | Life Points / Action Points / Load capacity |
|
||
| Bennies (Gg/Gp/Sg) | Luck tokens used in gameplay |
|
||
| Vermoegen | Wealth in gold/silver/copper pieces |
|
||
| Abwehr | Derived defense value |
|
||
| GSMaster | Game-system master data (canonical tables for skills, spells, equipment) |
|
||
| M5 | Midgard 5th edition rule set (the game system code used) |
|
||
|
||
---
|
||
|
||
## 2. High-Level Architecture
|
||
|
||
```
|
||
┌─────────────────────────────────────────────────────────────────┐
|
||
│ User (Browser) │
|
||
└──────────────────────────┬──────────────────────────────────────┘
|
||
│ HTTP / HTTPS
|
||
┌──────────────────────────▼──────────────────────────────────────┐
|
||
│ Vue 3 SPA (bamort-frontend) │
|
||
│ Vite dev server (port 5173) / Nginx (port 80) │
|
||
│ - Vue Router (client-side navigation) │
|
||
│ - Pinia (state management) │
|
||
│ - vue-i18n (DE/EN translations) │
|
||
│ - Axios (API calls to backend) │
|
||
└──────────────────────────┬──────────────────────────────────────┘
|
||
│ REST API calls to VITE_API_URL
|
||
│ Bearer token in Authorization header
|
||
┌──────────────────────────▼──────────────────────────────────────┐
|
||
│ Go / Gin REST API (bamort-backend) │
|
||
│ Port 8180 │
|
||
│ - Public routes: /register /login /password-reset/* │
|
||
│ - Protected routes: /api/* (auth middleware enforced) │
|
||
│ - Public info routes: /api/public/* │
|
||
└──────────────────────────┬──────────────────────────────────────┘
|
||
│ GORM
|
||
┌──────────────────────────▼──────────────────────────────────────┐
|
||
│ MariaDB 11.4 │
|
||
│ (bamort-mariadb, port 3306) │
|
||
│ Production: not exposed to host network │
|
||
│ Dev: accessible at localhost:3306 + phpMyAdmin at :8081 │
|
||
└─────────────────────────────────────────────────────────────────┘
|
||
```
|
||
|
||
The frontend communicates **directly** with the backend API via the `VITE_API_URL` environment variable — there is no Nginx reverse-proxy layer between them in production (TLS termination is the responsibility of an external proxy like Traefik).
|
||
|
||
---
|
||
|
||
## 3. Technology Stack
|
||
|
||
| Layer | Technology | Version | Purpose |
|
||
|-------|-----------|---------|---------|
|
||
| Backend language | Go | 1.25 | API server, business logic |
|
||
| Backend framework | Gin | latest | HTTP routing, middleware |
|
||
| ORM | GORM | latest | Database access, migrations |
|
||
| Database | MariaDB | 11.4 | Primary persistent storage |
|
||
| Test DB | SQLite | (via CGO) | Isolated per-test snapshot |
|
||
| PDF rendering | chromedp + Chromium | latest | HTML→PDF via headless browser |
|
||
| PDF merging | pdfcpu | latest | Multi-page PDF assembly |
|
||
| Live reload (dev) | Air | latest | Backend hot-reload during development |
|
||
| Frontend framework | Vue 3 | ^3.5.13 | UI (Options API) |
|
||
| Frontend build | Vite | ^6.0.1 | Dev server + production bundler |
|
||
| State management | Pinia | ^2.3.0 | Reactive global stores |
|
||
| HTTP client | Axios | ^1.7.9 | API communication with auth interceptors |
|
||
| i18n | vue-i18n | ^11.0.0 | German/English UI localisation |
|
||
| Frontend server | Nginx (prod) / Vite (dev) | alpine / 6.x | Static file serving + SPA routing |
|
||
| Container runtime | Docker + Docker Compose | — | Isolated service deployment |
|
||
|
||
---
|
||
|
||
## 4. Backend Architecture
|
||
|
||
### 4.1 Entry Point and Startup Sequence
|
||
|
||
**File:** `backend/cmd/main.go`
|
||
|
||
The `main()` function orchestrates startup in this order:
|
||
|
||
1. **Load configuration** — reads `.env` / `.env.local` then environment variable overrides via `config.Cfg` (auto-loaded in `config.init()`)
|
||
2. **Configure logger** — sets log level (`DEBUG`, `INFO`, `WARN`, `ERROR`) and debug mode based on config
|
||
3. **Set Gin mode** — `gin.ReleaseMode` in production, `gin.DebugMode` otherwise
|
||
4. **Connect database** — calls `database.ConnectDatabase()` which selects MySQL or SQLite based on `DATABASE_TYPE` and `ENVIRONMENT`
|
||
5. **Initialize PDF templates** — copies default templates from `/app/default_templates` to `cfg.TemplatesDir` if needed via `pdfrender.InitializeTemplates()`
|
||
6. **Set up Gin engine** — calls `router.SetupGin(r)` for CORS config
|
||
7. **Register routes** — each module calls `RegisterRoutes(protected)` on the protected `/api` group
|
||
8. **Register public routes** — pdfrender and appsystem register unauthenticated endpoints
|
||
9. **Start HTTP server** — `r.Run(cfg.GetServerAddress())`
|
||
|
||
### 4.2 Router and Middleware
|
||
|
||
**File:** `backend/router/routes.go` and `backend/router/setup.go`
|
||
|
||
**Route structure:**
|
||
```
|
||
POST /register → user.RegisterUser
|
||
POST /login → user.LoginUser
|
||
POST /password-reset/request → user.RequestPasswordReset
|
||
GET /password-reset/validate/:token → user.ValidateResetToken
|
||
POST /password-reset/reset → user.ResetPassword
|
||
|
||
/api/* ← ALL require valid Bearer token via user.AuthMiddleware()
|
||
├── /api/public/version → appsystem (no auth check inside group, separate route)
|
||
├── /api/public/systeminfo → appsystem
|
||
├── /api/user/* → user module
|
||
├── /api/users/* → user module (admin-only subset)
|
||
├── /api/characters/* → character module
|
||
├── /api/maintenance/* → gsmaster (read) + maintenance (maintainer-only write)
|
||
├── /api/importer/* → importer module
|
||
├── /api/pdf/* → pdfrender module
|
||
├── /api/transfer/* → transfer module
|
||
└── /api/appsystem/* → appsystem module
|
||
```
|
||
|
||
**CORS:** Configured in `router/setup.go` with allowed origins including `localhost:5173` (dev), `localhost:8180`, and configurable `BASE_URL` (production frontend).
|
||
|
||
### 4.3 Authentication System
|
||
|
||
**Token generation** (`user/handlers.go` → `GenerateToken`):
|
||
- Hashes `username + createdAt` with MD5
|
||
- Embeds the user's ID in an predictable position within the hex string
|
||
- Token is stored client-side in `localStorage`
|
||
|
||
**Token validation** (`user/handlers.go` → `CheckToken`):
|
||
- Extracts the Base64/hex-encoded user ID from position `len("Bearer ")` onward in the `Authorization` header
|
||
- Loads the user from the database by that ID
|
||
- **No cryptographic signature verification is performed** — this is a design limitation (see Section 13)
|
||
|
||
**AuthMiddleware:**
|
||
- Calls `CheckToken`, sets `userID`, `username`, `user` in the Gin context
|
||
- Returns `401 Unauthorized` if token is absent or user not found
|
||
|
||
**Password hashing:** MD5 (`crypto/md5`). The bcrypt implementation is present but commented out.
|
||
|
||
### 4.4 Module Breakdown (Functional View)
|
||
|
||
Each module follows the same structure: `handlers.go` (business logic + HTTP responses), `routes.go` (route registration), and `*_test.go` (tests).
|
||
|
||
---
|
||
|
||
#### `user/` — User Account Management
|
||
|
||
**Business function:** Registration, login, profile management, password reset via email, and role-based access control.
|
||
|
||
**Key responsibilities:**
|
||
- `RegisterUser` — creates a new user with MD5-hashed password and default `standard` role
|
||
- `LoginUser` — validates credentials, returns a token
|
||
- `AuthMiddleware` — protects all `/api/*` routes; injects user context
|
||
- `RequireAdmin()` / `RequireMaintainer()` — role-check middleware used on specific sub-routes
|
||
- Profile endpoints: `GET/PUT /api/user/profile`, display name, email, preferred language, password change
|
||
- Admin user management: `GET/PUT/DELETE /api/users/:id`
|
||
- Password reset flow: request → email with token → validate token → set new password (uses `mail/` module for SMTP)
|
||
|
||
**Roles:**
|
||
| Role | Permissions |
|
||
|------|------------|
|
||
| `standard` | Own characters + shared characters |
|
||
| `maintainer` | + Write access to GSMaster data and maintenance endpoints |
|
||
| `admin` | + User management (list, change role, delete, reset password) |
|
||
|
||
---
|
||
|
||
#### `character/` — Character Management (Core Module)
|
||
|
||
**Business function:** The heart of the application. Full CRUD for characters plus all RPG game-logic operations.
|
||
|
||
**Sub-responsibilities:**
|
||
|
||
| Sub-area | Files | Business purpose |
|
||
|----------|-------|-----------------|
|
||
| CRUD | `handlers.go`, `database.go` | Create / read / update / delete characters and their sub-entities (attributes, skills, spells, equipment) |
|
||
| Derived values | `derived_values_calculator.go` | Calculate secondary stats (Abwehr, Zaubern, Raufen, body/mind resistance) from primary attributes using M5 formulas |
|
||
| Derived values (dice) | `derived_values_calculator.go` | Calculate dice-dependent stats during character creation (Lp_max, Ap_max, B_max, PA, Wk) requiring die-roll input from frontend |
|
||
| Learning cost engine | `lerncost_handler.go` | Compute EP/TE/LE/gold cost to learn a new skill/spell or improve an existing one, considering class, category, difficulty, practice points, and reward bonuses |
|
||
| Skill actions | `handlers.go` | Add/remove/edit skills and weapon skills inline |
|
||
| Learn skill | Separate handler | Learn a new skill (deducts resources, creates audit log entries) |
|
||
| Improve skill | `ImproveSkill` handler | Increase an existing skill value (validates costs, writes audit trail) |
|
||
| Learn spell | `LearnSpell` handler | Add a new spell to the character |
|
||
| Practice points | `practice_points_handler.go` | Track PP per skill category; PP reduce TE costs in learning |
|
||
| Experience & wealth | `handlers.go` | Update EP/gold separately from full character update |
|
||
| Character sharing | `share_handlers.go` | Grant read or write access to specific other users |
|
||
| Character creation wizard | Multiple handlers | Multi-step session-based creation: basic info → attributes → derived rolling → skills → finalize |
|
||
| Creation rules | `creation_rules.go` | M5-specific tables for special abilities (W100 roll → bonus/malus), starting attribute bonuses by race/class |
|
||
| Audit log | `audit_log.go`, `audit_log_handlers.go` | Immutable log of EP/gold/stat changes with reason codes |
|
||
| Image | `image_handler.go` | Upload and store Base64 character portrait |
|
||
| Ownership guard | `ownership_guard_test.go` | Ensures only the owner (or share-granted user) can modify |
|
||
| System info | `system_information_handlers.go` | Returns available skill categories for UI dropdowns |
|
||
|
||
---
|
||
|
||
#### `gsmaster/` — Game-System Master Data
|
||
|
||
**Business function:** Canonical reference tables for the M5 game system. Skills, weapon skills, spells, equipment, weapons, and containers as defined by the Midgard rules. Users with the `maintainer` role can edit these tables.
|
||
|
||
**Read (all authenticated users):**
|
||
- `GET /api/maintenance/skills` — list all skills
|
||
- `GET /api/maintenance/spells` — list all spells
|
||
- `GET /api/maintenance/weapons` — list all weapons
|
||
- `GET /api/maintenance/equipment` — general equipment
|
||
- `GET /api/maintenance/*-enhanced` — enhanced views with additional metadata (learning costs, categories, etc.)
|
||
|
||
**Write (maintainer only):**
|
||
- POST/PUT/DELETE on all the above categories
|
||
|
||
**Learning cost integration:** `gsmaster` package provides lookup helpers used by the `character` module to calculate learning costs. Skills carry `Category`, `Difficulty`, and `InnateSkill` properties that drive cost lookup logic.
|
||
|
||
---
|
||
|
||
#### `equipment/` — Character Equipment Management
|
||
|
||
**Business function:** CRUD operations specifically for the equipment attached to a character — weapons (`EqWaffe`), containers/bags (`EqContainer`), transportation (`EqContainer` with `IsTransportation=true`), and general gear (`EqAusruestung`).
|
||
|
||
**Why separate from `character/`?** Equipment has its own handlers/routes to keep the character module focused on core stats and skills. Equipment can also reference master data items from `gsmaster/`.
|
||
|
||
---
|
||
|
||
#### `pdfrender/` — PDF Character Sheet Export
|
||
|
||
**Business function:** Generate printable PDF character sheets by rendering HTML templates with character data using a Chromium headless browser, then merging the resulting per-page PDFs into a single file.
|
||
|
||
**Flow:**
|
||
1. `ExportCharacterToPDF` receives a character ID and template name
|
||
2. Loads the character from the database (fully preloaded)
|
||
3. For each template page (page1_stats.html, page2_play.html, etc.):
|
||
- Injects character data into the HTML template using Go template engine
|
||
- Renders it to PDF via `chromedp` (headless Chromium)
|
||
- Checks `<!-- MaxItems: N -->` comment in template to detect overflow
|
||
- Generates continuation pages (page1.2_stats.html) for overflow
|
||
4. Merges all page PDFs with `pdfcpu` into one file
|
||
5. Saves to `cfg.ExportTempDir` and returns the filename
|
||
6. Frontend opens a download URL in a new window
|
||
|
||
**Templates:** Located in `backend/templates/Default_A4_Quer/`. The `InitializeTemplates` function copies the default template to the runtime directory on first run.
|
||
|
||
**Routes:**
|
||
- `GET /api/pdf/export/:id` — export, returns `{"filename": "..."}`
|
||
- `GET /api/pdf/file/:filename` — download the generated PDF (public route, no auth required, filename-based security)
|
||
- `GET /api/pdf/templates` — list available templates
|
||
- `POST /api/pdf/cleanup` — clean up old generated PDF files
|
||
|
||
---
|
||
|
||
#### `importer/` — VTT / CSV Import and Export
|
||
|
||
**Business function:** Import characters from Virtual Tabletop (VTT) JSON format or CSV files. Export characters to VTT JSON or CSV. Export/import spell lists via CSV.
|
||
|
||
**Routes:**
|
||
```
|
||
POST /api/importer/upload — Upload VTT JSON + optional CSV
|
||
POST /api/importer/spells/csv — Import spell data from CSV
|
||
GET /api/importer/export/vtt/:id — Export character as VTT JSON (API response)
|
||
GET /api/importer/export/vtt/:id/file — Export character as VTT JSON (file download)
|
||
GET /api/importer/export/csv/:id — Export character as CSV
|
||
GET /api/importer/export/spells/csv — Export all spells as CSV
|
||
```
|
||
|
||
---
|
||
|
||
#### `transfer/` — JSON Backup and Restore
|
||
|
||
**Business function:** Portable character backup/restore in a Bamort-native JSON format. Also supports full-database export/import (admin/migration use case).
|
||
|
||
**Routes:**
|
||
```
|
||
GET /api/transfer/export/:id — Export character as JSON
|
||
GET /api/transfer/download/:id — Download character JSON as file
|
||
POST /api/transfer/import — Import character from JSON
|
||
POST /api/transfer/database/export — Export entire database as JSON
|
||
POST /api/transfer/database/import — Import full database from JSON
|
||
```
|
||
|
||
---
|
||
|
||
#### `maintenance/` — Admin and Database Operations
|
||
|
||
**Business function:** Maintainer-only admin operations: database consistency checks, re-connect/reload env, data migration (SQLite→MariaDB), and managing auxiliary master data not covered by `gsmaster/`.
|
||
|
||
**Key operations:**
|
||
- `GET /api/maintenance/setupcheck` — validate database schema integrity
|
||
- `GET /api/maintenance/setupcheck-dev` — extended dev-mode diagnostics
|
||
- `GET /api/maintenance/reconndb` — reconnect database (useful after connection interruption)
|
||
- `GET /api/maintenance/reloadenv` — reload environment variables at runtime
|
||
- `POST /api/maintenance/transfer-sqlite-to-mariadb` — one-time data migration
|
||
- CRUD for beliefs (`gsm-believes`), game systems, literature sources, misc lookups, skill improvement cost tables
|
||
|
||
---
|
||
|
||
#### `appsystem/` — System Information
|
||
|
||
**Business function:** Exposes version and system health information.
|
||
|
||
**Routes:**
|
||
```
|
||
GET /api/public/version — { version, gitCommit }
|
||
GET /api/public/systeminfo — { version, gitCommit, userCount, charCount, dbVersion }
|
||
```
|
||
|
||
These are reachable without authentication (used by `LandingView` and `SystemInfoView` to display status without requiring login).
|
||
|
||
---
|
||
|
||
#### `gamesystem/` — Game System Records
|
||
|
||
**Business function:** Manages `GameSystem` records in the database (e.g., M5 = Midgard 5th edition). Used when creating characters and as a FK reference in master data. Initial seed data creates the M5 game system if it doesn't exist.
|
||
|
||
---
|
||
|
||
#### `logger/` — Application Logging
|
||
|
||
**Business function:** Custom structured logger with levels (DEBUG/INFO/WARN/ERROR). Used throughout all packages. Debug mode controlled by config.
|
||
|
||
---
|
||
|
||
#### `mail/` — Email Delivery
|
||
|
||
**Business function:** SMTP client used exclusively for password-reset emails. Supports TLS (port 465) and STARTTLS (port 587). Configured via `MAIL_HOST`, `MAIL_PORT`, `MAIL_USERNAME`, `MAIL_PASSWORD`, `MAIL_FROM` env vars.
|
||
|
||
---
|
||
|
||
## 5. Frontend Architecture
|
||
|
||
### 5.1 Application Bootstrap
|
||
|
||
**`src/main.js`** registers plugins in order:
|
||
|
||
1. **Pinia** — state store (must be first)
|
||
2. **Vue Router** — client-side routing
|
||
3. **vue-i18n** — i18n instance created and exported from `stores/languageStore.js`
|
||
4. **UtilsPlugin** — installs global utilities on all components: `$formatDate`, `$formatDateTime`, `$formatRelativeDate`, `$safeValue`, `$capitalize`, `$rollDie`, `$rollDice`, `$rollDiceWithSum`, `$rollNotation`, `$randomBetween`, `$randomChoice`, `$shuffleArray`
|
||
|
||
**`App.vue`** — root component:
|
||
- Shows `<Menu />` only when `localStorage.getItem('token')` is truthy
|
||
- Applies `.full-width` CSS class on `<main>` for non-logged-in pages (centers login/register forms)
|
||
- Reacts to `storage` and `auth-changed` window events to update `loggedIn` state reactively
|
||
|
||
### 5.2 Router and Navigation Guards
|
||
|
||
**File:** `src/router/index.js`
|
||
|
||
Uses `createWebHistory` (HTML5 pushstate — no hash URLs). Unauthenticated landing/auth pages are statically imported; all other views are lazy-loaded (code-split) for performance.
|
||
|
||
**Navigation guard logic (beforeEach):**
|
||
1. If route requires auth and user is not logged in → redirect to `/login`
|
||
2. If route requires admin role → lazy-load `userStore`, fetch profile if not loaded, check `isAdmin` getter → redirect to `/dashboard` if not admin
|
||
3. Otherwise → allow navigation
|
||
|
||
`isLoggedIn()` from `utils/auth.js` simply checks for a token in `localStorage`.
|
||
|
||
### 5.3 State Management (Pinia Stores)
|
||
|
||
**`userStore` (`src/stores/userStore.js`)**
|
||
|
||
Central user identity state. Used by `Menu.vue`, `CharacterDetails.vue`, `UserManagementView.vue`, and the router guard.
|
||
|
||
| State | Purpose |
|
||
|-------|---------|
|
||
| `currentUser` | Logged-in user object `{ id, username, display_name, email, role, preferred_language }` |
|
||
| `isLoading` | Loading flag for profile fetch |
|
||
|
||
| Getter | Returns |
|
||
|--------|---------|
|
||
| `isAuthenticated` | `true` if `currentUser` is not null |
|
||
| `userRole` | `currentUser.role` or `'standard'` |
|
||
| `isAdmin` | `role === 'admin'` |
|
||
| `isMaintainer` | `role === 'maintainer'` or `'admin'` |
|
||
| `isStandardUser` | any logged-in user |
|
||
|
||
Action `fetchCurrentUser()` calls `GET /api/user/profile`, hydrates `currentUser`, and also sets the UI language to the user's preferred language.
|
||
|
||
---
|
||
|
||
**`languageStore` (`src/stores/languageStore.js`)**
|
||
|
||
Holds current language (`de` or `en`). Unusually, the `i18n` instance itself is **created and exported from this file**, then imported in `main.js`. This coupling means language changes can be made from anywhere that imports `i18n`.
|
||
|
||
---
|
||
|
||
**`characterCreationStore` (`src/stores/characterCreation.js`)**
|
||
|
||
In-memory wizard state for multi-step character creation. Tracks current step (1–5), and partial data for each step. The actual server-side session persistence is handled directly by `CharacterCreation.vue` via API calls; the Pinia store mirrors this state in memory.
|
||
|
||
### 5.4 Views and Components (Functional View)
|
||
|
||
#### Public Pages (no auth required)
|
||
|
||
| Component | Route | Purpose |
|
||
|-----------|-------|---------|
|
||
| `LandingView` | `/` | Landing page; polls `GET /api/public/version` every 5s (up to 24 retries) to show backend status; disables login button until backend responds |
|
||
| `LoginView` → `LoginForm` | `/login` | Login form; on success stores token in `localStorage`, triggers `auth-changed` event |
|
||
| `RegisterView` → `RegisterForm` | `/register` | Registration form |
|
||
| `ForgotPasswordView` → `ForgotPasswordForm` | `/forgot-password` | Sends password-reset email request |
|
||
| `ResetPasswordView` → `ResetPasswordForm` | `/reset-password` | Accepts reset token from URL, sets new password |
|
||
| `HelpView` | `/help` | FAQ page built from i18n keys dynamically |
|
||
| `SponsorsView` | `/sponsors` | Static credits/sponsor page |
|
||
| `SystemInfoView` | `/system-info` | Shows system versions and live backend stats |
|
||
|
||
#### Authenticated Pages
|
||
|
||
| Component | Route | Purpose |
|
||
|-----------|-------|---------|
|
||
| `DashboardView` → `CharacterList` | `/dashboard` | Lists own and shared characters; shows active creation sessions; "Create New Character" button starts a new wizard session |
|
||
| `UserProfileView` | `/profile` | Self-service: change display name, language, email, password |
|
||
| `UserManagementView` | `/users` | Admin-only: list all users, change roles, delete users, reset passwords |
|
||
| `MaintenanceView` → `Maintenance` | `/maintenance` | Maintainer tools: database checks, master data management |
|
||
| `FileUploadPage` | `/upload` | Import VTT JSON + optional CSV |
|
||
| `AusruestungView` → `AusruestungList` | `/ausruestung/:characterId` | Standalone equipment list (legacy view) |
|
||
| `CharacterDetails` | `/character/:id` | Main character hub (see below) |
|
||
| `CharacterCreation` | `/character/create/:sessionId` | Multi-step character creation wizard |
|
||
|
||
#### CharacterDetails Component — Central Hub
|
||
|
||
`CharacterDetails.vue` is the most complex component. It:
|
||
1. Fetches full character data from `GET /api/characters/:id`
|
||
2. Determines `isOwner` by comparing `character.user_id` vs `userStore.currentUser.id`
|
||
3. Renders a **dynamic sub-component** (`<component :is="currentView">`) driven by a submenu
|
||
4. Passes `character` and `isOwner` as props to every sub-view
|
||
5. Listens for `@character-updated` events from sub-views to trigger a full data reload
|
||
|
||
**Sub-views (tabs):**
|
||
|
||
| Component | Business purpose |
|
||
|-----------|----------------|
|
||
| `DatasheetView` | Character biography, attributes (Au/Gs/Gw/In/Ko/St/Wk/Zt/PA), derived stats (Lp/Ap/B, Abwehr, Zaubern, Raufen, Resistenz), bennies, wealth. Inline double-click editing for owners |
|
||
| `SkillView` | Skills grouped by category. Learning mode (toggle 🎓) reveals resources (EP, Gold) and action buttons: learn new skill, improve existing, add manually |
|
||
| `WeaponView` | Weapon skills and equipped weapons |
|
||
| `SpellView` | Spell list with learn/improve actions |
|
||
| `EquipmentView` | Containers, general equipment, transported items |
|
||
| `ExperianceView` | Experience and wealth tracking; audit log access |
|
||
| `DeleteCharView` | Confirmation UI for deleting the character (owner only) |
|
||
| `AuditLogView` | Read-only history of EP/gold changes |
|
||
|
||
**Overlay dialogs accessible from CharacterDetails:**
|
||
- `ExportDialog` — multi-format export: choose PDF template, VTT JSON, or BaMoRT JSON and trigger download
|
||
- `VisibilityDialog` — toggle character between public/private (owner only)
|
||
|
||
#### Learning Dialogs
|
||
|
||
`SkillLearnDialog.vue`, `SkillImproveDialog.vue`, `SpellLearnDialog.vue` are modal dialogs that:
|
||
1. Call `POST /api/characters/lerncost-new` with skill name, current level, type, and action
|
||
2. Display the computed cost breakdown (EP, TE, LE, gold, PP reduction)
|
||
3. Allow the user to confirm → backend executes the learning/improvement and deducts resources
|
||
|
||
### 5.5 API Communication Layer
|
||
|
||
**File:** `src/utils/api.js`
|
||
|
||
A pre-configured Axios instance:
|
||
- `baseURL`: `import.meta.env.VITE_API_URL || 'https://bamort-api.trokan.de'`
|
||
- **Request interceptor:** Injects `Authorization: Bearer <token>` from `localStorage` automatically
|
||
- **Response interceptor:** On `401`, removes the stale token from `localStorage` and logs a warning (but does not redirect — the nav guard handles that on next navigation)
|
||
|
||
All components and stores import this `API` instance. Raw `axios` (without the interceptor) is used only in `LandingView` and `SystemInfoView` for unauthenticated public API calls.
|
||
|
||
---
|
||
|
||
## 6. Core Business Logic
|
||
|
||
### 6.1 Character Data Model
|
||
|
||
The character data model maps closely to the **Midgard M5 character sheet**:
|
||
|
||
```
|
||
Char (root entity, table: char_chars)
|
||
├── Eigenschaften[] — 9 primary attributes (Au, Gs, Gw, In, Ko, St, Wk, Zt, PA)
|
||
├── Lp — Life points (current + max)
|
||
├── Ap — Action points (current + max)
|
||
├── B — Load/burden capacity (current + max)
|
||
├── Merkmale — Physical description (eye/hair color, height, etc.)
|
||
├── Erfahrungsschatz — EP (experience points) + ES (experience treasure/milestone)
|
||
├── Bennies — Luck tokens: Gg (lucky coin), Gp (lucky penny), Sg (lucky stroke)
|
||
├── Vermoegen — Wealth: Goldstuecke, Silberstuecke, Kupferstuecke
|
||
├── Fertigkeiten[] — Skills (Fertigkeit), each with value, base, bonus, PP, category
|
||
├── Waffenfertigkeiten[] — Weapon skills (subtype with additional combat fields)
|
||
├── Zauber[] — Spells with description, bonus, source reference
|
||
├── Waffen[] — Equipped weapons (EqWaffe) with attack/defense bonuses
|
||
├── Behaeltnisse[] — Bags/containers (EqContainer)
|
||
├── Transportmittel[] — Vehicles (EqContainer with IsTransportation=true)
|
||
├── Ausruestung[] — General equipment items
|
||
└── Spezialisierung — JSON array of specialization strings
|
||
```
|
||
|
||
**`FeChar`** (Frontend Character) is the API response type: it embeds `Char` plus computed fields:
|
||
- `Git` (poison tolerance = 30 + Ko/2)
|
||
- `CategorizedSkills` — map grouping skills by category for `SkillView` rendering
|
||
- `InnateSkills` — innate/racial skills separated from learned ones
|
||
|
||
### 6.2 Derived Values Calculation
|
||
|
||
**File:** `backend/character/derived_values_calculator.go`
|
||
|
||
The backend implements the M5 formula set. Key derivations:
|
||
|
||
| Derived value | Formula |
|
||
|--------------|---------|
|
||
| AusdauerBonus | Ko/10 + St/20 |
|
||
| SchadensBonus | St/20 + Gs/30 − 3 |
|
||
| AngriffsBonus | Gs/20 + Gw/30 − 3 |
|
||
| AbwehrBonus | Gw/20 + Gs/30 − 3 |
|
||
| ZauberBonus | In/10 + Zt/20 − 3 |
|
||
| ResistenzBonusKoerper | Ko/10 + Wk/20 − 3 |
|
||
| ResistenzBonusGeist | In/10 + Wk/15 − 3 |
|
||
| Abwehr | (grade-based base) + AbwehrBonus + PA (parry) |
|
||
| Zaubern | (class-based base) + ZauberBonus |
|
||
| Raufen | (class-based base at Grad 1) |
|
||
|
||
Dice-dependent values (rolled during character creation) include: `lp_max`, `ap_max`, `b_max`, `pa` (parry), `wk` (willpower). The frontend rolls dice and sends the results to `POST /api/characters/create-session/:id/derived` where the server validates and stores them.
|
||
|
||
The frontend component in `CharacterCreation.vue` exposes the dice-roll UI and calls `GET /api/characters/calculate-static-fields` and `POST /api/characters/calculate-rolled-field` to get the values computed by the backend rather than computing them in JavaScript.
|
||
|
||
### 6.3 Skill and Spell Learning System
|
||
|
||
**File:** `backend/character/lerncost_handler.go`
|
||
|
||
The learning cost engine computes what it costs (EP + TE, or EP + LE for spells) to learn a new skill/spell or improve an existing one. This is the most complex piece of business logic in the application.
|
||
|
||
**Input:**
|
||
```json
|
||
{
|
||
"name": "Klettern",
|
||
"type": "skill",
|
||
"action": "improve",
|
||
"current_level": 10,
|
||
"target_level": 12,
|
||
"use_pp": 2,
|
||
"reward": { "type": "half_ep_improvement" }
|
||
}
|
||
```
|
||
|
||
**Processing steps:**
|
||
1. Look up the character's class abbreviation (`Typ`)
|
||
2. Normalize the skill/spell name (trim, lowercase for lookup)
|
||
3. Find the skill in `gsmaster` master data to get `Category` and `Difficulty`
|
||
4. Query learning cost tables:
|
||
- For **skills**: `ClassCategoryEPCost` (EP per TE by class+category) + `SkillImprovementCost` (TE per level step) + `SkillCategoryDifficulty` (base LE)
|
||
- For **spells**: `ClassSpellSchoolEPCost` (EP per LE by class+spell school) + `SpellLevelLECost` (LE by spell level)
|
||
- For **weapon skills**: separate weapon skill cost tables
|
||
5. Apply **practice points (PP)** reduction: each PP reduces required TE by 1 (capped at max PP available for category)
|
||
6. Apply **reward bonuses** if specified:
|
||
- `free_learning` — zero cost
|
||
- `free_spell_learning` — zero cost for spells
|
||
- `half_ep_improvement` — halve EP cost when improving
|
||
- `gold_for_ep` — substitute up to half the EP with 10 GS each
|
||
7. Return full cost breakdown plus `CanAfford` flag comparing costs to character's current EP/gold
|
||
|
||
For **improve** action, costs are computed **per level step** from `current_level+1` to `target_level` and aggregated: `MultiLevelCostResponse` contains both per-step and total costs.
|
||
|
||
The `ImproveSkill` and `LearnSkill` handlers execute the actual resource deduction and write audit log entries.
|
||
|
||
### 6.4 Character Creation Wizard
|
||
|
||
**Backend:** `backend/character/` (multiple handlers)
|
||
**Frontend:** `frontend/src/components/CharacterCreation.vue` and `CharacterCreation/` sub-components
|
||
**Session storage:** `models.CharacterCreationSession` (table: `char_creation_sessions`)
|
||
|
||
The wizard is a **server-side session** model. A session stores partial character data as JSON blobs per step. Sessions expire (stored `ExpiresAt`).
|
||
|
||
**Steps:**
|
||
|
||
| Step | Name | Backend endpoint | Data stored |
|
||
|------|------|-----------------|-------------|
|
||
| 1 | Basic Info | `PUT .../basic` | Name, race, class, origin, social class, faith, gender, handedness |
|
||
| 2 | Attributes | `PUT .../attributes` | Primary attributes (rolled client-side, validated server-side) |
|
||
| 3 | Derived Values | `PUT .../derived` | Dice-rolled derived stats (LP/AP/B/PA/Wk) |
|
||
| 4 | Skills | `PUT .../skills` | Selected starting skills with initial values |
|
||
| 5 | Finalize | `POST .../finalize` | Creates the final `Char` record from all session data |
|
||
|
||
**Available-skills-for-creation endpoint** (`POST /api/characters/available-skills-creation`):
|
||
Returns all learnable skills for a specific class, grouped by category, with starting LP (Learning Points) that determine how many skills the character can learn at creation. LP are class+category specific (`ClassCategoryLearningPoints` table).
|
||
|
||
**Special abilities** (`creation_rules.go`):
|
||
M5 allows rolling a W100 to gain a random special ability at character creation. The `GetSpecialAbilityByRoll(roll)` function maps dice outcomes to named bonuses (e.g., roll 56–60 → "Gute Reflexe+9"). This is pure backend logic; the frontend passes the rolled value as part of the creation session data, and the server calls this function when computing derived values.
|
||
|
||
**Dashboard integration:** `CharacterList.vue` shows in-progress sessions via `CharacterCreationSessions.vue`. Users can resume or delete draft sessions.
|
||
|
||
### 6.5 PDF Export Pipeline
|
||
|
||
**Flow (backend):**
|
||
```
|
||
ExportCharacterToPDF (pdfrender/handlers.go)
|
||
↓
|
||
LoadCharacterFromDB (full preload)
|
||
↓
|
||
For each template page (page1_stats.html, page2_play.html, ...):
|
||
→ Execute Go text/template with character data
|
||
→ chromedp: launch Chromium, navigate to data URL, print to PDF
|
||
→ Check <!-- MaxItems: N --> overflow marker
|
||
→ Generate continuation pages if needed (page1.2_stats.html)
|
||
↓
|
||
pdfcpu.MergeFiles → single merged PDF
|
||
↓
|
||
Save to cfg.ExportTempDir
|
||
↓
|
||
Return { filename: "CharName_20231225_143045.pdf" }
|
||
```
|
||
|
||
**Flow (frontend):**
|
||
```
|
||
ExportDialog.vue
|
||
↓
|
||
GET /api/pdf/templates → populate template selector
|
||
↓
|
||
User selects format (PDF / VTT / BaMoRT) + template
|
||
↓
|
||
For PDF format:
|
||
GET /api/pdf/export/:id?template=Default_A4_Quer
|
||
→ Response: { filename: "..." }
|
||
→ window.open(baseURL + /api/pdf/file/<filename>, '_blank')
|
||
|
||
For VTT format:
|
||
GET /api/importer/export/vtt/:id/file (blob) → browser download link
|
||
|
||
For BaMoRT format:
|
||
GET /api/transfer/download/:id (blob) → browser download link
|
||
```
|
||
|
||
**Note:** For PDF, `window.open` is called after the async API response, making it subject to popup blockers.
|
||
|
||
### 6.6 Audit Log
|
||
|
||
**Files:** `backend/character/audit_log.go`, `audit_log_handlers.go`
|
||
|
||
Every change to EP, gold, or significant character values writes an immutable `AuditLogEntry` record:
|
||
|
||
| Field | Purpose |
|
||
|-------|---------|
|
||
| `CharacterID` | Which character |
|
||
| `FieldName` | What changed (e.g., `"experience_points"`, `"goldstücke"`) |
|
||
| `OldValue` / `NewValue` | Before and after |
|
||
| `Difference` | `NewValue - OldValue` |
|
||
| `Reason` | Typed constant: `manual`, `skill_learning`, `skill_improvement`, `spell_learning`, `equipment`, `reward`, `correction`, `import` |
|
||
| `UserID` | Who made the change |
|
||
| `Notes` | Free-text context |
|
||
| `Timestamp` | Auto-set by GORM |
|
||
|
||
API endpoints:
|
||
- `GET /api/characters/:id/audit-log` — all entries or filtered by `?field=experience_points`
|
||
- `GET /api/characters/:id/audit-log/stats` — aggregated statistics
|
||
|
||
---
|
||
|
||
## 7. Data Access and Persistence
|
||
|
||
### Database Connection
|
||
|
||
- **Production/development:** MariaDB 11.4 via GORM MySQL driver. DSN from `DATABASE_URL` env var.
|
||
- **Tests:** SQLite (via CGO). `testutils.SetupTestDB()` copies `testdata/prepared_test_data.db` to a temp dir per test run, ensuring isolation.
|
||
- **Global variable:** `database.DB *gorm.DB` — shared across all packages by import.
|
||
|
||
### Schema Migrations
|
||
|
||
`models.MigrateStructure(db)` is called at startup and runs GORM `AutoMigrate` for all entity types in dependency order:
|
||
1. `game_systems`
|
||
2. GSMaster tables (skills, spells, equipment, weapons, containers, believes, misc lookups)
|
||
3. Character tables (chars, attributes, skills, spells, equipment sub-tables)
|
||
4. Equipment tables
|
||
5. Skill learning cost tables
|
||
6. Learning/cost relation tables
|
||
|
||
Migration history is tracked in `schema_version` and `migration_history` tables.
|
||
|
||
### Test Data
|
||
|
||
`testdata/prepared_test_data.db` is a pre-populated SQLite snapshot containing real test characters, including **character ID 18 ("Fanjo Vetrani")** which is used as the primary test fixture throughout all backend tests.
|
||
|
||
### Custom Types
|
||
|
||
`StringArray` is a custom GORM type that serializes `[]string` as JSON into a TEXT column:
|
||
```go
|
||
type StringArray []string
|
||
// Stored as: ["specialization1","specialization2"]
|
||
```
|
||
Used by `Char.Spezialisierung`.
|
||
|
||
---
|
||
|
||
## 8. Module Interaction Map
|
||
|
||
```
|
||
┌─────────────────────────────────────────────────────────────────────┐
|
||
│ Frontend (Vue 3) │
|
||
│ │
|
||
│ CharacterDetails ──────→ SkillView ──────→ SkillLearnDialog │
|
||
│ │ │ │
|
||
│ │ SpellView ──────→ SpellLearnDialog │
|
||
│ │ │ │
|
||
│ └──────→ ExportDialog │ │
|
||
│ │ │ │
|
||
└─────────────────────┼──────────────────────────────┼────────────────┘
|
||
│ REST API │ REST API
|
||
┌─────────────▼──────────────┐ ┌───────────▼───────────────┐
|
||
│ pdfrender module │ │ character module │
|
||
│ ExportCharacterToPDF │ │ GetLernCostNewSystem │
|
||
│ chromedp → Chromium │ │ LearnSkill / ImproveSkill │
|
||
│ pdfcpu merge │ │ Audit log writes │
|
||
└─────────────────────────── ┘ └───────────────────────────┘
|
||
│
|
||
┌────────────▼──────────────┐
|
||
│ gsmaster module │
|
||
│ Skill/Spell lookup │
|
||
│ Cost table queries │
|
||
└───────────────────────────┘
|
||
│
|
||
┌────────────▼──────────────┐
|
||
│ models package │
|
||
│ GORM entity definitions │
|
||
│ LearnCost, Char, etc. │
|
||
└───────────────────────────┘
|
||
│
|
||
┌────────────▼──────────────┐
|
||
│ database.DB │
|
||
│ MariaDB (prod/dev) │
|
||
│ SQLite (test) │
|
||
└───────────────────────────┘
|
||
```
|
||
|
||
**Cross-module dependencies (backend):**
|
||
- `character` depends on `gsmaster` (skill/spell lookup for cost calculations)
|
||
- `character` depends on `models` (all entity types)
|
||
- `character` depends on `database` (direct DB access)
|
||
- `gsmaster` depends on `models` and `database`
|
||
- `pdfrender` depends on `models` and `database` (loads full character)
|
||
- `maintenance` depends on `gsmaster`, `models`, `database`
|
||
- `user` is depended on by all modules via `AuthMiddleware` import into `router`
|
||
|
||
---
|
||
|
||
## 9. Complete API Route Reference
|
||
|
||
### Public Routes (no authentication)
|
||
```
|
||
POST /register
|
||
POST /login
|
||
POST /password-reset/request
|
||
GET /password-reset/validate/:token
|
||
POST /password-reset/reset
|
||
GET /api/public/version
|
||
GET /api/public/systeminfo
|
||
GET /api/pdf/file/:filename
|
||
```
|
||
|
||
### User Routes (authenticated)
|
||
```
|
||
GET /api/user/profile
|
||
PUT /api/user/display-name
|
||
PUT /api/user/language
|
||
PUT /api/user/email
|
||
PUT /api/user/password
|
||
```
|
||
|
||
### App System Routes
|
||
```
|
||
GET /api/version — version info (authenticated)
|
||
GET /api/systeminfo — system info (authenticated)
|
||
GET /api/public/version — version info (public, no auth)
|
||
GET /api/public/systeminfo — system info (public, no auth)
|
||
```
|
||
|
||
### Admin User Management (admin role required)
|
||
```
|
||
GET /api/users
|
||
GET /api/users/:id
|
||
PUT /api/users/:id/role
|
||
DELETE /api/users/:id
|
||
PUT /api/users/:id/password
|
||
```
|
||
|
||
### Character Routes
|
||
```
|
||
GET /api/characters — list own + shared characters
|
||
POST /api/characters — create character
|
||
GET /api/characters/:id — get full character (FeChar)
|
||
PUT /api/characters/:id — update character
|
||
PATCH /api/characters/:id — partial update
|
||
DELETE /api/characters/:id — delete (owner only)
|
||
PUT /api/characters/:id/image — update portrait
|
||
GET /api/characters/:id/datasheet-options — dropdowns for editing
|
||
|
||
GET /api/characters/:id/shares — list share grants
|
||
PUT /api/characters/:id/shares — update share grants
|
||
GET /api/characters/:id/available-users — users available for sharing
|
||
|
||
GET /api/characters/:id/experience-wealth — EP + wealth snapshot
|
||
PUT /api/characters/:id/experience — update EP
|
||
PUT /api/characters/:id/wealth — update wealth
|
||
|
||
GET /api/characters/:id/audit-log — change history
|
||
GET /api/characters/:id/audit-log/stats — audit statistics
|
||
|
||
POST /api/characters/lerncost-new — compute learning costs
|
||
POST /api/characters/lerncost — alias for lerncost-new
|
||
POST /api/characters/improve-skill-new — execute skill improvement
|
||
POST /api/characters/improve-skill — alias
|
||
POST /api/characters/:id/learn-skill-new — execute skill learning
|
||
POST /api/characters/:id/learn-skill — alias
|
||
POST /api/characters/:id/learn-spell-new — execute spell learning
|
||
POST /api/characters/:id/learn-spell — alias
|
||
|
||
POST /api/characters/available-skills-new — available skills with costs
|
||
POST /api/characters/available-skills — alias
|
||
POST /api/characters/available-skills-creation — skills for creation wizard
|
||
POST /api/characters/available-spells-creation — spells for creation wizard
|
||
POST /api/characters/available-spells-new — available spells with costs
|
||
POST /api/characters/available-spells — alias
|
||
GET /api/characters/spell-details — spell detail lookup
|
||
GET /api/characters/:id/reward-types — available reward types
|
||
|
||
GET /api/characters/:id/practice-points — PP by category
|
||
PUT /api/characters/:id/practice-points — update PP
|
||
POST /api/characters/:id/practice-points/add — add PP
|
||
POST /api/characters/:id/practice-points/use — consume PP
|
||
|
||
GET /api/characters/skill-categories — static skill category list
|
||
|
||
GET /api/characters/create-sessions — list wizard sessions
|
||
POST /api/characters/create-session — start new session
|
||
GET /api/characters/create-session/:id — get session data
|
||
PUT /api/characters/create-session/:id/basic — save step 1
|
||
PUT /api/characters/create-session/:id/attributes — save step 2
|
||
PUT /api/characters/create-session/:id/derived — save step 3
|
||
PUT /api/characters/create-session/:id/skills — save step 4
|
||
POST /api/characters/create-session/:id/finalize — create character from session
|
||
DELETE /api/characters/create-session/:id — discard session
|
||
|
||
GET /api/characters/races — available races list
|
||
GET /api/characters/classes — available character classes
|
||
GET /api/characters/classes/learning-points — LP per class+category for creation
|
||
GET /api/characters/origins — available origins
|
||
GET /api/characters/beliefs — belief/faith search
|
||
|
||
POST /api/characters/calculate-static-fields — derived values (no dice)
|
||
POST /api/characters/calculate-rolled-field — derived value requiring dice roll
|
||
```
|
||
|
||
### GSMaster Routes (read: all authenticated; write: maintainer+)
|
||
```
|
||
GET /api/maintenance — all master data
|
||
GET /api/maintenance/skills
|
||
GET /api/maintenance/skills-enhanced
|
||
GET /api/maintenance/skills/:id
|
||
GET /api/maintenance/skills-enhanced/:id
|
||
GET /api/maintenance/weaponskills[/*]
|
||
GET /api/maintenance/spells[/*]
|
||
GET /api/maintenance/equipment[/*]
|
||
GET /api/maintenance/weapons[/*]
|
||
POST/PUT/DELETE above (maintainer only)
|
||
```
|
||
|
||
### Maintenance Routes (maintainer role required)
|
||
```
|
||
GET /api/maintenance/gsm-believes
|
||
PUT /api/maintenance/gsm-believes/:id
|
||
GET /api/maintenance/game-systems
|
||
PUT /api/maintenance/game-systems/:id
|
||
GET /api/maintenance/gsm-lit-sources
|
||
PUT /api/maintenance/gsm-lit-sources/:id
|
||
GET /api/maintenance/gsm-misc
|
||
PUT /api/maintenance/gsm-misc/:id
|
||
GET /api/maintenance/skill-improvement-cost2
|
||
PUT /api/maintenance/skill-improvement-cost2/:id
|
||
GET /api/maintenance/setupcheck
|
||
GET /api/maintenance/setupcheck-dev
|
||
GET /api/maintenance/mktestdata
|
||
GET /api/maintenance/reconndb
|
||
GET /api/maintenance/reloadenv
|
||
POST /api/maintenance/transfer-sqlite-to-mariadb
|
||
```
|
||
|
||
### PDF Export Routes
|
||
```
|
||
GET /api/pdf/templates — list available templates
|
||
GET /api/pdf/export/:id — export character to PDF
|
||
POST /api/pdf/cleanup — clean up old generated PDF files
|
||
GET /api/pdf/file/:filename — download generated PDF (public, no auth)
|
||
```
|
||
|
||
### Importer Routes
|
||
```
|
||
POST /api/importer/upload — import VTT JSON + CSV
|
||
POST /api/importer/spells/csv — import spell CSV
|
||
GET /api/importer/export/vtt/:id — export as VTT JSON
|
||
GET /api/importer/export/vtt/:id/file — download VTT JSON file
|
||
GET /api/importer/export/csv/:id — export as CSV
|
||
GET /api/importer/export/spells/csv — export all spells as CSV
|
||
```
|
||
|
||
### Transfer Routes
|
||
```
|
||
GET /api/transfer/export/:id — export character JSON
|
||
GET /api/transfer/download/:id — download character JSON file
|
||
POST /api/transfer/import — import character JSON
|
||
POST /api/transfer/database/export — export full database
|
||
POST /api/transfer/database/import — import full database
|
||
```
|
||
|
||
---
|
||
|
||
## 10. Infrastructure and Deployment
|
||
|
||
### Development Environment
|
||
|
||
```
|
||
docker/docker-compose.dev.yml
|
||
bamort-backend-dev (port 8180) — Go source mounted + Air live-reload
|
||
bamort-frontend-dev (port 5173) — Vue source mounted + Vite HMR
|
||
bamort-mariadb-dev (port 3306) — MariaDB with health-check
|
||
bamort-phpmyadmin-dev (port 8081) — Database GUI
|
||
```
|
||
|
||
- Full source code is volume-mounted into each container.
|
||
- Backend uses `air -c .air.toml` for hot-reload on `.go` file changes.
|
||
- Frontend uses Vite's native HMR for instant module replacement.
|
||
- Database data persists in `docker/bamort-db-dev/` on the host.
|
||
- Database is initialized from `docker/init-db/*.sql` on first container creation only.
|
||
|
||
### Production Environment
|
||
|
||
```
|
||
docker/docker-compose.yml
|
||
bamort-backend (port 8182→8180) — compiled Go binary, no source mount
|
||
bamort-frontend (port 8181→80) — Nginx serving pre-built Vue bundle
|
||
bamort-mariadb (not exposed) — MariaDB accessible only within Docker network
|
||
```
|
||
|
||
- Backend image: multi-stage build. Stage 1 (golang:1.25-alpine) compiles the binary. Stage 2 (alpine:3.23) installs Chromium and copies only the binary + templates.
|
||
- Frontend image: multi-stage. Stage 1 (node:22-alpine) runs `npm run build` with `VITE_*` build args baked in. Stage 2 (nginx:alpine) serves the static bundle.
|
||
- **`VITE_API_URL`** is a **compile-time** build argument — runtime env vars cannot change it after image build.
|
||
- Database port is intentionally not exposed in production.
|
||
- TLS termination is expected at an external reverse proxy (e.g., Traefik).
|
||
- Templates are volume-mounted: `./templates:/app/templates`.
|
||
|
||
### Starting/Stopping
|
||
|
||
```bash
|
||
# Development
|
||
cd /data/dev/bamort
|
||
./docker/start-dev.sh # builds + starts all dev containers
|
||
./docker/stop-dev.sh # stops and removes dev containers
|
||
|
||
# Production
|
||
./docker/start-prd.sh # builds new images while old containers run, then switches
|
||
./docker/stop-prd.sh # stops production containers
|
||
```
|
||
|
||
---
|
||
|
||
## 11. User Roles and Access Control
|
||
|
||
| Capability | standard | maintainer | admin |
|
||
|-----------|----------|-----------|-------|
|
||
| Register / login | ✓ | ✓ | ✓ |
|
||
| View own characters | ✓ | ✓ | ✓ |
|
||
| View public/shared characters | ✓ | ✓ | ✓ |
|
||
| Create/edit/delete own characters | ✓ | ✓ | ✓ |
|
||
| Learn/improve skills and spells | ✓ | ✓ | ✓ |
|
||
| Export characters to PDF | ✓ | ✓ | ✓ |
|
||
| Import/export characters (JSON/CSV) | ✓ | ✓ | ✓ |
|
||
| Read GSMaster data | ✓ | ✓ | ✓ |
|
||
| Write GSMaster data | ✗ | ✓ | ✓ |
|
||
| Maintenance operations | ✗ | ✓ | ✓ |
|
||
| List all users | ✗ | ✗ | ✓ |
|
||
| Change user roles | ✗ | ✗ | ✓ |
|
||
| Delete users | ✗ | ✗ | ✓ |
|
||
| Reset any user's password | ✗ | ✗ | ✓ |
|
||
|
||
**Enforcement mechanisms:**
|
||
- Backend: `user.AuthMiddleware()` on all `/api/*` routes
|
||
- Backend: `user.RequireMaintainer()` middleware on maintenance write routes
|
||
- Backend: `user.RequireAdmin()` middleware on `/api/users/*` routes
|
||
- Backend: `checkCharacterOwnership()` inside character handlers (returns 403 if user doesn't own the character)
|
||
- Frontend: router navigation guard redirects non-admins away from `/users`
|
||
- Frontend: `isOwner` prop controls visibility of edit/delete buttons in `CharacterDetails`
|
||
|
||
---
|
||
|
||
## 12. Internationalisation (i18n)
|
||
|
||
**Implementation:** `vue-i18n` (v11, composition-mode `legacy: false`)
|
||
|
||
**Locales:** `src/locales/de/` and `src/locales/en/` — JS module files (not JSON) exporting translation objects. Both must be updated when adding new UI strings.
|
||
|
||
**Default language:** `de` (German)
|
||
|
||
**Language selection precedence:**
|
||
1. User's stored preference in `localStorage.language`
|
||
2. User's `preferred_language` field from their profile (set on `fetchCurrentUser()`)
|
||
3. Fallback to `de`
|
||
|
||
**Language change flow:**
|
||
- User changes language in `UserProfileView` → `PUT /api/user/language`
|
||
- Frontend immediately sets `this.$i18n.locale` and updates `localStorage.language`
|
||
- On next login, `userStore.fetchCurrentUser()` sets locale from profile
|
||
|
||
**Usage in components:**
|
||
```vue
|
||
{{ $t('skill.name') }} <!-- in template -->
|
||
this.$t('error.notFound') <!-- in script -->
|
||
this.$te('help.faq3.question') <!-- existence check for dynamic FAQ -->
|
||
```
|
||
|
||
---
|
||
|
||
## 13. Known Limitations and Security Notes
|
||
|
||
### Authentication Security
|
||
|
||
> **Important:** The current token scheme has cryptographic weaknesses.
|
||
|
||
1. **MD5 token generation** — MD5 is not a cryptographic authentication scheme. The token is derived predictably from `username + createdAt`, without a secret key. An attacker with knowledge of when a user registered could forge tokens.
|
||
2. **No token signature verification** — `CheckToken` extracts the user ID from the token string and loads the user from the database; it does not verify that the token was actually issued by the server.
|
||
3. **MD5 password hashing** — Passwords are hashed with MD5 (not bcrypt/argon2). MD5 is unsuitable for password storage; it is fast enough for brute-force attacks and not salted.
|
||
4. **Token scope** — Tokens do not expire. Once issued, a token is valid indefinitely.
|
||
|
||
The bcrypt implementation is commented out in `user/handlers.go` and `user/model.go` — migration to bcrypt for future versions is anticipated but not yet active.
|
||
|
||
### Other Notes
|
||
|
||
- `backend/startserver.sh` contains hardcoded development credentials — this file should not be committed to public repositories.
|
||
- The PDF download endpoint (`/api/pdf/file/:filename`) is unauthenticated by design (to allow direct browser downloads) but relies on filename unpredictability for security.
|
||
- `FileUploadPage.vue` manually adds an `Authorization` header that is already added by the Axios interceptor — this is a redundancy, not a security issue.
|
||
- The production `DATABASE_URL` in `docker-compose.yml` is constructed inline without fallback defaults — missing env vars will produce an empty credentials DSN.
|