Files
bamort/backend_findings.md
2026-04-01 15:16:12 +02:00

60 KiB
Raw Permalink Blame History

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
  2. Entry Point — cmd/main.go
  3. Router & Middleware Setup
  4. Authentication & Authorization
  5. Configuration
  6. Database Layer
  7. Models (GORM Entities)
  8. Module-by-Module Analysis
  9. Complete API Route Listing
  10. Testing Approach
  11. Key Dependencies
  12. Security Observations
  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

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):

  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):

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)

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. gameSystemMigrateStructuregame_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

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 helpers
  • handlers.go — RegisterUser, LoginUser, CheckToken, AuthMiddleware, password reset
  • admin_handlers.go — ListUsers, GetUser, UpdateUserRole, ChangeUserPassword, DeleteUser
  • middleware.go — RequireRole, RequireAdmin, RequireMaintainer
  • database.goMigrateStructure 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.goGetLernCostNewSystem, ImproveSkill, LearnSkill, LearnSpell — full learning economy
  • derived_values_calculator.goCalculateStaticFields, CalculateRolledField — character creation math
  • creation_rules.goGetSpecialAbilityByRoll — special ability table
  • audit_log.goCreateAuditLogEntry, GetAuditLogForCharacter/Field
  • share_handlers.goGetCharacterShares, UpdateCharacterShares, GetAvailableUsersForSharing
  • practice_points_handler.goGetPracticePoints, UpdatePracticePoints, AddPracticePoint, UsePracticePoint
  • skill_type_helper.go — skill categorization helpers
  • system_information_handlers.goGetSkillCategoriesHandlerStatic
  • spell_utils.go — spell availability and creation utilities
  • database.goSaveCharacterToDB
  • 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.goLearningCostsTable with full EP/TE cost tables for all 15 character classes
  • learning_costs_init.goValidateLearningCostsData, GetLearningCostsSummary
  • levelup.goCalculateImprovementCost, CalculateSkillImprovementCost
  • game_system.go — Game system lookup helper
  • misclookup.goGetMiscLookupByKey, 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.goListTemplates, ExportCharacterToPDF, CleanupExportTemp, GetPDFFile
  • init.goInitializeTemplates — copies bundled templates to TemplatesDir on startup
  • chromedp.goNewPDFRenderer, RenderHTMLToPDF using chromedp
  • mapper.goMapCharacterToViewModel — maps Char to CharacterSheetViewModel
  • viewmodel.go — All view model types (CharacterSheetViewModel, CharacterInfo, AttributeValues, DerivedValueSet, SkillViewModel, WeaponViewModel, SpellViewModel, EquipmentViewModel, MagicItemViewModel, PageMeta)
  • render_with_continuation.goRenderPageWithContinuations — auto-generates overflow pages
  • pagination.go / pagination_helper.go — item distribution across pages
  • fill_capacity.go — reads <!-- MaxItems: N --> comments from HTML templates
  • template_parser.go / template_metadata.go — template loading and Go html/template execution
  • inline_resources.go — inlines CSS/images into HTML before rendering
  • file_management.go — manages xporttemp directory

Business Logic:

  1. On startup: sync ./default_templatescfg.TemplatesDir.
  2. ExportCharacterToPDF:
    • Loads character, maps to view model.
    • Renders pages 14 (page_1.html through page_4.html) with continuation logic.
    • Each page checks <!-- MaxItems: N --> to determine when to create a continuation page.
    • All rendered PDFs are merged via pdfcpu.
    • Saves to xporttemp/ directory, returns {"filename": "..."}.
  3. GetPDFFile: public endpoint, serves files from xporttemp/ by filename.

HTTP Endpoints:

Method Path Handler Auth
GET /api/pdf/templates ListTemplates Auth
GET /api/pdf/export/:id ExportCharacterToPDF Auth
POST /api/pdf/cleanup CleanupExportTemp Auth
GET /api/pdf/file/:filename GetPDFFile Public

8.8 importer/

Purpose: Import characters from VTT JSON format or CSV; export characters as VTT JSON, CSV, or spell CSV.

Key Files:

  • handler.goUploadFiles, ImportSpellCSVHandler
  • importer.goImportChar, CheckSkill, VTT import logic
  • importVTTJson.goImportVTTJSON — main VTT import driver
  • exporter.goExportCharToVTT, ExportCharacterToCSV
  • model.goCharacterImport 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.goExportCharacter — serializes a full Char + metadata to CharacterExport
  • importer.goImportCharacter — deserializes and recreates character in DB
  • database.goExportDatabase, 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.goVersion = "0.2.4", GetInfo(), GetInfo2()
  • handlers.goVersionsinfo, 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:

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

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.goSetupGin(r) — CORS middleware
  • routes.goBaseRouterGrp(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:

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=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:

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

  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

  1. Debug Logging of Passwords: handlers.go has fmt.Printf("pwdh: %s", ...) that prints password hashes to stdout on every login — a security logging issue.

  2. 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.

  3. Importer File Upload: UploadFiles saves files to ./uploads/ relative path without sanitizing filenames for path traversal. The extension validation (isValidFileType) only checks the suffix.

  4. 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.

  5. 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