042a1d4773
* introduced central package registry by package init function * dynamic registration of routes, model, migrations and initializers. * setting a docker compose project name to prevent shutdown of other containers with the same (composer)name * ai documentation * app template * Create tests for ALL API entpoints in ALL packages Based on current data. Ensure that all API endpoints used in frontend are tested. These tests are crucial for the next refactoring tasks. * adopting agent instructions for a more consistent coding style * added desired module layout and debugging information * Fix All Failing tests All failing tests are fixed now that makes the refactoring more easy since all tests must pass * restored routes for maintenance * added common translations * added new tests for API Endpoint * Merge branch 'separate_business_logic' * added lern and skill improvement cost editing * Set Docker image tag when building to prevent rebuild when nothing has changed * add and remove PP for Weaponskill fixed * add and remove PP for same named skills fixed * add new task
557 lines
17 KiB
Go
557 lines
17 KiB
Go
package gsmaster
|
|
|
|
import (
|
|
"bamort/database"
|
|
"bamort/bmrt/models"
|
|
"fmt"
|
|
"log"
|
|
|
|
"gorm.io/gorm"
|
|
)
|
|
|
|
// MigrateLearningCostsToDatabase überträgt die Daten aus learningCostsData in die Datenbank
|
|
func MigrateLearningCostsToDatabase() error {
|
|
log.Println("Starting migration of learning costs data to database...")
|
|
|
|
// 1. Quellen migrieren
|
|
if err := migrateSources(); err != nil {
|
|
return fmt.Errorf("failed to migrate sources: %w", err)
|
|
}
|
|
|
|
// 2. Charakterklassen migrieren
|
|
if err := migrateCharacterClasses(); err != nil {
|
|
return fmt.Errorf("failed to migrate character classes: %w", err)
|
|
}
|
|
|
|
// 2. Fertigkeitskategorien migrieren
|
|
if err := migrateSkillCategories(); err != nil {
|
|
return fmt.Errorf("failed to migrate skill categories: %w", err)
|
|
}
|
|
|
|
// 3. Schwierigkeitsgrade migrieren
|
|
if err := migrateSkillDifficulties(); err != nil {
|
|
return fmt.Errorf("failed to migrate skill difficulties: %w", err)
|
|
}
|
|
|
|
// 4. Zauberschulen migrieren
|
|
if err := migrateSpellSchools(); err != nil {
|
|
return fmt.Errorf("failed to migrate spell schools: %w", err)
|
|
}
|
|
|
|
// 5. EP-Kosten für Kategorien migrieren
|
|
if err := migrateClassCategoryEPCosts(); err != nil {
|
|
return fmt.Errorf("failed to migrate class category EP costs: %w", err)
|
|
}
|
|
|
|
// 5a. EP-Kosten für "Unbekannt"-Kategorie hinzufügen
|
|
if err := migrateUnknownCategoryEPCosts(); err != nil {
|
|
return fmt.Errorf("failed to migrate unknown category EP costs: %w", err)
|
|
}
|
|
|
|
// 6. EP-Kosten für Zauberschulen migrieren
|
|
if err := migrateClassSpellSchoolEPCosts(); err != nil {
|
|
return fmt.Errorf("failed to migrate class spell school EP costs: %w", err)
|
|
}
|
|
|
|
// 7. Zauber-Level LE-Kosten migrieren
|
|
if err := migrateSpellLevelLECosts(); err != nil {
|
|
return fmt.Errorf("failed to migrate spell level LE costs: %w", err)
|
|
}
|
|
|
|
// 8. Fertigkeits-Kategorien-Schwierigkeiten migrieren
|
|
if err := migrateSkillCategoryDifficulties(); err != nil {
|
|
return fmt.Errorf("failed to migrate skill category difficulties: %w", err)
|
|
}
|
|
|
|
// 9. Verbesserungskosten migrieren
|
|
if err := migrateSkillImprovementCosts(); err != nil {
|
|
return fmt.Errorf("failed to migrate skill improvement costs: %w", err)
|
|
}
|
|
|
|
log.Println("Migration completed successfully!")
|
|
return nil
|
|
}
|
|
|
|
// migrateSources erstellt die Standardquellen
|
|
func migrateSources() error {
|
|
sources := []models.Source{
|
|
{
|
|
Code: "KOD",
|
|
Name: "Kodex",
|
|
FullName: "Midgard Regelwerk - Kodex",
|
|
Edition: "5. Edition",
|
|
Publisher: "Pegasus Spiele",
|
|
IsCore: true,
|
|
IsActive: true,
|
|
GameSystemId: 1,
|
|
Description: "Grundregelwerk für Midgard",
|
|
},
|
|
{
|
|
Code: "ARK",
|
|
Name: "Arkanum",
|
|
FullName: "Midgard Arkanum",
|
|
Edition: "5. Edition",
|
|
Publisher: "Pegasus Spiele",
|
|
IsCore: false,
|
|
IsActive: true,
|
|
GameSystemId: 1,
|
|
Description: "Erweiterungsregelwerk für Zauber und Magie",
|
|
},
|
|
{
|
|
Code: "MYS",
|
|
Name: "Mysterium",
|
|
FullName: "Midgard Mysterium",
|
|
Edition: "5. Edition",
|
|
Publisher: "Pegasus Spiele",
|
|
IsCore: false,
|
|
IsActive: true,
|
|
GameSystemId: 1,
|
|
Description: "Erweiterungsregelwerk für Geheimnisse und Mysterien",
|
|
},
|
|
{
|
|
Code: "UNB",
|
|
Name: "Unbekannt",
|
|
FullName: "Unbekannte Quelle",
|
|
IsCore: false,
|
|
IsActive: true,
|
|
GameSystemId: 1,
|
|
Description: "Für Inhalte ohne bekannte Quelle",
|
|
},
|
|
}
|
|
|
|
for _, source := range sources {
|
|
// Prüfe ob die Quelle bereits existiert
|
|
var existing models.Source
|
|
if err := existing.FirstByCode(source.Code); err != nil {
|
|
// Quelle existiert nicht, erstelle sie
|
|
if err := source.Create(); err != nil {
|
|
return fmt.Errorf("failed to create source %s: %w", source.Code, err)
|
|
}
|
|
log.Printf("Created source: %s - %s", source.Code, source.Name)
|
|
}
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// migrateCharacterClasses erstellt Charakterklassen-Einträge
|
|
func migrateCharacterClasses() error {
|
|
// Hole die KOD-Quelle für Charakterklassen (alle Grundklassen sind im Kodex)
|
|
var kodSource models.Source
|
|
if err := kodSource.FirstByCode("KOD"); err != nil {
|
|
return fmt.Errorf("KOD source not found: %w", err)
|
|
}
|
|
|
|
characterClasses := map[string]string{
|
|
"As": "Assassine",
|
|
"Bb": "Barbar",
|
|
"Gl": "Glücksritter",
|
|
"Hä": "Händler",
|
|
"Kr": "Krieger",
|
|
"Sp": "Spitzbube",
|
|
"Wa": "Waldläufer",
|
|
"Ba": "Barde",
|
|
"Or": "Ordenskrieger",
|
|
"Dr": "Druide",
|
|
"Hx": "Hexer",
|
|
"Ma": "Magier",
|
|
"PB": "Priester Beschützer",
|
|
"PS": "Priester Streiter",
|
|
"Sc": "Schamane",
|
|
}
|
|
gs := GetGameSystem(0, "")
|
|
|
|
for code, name := range characterClasses {
|
|
class := models.CharacterClass{
|
|
Code: code,
|
|
Name: name,
|
|
SourceID: kodSource.ID,
|
|
GameSystemId: gs.ID,
|
|
}
|
|
|
|
// Prüfe ob die Klasse bereits existiert
|
|
var existing models.CharacterClass
|
|
if err := existing.FirstByCode(code); err != nil {
|
|
// Klasse existiert nicht, erstelle sie
|
|
if err := class.Create(); err != nil {
|
|
return fmt.Errorf("failed to create character class %s: %w", code, err)
|
|
}
|
|
log.Printf("Created character class: %s - %s (Source: %s)", code, name, kodSource.Code)
|
|
}
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// migrateSkillCategories erstellt Fertigkeitskategorien
|
|
func migrateSkillCategories() error {
|
|
// Hole die KOD-Quelle für Kategorien
|
|
var kodSource models.Source
|
|
if err := kodSource.FirstByCode("KOD"); err != nil {
|
|
return fmt.Errorf("KOD source not found: %w", err)
|
|
}
|
|
|
|
categories := []string{
|
|
"Unbekannt", // Für Fertigkeiten ohne bekannte Kategorie
|
|
"Alltag", "Freiland", "Halbwelt", "Kampf", "Körper",
|
|
"Sozial", "Unterwelt", "Waffen", "Wissen", "Schilde und Parierwaffen",
|
|
}
|
|
gs := GetGameSystem(0, "")
|
|
|
|
for _, categoryName := range categories {
|
|
sourceID := kodSource.ID
|
|
// "Unbekannt" bekommt die UNB-Quelle
|
|
if categoryName == "Unbekannt" {
|
|
var unbSource models.Source
|
|
if err := unbSource.FirstByCode("UNB"); err != nil {
|
|
return fmt.Errorf("UNB source not found: %w", err)
|
|
}
|
|
sourceID = unbSource.ID
|
|
}
|
|
|
|
category := models.SkillCategory{
|
|
Name: categoryName,
|
|
SourceID: sourceID,
|
|
GameSystemId: gs.ID,
|
|
}
|
|
|
|
// Prüfe ob die Kategorie bereits existiert
|
|
var existing models.SkillCategory
|
|
if err := existing.FirstByName(categoryName); err != nil {
|
|
// Kategorie existiert nicht, erstelle sie
|
|
if err := category.Create(); err != nil {
|
|
return fmt.Errorf("failed to create skill category %s: %w", categoryName, err)
|
|
}
|
|
log.Printf("Created skill category: %s", categoryName)
|
|
}
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// migrateSkillDifficulties erstellt Schwierigkeitsgrade
|
|
func migrateSkillDifficulties() error {
|
|
difficulties := []string{"leicht", "normal", "schwer", "sehr schwer"}
|
|
gs := GetGameSystem(0, "")
|
|
|
|
for _, difficultyName := range difficulties {
|
|
difficulty := models.SkillDifficulty{
|
|
Name: difficultyName,
|
|
GameSystemId: gs.ID,
|
|
}
|
|
|
|
// Prüfe ob die Schwierigkeit bereits existiert
|
|
var existing models.SkillDifficulty
|
|
if err := existing.FirstByName(difficultyName); err != nil {
|
|
// Schwierigkeit existiert nicht, erstelle sie
|
|
if err := difficulty.Create(); err != nil {
|
|
return fmt.Errorf("failed to create skill difficulty %s: %w", difficultyName, err)
|
|
}
|
|
log.Printf("Created skill difficulty: %s", difficultyName)
|
|
}
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// migrateSpellSchools erstellt Zauberschulen
|
|
func migrateSpellSchools() error {
|
|
// Hole die KOD-Quelle für Basis-Zauberschulen, ARK für erweiterte
|
|
var kodSource models.Source
|
|
if err := kodSource.FirstByCode("KOD"); err != nil {
|
|
return fmt.Errorf("KOD source not found: %w", err)
|
|
}
|
|
|
|
var arkSource models.Source
|
|
if err := arkSource.FirstByCode("ARK"); err != nil {
|
|
return fmt.Errorf("ARK source not found: %w", err)
|
|
}
|
|
gs := GetGameSystem(0, "")
|
|
|
|
schools := map[string]uint{
|
|
// Basis-Zauberschulen (Kodex)
|
|
"Beherrschen": kodSource.ID,
|
|
"Bewegen": kodSource.ID,
|
|
"Erkennen": kodSource.ID,
|
|
"Erschaffen": kodSource.ID,
|
|
"Formen": kodSource.ID,
|
|
"Verändern": kodSource.ID,
|
|
"Zerstören": kodSource.ID,
|
|
"Wunder": kodSource.ID,
|
|
// Erweiterte Zauberschulen (Arkanum)
|
|
"Dweomer": arkSource.ID,
|
|
"Lied": arkSource.ID,
|
|
}
|
|
|
|
for schoolName, sourceID := range schools {
|
|
school := models.SpellSchool{
|
|
Name: schoolName,
|
|
SourceID: sourceID,
|
|
GameSystemId: gs.ID,
|
|
}
|
|
|
|
// Prüfe ob die Schule bereits existiert
|
|
var existing models.SpellSchool
|
|
if err := existing.FirstByName(schoolName); err != nil {
|
|
// Schule existiert nicht, erstelle sie
|
|
if err := school.Create(); err != nil {
|
|
return fmt.Errorf("failed to create spell school %s: %w", schoolName, err)
|
|
}
|
|
log.Printf("Created spell school: %s", schoolName)
|
|
}
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// migrateClassCategoryEPCosts migriert EP-Kosten für Kategorien
|
|
func migrateClassCategoryEPCosts() error {
|
|
for classCode, categories := range learningCostsData.EPPerTE {
|
|
// Hole die Charakterklasse
|
|
var characterClass models.CharacterClass
|
|
if err := characterClass.FirstByCode(classCode); err != nil {
|
|
return fmt.Errorf("character class %s not found: %w", classCode, err)
|
|
}
|
|
|
|
for categoryName, epCost := range categories {
|
|
// Hole die Kategorie
|
|
var skillCategory models.SkillCategory
|
|
if err := skillCategory.FirstByName(categoryName); err != nil {
|
|
return fmt.Errorf("skill category %s not found: %w", categoryName, err)
|
|
}
|
|
|
|
// Erstelle EP-Kosten-Eintrag
|
|
cost := models.ClassCategoryEPCost{
|
|
CharacterClassID: characterClass.ID,
|
|
SkillCategoryID: skillCategory.ID,
|
|
EPPerTE: epCost,
|
|
}
|
|
|
|
if err := cost.Create(); err != nil {
|
|
return fmt.Errorf("failed to create EP cost for class %s category %s: %w", classCode, categoryName, err)
|
|
}
|
|
log.Printf("Created EP cost: %s - %s = %d EP/TE", classCode, categoryName, epCost)
|
|
}
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// migrateClassSpellSchoolEPCosts migriert EP-Kosten für Zauberschulen
|
|
func migrateClassSpellSchoolEPCosts() error {
|
|
for classCode, schools := range learningCostsData.SpellEPPerLE {
|
|
// Hole die Charakterklasse
|
|
var characterClass models.CharacterClass
|
|
if err := characterClass.FirstByCode(classCode); err != nil {
|
|
return fmt.Errorf("character class %s not found: %w", classCode, err)
|
|
}
|
|
|
|
for schoolName, epCost := range schools {
|
|
// Überspringe Schulen mit 0 EP-Kosten (nicht verfügbar)
|
|
if epCost == 0 {
|
|
continue
|
|
}
|
|
|
|
// Hole die Zauberschule
|
|
var spellSchool models.SpellSchool
|
|
if err := spellSchool.FirstByName(schoolName); err != nil {
|
|
return fmt.Errorf("spell school %s not found: %w", schoolName, err)
|
|
}
|
|
|
|
// Erstelle EP-Kosten-Eintrag
|
|
cost := models.ClassSpellSchoolEPCost{
|
|
CharacterClassID: characterClass.ID,
|
|
SpellSchoolID: spellSchool.ID,
|
|
EPPerLE: epCost,
|
|
}
|
|
|
|
if err := cost.Create(); err != nil {
|
|
return fmt.Errorf("failed to create spell EP cost for class %s school %s: %w", classCode, schoolName, err)
|
|
}
|
|
log.Printf("Created spell EP cost: %s - %s = %d EP/LE", classCode, schoolName, epCost)
|
|
}
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// migrateSpellLevelLECosts migriert LE-Kosten pro Zauber-Level
|
|
func migrateSpellLevelLECosts() error {
|
|
gs := GetGameSystem(0, "")
|
|
for level, leCost := range learningCostsData.SpellLEPerLevel {
|
|
cost := models.SpellLevelLECost{
|
|
Level: level,
|
|
LERequired: leCost,
|
|
GameSystemId: gs.ID,
|
|
}
|
|
|
|
if err := cost.Create(); err != nil {
|
|
return fmt.Errorf("failed to create spell level LE cost for level %d: %w", level, err)
|
|
}
|
|
log.Printf("Created spell level LE cost: Level %d = %d LE", level, leCost)
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// migrateSkillCategoryDifficulties migriert Fertigkeits-Kategorien-Schwierigkeiten
|
|
func migrateSkillCategoryDifficulties() error {
|
|
for categoryName, difficulties := range learningCostsData.ImprovementCost {
|
|
// Hole die Kategorie
|
|
var skillCategory models.SkillCategory
|
|
if err := skillCategory.FirstByName(categoryName); err != nil {
|
|
return fmt.Errorf("skill category %s not found: %w", categoryName, err)
|
|
}
|
|
|
|
for difficultyName, data := range difficulties {
|
|
// Hole die Schwierigkeit
|
|
var skillDifficulty models.SkillDifficulty
|
|
if err := skillDifficulty.FirstByName(difficultyName); err != nil {
|
|
return fmt.Errorf("skill difficulty %s not found: %w", difficultyName, err)
|
|
}
|
|
|
|
// Für jede Fertigkeit in dieser Kategorie/Schwierigkeit
|
|
for _, skillName := range data.Skills {
|
|
// Hole die Fertigkeit aus der bestehenden Skill-Tabelle
|
|
var skill models.Skill
|
|
if err := skill.First(skillName); err != nil {
|
|
// Wenn die Fertigkeit nicht existiert, überspringe sie oder logge eine Warnung
|
|
log.Printf("Warning: Skill %s not found in database, skipping", skillName)
|
|
continue
|
|
}
|
|
|
|
// Erstelle Kategorie-Schwierigkeits-Eintrag
|
|
categoryDifficulty := models.SkillCategoryDifficulty{
|
|
SkillID: skill.ID,
|
|
SkillCategoryID: skillCategory.ID,
|
|
SkillDifficultyID: skillDifficulty.ID,
|
|
LearnCost: data.LearnCost,
|
|
}
|
|
|
|
if err := categoryDifficulty.Create(); err != nil {
|
|
return fmt.Errorf("failed to create skill category difficulty for %s: %w", skillName, err)
|
|
}
|
|
log.Printf("Created skill category difficulty: %s - %s - %s (Learn: %d LE)",
|
|
skillName, categoryName, difficultyName, data.LearnCost)
|
|
}
|
|
}
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// migrateSkillImprovementCosts migriert Verbesserungskosten
|
|
func migrateSkillImprovementCosts() error {
|
|
for categoryName, difficulties := range learningCostsData.ImprovementCost {
|
|
// Hole die Kategorie
|
|
var skillCategory models.SkillCategory
|
|
if err := skillCategory.FirstByName(categoryName); err != nil {
|
|
return fmt.Errorf("skill category %s not found: %w", categoryName, err)
|
|
}
|
|
|
|
for difficultyName, data := range difficulties {
|
|
// Hole die Schwierigkeit
|
|
var skillDifficulty models.SkillDifficulty
|
|
if err := skillDifficulty.FirstByName(difficultyName); err != nil {
|
|
return fmt.Errorf("skill difficulty %s not found: %w", difficultyName, err)
|
|
}
|
|
|
|
// Für jede Fertigkeit in dieser Kategorie/Schwierigkeit
|
|
for _, skillName := range data.Skills {
|
|
// Hole die Fertigkeit
|
|
var skill models.Skill
|
|
if err := skill.First(skillName); err != nil {
|
|
log.Printf("Warning: Skill %s not found in database, skipping improvement costs", skillName)
|
|
continue
|
|
}
|
|
|
|
// Finde den entsprechenden SkillCategoryDifficulty-Eintrag
|
|
var categoryDifficulty models.SkillCategoryDifficulty
|
|
if err := database.DB.Where("skill_id = ? AND skill_category_id = ? AND skill_difficulty_id = ?",
|
|
skill.ID, skillCategory.ID, skillDifficulty.ID).First(&categoryDifficulty).Error; err != nil {
|
|
log.Printf("Warning: SkillCategoryDifficulty not found for %s, skipping improvement costs", skillName)
|
|
continue
|
|
}
|
|
|
|
// Für jeden Level in TrainCosts
|
|
for level, teCost := range data.TrainCosts {
|
|
var improvementCost models.SkillImprovementCost
|
|
err := database.DB.Where(
|
|
"skill_category_id = ? AND skill_difficulty_id = ? AND current_level = ?",
|
|
skillCategory.ID, skillDifficulty.ID, level,
|
|
).First(&improvementCost).Error
|
|
|
|
if err == gorm.ErrRecordNotFound {
|
|
improvementCost = models.SkillImprovementCost{
|
|
CategoryID: skillCategory.ID,
|
|
DifficultyID: skillDifficulty.ID,
|
|
CurrentLevel: level,
|
|
TERequired: teCost,
|
|
}
|
|
if err := improvementCost.Create(); err != nil {
|
|
return fmt.Errorf("failed to create improvement cost for %s level %d: %w", skillName, level, err)
|
|
}
|
|
} else if err != nil {
|
|
return fmt.Errorf("failed to query improvement cost for %s level %d: %w", skillName, level, err)
|
|
} else {
|
|
improvementCost.TERequired = teCost
|
|
if err := database.DB.Save(&improvementCost).Error; err != nil {
|
|
return fmt.Errorf("failed to update improvement cost for %s level %d: %w", skillName, level, err)
|
|
}
|
|
}
|
|
|
|
log.Printf("Upserted improvement cost: %s - %s - %s Level %d = %d TE",
|
|
skillName, categoryName, difficultyName, level, teCost)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// migrateUnknownCategoryEPCosts fügt EP-Kosten für die "Unbekannt"-Kategorie hinzu
|
|
func migrateUnknownCategoryEPCosts() error {
|
|
// Hole die "Unbekannt"-Kategorie
|
|
var unknownCategory models.SkillCategory
|
|
if err := unknownCategory.FirstByName("Unbekannt"); err != nil {
|
|
return fmt.Errorf("unknown category not found: %w", err)
|
|
}
|
|
|
|
// Hole alle Charakterklassen
|
|
var characterClasses []models.CharacterClass
|
|
if err := database.DB.Find(&characterClasses).Error; err != nil {
|
|
return fmt.Errorf("failed to fetch character classes: %w", err)
|
|
}
|
|
|
|
// Standard EP-Kosten für "Unbekannt": 50 EP/TE
|
|
standardEPCost := 50
|
|
|
|
for _, characterClass := range characterClasses {
|
|
// Prüfe ob bereits EP-Kosten für diese Klasse und "Unbekannt" existieren
|
|
var existingCost models.ClassCategoryEPCost
|
|
err := database.DB.Where("character_class_id = ? AND skill_category_id = ?",
|
|
characterClass.ID, unknownCategory.ID).First(&existingCost).Error
|
|
|
|
if err == nil {
|
|
// EP-Kosten existieren bereits, überspringe
|
|
log.Printf("EP cost for class %s and category 'Unbekannt' already exists, skipping", characterClass.Code)
|
|
continue
|
|
}
|
|
|
|
// Erstelle EP-Kosten-Eintrag für "Unbekannt"
|
|
cost := models.ClassCategoryEPCost{
|
|
CharacterClassID: characterClass.ID,
|
|
SkillCategoryID: unknownCategory.ID,
|
|
EPPerTE: standardEPCost,
|
|
}
|
|
|
|
if err := cost.Create(); err != nil {
|
|
return fmt.Errorf("failed to create EP cost for class %s category 'Unbekannt': %w", characterClass.Code, err)
|
|
}
|
|
log.Printf("Created EP cost: %s - Unbekannt = %d EP/TE", characterClass.Code, standardEPCost)
|
|
}
|
|
|
|
return nil
|
|
}
|