1363 lines
60 KiB
Markdown
1363 lines
60 KiB
Markdown
# BaMoRT Backend — Comprehensive Architecture & Code Findings
|
||
|
||
> Generated: 2026-03-13
|
||
> Source: `/home/de31a2/bamort/backend`
|
||
> Module: `bamort` (Go 1.24, toolchain 1.24.4)
|
||
|
||
---
|
||
|
||
## Table of Contents
|
||
|
||
1. [Module Structure Overview](#1-module-structure-overview)
|
||
2. [Entry Point — cmd/main.go](#2-entry-point--cmdmaingo)
|
||
3. [Router & Middleware Setup](#3-router--middleware-setup)
|
||
4. [Authentication & Authorization](#4-authentication--authorization)
|
||
5. [Configuration](#5-configuration)
|
||
6. [Database Layer](#6-database-layer)
|
||
7. [Models (GORM Entities)](#7-models-gorm-entities)
|
||
8. [Module-by-Module Analysis](#8-module-by-module-analysis)
|
||
- [user/](#81-user)
|
||
- [character/](#82-character)
|
||
- [equipment/](#83-equipment)
|
||
- [gsmaster/](#84-gsmaster)
|
||
- [gamesystem/](#85-gamesystem)
|
||
- [maintenance/](#86-maintenance)
|
||
- [pdfrender/](#87-pdfrender)
|
||
- [importer/](#88-importer)
|
||
- [transfer/](#89-transfer)
|
||
- [appsystem/](#810-appsystem)
|
||
- [logger/](#811-logger)
|
||
- [mail/](#812-mail)
|
||
- [router/](#813-router)
|
||
- [config/](#814-config)
|
||
- [database/](#815-database)
|
||
- [testutils/](#816-testutils)
|
||
- [api/](#817-api)
|
||
9. [Complete API Route Listing](#9-complete-api-route-listing)
|
||
10. [Testing Approach](#10-testing-approach)
|
||
11. [Key Dependencies](#11-key-dependencies)
|
||
12. [Security Observations](#12-security-observations)
|
||
13. [Cross-Module Relationships Diagram](#13-cross-module-relationships-diagram)
|
||
|
||
---
|
||
|
||
## 1. Module Structure Overview
|
||
|
||
```
|
||
backend/
|
||
├── cmd/ # Entry point (main.go)
|
||
├── config/ # App configuration loading from env/.env
|
||
├── database/ # DB connection, migrations, test helpers
|
||
├── models/ # All GORM entities and migration orchestration
|
||
├── router/ # Gin engine setup, CORS, base route group + JWT guard
|
||
├── user/ # User accounts, auth, roles, password reset
|
||
├── character/ # Core character CRUD, skills, spells, learning system
|
||
├── equipment/ # Character equipment (weapons, containers, gear)
|
||
├── gsmaster/ # Game-system master data (skills, spells, weapons, learning costs)
|
||
├── gamesystem/ # Game system record management and initial seed data
|
||
├── maintenance/ # Admin/maintainer ops – DB checks, migrations, scratchpad endpoints
|
||
├── pdfrender/ # HTML→PDF export using chromedp + pdfcpu merge
|
||
├── importer/ # VTT-JSON / CSV import and export of characters/spells
|
||
├── transfer/ # JSON-based character and full-database import/export
|
||
├── appsystem/ # Version and system-info endpoints
|
||
├── logger/ # Custom leveled logger (DEBUG/INFO/WARN/ERROR)
|
||
├── mail/ # SMTP email client (password reset emails)
|
||
├── api/ # Integration tests spanning multiple modules
|
||
├── testutils/ # Shared test environment setup helpers
|
||
├── testdata/ # Prepared SQLite snapshot used by all tests
|
||
├── templates/ # PDF HTML templates (Default_A4_Quer/)
|
||
└── scripts/ # Shell scripts (e.g., SQLite→MariaDB transfer)
|
||
```
|
||
|
||
Each domain module follows the pattern:
|
||
|
||
```
|
||
module/
|
||
handlers.go HTTP handler functions (Gin controllers)
|
||
routes.go RegisterRoutes(r *gin.RouterGroup)
|
||
*_test.go Tests using setupTestEnvironment(t)
|
||
```
|
||
|
||
---
|
||
|
||
## 2. Entry Point — cmd/main.go
|
||
|
||
**File:** `cmd/main.go`
|
||
|
||
### Startup Sequence
|
||
|
||
```go
|
||
func main() {
|
||
cfg := config.Cfg // 1. Load config (auto-loaded in init())
|
||
|
||
logger.SetDebugMode(cfg.DebugMode) // 2. Configure logger
|
||
logger.SetMinLogLevel(...)
|
||
|
||
if cfg.IsProduction() {
|
||
gin.SetMode(gin.ReleaseMode) // 3. Set Gin mode
|
||
}
|
||
|
||
database.ConnectDatabase() // 4. Connect to database
|
||
|
||
pdfrender.InitializeTemplates(...) // 5. Sync PDF templates
|
||
|
||
r := gin.Default()
|
||
router.SetupGin(r) // 6. CORS middleware
|
||
|
||
protected := router.BaseRouterGrp(r) // 7. Public auth routes + JWT-protected group
|
||
|
||
// 8. Register module routes
|
||
user.RegisterRoutes(protected)
|
||
gsmaster.RegisterRoutes(protected)
|
||
character.RegisterRoutes(protected)
|
||
equipment.RegisterRoutes(protected)
|
||
maintenance.RegisterRoutes(protected)
|
||
importer.RegisterRoutes(protected)
|
||
pdfrender.RegisterRoutes(protected)
|
||
transfer.RegisterRoutes(protected)
|
||
appsystem.RegisterRoutes(protected)
|
||
|
||
// 9. Public routes (no auth)
|
||
pdfrender.RegisterPublicRoutes(r)
|
||
appsystem.RegisterPublicRoutes(r)
|
||
|
||
r.Run(cfg.GetServerAddress()) // 10. Start HTTP server
|
||
}
|
||
```
|
||
|
||
### Swagger Annotations
|
||
The file contains `@title BaMoRT API`, `@version 1`, `@host localhost:8180` – indicates Swagger/OpenAPI doc generation is intended.
|
||
|
||
---
|
||
|
||
## 3. Router & Middleware Setup
|
||
|
||
### CORS (`router/setup.go`)
|
||
|
||
```go
|
||
func SetupGin(r *gin.Engine) {
|
||
allowedOrigins := []string{
|
||
config.Cfg.FrontendURL,
|
||
"http://localhost:5173",
|
||
"http://192.168.0.48:5173",
|
||
"http://192.168.0.36:5173",
|
||
"https://bamort.trokan.de",
|
||
}
|
||
r.Use(cors.New(cors.Config{
|
||
AllowOrigins: allowedOrigins,
|
||
AllowMethods: []string{"GET", "POST", "PUT", "PATCH", "DELETE"},
|
||
AllowHeaders: []string{"Origin", "Content-Type", "Authorization"},
|
||
ExposeHeaders: []string{"Content-Length"},
|
||
AllowCredentials: true,
|
||
MaxAge: 12 * 3600,
|
||
}))
|
||
}
|
||
```
|
||
|
||
### Route Groups (`router/routes.go`)
|
||
|
||
**Public (unauthenticated) routes:**
|
||
|
||
| Method | Path | Handler |
|
||
|--------|------|---------|
|
||
| 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` |
|
||
|
||
**Protected group:** `/api` — guarded by `user.AuthMiddleware()`.
|
||
|
||
---
|
||
|
||
## 4. Authentication & Authorization
|
||
|
||
### Token Scheme
|
||
|
||
The project uses a **custom MD5-based token**, not JWT.
|
||
|
||
**Token Generation (`user/handlers.go`):**
|
||
```go
|
||
func GenerateToken(u *User) string {
|
||
tx := md5.Sum([]byte(u.Username + u.CreatedAt.String()))
|
||
hashString := hex.EncodeToString(tx[:])
|
||
// Insert ".{userID}:" at position 7
|
||
pos := 7
|
||
idm := "." + fmt.Sprintf("%d", u.UserID) + ":"
|
||
token := hashString[:pos] + idm + hashString[pos:]
|
||
return token
|
||
}
|
||
```
|
||
|
||
The token embeds the user ID in a predictable location. Clients send it as the `Authorization` header value (prefixed `Bearer `).
|
||
|
||
**Token Validation (`CheckToken`):**
|
||
1. Extracts user ID from position `7 + len("Bearer ")` in the `Authorization` header.
|
||
2. Loads the user from the database by that ID.
|
||
3. Returns the user or `nil`.
|
||
|
||
**No cryptographic signature is verified.** Any token with a valid user ID at the expected position is accepted.
|
||
|
||
### `AuthMiddleware()` (`user/handlers.go`)
|
||
|
||
Sets `userID`, `username`, and `user` in the Gin context for downstream handlers.
|
||
|
||
### Password Hashing
|
||
|
||
Passwords are hashed with **MD5** (not bcrypt):
|
||
```go
|
||
hashedPassword := md5.Sum([]byte(user.PasswordHash))
|
||
user.PasswordHash = hex.EncodeToString(hashedPassword[:])
|
||
```
|
||
The bcrypt implementation is commented out.
|
||
|
||
### Roles (`user/model.go`)
|
||
|
||
| Constant | Value | Description |
|
||
|----------|-------|-------------|
|
||
| `RoleStandardUser` | `"standard"` | Default for new registrations |
|
||
| `RoleMaintainer` | `"maintainer"` | Can manage master data |
|
||
| `RoleAdmin` | `"admin"` | Can manage users |
|
||
|
||
Role-checking middleware:
|
||
- `RequireAdmin()` → used on `/api/users/*` admin endpoints
|
||
- `RequireMaintainer()` → used on maintenance/gsmaster write endpoints
|
||
|
||
---
|
||
|
||
## 5. Configuration
|
||
|
||
**File:** `config/config.go`
|
||
|
||
### `Config` Struct Fields
|
||
|
||
| Field | Env Var(s) | Default | Description |
|
||
|-------|-----------|---------|-------------|
|
||
| `ServerPort` | `API_PORT`, `SERVER_PORT` | `"8180"` | HTTP listen port |
|
||
| `DatabaseURL` | `DATABASE_URL` | `""` | DSN string for DB driver |
|
||
| `DatabaseType` | `DATABASE_TYPE` | `"mysql"` | `mysql` or `sqlite` |
|
||
| `DebugMode` | `DEBUG` | `false` | Enables verbose logging |
|
||
| `LogLevel` | `LOG_LEVEL` | `"INFO"` | `DEBUG`, `INFO`, `WARN`, `ERROR` |
|
||
| `Environment` | `ENVIRONMENT`, `GO_ENV` | `"production"` | `production`, `development`, `test` |
|
||
| `DevTesting` | `DEVTESTING` | `"no"` | `"yes"` redirects to SQLite test DB |
|
||
| `FrontendURL` | `BASE_URL` | `"http://localhost:5173"` | CORS allowed origin |
|
||
| `TemplatesDir` | `TEMPLATES_DIR` | `"./templates"` | PDF template directory |
|
||
| `ExportTempDir` | `EXPORT_TEMP_DIR` | `"./xporttemp"` | Temporary PDF output directory |
|
||
| `MailHost` | `MAIL_HOST` | `""` | SMTP server hostname |
|
||
| `MailPort` | `MAIL_PORT` | `465` | SMTP port (465=TLS, 587=STARTTLS) |
|
||
| `MailUsername` | `MAIL_USERNAME` | `""` | SMTP credentials |
|
||
| `MailPassword` | `MAIL_PASSWORD` | `""` | SMTP credentials |
|
||
| `MailFrom` | `MAIL_FROM` | (falls back to username) | Sender email address |
|
||
|
||
### Loading Logic
|
||
|
||
1. Reads `.env` and `.env.local` files from the working directory.
|
||
2. Overrides with env vars.
|
||
3. `ENVIRONMENT=development` automatically enables `DebugMode=true` and `LogLevel=DEBUG`.
|
||
4. `ENVIRONMENT=test` or `DEVTESTING=yes` causes `ConnectDatabase()` to use the SQLite test snapshot.
|
||
|
||
---
|
||
|
||
## 6. Database Layer
|
||
|
||
**Package:** `database/`
|
||
|
||
### Connection (`database/config.go`)
|
||
|
||
```go
|
||
func ConnectDatabase() *gorm.DB {
|
||
if envIsTest || devTestingIsYes {
|
||
SetupTestDB() // Use prepared SQLite snapshot
|
||
} else {
|
||
ConnectDatabaseOrig() // MySQL or SQLite via DATABASE_URL
|
||
}
|
||
return DB
|
||
}
|
||
```
|
||
|
||
Supported drivers: `mysql` (default), `sqlite`.
|
||
|
||
### Global Variable
|
||
|
||
`database.DB *gorm.DB` — all packages import this directly.
|
||
|
||
### Test Database
|
||
|
||
- **Source:** `testdata/prepared_test_data.db` (SQLite snapshot with real test data including character ID 18 "Fanjo Vetrani")
|
||
- **Mechanism:** `SetupTestDB()` copies the snapshot to a `os.MkdirTemp` directory, opens it, and assigns it to `database.DB`.
|
||
- Each test run gets a fresh isolated copy.
|
||
|
||
### Migration Tables (`database/model.go`)
|
||
|
||
| Table | Purpose |
|
||
|-------|---------|
|
||
| `schema_version` | Current schema version tracking |
|
||
| `migration_history` | Log of applied migrations |
|
||
|
||
### Migration Orchestration (`models/database.go`)
|
||
|
||
`models.MigrateStructure(db)` calls in order:
|
||
1. `gameSystemMigrateStructure` — `game_systems`
|
||
2. `gsMasterMigrateStructure` — skills, spells, equipment, weapons, etc.
|
||
3. `characterMigrateStructure` — characters, skills, spells, equipment
|
||
4. `equipmentMigrateStructure`
|
||
5. `skillsMigrateStructure` — learning cost tables
|
||
6. `importerMigrateStructure` — (currently no-op)
|
||
7. `learningMigrateStructure` — all learning/cost relation tables
|
||
|
||
### `StringArray` Custom Type
|
||
|
||
```go
|
||
type StringArray []string
|
||
// Stored as JSON in TEXT column (Value+Scan implementations)
|
||
```
|
||
|
||
Used for `Char.Spezialisierung` (character specializations).
|
||
|
||
---
|
||
|
||
## 7. Models (GORM Entities)
|
||
|
||
### Base Types
|
||
|
||
```go
|
||
type BamortBase struct {
|
||
ID uint `gorm:"primaryKey"`
|
||
Name string
|
||
}
|
||
|
||
type BamortCharTrait struct {
|
||
BamortBase
|
||
CharacterID uint `gorm:"index"`
|
||
UserID uint `gorm:"index"`
|
||
}
|
||
|
||
type Magisch struct {
|
||
IstMagisch bool
|
||
Abw int
|
||
Ausgebrannt bool
|
||
}
|
||
```
|
||
|
||
---
|
||
|
||
### User Model (`user/model.go` → table: `users`)
|
||
|
||
| Field | Type | Notes |
|
||
|-------|------|-------|
|
||
| `UserID` | uint PK | |
|
||
| `Username` | string unique | |
|
||
| `DisplayName` | string | Falls back to Username if empty |
|
||
| `PasswordHash` | string | MD5 hex string |
|
||
| `Email` | string unique | |
|
||
| `Role` | string | `standard`, `maintainer`, `admin` |
|
||
| `PreferredLanguage` | string | default `de` |
|
||
| `ResetPwHash` | *string | Hidden from JSON serialization |
|
||
| `ResetPwHashExpires` | *time.Time | 14 days validity |
|
||
| `CreatedAt`, `UpdatedAt` | time.Time | |
|
||
|
||
---
|
||
|
||
### Character Model (`models/model_character.go` → table: `char_chars`)
|
||
|
||
**Main struct `Char`:**
|
||
|
||
| Field | Type | Notes |
|
||
|-------|------|-------|
|
||
| `ID` | uint PK | (via BamortBase) |
|
||
| `Name` | string | Character name |
|
||
| `GameSystem` | string | e.g. `"midgard"` |
|
||
| `GameSystemId` | uint | FK to `game_systems` |
|
||
| `UserID` | uint index | FK to `users` |
|
||
| `User` | User | FK `UserID→users.user_id` CASCADE |
|
||
| `Rasse` | string | Race (e.g. "Mensch", "Zwerg") |
|
||
| `Typ` | string | Class code (e.g. "Kr", "Ma") |
|
||
| `Alter` | int | Age |
|
||
| `Anrede` | string | Title/salutation |
|
||
| `Grad` | int | Character grade/level |
|
||
| `Gender` | string | |
|
||
| `SocialClass` | string | |
|
||
| `Groesse` | int | Height (cm) |
|
||
| `Gewicht` | int | Weight (kg) |
|
||
| `Herkunft` | string | Origin/homeland |
|
||
| `Glaube` | string | Faith |
|
||
| `Hand` | string | Dominant hand |
|
||
| `Public` | bool | Publicly visible |
|
||
| `ResistenzKoerper` | int | Body resistance (derived) |
|
||
| `ResistenzGeist` | int | Mental resistance (derived) |
|
||
| `Abwehr` | int | Defense value (derived) |
|
||
| `Zaubern` | int | Spell casting value (derived) |
|
||
| `Raufen` | int | Brawling value (derived) |
|
||
| `Lp` | Lp | Life points (has-one, CASCADE) |
|
||
| `Ap` | Ap | Action points (has-one) |
|
||
| `B` | B | Load/burden (has-one) |
|
||
| `Merkmale` | Merkmale | Physical traits (has-one) |
|
||
| `Eigenschaften` | []Eigenschaft | Attributes Au/Gs/Gw/In/Ko/St/Wk/Zt/PA |
|
||
| `Fertigkeiten` | []SkFertigkeit | Skills (has-many) |
|
||
| `Waffenfertigkeiten` | []SkWaffenfertigkeit | Weapon skills |
|
||
| `Zauber` | []SkZauber | Spells |
|
||
| `Spezialisierung` | StringArray | TEXT/JSON stored |
|
||
| `Bennies` | Bennies | Gg/Gp/Sg |
|
||
| `Vermoegen` | Vermoegen | GS/SS/KS wealth |
|
||
| `Erfahrungsschatz` | Erfahrungsschatz | EP/ES experience |
|
||
| `Waffen` | []EqWaffe | Weapons (has-many) |
|
||
| `Behaeltnisse` | []EqContainer | Containers |
|
||
| `Transportmittel` | []EqContainer | Transportation |
|
||
| `Ausruestung` | []EqAusruestung | General equipment |
|
||
| `Image` | string | Base64 or URL |
|
||
|
||
**Helper types:**
|
||
|
||
| Type | Table | Fields |
|
||
|------|-------|--------|
|
||
| `Eigenschaft` | (embedded in preload) | ID, CharacterID, UserID, Name, Value |
|
||
| `Lp` | — | ID, CharacterID, Max, Value |
|
||
| `Ap` | — | ID, CharacterID, Max, Value |
|
||
| `B` | — | ID, CharacterID, Max, Value |
|
||
| `Merkmale` | — | BamortCharTrait + Augenfarbe/Haarfarbe/Sonstige/Breite/Groesse |
|
||
| `Erfahrungsschatz` | — | BamortCharTrait + ES, EP |
|
||
| `Bennies` | — | BamortCharTrait + Gg, Gp, Sg |
|
||
| `Vermoegen` | — | BamortCharTrait + Goldstuecke, Silberstuecke, Kupferstuecke |
|
||
|
||
**`FeChar`** (frontend-facing):
|
||
Embeds `Char` + `Git` (gift tolerance = 30+Ko/2) + `CategorizedSkills` map + `InnateSkills` slice.
|
||
|
||
---
|
||
|
||
### Skill Models (`models/model_char_skills.go`)
|
||
|
||
| Type | Table | Key Fields |
|
||
|------|-------|------------|
|
||
| `SkFertigkeit` | `char_skills` | BamortCharTrait + Fertigkeitswert, BasisWert, Bonus, Pp, Category, Improvable, LearningCost |
|
||
| `SkWaffenfertigkeit` | `char_weaponskills` | Embeds SkFertigkeit |
|
||
| `SkAngeboreneFertigkeit` | (no own table) | Embeds SkFertigkeit |
|
||
| `SkZauber` | `char_spells` | BamortCharTrait + Beschreibung, Bonus, Quelle |
|
||
|
||
---
|
||
|
||
### Equipment Models (`models/model_char_equipment.go`)
|
||
|
||
| Type | Table | Key Fields |
|
||
|------|-------|------------|
|
||
| `EqAusruestung` | `equi_equipments` | BamortCharTrait + Magisch + Anzahl, ContainedIn, Bonus, Gewicht, Wert |
|
||
| `EqWaffe` | `equi_weapons` | BamortCharTrait + Magisch + Abwb, Anb, Anzahl, Schb, NameFuerSpezialisierung |
|
||
| `EqContainer` | `equi_containers` | BamortCharTrait + Magisch + IsTransportation, Tragkraft, Volumen, ExtID |
|
||
|
||
---
|
||
|
||
### GSMaster Models (`models/model_gsmaster.go`)
|
||
|
||
| Type | Table | Key Fields |
|
||
|------|-------|------------|
|
||
| `Skill` | `gsm_skills` | ID, GameSystem/Id, Name, Initialwert, BasisWert, Bonuseigenschaft, Improvable, InnateSkill, Category, Difficulty |
|
||
| `WeaponSkill` | (inherits Skill) | Embeds Skill |
|
||
| `Spell` | (via gsmaster) | ID, Name, Stufe, AP, Art, Zauberdauer, Reichweite, Ursprung, Category, LearningCategory |
|
||
| `Equipment` | (via gsmaster) | ID, Name, Gewicht, Wert, PersonalItem |
|
||
| `Weapon` | (extends Equipment) | SkillRequired, Damage, RangeNear/Middle/Far |
|
||
| `Container` | (extends Equipment) | Tragkraft, Volumen |
|
||
| `Transportation` | (extends Container) | — |
|
||
| `Believe` | (via gsmaster) | ID, GameSystem/Id, Name, Beschreibung, SourceID |
|
||
| `MiscLookup` | `gsm_misc_lookups` | ID, GameSystem/Id, Key, Value |
|
||
| `LookupList` | `gsm_lookup_lists` | Generic lookup |
|
||
| `LearnCost` | (computed, no own table) | GameSystem, Stufe, LE, TE, Ep, Money, PP |
|
||
|
||
---
|
||
|
||
### Game System (`models/model_game_system.go` → table: `game_systems`)
|
||
|
||
| Field | Notes |
|
||
|-------|-------|
|
||
| `Code` | Unique, e.g. `"M5"` |
|
||
| `Name` | e.g. `"M-System"` |
|
||
| `IsActive` | bool |
|
||
|
||
Default game system: Code=`M5`, Name=`M-System`.
|
||
|
||
---
|
||
|
||
### Learning-Cost Models (`models/model_learning_costs.go`)
|
||
|
||
| Type | Table | Purpose |
|
||
|------|-------|---------|
|
||
| `Source` | `gsm_lit_sources` | Rulebook/sourcebook references |
|
||
| `CharacterClass` | `gsm_character_classes` | Class codes (Kr, Ma, Hx …) + source link |
|
||
| `SkillCategory` | `learning_skill_categories` | Alltag, Freiland, Halbwelt, Kampf, Körper, Sozial, Unterwelt, Waffen, Wissen |
|
||
| `SkillDifficulty` | `learning_skill_difficulties` | leicht, normal, schwer, sehr schwer |
|
||
| `SpellSchool` | `learning_spell_schools` | Beherrschen, Bewegen, Erkennen, … |
|
||
| `ClassCategoryEPCost` | `learning_class_category_ep_costs` | EP per TE by class+category |
|
||
| `ClassSpellSchoolEPCost` | `learning_class_spell_school_ep_costs` | EP per LE by class+school |
|
||
| `SpellLevelLECost` | `learning_spell_level_le_costs` | LE required by spell level |
|
||
| `SkillCategoryDifficulty` | `learning_skill_category_difficulties` | LE cost for skill in category+difficulty |
|
||
| `WeaponSkillCategoryDifficulty` | (separate table) | LE cost for weapon skills |
|
||
| `SkillImprovementCost` | `learning_skill_improvement_costs` | TE to improve by current level |
|
||
| `ClassCategoryLearningPoints` | — | Starting LP per class+category for creation |
|
||
| `AuditLogEntry` | `audit_log_entries`(?) | EP/gold change audit trail |
|
||
|
||
---
|
||
|
||
### Character Creation Session (`models/model_character_creation.go`)
|
||
|
||
| Type | Table | Purpose |
|
||
|------|-------|---------|
|
||
| `CharacterCreationSession` | `char_creation_sessions` | Multi-step wizard persistence |
|
||
|
||
Fields: UserID, Name, Rasse, Typ, Herkunft, Stand, Glaube, Attributes (JSON), DerivedValues (JSON), Skills (JSON), Spells (JSON), SkillPoints (JSON), CurrentStep, ExpiresAt.
|
||
|
||
---
|
||
|
||
### Character Share (`models/model_character_share.go` → table: `char_shares`)
|
||
|
||
| Field | Notes |
|
||
|-------|-------|
|
||
| `CharacterID` | FK to char_chars |
|
||
| `UserID` | FK to users (recipient) |
|
||
| `Permission` | `"read"` or `"write"` |
|
||
|
||
---
|
||
|
||
### Database Schema/Migration Models (`database/model.go`)
|
||
|
||
| Type | Table |
|
||
|------|-------|
|
||
| `SchemaVersion` | `schema_version` |
|
||
| `MigrationHistory` | `migration_history` |
|
||
|
||
---
|
||
|
||
## 8. Module-by-Module Analysis
|
||
|
||
---
|
||
|
||
### 8.1 `user/`
|
||
|
||
**Purpose:** User account management, authentication, authorization, password reset.
|
||
|
||
**Key Files:**
|
||
- `model.go` — User struct, CRUD methods, reset-hash helpers
|
||
- `handlers.go` — RegisterUser, LoginUser, CheckToken, AuthMiddleware, password reset
|
||
- `admin_handlers.go` — ListUsers, GetUser, UpdateUserRole, ChangeUserPassword, DeleteUser
|
||
- `middleware.go` — RequireRole, RequireAdmin, RequireMaintainer
|
||
- `database.go` — `MigrateStructure` for `users` table
|
||
- `routes.go` — Route registration
|
||
|
||
**Business Logic:**
|
||
- Registration creates users with MD5-hashed passwords and a default `standard` role.
|
||
- Login returns a custom MD5 token containing the encoded user ID.
|
||
- Password reset uses a 32-byte random hex hash (secure) with a 14-day expiry, sent via SMTP.
|
||
- `AuthMiddleware` validates the token structure, loads the user from DB, injects `userID`/`username`/`user` into context.
|
||
|
||
**HTTP Endpoints (protected unless noted):**
|
||
|
||
| Method | Path | Handler | Auth level |
|
||
|--------|------|---------|-----------|
|
||
| POST | `/register` | `RegisterUser` | Public |
|
||
| POST | `/login` | `LoginUser` | Public |
|
||
| POST | `/password-reset/request` | `RequestPasswordReset` | Public |
|
||
| GET | `/password-reset/validate/:token` | `ValidateResetToken` | Public |
|
||
| POST | `/password-reset/reset` | `ResetPassword` | Public |
|
||
| GET | `/api/user/profile` | `GetUserProfile` | Auth |
|
||
| PUT | `/api/user/display-name` | `UpdateDisplayName` | Auth |
|
||
| PUT | `/api/user/email` | `UpdateEmail` | Auth |
|
||
| PUT | `/api/user/password` | `UpdatePassword` | Auth |
|
||
| PUT | `/api/user/language` | `UpdateLanguage` | Auth |
|
||
| GET | `/api/users` | `ListUsers` | Admin |
|
||
| GET | `/api/users/:id` | `GetUser` | Admin (own profile also allowed) |
|
||
| PUT | `/api/users/:id/role` | `UpdateUserRole` | Admin |
|
||
| PUT | `/api/users/:id/password` | `ChangeUserPassword` | Admin |
|
||
| DELETE | `/api/users/:id` | `DeleteUser` | Admin |
|
||
|
||
---
|
||
|
||
### 8.2 `character/`
|
||
|
||
**Purpose:** Core character management — CRUD, skill/spell learning system, character creation wizard, audit logging, sharing, practice points, PDF-relevant data prep.
|
||
|
||
**Key Files:**
|
||
- `handlers.go` — ListCharacters, CreateCharacter, GetCharacter, UpdateCharacter, DeleteCharacter, ToFeChar, UpdateCharacterImage, GetDatasheetOptions
|
||
- `lerncost_handler.go` — `GetLernCostNewSystem`, `ImproveSkill`, `LearnSkill`, `LearnSpell` — full learning economy
|
||
- `derived_values_calculator.go` — `CalculateStaticFields`, `CalculateRolledField` — character creation math
|
||
- `creation_rules.go` — `GetSpecialAbilityByRoll` — special ability table
|
||
- `audit_log.go` — `CreateAuditLogEntry`, `GetAuditLogForCharacter/Field`
|
||
- `share_handlers.go` — `GetCharacterShares`, `UpdateCharacterShares`, `GetAvailableUsersForSharing`
|
||
- `practice_points_handler.go` — `GetPracticePoints`, `UpdatePracticePoints`, `AddPracticePoint`, `UsePracticePoint`
|
||
- `skill_type_helper.go` — skill categorization helpers
|
||
- `system_information_handlers.go` — `GetSkillCategoriesHandlerStatic`
|
||
- `spell_utils.go` — spell availability and creation utilities
|
||
- `database.go` — `SaveCharacterToDB`
|
||
- `image_handler.go` — character image upload/handling
|
||
|
||
**Business Logic:**
|
||
- `ListCharacters`: Returns `{ self_owned: [], others: [] }` where `others` = public + shared.
|
||
- `GetCharacter`: Returns `FeChar` (augmented view with categorized skills).
|
||
- `toFeChar`: Adds bonus field from attributes, splits skills by category.
|
||
- `checkCharacterOwnership`: Guards update/delete/share with `character.UserID == c.GetUint("userID")`.
|
||
- **Learning System** (`GetLernCostNewSystem`): Calculates LE/TE/EP/Money cost for learning or improving a skill/spell/weapon. Considers: character class, skill category, difficulty, current level, practice points (PP) reductions, gold-for-EP conversion, reward types (free learning, half EP).
|
||
- **Derived Values**: `CalculateStaticFieldsLogic` implements Midgard 5th edition formulae for Ausdauer-, Schadens-, Angriffs-, Abwehr-, Zauber-Bonus, Resistenz, Abwehr, Zaubern, Raufen.
|
||
|
||
**HTTP Endpoints (`/api/characters/...`):**
|
||
|
||
| Method | Path | Handler |
|
||
|--------|------|---------|
|
||
| GET | `/api/characters` | `ListCharacters` |
|
||
| POST | `/api/characters` | `CreateCharacter` |
|
||
| GET | `/api/characters/:id` | `GetCharacter` |
|
||
| PUT/PATCH | `/api/characters/:id` | `UpdateCharacter` |
|
||
| DELETE | `/api/characters/:id` | `DeleteCharacter` |
|
||
| PUT | `/api/characters/:id/image` | `UpdateCharacterImage` |
|
||
| GET | `/api/characters/:id/datasheet-options` | `GetDatasheetOptions` |
|
||
| GET | `/api/characters/:id/shares` | `GetCharacterShares` |
|
||
| PUT | `/api/characters/:id/shares` | `UpdateCharacterShares` |
|
||
| GET | `/api/characters/:id/available-users` | `GetAvailableUsersForSharing` |
|
||
| GET | `/api/characters/:id/experience-wealth` | `GetCharacterExperienceAndWealth` |
|
||
| PUT | `/api/characters/:id/experience` | `UpdateCharacterExperience` |
|
||
| PUT | `/api/characters/:id/wealth` | `UpdateCharacterWealth` |
|
||
| GET | `/api/characters/:id/audit-log` | `GetCharacterAuditLog` |
|
||
| GET | `/api/characters/:id/audit-log/stats` | `GetAuditLogStats` |
|
||
| POST | `/api/characters/lerncost-new` | `GetLernCostNewSystem` |
|
||
| POST | `/api/characters/lerncost` | `GetLernCostNewSystem` |
|
||
| POST | `/api/characters/improve-skill-new` | `ImproveSkill` |
|
||
| POST | `/api/characters/improve-skill` | `ImproveSkill` |
|
||
| POST | `/api/characters/:id/learn-skill-new` | `LearnSkill` |
|
||
| POST | `/api/characters/:id/learn-skill` | `LearnSkill` |
|
||
| POST | `/api/characters/:id/learn-spell-new` | `LearnSpell` |
|
||
| POST | `/api/characters/:id/learn-spell` | `LearnSpell` |
|
||
| POST | `/api/characters/available-skills-new` | `GetAvailableSkillsNewSystem` |
|
||
| POST | `/api/characters/available-skills` | `GetAvailableSkillsNewSystem` |
|
||
| POST | `/api/characters/available-skills-creation` | `GetAvailableSkillsForCreation` |
|
||
| POST | `/api/characters/available-spells-creation` | `GetAvailableSpellsForCreation` |
|
||
| POST | `/api/characters/available-spells-new` | `GetAvailableSpellsNewSystem` |
|
||
| POST | `/api/characters/available-spells` | `GetAvailableSpellsNewSystem` |
|
||
| GET | `/api/characters/spell-details` | `GetSpellDetails` |
|
||
| GET | `/api/characters/:id/reward-types` | `GetRewardTypesStatic` |
|
||
| GET | `/api/characters/:id/practice-points` | `GetPracticePoints` |
|
||
| PUT | `/api/characters/:id/practice-points` | `UpdatePracticePoints` |
|
||
| POST | `/api/characters/:id/practice-points/add` | `AddPracticePoint` |
|
||
| POST | `/api/characters/:id/practice-points/use` | `UsePracticePoint` |
|
||
| GET | `/api/characters/skill-categories` | `GetSkillCategoriesHandlerStatic` |
|
||
| GET | `/api/characters/create-sessions` | `ListCharacterSessions` |
|
||
| POST | `/api/characters/create-session` | `CreateCharacterSession` |
|
||
| GET | `/api/characters/create-session/:sessionId` | `GetCharacterSession` |
|
||
| PUT | `/api/characters/create-session/:sessionId/basic` | `UpdateCharacterBasicInfo` |
|
||
| PUT | `/api/characters/create-session/:sessionId/attributes` | `UpdateCharacterAttributes` |
|
||
| PUT | `/api/characters/create-session/:sessionId/derived` | `UpdateCharacterDerivedValues` |
|
||
| PUT | `/api/characters/create-session/:sessionId/skills` | `UpdateCharacterSkills` |
|
||
| POST | `/api/characters/create-session/:sessionId/finalize` | `FinalizeCharacterCreation` |
|
||
| DELETE | `/api/characters/create-session/:sessionId` | `DeleteCharacterSession` |
|
||
| GET | `/api/characters/races` | `GetRaces` |
|
||
| GET | `/api/characters/classes` | `GetCharacterClasses` |
|
||
| GET | `/api/characters/classes/learning-points` | `GetCharacterClassLearningPoints` |
|
||
| GET | `/api/characters/origins` | `GetOrigins` |
|
||
| GET | `/api/characters/beliefs` | `SearchBeliefs` |
|
||
| POST | `/api/characters/calculate-static-fields` | `CalculateStaticFields` |
|
||
| POST | `/api/characters/calculate-rolled-field` | `CalculateRolledField` |
|
||
|
||
---
|
||
|
||
### 8.3 `equipment/`
|
||
|
||
**Purpose:** CRUD for character-owned equipment items and weapons.
|
||
|
||
**Key Files:** `handlers.go`, `routes.go`
|
||
|
||
**Business Logic:**
|
||
- `checkEquipmentOwnership`: Loads character by ID, compares `character.UserID` to logged-in user.
|
||
- All operations (CRUD) filter/verify ownership before executing DB writes.
|
||
|
||
**HTTP Endpoints:**
|
||
|
||
| Method | Path | Handler |
|
||
|--------|------|---------|
|
||
| POST | `/api/equipment` | `CreateAusruestung` |
|
||
| GET | `/api/equipment/character/:character_id` | `ListAusruestung` |
|
||
| PUT | `/api/equipment/:ausruestung_id` | `UpdateAusruestung` |
|
||
| DELETE | `/api/equipment/:ausruestung_id` | `DeleteAusruestung` |
|
||
| POST | `/api/weapons` | `CreateWaffe` |
|
||
| GET | `/api/weapons/character/:character_id` | `ListWaffen` |
|
||
| PUT | `/api/weapons/:waffe_id` | `UpdateWaffe` |
|
||
| DELETE | `/api/weapons/:waffe_id` | `DeleteWaffe` |
|
||
|
||
---
|
||
|
||
### 8.4 `gsmaster/`
|
||
|
||
**Purpose:** Game-system master data management — the reference library for skills, weapon skills, spells, equipment, weapons, and learning costs.
|
||
|
||
**Key Files:**
|
||
- `handlers.go` — Generic get/add/update/delete using Go generics (`getMDItem[T]`, `getMDItems[T]`, etc.)
|
||
- `routes.go` — Public read endpoints + maintainer-guarded write endpoints
|
||
- `learning_costs.go` — `LearningCostsTable` with full EP/TE cost tables for all 15 character classes
|
||
- `learning_costs_init.go` — `ValidateLearningCostsData`, `GetLearningCostsSummary`
|
||
- `levelup.go` — `CalculateImprovementCost`, `CalculateSkillImprovementCost`
|
||
- `game_system.go` — Game system lookup helper
|
||
- `misclookup.go` — `GetMiscLookupByKey`, `GetMiscLookupByKeyForSystem` (races, faiths, origins, etc.)
|
||
- `skill_enhanced_handlers.go`, `spell_enhanced_handlers.go`, `weapon_enhanced_handlers.go`, `weapon_skill_enhanced_handlers.go`, `equipment_enhanced_handlers.go` — Enhanced CRUD with source/difficulty enrichment
|
||
|
||
**Business Logic:**
|
||
- Learning costs are stored both as hardcoded Go maps (in `learning_costs.go`, 15 classes × 8-9 categories) and in the database (`ClassCategoryEPCost` tables).
|
||
- `GetMiscLookupByKey("faiths")` merges entries from both `gsm_misc_lookups` and `gsm_believes` tables.
|
||
- Generic handlers use interface constraints (`Creator`, `Saver`, `FirstIdGetter`, `Deleter`) via Go generics for DRY CRUD.
|
||
|
||
**HTTP Endpoints (`/api/maintenance/...`):**
|
||
|
||
| Method | Path | Handler | Auth Level |
|
||
|--------|------|---------|-----------|
|
||
| GET | `/api/maintenance` | `GetMasterData` | Auth |
|
||
| GET | `/api/maintenance/skills` | `GetMDSkills` | Auth |
|
||
| GET | `/api/maintenance/skills-enhanced` | `GetEnhancedMDSkills` | Auth |
|
||
| GET | `/api/maintenance/skills/:id` | `GetMDSkill` | Auth |
|
||
| GET | `/api/maintenance/skills-enhanced/:id` | `GetEnhancedMDSkill` | Auth |
|
||
| GET | `/api/maintenance/weaponskills` | `GetMDWeaponSkills` | Auth |
|
||
| GET | `/api/maintenance/weaponskills-enhanced` | `GetEnhancedMDWeaponSkills` | Auth |
|
||
| GET | `/api/maintenance/weaponskills/:id` | `GetMDWeaponSkill` | Auth |
|
||
| GET | `/api/maintenance/weaponskills-enhanced/:id` | `GetEnhancedMDWeaponSkill` | Auth |
|
||
| GET | `/api/maintenance/spells` | `GetMDSpells` | Auth |
|
||
| GET | `/api/maintenance/spells-enhanced` | `GetEnhancedMDSpells` | Auth |
|
||
| GET | `/api/maintenance/spells/:id` | `GetMDSpell` | Auth |
|
||
| GET | `/api/maintenance/spells-enhanced/:id` | `GetEnhancedMDSpell` | Auth |
|
||
| GET | `/api/maintenance/equipment` | `GetMDEquipments` | Auth |
|
||
| GET | `/api/maintenance/equipment-enhanced` | `GetEnhancedMDEquipment` | Auth |
|
||
| GET | `/api/maintenance/equipment/:id` | `GetMDEquipment` | Auth |
|
||
| GET | `/api/maintenance/equipment-enhanced/:id` | `GetEnhancedMDEquipmentItem` | Auth |
|
||
| GET | `/api/maintenance/weapons` | `GetMDWeapons` | Auth |
|
||
| GET | `/api/maintenance/weapons-enhanced` | `GetEnhancedMDWeapons` | Auth |
|
||
| GET | `/api/maintenance/weapons/:id` | `GetMDWeapon` | Auth |
|
||
| GET | `/api/maintenance/weapons-enhanced/:id` | `GetEnhancedMDWeapon` | Auth |
|
||
| POST | `/api/maintenance/skills-enhanced` | `CreateEnhancedMDSkill` | Maintainer |
|
||
| PUT | `/api/maintenance/skills/:id` | `UpdateMDSkill` | Maintainer |
|
||
| PUT | `/api/maintenance/skills-enhanced/:id` | `UpdateEnhancedMDSkill` | Maintainer |
|
||
| POST | `/api/maintenance/skills` | `AddSkill` | Maintainer |
|
||
| DELETE | `/api/maintenance/skills/:id` | `DeleteMDSkill` | Maintainer |
|
||
| PUT | `/api/maintenance/weaponskills/:id` | `UpdateMDWeaponSkill` | Maintainer |
|
||
| PUT | `/api/maintenance/weaponskills-enhanced/:id` | `UpdateEnhancedMDWeaponSkill` | Maintainer |
|
||
| POST | `/api/maintenance/weaponskills` | `AddWeaponSkill` | Maintainer |
|
||
| DELETE | `/api/maintenance/weaponskills/:id` | `DeleteMDWeaponSkill` | Maintainer |
|
||
| PUT | `/api/maintenance/spells/:id` | `UpdateMDSpell` | Maintainer |
|
||
| PUT | `/api/maintenance/spells-enhanced/:id` | `UpdateEnhancedMDSpell` | Maintainer |
|
||
| POST | `/api/maintenance/spells` | `AddSpell` | Maintainer |
|
||
| DELETE | `/api/maintenance/spells/:id` | `DeleteMDSpell` | Maintainer |
|
||
| PUT | `/api/maintenance/equipment/:id` | `UpdateMDEquipment` | Maintainer |
|
||
| PUT | `/api/maintenance/equipment-enhanced/:id` | `UpdateEnhancedMDEquipmentItem` | Maintainer |
|
||
| POST | `/api/maintenance/equipment` | `AddEquipment` | Maintainer |
|
||
| DELETE | `/api/maintenance/equipment/:id` | `DeleteMDEquipment` | Maintainer |
|
||
| PUT | `/api/maintenance/weapons/:id` | `UpdateMDWeapon` | Maintainer |
|
||
| PUT | `/api/maintenance/weapons-enhanced/:id` | `UpdateEnhancedMDWeapon` | Maintainer |
|
||
| POST | `/api/maintenance/weapons` | `AddWeapon` | Maintainer |
|
||
| DELETE | `/api/maintenance/weapons/:id` | `DeleteMDWeapon` | Maintainer |
|
||
|
||
---
|
||
|
||
### 8.5 `gamesystem/`
|
||
|
||
**Purpose:** Minimal package that handles migration of the `game_systems` table and seeds the initial Midgard 5 game system.
|
||
|
||
**Key File:** `database.go`
|
||
|
||
- `MigrateStructure(db)` — AutoMigrates `GameSystem`.
|
||
- `MigrateDataIfNeeded(db)` — Creates `{Code:"M5", Name:"M-System"}` if no row with ID=1 exists.
|
||
|
||
No HTTP endpoints registered.
|
||
|
||
---
|
||
|
||
### 8.6 `maintenance/`
|
||
|
||
**Purpose:** Admin/maintainer tooling — DB health checks, data migrations, test data generation, SQLite→MariaDB transfer.
|
||
|
||
**Key Files:**
|
||
- `handlers.go` — SetupCheck, MakeTestdataFromLive, ReconnectDataBase, ReloadENV, TransferSQLiteToMariaDB, migrateAllStructures
|
||
- `masterdata_handlers.go` — GetGameSystems, UpdateGameSystem, GetLitSources, UpdateLitSource, GetMisc, UpdateMisc, GetSkillImprovementCost2, UpdateSkillImprovementCost2
|
||
- `believe_handlers.go` — GetBelieves, UpdateBelieve
|
||
- `skill_migration.go` — Skill-related migration helpers
|
||
|
||
**Business Logic:**
|
||
- `migrateAllStructures` calls all module `MigrateStructure()` functions in correct dependency order.
|
||
- `MakeTestdataFromLive` exports current live DB to a SQLite file for test snapshots.
|
||
- `TransferSQLiteToMariaDB` copies records table-by-table from SQLite to MariaDB.
|
||
- All endpoints require `RequireMaintainer()` or `RequireAdmin()` — no public access.
|
||
|
||
**HTTP Endpoints (`/api/maintenance/...` — Maintainer required):**
|
||
|
||
| Method | Path | Handler |
|
||
|--------|------|---------|
|
||
| GET | `/api/maintenance/gsm-believes` | `GetBelieves` |
|
||
| PUT | `/api/maintenance/gsm-believes/:id` | `UpdateBelieve` |
|
||
| GET | `/api/maintenance/game-systems` | `GetGameSystems` |
|
||
| PUT | `/api/maintenance/game-systems/:id` | `UpdateGameSystem` |
|
||
| GET | `/api/maintenance/gsm-lit-sources` | `GetLitSources` |
|
||
| PUT | `/api/maintenance/gsm-lit-sources/:id` | `UpdateLitSource` |
|
||
| GET | `/api/maintenance/gsm-misc` | `GetMisc` |
|
||
| PUT | `/api/maintenance/gsm-misc/:id` | `UpdateMisc` |
|
||
| GET | `/api/maintenance/skill-improvement-cost2` | `GetSkillImprovementCost2` |
|
||
| PUT | `/api/maintenance/skill-improvement-cost2/:id` | `UpdateSkillImprovementCost2` |
|
||
| GET | `/api/maintenance/setupcheck` | `SetupCheck` |
|
||
| GET | `/api/maintenance/setupcheck-dev` | `SetupCheckDev` |
|
||
| GET | `/api/maintenance/mktestdata` | `MakeTestdataFromLive` |
|
||
| GET | `/api/maintenance/reconndb` | `ReconnectDataBase` |
|
||
| GET | `/api/maintenance/reloadenv` | `ReloadENV` |
|
||
| POST | `/api/maintenance/transfer-sqlite-to-mariadb` | `TransferSQLiteToMariaDB` |
|
||
|
||
*(These overlap with the `gsmaster` maintenance prefix — both modules register under `/api/maintenance` with `gsmaster` first, then `maintenance` module adds its own routes. Both use `RequireMaintainer()` for writes.)*
|
||
|
||
---
|
||
|
||
### 8.7 `pdfrender/`
|
||
|
||
**Purpose:** Generate multi-page PDF character sheets from HTML templates using headless Chromium + pdfcpu merge.
|
||
|
||
**Key Files:**
|
||
- `handlers.go` — `ListTemplates`, `ExportCharacterToPDF`, `CleanupExportTemp`, `GetPDFFile`
|
||
- `init.go` — `InitializeTemplates` — copies bundled templates to `TemplatesDir` on startup
|
||
- `chromedp.go` — `NewPDFRenderer`, `RenderHTMLToPDF` using chromedp
|
||
- `mapper.go` — `MapCharacterToViewModel` — maps `Char` to `CharacterSheetViewModel`
|
||
- `viewmodel.go` — All view model types (`CharacterSheetViewModel`, `CharacterInfo`, `AttributeValues`, `DerivedValueSet`, `SkillViewModel`, `WeaponViewModel`, `SpellViewModel`, `EquipmentViewModel`, `MagicItemViewModel`, `PageMeta`)
|
||
- `render_with_continuation.go` — `RenderPageWithContinuations` — auto-generates overflow pages
|
||
- `pagination.go` / `pagination_helper.go` — item distribution across pages
|
||
- `fill_capacity.go` — reads `<!-- MaxItems: N -->` comments from HTML templates
|
||
- `template_parser.go` / `template_metadata.go` — template loading and Go `html/template` execution
|
||
- `inline_resources.go` — inlines CSS/images into HTML before rendering
|
||
- `file_management.go` — manages `xporttemp` directory
|
||
|
||
**Business Logic:**
|
||
1. On startup: sync `./default_templates` → `cfg.TemplatesDir`.
|
||
2. `ExportCharacterToPDF`:
|
||
- Loads character, maps to view model.
|
||
- Renders pages 1–4 (`page_1.html` through `page_4.html`) with continuation logic.
|
||
- Each page checks `<!-- MaxItems: N -->` to determine when to create a continuation page.
|
||
- All rendered PDFs are merged via `pdfcpu`.
|
||
- Saves to `xporttemp/` directory, returns `{"filename": "..."}`.
|
||
3. `GetPDFFile`: public endpoint, serves files from `xporttemp/` by filename.
|
||
|
||
**HTTP Endpoints:**
|
||
|
||
| Method | Path | Handler | Auth |
|
||
|--------|------|---------|------|
|
||
| GET | `/api/pdf/templates` | `ListTemplates` | Auth |
|
||
| GET | `/api/pdf/export/:id` | `ExportCharacterToPDF` | Auth |
|
||
| POST | `/api/pdf/cleanup` | `CleanupExportTemp` | Auth |
|
||
| GET | `/api/pdf/file/:filename` | `GetPDFFile` | Public |
|
||
|
||
---
|
||
|
||
### 8.8 `importer/`
|
||
|
||
**Purpose:** Import characters from VTT JSON format or CSV; export characters as VTT JSON, CSV, or spell CSV.
|
||
|
||
**Key Files:**
|
||
- `handler.go` — `UploadFiles`, `ImportSpellCSVHandler`
|
||
- `importer.go` — `ImportChar`, `CheckSkill`, VTT import logic
|
||
- `importVTTJson.go` — `ImportVTTJSON` — main VTT import driver
|
||
- `exporter.go` — `ExportCharToVTT`, `ExportCharacterToCSV`
|
||
- `model.go` — `CharacterImport` struct (VTT format), sub-structs for Fertigkeiten/Spells/Equipment
|
||
- `routes.go` — Route registration
|
||
|
||
**Business Logic:**
|
||
- `UploadFiles`: Accepts `file_vtt` (required) + `file_csv` (optional), validates extensions (.csv/.json), calls `ImportVTTJSON`.
|
||
- `CheckSkill`: Looks up skill in `gsm_skills`, auto-creates if `autocreate=true`.
|
||
- Source IDs are cached (`sourceIDCache`) to reduce DB queries during bulk import.
|
||
- `ExportCharacterVTTFileHandler`: Returns character data as downloadable `.json` file.
|
||
|
||
**HTTP Endpoints:**
|
||
|
||
| Method | Path | Handler |
|
||
|--------|------|---------|
|
||
| POST | `/api/importer/upload` | `UploadFiles` |
|
||
| POST | `/api/importer/spells/csv` | `ImportSpellCSVHandler` |
|
||
| GET | `/api/importer/export/vtt/:id` | `ExportCharacterVTTHandler` |
|
||
| GET | `/api/importer/export/vtt/:id/file` | `ExportCharacterVTTFileHandler` |
|
||
| GET | `/api/importer/export/csv/:id` | `ExportCharacterCSVHandler` |
|
||
| GET | `/api/importer/export/spells/csv` | `ExportSpellsCSVHandler` |
|
||
|
||
---
|
||
|
||
### 8.9 `transfer/`
|
||
|
||
**Purpose:** JSON-serialized character import/export and full database backup/restore.
|
||
|
||
**Key Files:**
|
||
- `handlers.go` — HTTP handlers for all transfer operations
|
||
- `exporter.go` — `ExportCharacter` — serializes a full `Char` + metadata to `CharacterExport`
|
||
- `importer.go` — `ImportCharacter` — deserializes and recreates character in DB
|
||
- `database.go` — `ExportDatabase`, `ImportDatabase` — full table serialization
|
||
- `routes.go` — Route registration
|
||
|
||
**Business Logic:**
|
||
- `ExportCharacter` / `ImportCharacter`: Full round-trip JSON serialization of characters with all associations.
|
||
- `ExportDatabase` / `ImportDatabase`: Table-level JSON snapshot used for backup/restore.
|
||
- `DownloadCharacterHandler` sets `Content-Disposition: attachment` for browser download.
|
||
|
||
**HTTP Endpoints:**
|
||
|
||
| Method | Path | Handler |
|
||
|--------|------|---------|
|
||
| GET | `/api/transfer/export/:id` | `ExportCharacterHandler` |
|
||
| GET | `/api/transfer/download/:id` | `DownloadCharacterHandler` |
|
||
| POST | `/api/transfer/import` | `ImportCharacterHandler` |
|
||
| POST | `/api/transfer/database/export` | `ExportDatabaseHandler` |
|
||
| POST | `/api/transfer/database/import` | `ImportDatabaseHandler` |
|
||
|
||
---
|
||
|
||
### 8.10 `appsystem/`
|
||
|
||
**Purpose:** Exposes application version and system info (user/character counts, DB version).
|
||
|
||
**Key Files:**
|
||
- `version.go` — `Version = "0.2.4"`, `GetInfo()`, `GetInfo2()`
|
||
- `handlers.go` — `Versionsinfo`, `SystemInfo`
|
||
- `routes.go` — Route registration (both protected and public)
|
||
|
||
**HTTP Endpoints:**
|
||
|
||
| Method | Path | Handler | Auth |
|
||
|--------|------|---------|------|
|
||
| GET | `/api/version` | `Versionsinfo` | Auth |
|
||
| GET | `/api/systeminfo` | `SystemInfo` | Auth |
|
||
| GET | `/api/public/version` | `Versionsinfo` | Public |
|
||
| GET | `/api/public/systeminfo` | `SystemInfo` | Public |
|
||
|
||
---
|
||
|
||
### 8.11 `logger/`
|
||
|
||
**Purpose:** Custom leveled logger with timestamp prefixing.
|
||
|
||
**Key API:**
|
||
|
||
```go
|
||
logger.Debug(format, args...)
|
||
logger.Info(format, args...)
|
||
logger.Warn(format, args...)
|
||
logger.Error(format, args...)
|
||
logger.SetDebugMode(bool)
|
||
logger.SetMinLogLevel(logger.DEBUG | INFO | WARN | ERROR)
|
||
```
|
||
|
||
- Outputs to `os.Stdout` with format: `[YYYY-MM-DD HH:MM:SS] LEVEL: message`
|
||
- Debug messages are suppressed unless both `debugEnabled=true` AND `minLevel<=DEBUG`.
|
||
- Configured once in `main()` from `config.Cfg`.
|
||
|
||
---
|
||
|
||
### 8.12 `mail/`
|
||
|
||
**Purpose:** SMTP email client used exclusively for password-reset emails.
|
||
|
||
**Key File:** `smtp.go`
|
||
|
||
```go
|
||
type Client struct {
|
||
host, username, password, from string
|
||
port int
|
||
}
|
||
|
||
func NewClient() *Client // reads from config.Cfg
|
||
func (c *Client) Send(msg Message) error
|
||
```
|
||
|
||
- Port 465: Direct TLS (`tls.Dial`)
|
||
- Port 587: STARTTLS (`smtp.Dial` + `TLSConfig`)
|
||
- If `host == ""`: logs the email content instead of sending (fallback for unconfigured environments).
|
||
|
||
---
|
||
|
||
### 8.13 `router/`
|
||
|
||
**Purpose:** Central Gin setup and base route group creation.
|
||
|
||
**Files:**
|
||
- `setup.go` — `SetupGin(r)` — CORS middleware
|
||
- `routes.go` — `BaseRouterGrp(r)` — public auth routes + protected `/api` group
|
||
- `routes_test.go` — router tests
|
||
|
||
---
|
||
|
||
### 8.14 `config/`
|
||
|
||
**Purpose:** Application configuration loading and validation.
|
||
|
||
**Key Functions:**
|
||
- `init()` — auto-loads config on package import
|
||
- `LoadConfig()` — reads `.env`/`.env.local`, then env vars
|
||
- `loadEnvFile()` / `loadEnvFileContent()` — parse `KEY=VALUE` lines
|
||
- `cfg.IsProduction() bool`
|
||
- `cfg.GetServerAddress() string` — returns `":PORT"` format
|
||
|
||
---
|
||
|
||
### 8.15 `database/`
|
||
|
||
**Purpose:** Database connection management, test helpers, and migration execution.
|
||
|
||
**Key Functions:**
|
||
- `ConnectDatabase()` — routes to `SetupTestDB()` or `ConnectDatabaseOrig()` based on env
|
||
- `ConnectDatabaseOrig()` — opens MySQL or SQLite with GORM
|
||
- `SetupTestDB(opts ...bool)` — copies snapshot `testdata/prepared_test_data.db` → temp dir, opens it
|
||
- `MigrateStructure(db)` — migrates `schema_version` + `migration_history`
|
||
- `MigrateDataIfNeeded(db)` — ensures schema version record exists
|
||
- `GetDB()` — lazy init getter
|
||
|
||
**Path Constants:**
|
||
```go
|
||
PreparedTestDB = filepath.Join(backendDir, "testdata", "prepared_test_data.db")
|
||
TestDataDir = filepath.Join(backendDir, "maintenance", "testdata")
|
||
```
|
||
|
||
---
|
||
|
||
### 8.16 `testutils/`
|
||
|
||
**Purpose:** Shared test environment setup utility.
|
||
|
||
```go
|
||
func SetupTestEnvironment(t *testing.T)
|
||
func SetupTestEnvironmentWithConfig(t *testing.T, envVars map[string]string)
|
||
func EnsureTestEnvironment(t *testing.T)
|
||
```
|
||
|
||
- Sets `ENVIRONMENT=test` to redirect DB connection to SQLite test snapshot.
|
||
- Registers `t.Cleanup()` to restore original env vars after each test.
|
||
|
||
---
|
||
|
||
### 8.17 `api/`
|
||
|
||
**Purpose:** Integration tests that test multi-module interactions via full HTTP stack.
|
||
|
||
Key test: `TestListCharacters` — spins up Gin with all essential routes, uses `database.SetupTestDB(true)`.
|
||
|
||
---
|
||
|
||
## 9. Complete API Route Listing
|
||
|
||
### Public (No Authentication)
|
||
|
||
| Method | Path | Module | Purpose |
|
||
|--------|------|--------|---------|
|
||
| POST | `/register` | user | Register a new user |
|
||
| POST | `/login` | user | Login, get token |
|
||
| POST | `/password-reset/request` | user | Request password reset email |
|
||
| GET | `/password-reset/validate/:token` | user | Validate reset token |
|
||
| POST | `/password-reset/reset` | user | Set new password |
|
||
| GET | `/api/pdf/file/:filename` | pdfrender | Download generated PDF |
|
||
| GET | `/api/public/version` | appsystem | App version (no auth) |
|
||
| GET | `/api/public/systeminfo` | appsystem | System info (no auth) |
|
||
|
||
### Protected — All Authenticated Users (`/api/...`)
|
||
|
||
**User Management:**
|
||
|
||
| Method | Path | Purpose |
|
||
|--------|------|---------|
|
||
| GET | `/api/user/profile` | Own profile |
|
||
| PUT | `/api/user/display-name` | Update display name |
|
||
| PUT | `/api/user/email` | Update email |
|
||
| PUT | `/api/user/password` | Change password |
|
||
| PUT | `/api/user/language` | Set preferred language |
|
||
|
||
**Characters:**
|
||
|
||
| Method | Path | Purpose |
|
||
|--------|------|---------|
|
||
| GET | `/api/characters` | List own + shared + public characters |
|
||
| POST | `/api/characters` | Create character |
|
||
| GET | `/api/characters/:id` | Get full character (FeChar) |
|
||
| PUT/PATCH | `/api/characters/:id` | Update character |
|
||
| DELETE | `/api/characters/:id` | Delete character |
|
||
| PUT | `/api/characters/:id/image` | Upload character image |
|
||
| GET | `/api/characters/:id/datasheet-options` | PDF datasheet options |
|
||
| GET | `/api/characters/:id/shares` | List share recipients |
|
||
| PUT | `/api/characters/:id/shares` | Update share recipients |
|
||
| GET | `/api/characters/:id/available-users` | Users available to share with |
|
||
| GET | `/api/characters/:id/experience-wealth` | Current EP + wealth |
|
||
| PUT | `/api/characters/:id/experience` | Update experience points |
|
||
| PUT | `/api/characters/:id/wealth` | Update wealth |
|
||
| GET | `/api/characters/:id/audit-log` | Change history |
|
||
| GET | `/api/characters/:id/audit-log/stats` | Change statistics |
|
||
| POST | `/api/characters/lerncost` | Calculate learn/improve cost |
|
||
| POST | `/api/characters/lerncost-new` | (Alias) Calculate learn/improve cost |
|
||
| POST | `/api/characters/improve-skill` | Apply skill improvement |
|
||
| POST | `/api/characters/improve-skill-new` | (Alias) Apply skill improvement |
|
||
| POST | `/api/characters/:id/learn-skill` | Learn new skill |
|
||
| POST | `/api/characters/:id/learn-skill-new` | (Alias) Learn new skill |
|
||
| POST | `/api/characters/:id/learn-spell` | Learn new spell |
|
||
| POST | `/api/characters/:id/learn-spell-new` | (Alias) Learn new spell |
|
||
| POST | `/api/characters/available-skills` | Available skills to learn |
|
||
| POST | `/api/characters/available-skills-new` | (Alias) |
|
||
| POST | `/api/characters/available-skills-creation` | Skills for creation wizard |
|
||
| POST | `/api/characters/available-spells` | Available spells to learn |
|
||
| POST | `/api/characters/available-spells-new` | (Alias) |
|
||
| POST | `/api/characters/available-spells-creation` | Spells for creation wizard |
|
||
| GET | `/api/characters/spell-details` | Spell detail lookup |
|
||
| GET | `/api/characters/:id/reward-types` | Reward options for this context |
|
||
| GET | `/api/characters/:id/practice-points` | Current practice points |
|
||
| PUT | `/api/characters/:id/practice-points` | Update practice points |
|
||
| POST | `/api/characters/:id/practice-points/add` | Add a practice point |
|
||
| POST | `/api/characters/:id/practice-points/use` | Use a practice point |
|
||
| GET | `/api/characters/skill-categories` | Static skill category listing |
|
||
| GET | `/api/characters/create-sessions` | List creation wizard sessions |
|
||
| POST | `/api/characters/create-session` | Start creation wizard session |
|
||
| GET | `/api/characters/create-session/:sessionId` | Get session data |
|
||
| PUT | `/api/characters/create-session/:sessionId/basic` | Save basic info |
|
||
| PUT | `/api/characters/create-session/:sessionId/attributes` | Save attributes |
|
||
| PUT | `/api/characters/create-session/:sessionId/derived` | Save derived values |
|
||
| PUT | `/api/characters/create-session/:sessionId/skills` | Save skills selection |
|
||
| POST | `/api/characters/create-session/:sessionId/finalize` | Finalize creation |
|
||
| DELETE | `/api/characters/create-session/:sessionId` | Discard session |
|
||
| GET | `/api/characters/races` | Available races |
|
||
| GET | `/api/characters/classes` | Available character classes |
|
||
| GET | `/api/characters/classes/learning-points` | LP by class |
|
||
| GET | `/api/characters/origins` | Available origins |
|
||
| GET | `/api/characters/beliefs` | Faith search |
|
||
| POST | `/api/characters/calculate-static-fields` | Calc derived values (no dice) |
|
||
| POST | `/api/characters/calculate-rolled-field` | Calc derived value (with dice) |
|
||
|
||
**Equipment:**
|
||
|
||
| Method | Path | Purpose |
|
||
|--------|------|---------|
|
||
| POST | `/api/equipment` | Add equipment item |
|
||
| GET | `/api/equipment/character/:character_id` | List equipment for character |
|
||
| PUT | `/api/equipment/:ausruestung_id` | Update equipment |
|
||
| DELETE | `/api/equipment/:ausruestung_id` | Delete equipment |
|
||
| POST | `/api/weapons` | Add weapon |
|
||
| GET | `/api/weapons/character/:character_id` | List weapons for character |
|
||
| PUT | `/api/weapons/:waffe_id` | Update weapon |
|
||
| DELETE | `/api/weapons/:waffe_id` | Delete weapon |
|
||
|
||
**Master Data (read — all authenticated, write — maintainers only):**
|
||
|
||
| Method | Path | Auth | Purpose |
|
||
|--------|------|------|---------|
|
||
| GET | `/api/maintenance` | Auth | All master data overview |
|
||
| GET | `/api/maintenance/skills` | Auth | List skills |
|
||
| GET | `/api/maintenance/skills/:id` | Auth | Get skill |
|
||
| GET | `/api/maintenance/skills-enhanced` | Auth | List skills (rich) |
|
||
| GET | `/api/maintenance/skills-enhanced/:id` | Auth | Get skill (rich) |
|
||
| POST | `/api/maintenance/skills` | Maintainer | Create skill |
|
||
| POST | `/api/maintenance/skills-enhanced` | Maintainer | Create skill (rich) |
|
||
| PUT | `/api/maintenance/skills/:id` | Maintainer | Update skill |
|
||
| PUT | `/api/maintenance/skills-enhanced/:id` | Maintainer | Update skill (rich) |
|
||
| DELETE | `/api/maintenance/skills/:id` | Maintainer | Delete skill |
|
||
| GET | `/api/maintenance/weaponskills[/:id]` | Auth | Weapon skill(s) |
|
||
| GET | `/api/maintenance/weaponskills-enhanced[/:id]` | Auth | Weapon skill(s) (rich) |
|
||
| POST/PUT/DELETE | `/api/maintenance/weaponskills[/:id]` | Maintainer | Weapon skill CRUD |
|
||
| GET | `/api/maintenance/spells[/:id]` | Auth | Spell(s) |
|
||
| GET | `/api/maintenance/spells-enhanced[/:id]` | Auth | Spell(s) (rich) |
|
||
| POST/PUT/DELETE | `/api/maintenance/spells[/:id]` | Maintainer | Spell CRUD |
|
||
| GET | `/api/maintenance/equipment[/:id]` | Auth | Equipment item(s) |
|
||
| GET | `/api/maintenance/equipment-enhanced[/:id]` | Auth | Equipment (rich) |
|
||
| POST/PUT/DELETE | `/api/maintenance/equipment[/:id]` | Maintainer | Equipment CRUD |
|
||
| GET | `/api/maintenance/weapons[/:id]` | Auth | Master weapon(s) |
|
||
| GET | `/api/maintenance/weapons-enhanced[/:id]` | Auth | Master weapon(s) (rich) |
|
||
| POST/PUT/DELETE | `/api/maintenance/weapons[/:id]` | Maintainer | Weapon CRUD |
|
||
|
||
**Maintenance Admin (Maintainer required):**
|
||
|
||
| Method | Path | Purpose |
|
||
|--------|------|---------|
|
||
| GET | `/api/maintenance/gsm-believes` | List faith entries |
|
||
| PUT | `/api/maintenance/gsm-believes/:id` | Update faith entry |
|
||
| GET | `/api/maintenance/game-systems` | List game systems |
|
||
| PUT | `/api/maintenance/game-systems/:id` | Update game system |
|
||
| GET | `/api/maintenance/gsm-lit-sources` | List source books |
|
||
| PUT | `/api/maintenance/gsm-lit-sources/:id` | Update source book |
|
||
| GET | `/api/maintenance/gsm-misc` | MiscLookup entries |
|
||
| PUT | `/api/maintenance/gsm-misc/:id` | Update misc entry |
|
||
| GET/PUT | `/api/maintenance/skill-improvement-cost2[/:id]` | Improvement cost table |
|
||
| GET | `/api/maintenance/setupcheck` | DB structure validation |
|
||
| GET | `/api/maintenance/setupcheck-dev` | Extended setup check |
|
||
| GET | `/api/maintenance/mktestdata` | Generate test snapshot from live DB |
|
||
| GET | `/api/maintenance/reconndb` | Reconnect database |
|
||
| GET | `/api/maintenance/reloadenv` | Reload .env config |
|
||
| POST | `/api/maintenance/transfer-sqlite-to-mariadb` | Migrate data |
|
||
|
||
**PDF:**
|
||
|
||
| Method | Path | Auth | Purpose |
|
||
|--------|------|------|---------|
|
||
| GET | `/api/pdf/templates` | Auth | List available PDF templates |
|
||
| GET | `/api/pdf/export/:id` | Auth | Export character to PDF |
|
||
| POST | `/api/pdf/cleanup` | Auth | Clean up old PDFs |
|
||
| GET | `/api/pdf/file/:filename` | Public | Download PDF file |
|
||
|
||
**Import/Export:**
|
||
|
||
| Method | Path | Purpose |
|
||
|--------|------|---------|
|
||
| POST | `/api/importer/upload` | Import character from VTT JSON |
|
||
| POST | `/api/importer/spells/csv` | Import spells from CSV |
|
||
| GET | `/api/importer/export/vtt/:id` | Export character as VTT JSON (response body) |
|
||
| GET | `/api/importer/export/vtt/:id/file` | Export character as downloadable VTT JSON |
|
||
| GET | `/api/importer/export/csv/:id` | Export character as CSV |
|
||
| GET | `/api/importer/export/spells/csv` | Export all spells as CSV |
|
||
| GET | `/api/transfer/export/:id` | Export character as BaMoRT JSON |
|
||
| GET | `/api/transfer/download/:id` | Download character as JSON file |
|
||
| POST | `/api/transfer/import` | Import character from BaMoRT JSON |
|
||
| POST | `/api/transfer/database/export` | Full DB export to JSON |
|
||
| POST | `/api/transfer/database/import` | Full DB import from JSON |
|
||
|
||
**System:**
|
||
|
||
| Method | Path | Auth | Purpose |
|
||
|--------|------|------|---------|
|
||
| GET | `/api/version` | Auth | App version + git commit |
|
||
| GET | `/api/systeminfo` | Auth | Version + user/char counts + DB version |
|
||
|
||
**Admin (Admin role required):**
|
||
|
||
| Method | Path | Purpose |
|
||
|--------|------|---------|
|
||
| GET | `/api/users` | List all users |
|
||
| GET | `/api/users/:id` | Get user by ID |
|
||
| PUT | `/api/users/:id/role` | Change user role |
|
||
| PUT | `/api/users/:id/password` | Change user password |
|
||
| DELETE | `/api/users/:id` | Delete user |
|
||
|
||
---
|
||
|
||
## 10. Testing Approach
|
||
|
||
### Test Environment Setup
|
||
|
||
Every test file that touches the database must call:
|
||
|
||
```go
|
||
func setupTestEnvironment(t *testing.T) {
|
||
original := os.Getenv("ENVIRONMENT")
|
||
os.Setenv("ENVIRONMENT", "test")
|
||
t.Cleanup(func() { os.Setenv("ENVIRONMENT", original) })
|
||
}
|
||
```
|
||
|
||
Or the shared utility:
|
||
```go
|
||
testutils.SetupTestEnvironment(t)
|
||
```
|
||
|
||
This causes `database.ConnectDatabase()` to open the SQLite test snapshot instead of MariaDB.
|
||
|
||
### Test Database Mechanism
|
||
|
||
1. `testdata/prepared_test_data.db` — a SQLite snapshot with real-world test data.
|
||
2. `database.SetupTestDB(true)` copies this to a `os.MkdirTemp` location and opens it.
|
||
3. Tests run against this isolated copy — no pollution of live data.
|
||
4. Character ID 18 ("Fanjo Vetrani") is guaranteed to exist in the test DB.
|
||
|
||
### Test Structure Conventions
|
||
|
||
- All test files use `_test.go` suffix.
|
||
- Test functions call `database.SetupTestDB(true)` or `testutils.SetupTestEnvironment(t)` first.
|
||
- Integration tests use `testify/assert`.
|
||
- Many module tests (`character_test.go`, `handlers_test.go`, etc.) use helper constructors from `test_data_helper.go`.
|
||
- The `api/api_test.go` package runs HTTP tests using `httptest.NewRecorder()`.
|
||
- `maintenance/copy_db_test.go` tests the DB snapshot refresh mechanism.
|
||
- Test data helpers (`character/test_data_helper.go`) provide `CreateTestCharacter()`, etc.
|
||
|
||
### Key Test Files by Module
|
||
|
||
| Module | Test Files |
|
||
|--------|-----------|
|
||
| `api/` | `api_test.go` — Integration |
|
||
| `character/` | `character_test.go`, `handlers_test.go`, `lerncost_handler_test.go`, `skill_update_test.go`, `learn_spell_test.go`, etc. |
|
||
| `database/` | `database_test.go`, `testhelper_test.go` |
|
||
| `gsmaster/` | `gsmaster_test.go`, `learning_costs_test.go`, `export_import_test.go` |
|
||
| `importer/` | `importer_test.go`, `charimport_test.go`, `upload_test.go` |
|
||
| `maintenance/` | `handlers_test.go`, `masterdata_handlers_test.go`, `copy_db_test.go` |
|
||
| `models/` | `model_character_test.go`, `model_char_skills_test.go`, `model_learning_costs_test.go` |
|
||
| `pdfrender/` | `chromedp_test.go`, `mapper_test.go`, `pagination_test.go`, `integration_test.go` |
|
||
| `transfer/` | `database_test.go`, `exporter_test.go`, `importer_test.go` |
|
||
| `user/` | `handlers_test.go`, `role_test.go` |
|
||
|
||
---
|
||
|
||
## 11. Key Dependencies
|
||
|
||
| Dependency | Version | Purpose |
|
||
|-----------|---------|---------|
|
||
| `github.com/gin-gonic/gin` | v1.10.0 | HTTP framework |
|
||
| `github.com/gin-contrib/cors` | v1.7.3 | CORS middleware |
|
||
| `gorm.io/gorm` | v1.25.12 | ORM |
|
||
| `gorm.io/driver/mysql` | v1.5.7 | MySQL/MariaDB driver |
|
||
| `gorm.io/driver/sqlite` | v1.5.7 | SQLite driver (tests + dev) |
|
||
| `github.com/chromedp/chromedp` | v0.14.2 | Headless Chrome for PDF rendering |
|
||
| `github.com/pdfcpu/pdfcpu` | v0.11.1 | PDF merging |
|
||
| `github.com/stretchr/testify` | v1.10.0 | Test assertions |
|
||
|
||
---
|
||
|
||
## 12. Security Observations
|
||
|
||
### Critical Issues
|
||
|
||
1. **Weak Token Authentication**: The custom token embeds the user ID in a predictable location. Any token with any valid user ID at position `7 + len("Bearer ")` is accepted. There is **no HMAC or digital signature**. An attacker can forge tokens.
|
||
|
||
2. **MD5 Password Hashing**: Passwords are stored as MD5 hashes. MD5 is cryptographically broken for password storage; brute-force and rainbow-table attacks are trivial. Should be replaced with bcrypt, argon2id, or scrypt.
|
||
|
||
3. **Token Leaks User ID**: The token format encodes the user ID in plaintext, revealing enumerable user IDs to clients.
|
||
|
||
### Moderate Concerns
|
||
|
||
4. **Debug Logging of Passwords**: `handlers.go` has `fmt.Printf("pwdh: %s", ...)` that prints password hashes to stdout on every login — a security logging issue.
|
||
|
||
5. **Hardcoded Origins in CORS**: Local network IPs (`192.168.0.48`, `192.168.0.36`) are hardcoded — appropriate for development but should be env-driven for production.
|
||
|
||
6. **Importer File Upload**: `UploadFiles` saves files to `./uploads/` relative path without sanitizing filenames for path traversal. The extension validation (`isValidFileType`) only checks the suffix.
|
||
|
||
7. **SQL Injection via `fieldName`**: `GetMiscLookupByKeyForSystem` constructs `query.Order(orderBy)` from a user-supplied `order` parameter — however the value is mapped to a safe set of constants before use, so actual injection risk is mitigated.
|
||
|
||
8. **No rate limiting**: Login and registration endpoints have no rate limiting, making them susceptible to brute-force attacks.
|
||
|
||
### Notes
|
||
|
||
- The bcrypt implementation is commented out in `handlers.go` with the MD5 hash in use — this is intentional but documents the known deficit.
|
||
- Password reset tokens use `crypto/rand` (secure, 32 bytes), which is correct.
|
||
- GORM parameterized queries protect most DB operations from SQL injection.
|
||
|
||
---
|
||
|
||
## 13. Cross-Module Relationships Diagram
|
||
|
||
```
|
||
cmd/main.go
|
||
├── config ──────────────────────────────────── read by all modules
|
||
├── logger ──────────────────────────────────── used by all modules
|
||
├── database ─────────────────────────────────── .DB used by all models
|
||
│ └── testutils ────────────────────────── test env setup
|
||
├── router
|
||
│ └── user (AuthMiddleware, role guards)
|
||
├── user ─────────────────── handlers, model, auth
|
||
│ └── mail (SMTP for password reset)
|
||
├── models ────────────────── GORM entities, MigrateStructure
|
||
│ ├── user.User (FK in Char)
|
||
│ └── database.DB (queries)
|
||
├── character ─────────────── CRUD + learning + creation wizard
|
||
│ ├── models (Char, SkFertigkeit, SkZauber, etc.)
|
||
│ ├── gsmaster (LerncostRequest, skill lookups)
|
||
│ └── database.DB
|
||
├── equipment ─────────────── Equipment/Weapon CRUD
|
||
│ ├── models (EqAusruestung, EqWaffe)
|
||
│ └── database.DB
|
||
├── gsmaster ──────────────── Master data (skills/spells/weapons)
|
||
│ ├── models (Skill, Spell, Weapon, etc.)
|
||
│ ├── user (RequireMaintainer guard)
|
||
│ └── database.DB
|
||
├── gamesystem ────────────── GameSystem seed/migration
|
||
│ └── models.GameSystem
|
||
├── maintenance ───────────── Admin tooling, migration orchestration
|
||
│ ├── database, gamesystem, user, models (all MigrateStructure)
|
||
│ └── user (RequireMaintainer guard)
|
||
├── pdfrender ─────────────── HTML→PDF pipeline
|
||
│ ├── models.Char (character data)
|
||
│ └── config (TemplatesDir, ExportTempDir)
|
||
├── importer ──────────────── VTT/CSV import/export
|
||
│ └── models (Char, Skill, Spell)
|
||
├── transfer ──────────────── JSON character + DB backup
|
||
│ └── models.Char
|
||
└── appsystem ─────────────── Version info
|
||
└── database.DB (count queries)
|
||
```
|
||
|
||
---
|
||
|
||
*End of findings — /home/de31a2/bamort/backend_findings.md*
|