* introduced central package registry by package init function * dynamic registration of routes, model, migrations and initializers. * setting a docker compose project name to prevent shutdown of other containers with the same (composer)name * ai documentation * app template * Create tests for ALL API entpoints in ALL packages Based on current data. Ensure that all API endpoints used in frontend are tested. These tests are crucial for the next refactoring tasks. * adopting agent instructions for a more consistent coding style * added desired module layout and debugging information * Fix All Failing tests All failing tests are fixed now that makes the refactoring more easy since all tests must pass * restored routes for maintenance * added common translations * added new tests for API Endpoint * Merge branch 'separate_business_logic' * added lern and skill improvement cost editing * Set Docker image tag when building to prevent rebuild when nothing has changed * add and remove PP for Weaponskill fixed * add and remove PP for same named skills fixed * add new task
60 KiB
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
- Module Structure Overview
- Entry Point — cmd/main.go
- Router & Middleware Setup
- Authentication & Authorization
- Configuration
- Database Layer
- Models (GORM Entities)
- Module-by-Module Analysis
- Complete API Route Listing
- Testing Approach
- Key Dependencies
- Security Observations
- 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
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)
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):
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):
- Extracts user ID from position
7 + len("Bearer ")in theAuthorizationheader. - Loads the user from the database by that ID.
- 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):
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 endpointsRequireMaintainer()→ 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
- Reads
.envand.env.localfiles from the working directory. - Overrides with env vars.
ENVIRONMENT=developmentautomatically enablesDebugMode=trueandLogLevel=DEBUG.ENVIRONMENT=testorDEVTESTING=yescausesConnectDatabase()to use the SQLite test snapshot.
6. Database Layer
Package: database/
Connection (database/config.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 aos.MkdirTempdirectory, opens it, and assigns it todatabase.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:
gameSystemMigrateStructure—game_systemsgsMasterMigrateStructure— skills, spells, equipment, weapons, etc.characterMigrateStructure— characters, skills, spells, equipmentequipmentMigrateStructureskillsMigrateStructure— learning cost tablesimporterMigrateStructure— (currently no-op)learningMigrateStructure— all learning/cost relation tables
StringArray Custom Type
type StringArray []string
// Stored as JSON in TEXT column (Value+Scan implementations)
Used for Char.Spezialisierung (character specializations).
7. Models (GORM Entities)
Base Types
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 helpershandlers.go— RegisterUser, LoginUser, CheckToken, AuthMiddleware, password resetadmin_handlers.go— ListUsers, GetUser, UpdateUserRole, ChangeUserPassword, DeleteUsermiddleware.go— RequireRole, RequireAdmin, RequireMaintainerdatabase.go—MigrateStructureforuserstableroutes.go— Route registration
Business Logic:
- Registration creates users with MD5-hashed passwords and a default
standardrole. - 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.
AuthMiddlewarevalidates the token structure, loads the user from DB, injectsuserID/username/userinto 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, GetDatasheetOptionslerncost_handler.go—GetLernCostNewSystem,ImproveSkill,LearnSkill,LearnSpell— full learning economyderived_values_calculator.go—CalculateStaticFields,CalculateRolledField— character creation mathcreation_rules.go—GetSpecialAbilityByRoll— special ability tableaudit_log.go—CreateAuditLogEntry,GetAuditLogForCharacter/Fieldshare_handlers.go—GetCharacterShares,UpdateCharacterShares,GetAvailableUsersForSharingpractice_points_handler.go—GetPracticePoints,UpdatePracticePoints,AddPracticePoint,UsePracticePointskill_type_helper.go— skill categorization helperssystem_information_handlers.go—GetSkillCategoriesHandlerStaticspell_utils.go— spell availability and creation utilitiesdatabase.go—SaveCharacterToDBimage_handler.go— character image upload/handling
Business Logic:
ListCharacters: Returns{ self_owned: [], others: [] }whereothers= public + shared.GetCharacter: ReturnsFeChar(augmented view with categorized skills).toFeChar: Adds bonus field from attributes, splits skills by category.checkCharacterOwnership: Guards update/delete/share withcharacter.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:
CalculateStaticFieldsLogicimplements 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, comparescharacter.UserIDto 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 endpointslearning_costs.go—LearningCostsTablewith full EP/TE cost tables for all 15 character classeslearning_costs_init.go—ValidateLearningCostsData,GetLearningCostsSummarylevelup.go—CalculateImprovementCost,CalculateSkillImprovementCostgame_system.go— Game system lookup helpermisclookup.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 (ClassCategoryEPCosttables). GetMiscLookupByKey("faiths")merges entries from bothgsm_misc_lookupsandgsm_believestables.- 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)— AutoMigratesGameSystem.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, migrateAllStructuresmasterdata_handlers.go— GetGameSystems, UpdateGameSystem, GetLitSources, UpdateLitSource, GetMisc, UpdateMisc, GetSkillImprovementCost2, UpdateSkillImprovementCost2believe_handlers.go— GetBelieves, UpdateBelieveskill_migration.go— Skill-related migration helpers
Business Logic:
migrateAllStructurescalls all moduleMigrateStructure()functions in correct dependency order.MakeTestdataFromLiveexports current live DB to a SQLite file for test snapshots.TransferSQLiteToMariaDBcopies records table-by-table from SQLite to MariaDB.- All endpoints require
RequireMaintainer()orRequireAdmin()— 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,GetPDFFileinit.go—InitializeTemplates— copies bundled templates toTemplatesDiron startupchromedp.go—NewPDFRenderer,RenderHTMLToPDFusing chromedpmapper.go—MapCharacterToViewModel— mapsChartoCharacterSheetViewModelviewmodel.go— All view model types (CharacterSheetViewModel,CharacterInfo,AttributeValues,DerivedValueSet,SkillViewModel,WeaponViewModel,SpellViewModel,EquipmentViewModel,MagicItemViewModel,PageMeta)render_with_continuation.go—RenderPageWithContinuations— auto-generates overflow pagespagination.go/pagination_helper.go— item distribution across pagesfill_capacity.go— reads<!-- MaxItems: N -->comments from HTML templatestemplate_parser.go/template_metadata.go— template loading and Gohtml/templateexecutioninline_resources.go— inlines CSS/images into HTML before renderingfile_management.go— managesxporttempdirectory
Business Logic:
- On startup: sync
./default_templates→cfg.TemplatesDir. ExportCharacterToPDF:- Loads character, maps to view model.
- Renders pages 1–4 (
page_1.htmlthroughpage_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": "..."}.
GetPDFFile: public endpoint, serves files fromxporttemp/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,ImportSpellCSVHandlerimporter.go—ImportChar,CheckSkill, VTT import logicimportVTTJson.go—ImportVTTJSON— main VTT import driverexporter.go—ExportCharToVTT,ExportCharacterToCSVmodel.go—CharacterImportstruct (VTT format), sub-structs for Fertigkeiten/Spells/Equipmentroutes.go— Route registration
Business Logic:
UploadFiles: Acceptsfile_vtt(required) +file_csv(optional), validates extensions (.csv/.json), callsImportVTTJSON.CheckSkill: Looks up skill ingsm_skills, auto-creates ifautocreate=true.- Source IDs are cached (
sourceIDCache) to reduce DB queries during bulk import. ExportCharacterVTTFileHandler: Returns character data as downloadable.jsonfile.
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 operationsexporter.go—ExportCharacter— serializes a fullChar+ metadata toCharacterExportimporter.go—ImportCharacter— deserializes and recreates character in DBdatabase.go—ExportDatabase,ImportDatabase— full table serializationroutes.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.DownloadCharacterHandlersetsContent-Disposition: attachmentfor 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,SystemInforoutes.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:
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.Stdoutwith format:[YYYY-MM-DD HH:MM:SS] LEVEL: message - Debug messages are suppressed unless both
debugEnabled=trueANDminLevel<=DEBUG. - Configured once in
main()fromconfig.Cfg.
8.12 mail/
Purpose: SMTP email client used exclusively for password-reset emails.
Key File: smtp.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 middlewareroutes.go—BaseRouterGrp(r)— public auth routes + protected/apigrouproutes_test.go— router tests
8.14 config/
Purpose: Application configuration loading and validation.
Key Functions:
init()— auto-loads config on package importLoadConfig()— reads.env/.env.local, then env varsloadEnvFile()/loadEnvFileContent()— parseKEY=VALUElinescfg.IsProduction() boolcfg.GetServerAddress() string— returns":PORT"format
8.15 database/
Purpose: Database connection management, test helpers, and migration execution.
Key Functions:
ConnectDatabase()— routes toSetupTestDB()orConnectDatabaseOrig()based on envConnectDatabaseOrig()— opens MySQL or SQLite with GORMSetupTestDB(opts ...bool)— copies snapshottestdata/prepared_test_data.db→ temp dir, opens itMigrateStructure(db)— migratesschema_version+migration_historyMigrateDataIfNeeded(db)— ensures schema version record existsGetDB()— lazy init getter
Path Constants:
PreparedTestDB = filepath.Join(backendDir, "testdata", "prepared_test_data.db")
TestDataDir = filepath.Join(backendDir, "maintenance", "testdata")
8.16 testutils/
Purpose: Shared test environment setup utility.
func SetupTestEnvironment(t *testing.T)
func SetupTestEnvironmentWithConfig(t *testing.T, envVars map[string]string)
func EnsureTestEnvironment(t *testing.T)
- Sets
ENVIRONMENT=testto 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:
func setupTestEnvironment(t *testing.T) {
original := os.Getenv("ENVIRONMENT")
os.Setenv("ENVIRONMENT", "test")
t.Cleanup(func() { os.Setenv("ENVIRONMENT", original) })
}
Or the shared utility:
testutils.SetupTestEnvironment(t)
This causes database.ConnectDatabase() to open the SQLite test snapshot instead of MariaDB.
Test Database Mechanism
testdata/prepared_test_data.db— a SQLite snapshot with real-world test data.database.SetupTestDB(true)copies this to aos.MkdirTemplocation and opens it.- Tests run against this isolated copy — no pollution of live data.
- Character ID 18 ("Fanjo Vetrani") is guaranteed to exist in the test DB.
Test Structure Conventions
- All test files use
_test.gosuffix. - Test functions call
database.SetupTestDB(true)ortestutils.SetupTestEnvironment(t)first. - Integration tests use
testify/assert. - Many module tests (
character_test.go,handlers_test.go, etc.) use helper constructors fromtest_data_helper.go. - The
api/api_test.gopackage runs HTTP tests usinghttptest.NewRecorder(). maintenance/copy_db_test.gotests the DB snapshot refresh mechanism.- Test data helpers (
character/test_data_helper.go) provideCreateTestCharacter(), 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
-
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. -
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.
-
Token Leaks User ID: The token format encodes the user ID in plaintext, revealing enumerable user IDs to clients.
Moderate Concerns
-
Debug Logging of Passwords:
handlers.gohasfmt.Printf("pwdh: %s", ...)that prints password hashes to stdout on every login — a security logging issue. -
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. -
Importer File Upload:
UploadFilessaves files to./uploads/relative path without sanitizing filenames for path traversal. The extension validation (isValidFileType) only checks the suffix. -
SQL Injection via
fieldName:GetMiscLookupByKeyForSystemconstructsquery.Order(orderBy)from a user-suppliedorderparameter — however the value is mapped to a safe set of constants before use, so actual injection risk is mitigated. -
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.gowith 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