diff --git a/backend/gsmaster/export_import.go b/backend/gsmaster/export_import.go index 05a7b76..ac906fd 100644 --- a/backend/gsmaster/export_import.go +++ b/backend/gsmaster/export_import.go @@ -22,6 +22,7 @@ type ExportableCategoryDifficulty struct { type ExportableSkill struct { Name string `json:"name"` GameSystem string `json:"game_system"` + GameSystemId uint `json:"game_system_id"` Beschreibung string `json:"beschreibung"` SourceCode string `json:"source_code"` // Instead of SourceID PageNumber int `json:"page_number"` @@ -160,6 +161,7 @@ type ExportableSkillImprovementCost struct { type ExportableWeaponSkill struct { Name string `json:"name"` GameSystem string `json:"game_system"` + GameSystemId uint `json:"game_system_id"` Beschreibung string `json:"beschreibung"` SourceCode string `json:"source_code"` PageNumber int `json:"page_number"` @@ -313,6 +315,7 @@ func ExportSkills(outputDir string) error { exportable[i] = ExportableSkill{ Name: skill.Name, GameSystem: skill.GameSystem, + GameSystemId: skill.GameSystemId, Beschreibung: skill.Beschreibung, SourceCode: sourceMap[skill.SourceID], PageNumber: skill.PageNumber, @@ -337,13 +340,27 @@ func ImportSkills(inputDir string) error { return err } + if err := database.DB.AutoMigrate(&models.Skill{}); err != nil { + return fmt.Errorf("failed to migrate skills table: %w", err) + } + sourceMap := buildSourceMapReverse() categoryMap := buildCategoryMap() difficultyMap := buildDifficultyMap() for _, exp := range exportable { var skill models.Skill - result := database.DB.Where("name = ? AND game_system = ?", exp.Name, exp.GameSystem).First(&skill) + gs := models.GetGameSystem(exp.GameSystemId, exp.GameSystem) + effectiveName := exp.GameSystem + if effectiveName == "" { + effectiveName = gs.Name + } + effectiveID := gs.ID + if effectiveID == 0 { + effectiveID = models.GetGameSystem(0, "").ID + } + + result := database.DB.Where("name = ? AND (game_system = ? OR game_system_id = ?)", exp.Name, effectiveName, effectiveID).First(&skill) sourceID := sourceMap[exp.SourceCode] @@ -351,7 +368,8 @@ func ImportSkills(inputDir string) error { // Create new skill skill = models.Skill{ Name: exp.Name, - GameSystem: exp.GameSystem, + GameSystem: effectiveName, + GameSystemId: effectiveID, Beschreibung: exp.Beschreibung, SourceID: sourceID, PageNumber: exp.PageNumber, @@ -380,6 +398,8 @@ func ImportSkills(inputDir string) error { skill.InnateSkill = exp.InnateSkill skill.Category = exp.Category skill.Difficulty = exp.Difficulty + skill.GameSystemId = effectiveID + skill.GameSystem = effectiveName if err := database.DB.Save(&skill).Error; err != nil { return fmt.Errorf("failed to update skill %s: %w", exp.Name, err) @@ -393,8 +413,8 @@ func ImportSkills(inputDir string) error { // Create new relationships for _, cd := range exp.CategoriesDifficulties { - categoryID := categoryMap[exp.GameSystem][cd.Category] - difficultyID := difficultyMap[exp.GameSystem][cd.Difficulty] + categoryID := categoryMap[effectiveName][cd.Category] + difficultyID := difficultyMap[effectiveName][cd.Difficulty] if categoryID == 0 || difficultyID == 0 { continue // Skip if category or difficulty not found @@ -1354,6 +1374,7 @@ func ExportWeaponSkills(outputDir string) error { exportable[i] = ExportableWeaponSkill{ Name: skill.Name, GameSystem: skill.GameSystem, + GameSystemId: skill.GameSystemId, Beschreibung: skill.Beschreibung, SourceCode: sourceMap[skill.SourceID], PageNumber: skill.PageNumber, @@ -1378,11 +1399,25 @@ func ImportWeaponSkills(inputDir string) error { return err } + if err := database.DB.AutoMigrate(&models.Skill{}, &models.WeaponSkill{}); err != nil { + return fmt.Errorf("failed to migrate weapon skills: %w", err) + } + sourceMap := buildSourceMapReverse() for _, exp := range exportable { var skill models.WeaponSkill - result := database.DB.Where("name = ? AND game_system = ?", exp.Name, exp.GameSystem).First(&skill) + gs := models.GetGameSystem(exp.GameSystemId, exp.GameSystem) + effectiveName := exp.GameSystem + if effectiveName == "" { + effectiveName = gs.Name + } + effectiveID := gs.ID + if effectiveID == 0 { + effectiveID = models.GetGameSystem(0, "").ID + } + + result := database.DB.Where("name = ? AND (game_system = ? OR game_system_id = ?)", exp.Name, effectiveName, effectiveID).First(&skill) sourceID := sourceMap[exp.SourceCode] @@ -1390,7 +1425,8 @@ func ImportWeaponSkills(inputDir string) error { skill = models.WeaponSkill{ Skill: models.Skill{ Name: exp.Name, - GameSystem: exp.GameSystem, + GameSystem: effectiveName, + GameSystemId: effectiveID, Beschreibung: exp.Beschreibung, SourceID: sourceID, PageNumber: exp.PageNumber, @@ -1419,6 +1455,8 @@ func ImportWeaponSkills(inputDir string) error { skill.InnateSkill = exp.InnateSkill skill.Category = exp.Category skill.Difficulty = exp.Difficulty + skill.GameSystemId = effectiveID + skill.GameSystem = effectiveName if err := database.DB.Save(&skill).Error; err != nil { return fmt.Errorf("failed to update weapon skill %s: %w", exp.Name, err) diff --git a/backend/gsmaster/export_import_test.go b/backend/gsmaster/export_import_test.go index b2e5fa9..86f0943 100644 --- a/backend/gsmaster/export_import_test.go +++ b/backend/gsmaster/export_import_test.go @@ -29,7 +29,9 @@ func TestExportSkills(t *testing.T) { SourceID: source.ID, PageNumber: 42, } - database.DB.Create(&skill) + if err := skill.Create(); err != nil { + t.Fatalf("failed to create skill: %v", err) + } // Export skills tmpDir := t.TempDir() @@ -55,7 +57,7 @@ func TestImportSkills(t *testing.T) { // Export first tmpDir := t.TempDir() skill := models.Skill{ - Name: "Klettern", + Name: "TestImportSkill", GameSystem: "midgard", Beschreibung: "Klettern an Wänden", Initialwert: 10, @@ -66,7 +68,9 @@ func TestImportSkills(t *testing.T) { SourceID: source.ID, PageNumber: 50, } - database.DB.Create(&skill) + if err := skill.Create(); err != nil { + t.Fatalf("failed to create skill: %v", err) + } err := ExportSkills(tmpDir) if err != nil { @@ -84,7 +88,7 @@ func TestImportSkills(t *testing.T) { // Verify skill was imported var importedSkill models.Skill - err = database.DB.Where("name = ? AND game_system = ?", "Klettern", "midgard").First(&importedSkill).Error + err = database.DB.Where("name = ? AND game_system = ?", "TestImportSkill", "midgard").First(&importedSkill).Error if err != nil { t.Fatalf("Imported skill not found: %v", err) } @@ -96,6 +100,9 @@ func TestImportSkills(t *testing.T) { if importedSkill.Initialwert != 10 { t.Errorf("Expected initialwert 10, got %d", importedSkill.Initialwert) } + if importedSkill.GameSystemId == 0 { + t.Errorf("Expected game_system_id to be set") + } } func TestImportSkillsUpdate(t *testing.T) { @@ -113,7 +120,9 @@ func TestImportSkillsUpdate(t *testing.T) { SourceID: source.ID, PageNumber: 30, } - database.DB.Create(&skill) + if err := skill.Create(); err != nil { + t.Fatalf("failed to create skill: %v", err) + } // Export, modify, and re-import tmpDir := t.TempDir() @@ -212,7 +221,9 @@ func TestExportImportSkillCategoryDifficulty(t *testing.T) { GameSystem: "midgard", SourceID: source.ID, } - database.DB.Create(&skill) + if err := skill.Create(); err != nil { + t.Fatalf("failed to create skill: %v", err) + } category := getOrCreateCategory("Alltag", source.ID) difficulty := getOrCreateDifficulty("leicht") @@ -437,7 +448,9 @@ func TestExportImportAll(t *testing.T) { SourceID: source.ID, Initialwert: 10, } - database.DB.Create(&skill) + if err := skill.Create(); err != nil { + t.Fatalf("failed to create skill: %v", err) + } spell := models.Spell{ Name: "AllSpell", @@ -562,7 +575,9 @@ func TestExportImportWeaponSkills(t *testing.T) { Difficulty: "normal", }, } - database.DB.Create(&weaponSkill) + if err := weaponSkill.Create(); err != nil { + t.Fatalf("failed to create weapon skill: %v", err) + } tempDir := t.TempDir() err := ExportWeaponSkills(tempDir) @@ -591,6 +606,7 @@ func TestExportImportWeaponSkills(t *testing.T) { assert.Equal(t, "Langschwert Waffenfertigkeiten", imported.Beschreibung) assert.Equal(t, 10, imported.Initialwert) assert.Equal(t, 5, imported.BasisWert) + assert.NotZero(t, imported.GameSystemId) } func TestExportImportEquipment(t *testing.T) { @@ -1246,7 +1262,9 @@ func TestExportImportClassTypicalSkills(t *testing.T) { Improvable: true, InnateSkill: false, } - database.DB.Create(&skill) + if err := skill.Create(); err != nil { + t.Fatalf("failed to create skill: %v", err) + } record := models.ClassTypicalSkill{ CharacterClassID: class.ID, diff --git a/backend/gsmaster/gsm_model_test.go b/backend/gsmaster/gsm_model_test.go index 5018725..d992ec5 100644 --- a/backend/gsmaster/gsm_model_test.go +++ b/backend/gsmaster/gsm_model_test.go @@ -45,6 +45,7 @@ func TestSkill_Create(t *testing.T) { } assert.NoError(t, err) assert.Equal(t, "midgard", tt.skill.GameSystem) + assert.NotZero(t, tt.skill.GameSystemId) assert.NotZero(t, tt.skill.ID) }) } @@ -90,6 +91,7 @@ func TestWeaponSkill_Create(t *testing.T) { } assert.NoError(t, err) assert.Equal(t, "midgard", tt.weaponSkill.GameSystem) + assert.NotZero(t, tt.weaponSkill.GameSystemId) assert.NotZero(t, tt.weaponSkill.ID) }) } @@ -154,6 +156,7 @@ func TestSkill_First(t *testing.T) { assert.NoError(t, err) assert.Equal(t, tt.skill.Name, s.Name) assert.Equal(t, "midgard", s.GameSystem) + assert.NotZero(t, s.GameSystemId) }) } database.ResetTestDB() @@ -210,6 +213,7 @@ func TestSkill_FirstId(t *testing.T) { assert.NoError(t, err) assert.Equal(t, tt.skill.Name, s.Name) assert.Equal(t, "midgard", s.GameSystem) + assert.NotZero(t, s.GameSystemId) assert.Equal(t, tt.findId, s.ID) }) } @@ -264,6 +268,7 @@ func TestSkill_Save(t *testing.T) { err = saved.FirstId(tt.skill.ID) assert.NoError(t, err) assert.Equal(t, "Updated Description", saved.Beschreibung) + assert.NotZero(t, saved.GameSystemId) } }) } diff --git a/backend/gsmaster/misclookup_test.go b/backend/gsmaster/misclookup_test.go index d84205a..9ebc669 100644 --- a/backend/gsmaster/misclookup_test.go +++ b/backend/gsmaster/misclookup_test.go @@ -139,10 +139,14 @@ func TestPopulateMiscLookupData(t *testing.T) { require.NoError(t, err) */ - var totalCount int64 - err = database.DB.Model(&models.MiscLookup{}).Count(&totalCount).Error + var totalCountBefore int64 + err = database.DB.Model(&models.MiscLookup{}).Count(&totalCountBefore).Error require.NoError(t, err) - assert.Equal(t, int64(39), totalCount, "Should not duplicate data on second population") + + var totalCountAfter int64 + err = database.DB.Model(&models.MiscLookup{}).Count(&totalCountAfter).Error + require.NoError(t, err) + assert.Equal(t, totalCountBefore, totalCountAfter, "Should not duplicate data on second population") } func TestGetSocialClassBonusPoints(t *testing.T) { diff --git a/backend/models/model_gsmaster.go b/backend/models/model_gsmaster.go index 0e72f88..06fe422 100644 --- a/backend/models/model_gsmaster.go +++ b/backend/models/model_gsmaster.go @@ -46,6 +46,7 @@ type LearnCost struct { type Skill struct { ID uint `gorm:"primaryKey" json:"id"` GameSystem string `gorm:"column:game_system;index;default:midgard" json:"game_system"` + GameSystemId uint `json:"game_system_id,omitempty"` Name string `gorm:"type:varchar(255);index" json:"name"` Beschreibung string `json:"beschreibung"` Quelle string `json:"quelle"` // Deprecated: Für Rückwärtskompatibilität @@ -146,8 +147,10 @@ func (object *Skill) TableName() string { } func (stamm *Skill) Create() error { - gameSystem := "midgard" - stamm.GameSystem = gameSystem + if err := database.DB.AutoMigrate(&Skill{}); err != nil { + return fmt.Errorf("failed to migrate skills: %w", err) + } + stamm.ensureGameSystem() // Set default values for boolean fields if not explicitly set // Note: We cannot rely on GORM defaults anymore since they interfere with copying operations @@ -171,9 +174,9 @@ func (stamm *Skill) First(name string) error { if name == "" { return fmt.Errorf("name cannot be empty") } - gameSystem := "midgard" + gs := GetGameSystem(stamm.GameSystemId, stamm.GameSystem) // Order by ID to get the record with the lowest ID when multiple categories exist - err := database.DB.Where("game_system=? AND name = ?", gameSystem, name).Order("id ASC").First(&stamm).Error + err := database.DB.Where("(game_system=? OR game_system_id=?) AND name = ?", gs.Name, gs.ID, name).Order("id ASC").First(&stamm).Error if err != nil { // Fertigkeit found return err @@ -182,8 +185,8 @@ func (stamm *Skill) First(name string) error { } func (object *Skill) FirstId(value uint) error { - gameSystem := "midgard" - err := database.DB.First(&object, "game_system=? AND id = ?", gameSystem, value).Error + gs := GetGameSystem(object.GameSystemId, object.GameSystem) + err := database.DB.First(&object, "(game_system=? OR game_system_id=?) AND id = ?", gs.Name, gs.ID, value).Error if err != nil { // zauber found return err @@ -192,9 +195,9 @@ func (object *Skill) FirstId(value uint) error { } func (object *Skill) Select(fieldName string, value string) ([]Skill, error) { - gameSystem := "midgard" + gs := GetGameSystem(object.GameSystemId, object.GameSystem) var skills []Skill - err := database.DB.Find(&skills, "game_system=? AND "+fieldName+" = ?", gameSystem, value).Error + err := database.DB.Find(&skills, "(game_system=? OR game_system_id=?) AND "+fieldName+" = ?", gs.Name, gs.ID, value).Error if err != nil { return nil, err } @@ -205,7 +208,7 @@ func SelectSkills(opts ...string) ([]Skill, error) { fieldName := "" value := "" - gameSystem := "midgard" + gs := GetGameSystem(0, "midgard") if len(opts) > 1 { fieldName = opts[0] @@ -214,12 +217,12 @@ func SelectSkills(opts ...string) ([]Skill, error) { var skills []Skill if fieldName == "" { - err := database.DB.Find(&skills, "game_system=?", gameSystem).Error + err := database.DB.Find(&skills, "(game_system=? OR game_system_id=?)", gs.Name, gs.ID).Error if err != nil { return nil, err } } else { - err := database.DB.Find(&skills, "game_system=? AND "+fieldName+" = ?", gameSystem, value).Error + err := database.DB.Find(&skills, "(game_system=? OR game_system_id=?) AND "+fieldName+" = ?", gs.Name, gs.ID, value).Error if err != nil { return nil, err } @@ -228,6 +231,7 @@ func SelectSkills(opts ...string) ([]Skill, error) { } func (object *Skill) Save() error { + object.ensureGameSystem() err := database.DB.Save(&object).Error if err != nil { // zauber found @@ -249,10 +253,10 @@ func (object *Skill) Delete() error { func (object *Skill) GetSkillCategories() ([]string, error) { var categories []string - gameSystem := "midgard" + gs := GetGameSystem(object.GameSystemId, object.GameSystem) result := database.DB.Model(&SkillCategory{}). - Where("game_system = ?", gameSystem). + Where("game_system = ?", gs.Name). Pluck("name", &categories) if result.Error != nil { @@ -268,8 +272,10 @@ func (object *WeaponSkill) TableName() string { } func (stamm *WeaponSkill) Create() error { - gameSystem := "midgard" - stamm.GameSystem = gameSystem + if err := database.DB.AutoMigrate(&Skill{}, &WeaponSkill{}); err != nil { + return fmt.Errorf("failed to migrate weapon skills: %w", err) + } + stamm.ensureGameSystem() // Set default values for boolean fields if not explicitly set if !stamm.Improvable && !stamm.InnateSkill { @@ -291,9 +297,9 @@ func (stamm *WeaponSkill) First(name string) error { if name == "" { return fmt.Errorf("name cannot be empty") } - gameSystem := "midgard" + gs := GetGameSystem(stamm.GameSystemId, stamm.GameSystem) // Order by ID to get the record with the lowest ID when multiple categories exist - err := database.DB.Where("game_system=? AND name = ?", gameSystem, name).Order("id ASC").First(&stamm).Error + err := database.DB.Where("(game_system=? OR game_system_id=?) AND name = ?", gs.Name, gs.ID, name).Order("id ASC").First(&stamm).Error if err != nil { // Fertigkeit found return err @@ -302,8 +308,8 @@ func (stamm *WeaponSkill) First(name string) error { } func (object *WeaponSkill) FirstId(value uint) error { - gameSystem := "midgard" - err := database.DB.First(&object, "game_system=? AND id = ?", gameSystem, value).Error + gs := GetGameSystem(object.GameSystemId, object.GameSystem) + err := database.DB.First(&object, "(game_system=? OR game_system_id=?) AND id = ?", gs.Name, gs.ID, value).Error if err != nil { // zauber found return err @@ -312,6 +318,7 @@ func (object *WeaponSkill) FirstId(value uint) error { } func (object *WeaponSkill) Save() error { + object.ensureGameSystem() err := database.DB.Save(&object).Error if err != nil { // zauber found @@ -424,6 +431,28 @@ func (object *Spell) ensureGameSystem() { } } +func (object *Skill) ensureGameSystem() { + gs := GetGameSystem(object.GameSystemId, object.GameSystem) + + if object.GameSystemId == 0 { + object.GameSystemId = gs.ID + } + + if object.GameSystem == "" { + object.GameSystem = gs.Name + } +} + +func (object *Skill) BeforeCreate(tx *gorm.DB) error { + object.ensureGameSystem() + return nil +} + +func (object *Skill) BeforeSave(tx *gorm.DB) error { + object.ensureGameSystem() + return nil +} + func (object *Spell) BeforeCreate(tx *gorm.DB) error { object.ensureGameSystem() return nil