# 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 `` 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 `` 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*