From 5e7acb8099a0f1ec2d1a02473f85c01d8a41f328 Mon Sep 17 00:00:00 2001 From: Frank Date: Mon, 12 Jan 2026 16:36:35 +0100 Subject: [PATCH] renamed tables for a name more matching the purpose --- ToDo.md | 1 + backend/doc/EXPORT_IMPORT.md | 2 +- backend/gsmaster/learning_costs_init.go | 4 +- backend/maintenance/handlers.go | 35 +++-- backend/maintenance/handlers_test.go | 139 ++++++++++++++++++ backend/models/database.go | 2 +- backend/models/model_character_creation.go | 99 +++++++++++++ backend/models/model_class_learning_points.go | 101 ------------- backend/models/model_gsmaster.go | 22 ++- backend/models/model_gsmaster_test.go | 84 +++++++++++ backend/models/model_learning_costs.go | 18 ++- backend/models/model_skills.go | 8 +- backend/models/model_skills_test.go | 20 +-- .../models/populate_class_learning_points.go | 2 +- backend/transfer/database.go | 4 +- 15 files changed, 389 insertions(+), 152 deletions(-) delete mode 100644 backend/models/model_class_learning_points.go diff --git a/ToDo.md b/ToDo.md index 2fe6641..485d0ea 100644 --- a/ToDo.md +++ b/ToDo.md @@ -37,6 +37,7 @@ ./backend$ go test ./... -v 2>&1 |grep FAIL * API endpunkte für Export/Import aus Commit 2dcb4e00faaf316b98eb28e83cc5137bf0d1385d * wouldn't it be a good idea to remove the GameSystem from all the records and define it in a kind of manifest. The values in the manifest could be applied to all records (where needed) during the import session. export_import.go +* maintanance view for gsm_cc_class_category_points ## Refaktor * Export Import Module neu grupieren diff --git a/backend/doc/EXPORT_IMPORT.md b/backend/doc/EXPORT_IMPORT.md index 3b81f3b..92d8455 100644 --- a/backend/doc/EXPORT_IMPORT.md +++ b/backend/doc/EXPORT_IMPORT.md @@ -12,7 +12,7 @@ The export/import mechanism allows exporting all master data from the `model_gsm ## Supported Entities ### From `model_learning_costs.go`: -- **Sources** (`learning_sources`) - Game books and source materials +- **Sources** (`gsm_lit_sources`) - Game books and source materials - **SkillCategories** (`learning_skill_categories`) - Skill classification categories - **SkillDifficulties** (`learning_skill_difficulties`) - Difficulty levels - **SkillCategoryDifficulties** (`learning_skill_category_difficulties`) - Relationship between skills, categories, and difficulties with learning costs diff --git a/backend/gsmaster/learning_costs_init.go b/backend/gsmaster/learning_costs_init.go index da229f0..5b3a424 100644 --- a/backend/gsmaster/learning_costs_init.go +++ b/backend/gsmaster/learning_costs_init.go @@ -41,7 +41,7 @@ func ValidateLearningCostsData() error { query string expected int }{ - {"Character Classes", "SELECT COUNT(*) FROM learning_character_classes", 15}, + {"Character Classes", "SELECT COUNT(*) FROM gsm_character_classes", 15}, {"Skill Categories", "SELECT COUNT(*) FROM learning_skill_categories", 10}, {"Skill Difficulties", "SELECT COUNT(*) FROM learning_skill_difficulties", 4}, {"Spell Schools", "SELECT COUNT(*) FROM learning_spell_schools", 10}, @@ -70,7 +70,7 @@ func GetLearningCostsSummary() (map[string]interface{}, error) { // Zähle Einträge in verschiedenen Tabellen tables := []string{ - "learning_character_classes", + "gsm_character_classes", "learning_skill_categories", "learning_skill_difficulties", "learning_spell_schools", diff --git a/backend/maintenance/handlers.go b/backend/maintenance/handlers.go index df08c60..e934e34 100644 --- a/backend/maintenance/handlers.go +++ b/backend/maintenance/handlers.go @@ -219,7 +219,7 @@ func copyMariaDBToSQLite(mariaDB, sqliteDB *gorm.DB) error { &models.SkillCategoryDifficulty{}, &models.WeaponSkillCategoryDifficulty{}, &models.SkillImprovementCost{}, - &models.ClassLearningPoints{}, + &models.ClassCategoryLearningPoints{}, &models.ClassSpellPoints{}, &models.ClassTypicalSkill{}, &models.ClassTypicalSpell{}, @@ -330,25 +330,24 @@ func copyTableData(sourceDB, targetDB *gorm.DB, model interface{}) error { return err } - // Get the actual records from reflection - records := recordsPtr.Elem().Interface() - recordsLen := recordsPtr.Elem().Len() - - if recordsLen == 0 { + // Get the records for iteration + recordsVal := recordsPtr.Elem() + if recordsVal.Len() == 0 { logger.Debug("Keine weiteren Datensätze für %s", tableName) break } - // Batch in SQLite einfügen mit Konflikt-Behandlung - // Verwende Clauses.OnConflict um bestehende Datensätze zu ersetzen - if err := targetDB.Model(model).Clauses(clause.OnConflict{ - UpdateAll: true, - }).Create(records).Error; err != nil { - logger.Error("Fehler beim Einfügen von Batch %d für %s: %s", batchNum, tableName, err.Error()) - return err + // Batch in SQLite einfügen + // Use Save() instead of Create() to avoid GORM applying default values to zero values (e.g., false for booleans) + for i := 0; i < recordsVal.Len(); i++ { + record := recordsVal.Index(i).Addr().Interface() + if err := targetDB.Save(record).Error; err != nil { + logger.Error("Fehler beim Speichern von Datensatz in Batch %d für %s: %s", batchNum, tableName, err.Error()) + return err + } } - logger.Debug("Batch %d/%d für %s erfolgreich kopiert (%d Datensätze)", batchNum, totalBatches, tableName, recordsLen) + logger.Debug("Batch %d/%d für %s erfolgreich kopiert (%d Datensätze)", batchNum, totalBatches, tableName, recordsVal.Len()) } logger.Info("Tabelle %s erfolgreich kopiert (%d Datensätze total)", tableName, count) @@ -755,7 +754,7 @@ func copySQLiteToMariaDB(sqliteDB, mariaDB *gorm.DB) error { &models.SkillCategoryDifficulty{}, // Jetzt nach Skills &models.WeaponSkillCategoryDifficulty{}, &models.SkillImprovementCost{}, - &models.ClassLearningPoints{}, + &models.ClassCategoryLearningPoints{}, &models.ClassSpellPoints{}, &models.ClassTypicalSkill{}, &models.ClassTypicalSpell{}, @@ -913,8 +912,8 @@ func copyTableDataReverse(sourceDB, targetDB *gorm.DB, model interface{}) error return fmt.Errorf("failed to read batch from source: %w", err) } records = batch - case *models.ClassLearningPoints: - var batch []models.ClassLearningPoints + case *models.ClassCategoryLearningPoints: + var batch []models.ClassCategoryLearningPoints if err := sourceDB.Limit(batchSize).Offset(offset).Find(&batch).Error; err != nil { return fmt.Errorf("failed to read batch from source: %w", err) } @@ -1155,7 +1154,7 @@ func clearMariaDBData(db *gorm.DB) error { &models.ClassTypicalSpell{}, &models.ClassTypicalSkill{}, &models.ClassSpellPoints{}, - &models.ClassLearningPoints{}, + &models.ClassCategoryLearningPoints{}, // GSMaster Basis-Daten &models.Believe{}, diff --git a/backend/maintenance/handlers_test.go b/backend/maintenance/handlers_test.go index 3ce99fc..5e61946 100644 --- a/backend/maintenance/handlers_test.go +++ b/backend/maintenance/handlers_test.go @@ -1,9 +1,15 @@ package maintenance import ( + "bamort/database" "bamort/models" "bamort/user" "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "gorm.io/driver/sqlite" + "gorm.io/gorm" ) // TestTableListCompleteness verifies that all database models are included in the table lists @@ -136,6 +142,139 @@ func TestTableListCompleteness(t *testing.T) { t.Logf("Total expected models: %d", len(expectedModels)) } +// TestImprovableFieldTransfer tests that the Improvable field is correctly transferred from MariaDB to SQLite +func TestImprovableFieldTransfer(t *testing.T) { + setupTestEnvironment(t) + + // Create source database (simulating MariaDB) + sourceDB, err := gorm.Open(sqlite.Open(":memory:"), &gorm.Config{}) + require.NoError(t, err) + + // Create target database (simulating SQLite) + targetDB, err := gorm.Open(sqlite.Open(":memory:"), &gorm.Config{}) + require.NoError(t, err) + + // Migrate structures + err = sourceDB.AutoMigrate(&models.Skill{}) + require.NoError(t, err) + err = targetDB.AutoMigrate(&models.Skill{}) + require.NoError(t, err) + + // Insert test data with different Improvable values + // Use raw SQL to avoid GORM applying default values + testSkills := []struct { + ID uint + Name string + GameSystem string + Improvable bool + InnateSkill bool + }{ + {1, "Hören", "midgard", false, true}, + {2, "Alchimie", "midgard", true, false}, + {3, "Nachtsicht", "midgard", false, true}, + } + + for _, skill := range testSkills { + err = sourceDB.Exec(`INSERT INTO gsm_skills (id, name, game_system, improvable, innate_skill, initialwert, basis_wert) VALUES (?, ?, ?, ?, ?, 5, 0)`, + skill.ID, skill.Name, skill.GameSystem, skill.Improvable, skill.InnateSkill).Error + require.NoError(t, err) + } + + // Verify source data + var sourceSkill models.Skill + err = sourceDB.First(&sourceSkill, 1).Error + require.NoError(t, err) + assert.Equal(t, "Hören", sourceSkill.Name) + t.Logf("DEBUG: Source skill Hören - Improvable: %v, InnateSkill: %v", sourceSkill.Improvable, sourceSkill.InnateSkill) + + // Also check raw data from database + var rawImprovable int + err = sourceDB.Raw("SELECT improvable FROM gsm_skills WHERE id = 1").Scan(&rawImprovable).Error + require.NoError(t, err) + t.Logf("DEBUG: Raw SQL value for improvable: %d", rawImprovable) + + assert.False(t, sourceSkill.Improvable, "Source skill should have Improvable=false") + + // Copy data using copyTableData + err = copyTableData(sourceDB, targetDB, &models.Skill{}) + require.NoError(t, err) + + // Verify all skills in target database + var targetSkills []models.Skill + err = targetDB.Find(&targetSkills).Error + require.NoError(t, err) + require.Len(t, targetSkills, 3, "Should have 3 skills in target") + + // Check each skill's Improvable field + expectedValues := map[uint]bool{ + 1: false, // Hören + 2: true, // Alchimie + 3: false, // Nachtsicht + } + + for _, targetSkill := range targetSkills { + expectedImprovable := expectedValues[targetSkill.ID] + assert.Equal(t, expectedImprovable, targetSkill.Improvable, + "Skill %s (ID: %d) should have Improvable=%v, got %v", + targetSkill.Name, targetSkill.ID, expectedImprovable, targetSkill.Improvable) + } + + // Specific checks + var hoeren models.Skill + err = targetDB.Where("name = ?", "Hören").First(&hoeren).Error + require.NoError(t, err) + assert.False(t, hoeren.Improvable, "Hören should have Improvable=false after transfer") + + var alchimie models.Skill + err = targetDB.Where("name = ?", "Alchimie").First(&alchimie).Error + require.NoError(t, err) + assert.True(t, alchimie.Improvable, "Alchimie should have Improvable=true after transfer") +} + +// TestImprovableFieldInPreparedTestDB verifies the prepared test database has correct Improvable values +func TestImprovableFieldInPreparedTestDB(t *testing.T) { + setupTestEnvironment(t) + + // Ensure clean database state before setup + database.ResetTestDB() + + // Use the prepared test database + database.SetupTestDB(true) + require.NotNil(t, database.DB) + + // Ensure database cleanup after test + t.Cleanup(func() { + database.ResetTestDB() + }) + + // Check specific skills that should have Improvable=false (innate skills) + innateSkills := []string{"Hören", "Nachtsicht", "Riechen", "Sechster Sinn", "Sehen"} + + for _, skillName := range innateSkills { + var skill models.Skill + err := database.DB.Where("name = ?", skillName).First(&skill).Error + if err == gorm.ErrRecordNotFound { + t.Logf("Skill %s not found in prepared test DB - skipping", skillName) + continue + } + require.NoError(t, err) + + // These are innate skills and should not be improvable + assert.True(t, skill.InnateSkill, "Skill %s should be marked as InnateSkill", skillName) + // Note: Based on game rules, innate skills are typically not improvable + t.Logf("Skill: %s, Improvable: %v, InnateSkill: %v", skillName, skill.Improvable, skill.InnateSkill) + } + + // Check a regular skill that should be improvable + var alchimie models.Skill + err := database.DB.Where("name = ?", "Alchimie").First(&alchimie).Error + if err != gorm.ErrRecordNotFound { + require.NoError(t, err) + assert.True(t, alchimie.Improvable, "Alchimie should be improvable") + assert.False(t, alchimie.InnateSkill, "Alchimie should not be an innate skill") + } +} + // getModelTypeName returns the type name of a model func getModelTypeName(model interface{}) string { switch model.(type) { diff --git a/backend/models/database.go b/backend/models/database.go index 86e5fae..4921d07 100644 --- a/backend/models/database.go +++ b/backend/models/database.go @@ -157,7 +157,7 @@ func learningMigrateStructure(db ...*gorm.DB) error { &SkillCategoryDifficulty{}, &WeaponSkillCategoryDifficulty{}, &SkillImprovementCost{}, - &ClassLearningPoints{}, + &ClassCategoryLearningPoints{}, &ClassSpellPoints{}, &ClassTypicalSkill{}, &ClassTypicalSpell{}, diff --git a/backend/models/model_character_creation.go b/backend/models/model_character_creation.go index 6b04473..ca9bfd2 100644 --- a/backend/models/model_character_creation.go +++ b/backend/models/model_character_creation.go @@ -1,6 +1,7 @@ package models import ( + "bamort/database" "bamort/user" "encoding/json" "time" @@ -87,6 +88,70 @@ type CharacterCreationSpell struct { type CharacterCreationSkills []CharacterCreationSkill type CharacterCreationSpells []CharacterCreationSpell +// ClassCategoryLearningPoints stores the learning points distribution for a character class +type ClassCategoryLearningPoints struct { + ID uint `gorm:"primaryKey" json:"id"` + CharacterClassID uint `gorm:"uniqueIndex:idx_class_category;not null" json:"character_class_id"` + SkillCategoryID uint `gorm:"uniqueIndex:idx_class_category;not null" json:"skill_category_id"` + Points int `gorm:"not null;default:0" json:"points"` + CharacterClass CharacterClass `gorm:"foreignKey:CharacterClassID;constraint:OnUpdate:CASCADE,OnDelete:CASCADE" json:"character_class"` + SkillCategory SkillCategory `gorm:"foreignKey:SkillCategoryID;constraint:OnUpdate:CASCADE,OnDelete:CASCADE" json:"skill_category"` +} + +// ClassSpellPoints stores the spell learning points for a character class +type ClassSpellPoints struct { + ID uint `gorm:"primaryKey" json:"id"` + CharacterClassID uint `gorm:"uniqueIndex;not null" json:"character_class_id"` + SpellPoints int `gorm:"not null;default:0" json:"spell_points"` // Zauberlerneinheiten + CharacterClass CharacterClass `gorm:"foreignKey:CharacterClassID;constraint:OnUpdate:CASCADE,OnDelete:CASCADE" json:"character_class"` +} + +// ClassTypicalSkill stores typical skills for a character class with bonuses +type ClassTypicalSkill struct { + ID uint `gorm:"primaryKey" json:"id"` + CharacterClassID uint `gorm:"not null;index" json:"character_class_id"` + SkillID uint `gorm:"not null;index" json:"skill_id"` + Bonus int `gorm:"not null;default:0" json:"bonus"` + Attribute string `gorm:"size:10" json:"attribute,omitempty"` // z.B. "Gs", "In", "St" + Notes string `json:"notes,omitempty"` // Zusätzliche Notizen + CharacterClass CharacterClass `gorm:"foreignKey:CharacterClassID;constraint:OnUpdate:CASCADE,OnDelete:CASCADE" json:"character_class"` + Skill Skill `gorm:"foreignKey:SkillID;constraint:OnUpdate:CASCADE,OnDelete:CASCADE" json:"skill"` +} + +// ClassTypicalSpell stores typical spells for a character class +type ClassTypicalSpell struct { + ID uint `gorm:"primaryKey" json:"id"` + CharacterClassID uint `gorm:"not null;index" json:"character_class_id"` + SpellID uint `gorm:"not null;index" json:"spell_id"` + Notes string `json:"notes,omitempty"` // z.B. "beliebig außer Dweomer" + CharacterClass CharacterClass `gorm:"foreignKey:CharacterClassID;constraint:OnUpdate:CASCADE,OnDelete:CASCADE" json:"character_class"` + Spell Spell `gorm:"foreignKey:SpellID;constraint:OnUpdate:CASCADE,OnDelete:CASCADE" json:"spell"` +} + +// TableName specifies the table name for ClassLearningPoints +func (ClassCategoryLearningPoints) TableName() string { + //class_learning_points + return "gsm_cc_class_category_points" +} + +// TableName specifies the table name for ClassSpellPoints +func (ClassSpellPoints) TableName() string { + //class_spell_points + return "gsm_cc_class_spell_points" +} + +// TableName specifies the table name for ClassTypicalSkill +func (ClassTypicalSkill) TableName() string { + //class_typical_skills + return "gsm_cc_class_typical_skills" +} + +// TableName specifies the table name for ClassTypicalSpell +func (ClassTypicalSpell) TableName() string { + //class_typical_spells + return "gsm_cc_class_typical_spells" +} + // JSON scanning methods for Skills slice func (s *CharacterCreationSkills) Scan(value interface{}) error { if value == nil { @@ -201,3 +266,37 @@ func GetUserSessions(db *gorm.DB, userID uint) ([]CharacterCreationSession, erro err := db.Where("user_id = ? AND expires_at > ?", userID, time.Now()).Find(&sessions).Error return sessions, err } + +// GetLearningPointsForClass retrieves all learning points for a character class +func GetLearningPointsForClass(classID uint) ([]ClassCategoryLearningPoints, error) { + var points []ClassCategoryLearningPoints + err := database.DB.Preload("SkillCategory").Where("character_class_id = ?", classID).Find(&points).Error + return points, err +} + +// GetSpellPointsForClass retrieves spell points for a character class +func GetSpellPointsForClass(classID uint) (*ClassSpellPoints, error) { + var points ClassSpellPoints + err := database.DB.Where("character_class_id = ?", classID).First(&points).Error + if err != nil { + if err == gorm.ErrRecordNotFound { + return &ClassSpellPoints{SpellPoints: 0}, nil + } + return nil, err + } + return &points, nil +} + +// GetTypicalSkillsForClass retrieves typical skills for a character class +func GetTypicalSkillsForClass(classID uint) ([]ClassTypicalSkill, error) { + var skills []ClassTypicalSkill + err := database.DB.Preload("Skill").Where("character_class_id = ?", classID).Find(&skills).Error + return skills, err +} + +// GetTypicalSpellsForClass retrieves typical spells for a character class +func GetTypicalSpellsForClass(classID uint) ([]ClassTypicalSpell, error) { + var spells []ClassTypicalSpell + err := database.DB.Preload("Spell").Where("character_class_id = ?", classID).Find(&spells).Error + return spells, err +} diff --git a/backend/models/model_class_learning_points.go b/backend/models/model_class_learning_points.go deleted file mode 100644 index 0737a15..0000000 --- a/backend/models/model_class_learning_points.go +++ /dev/null @@ -1,101 +0,0 @@ -package models - -import ( - "bamort/database" - - "gorm.io/gorm" -) - -// ClassLearningPoints stores the learning points distribution for a character class -type ClassLearningPoints struct { - ID uint `gorm:"primaryKey" json:"id"` - CharacterClassID uint `gorm:"uniqueIndex:idx_class_category;not null" json:"character_class_id"` - SkillCategoryID uint `gorm:"uniqueIndex:idx_class_category;not null" json:"skill_category_id"` - Points int `gorm:"not null;default:0" json:"points"` - CharacterClass CharacterClass `gorm:"foreignKey:CharacterClassID;constraint:OnUpdate:CASCADE,OnDelete:CASCADE" json:"character_class"` - SkillCategory SkillCategory `gorm:"foreignKey:SkillCategoryID;constraint:OnUpdate:CASCADE,OnDelete:CASCADE" json:"skill_category"` -} - -// ClassSpellPoints stores the spell learning points for a character class -type ClassSpellPoints struct { - ID uint `gorm:"primaryKey" json:"id"` - CharacterClassID uint `gorm:"uniqueIndex;not null" json:"character_class_id"` - SpellPoints int `gorm:"not null;default:0" json:"spell_points"` // Zauberlerneinheiten - CharacterClass CharacterClass `gorm:"foreignKey:CharacterClassID;constraint:OnUpdate:CASCADE,OnDelete:CASCADE" json:"character_class"` -} - -// ClassTypicalSkill stores typical skills for a character class with bonuses -type ClassTypicalSkill struct { - ID uint `gorm:"primaryKey" json:"id"` - CharacterClassID uint `gorm:"not null;index" json:"character_class_id"` - SkillID uint `gorm:"not null;index" json:"skill_id"` - Bonus int `gorm:"not null;default:0" json:"bonus"` - Attribute string `gorm:"size:10" json:"attribute,omitempty"` // z.B. "Gs", "In", "St" - Notes string `json:"notes,omitempty"` // Zusätzliche Notizen - CharacterClass CharacterClass `gorm:"foreignKey:CharacterClassID;constraint:OnUpdate:CASCADE,OnDelete:CASCADE" json:"character_class"` - Skill Skill `gorm:"foreignKey:SkillID;constraint:OnUpdate:CASCADE,OnDelete:CASCADE" json:"skill"` -} - -// ClassTypicalSpell stores typical spells for a character class -type ClassTypicalSpell struct { - ID uint `gorm:"primaryKey" json:"id"` - CharacterClassID uint `gorm:"not null;index" json:"character_class_id"` - SpellID uint `gorm:"not null;index" json:"spell_id"` - Notes string `json:"notes,omitempty"` // z.B. "beliebig außer Dweomer" - CharacterClass CharacterClass `gorm:"foreignKey:CharacterClassID;constraint:OnUpdate:CASCADE,OnDelete:CASCADE" json:"character_class"` - Spell Spell `gorm:"foreignKey:SpellID;constraint:OnUpdate:CASCADE,OnDelete:CASCADE" json:"spell"` -} - -// TableName specifies the table name for ClassLearningPoints -func (ClassLearningPoints) TableName() string { - return "class_learning_points" -} - -// TableName specifies the table name for ClassSpellPoints -func (ClassSpellPoints) TableName() string { - return "class_spell_points" -} - -// TableName specifies the table name for ClassTypicalSkill -func (ClassTypicalSkill) TableName() string { - return "class_typical_skills" -} - -// TableName specifies the table name for ClassTypicalSpell -func (ClassTypicalSpell) TableName() string { - return "class_typical_spells" -} - -// GetLearningPointsForClass retrieves all learning points for a character class -func GetLearningPointsForClass(classID uint) ([]ClassLearningPoints, error) { - var points []ClassLearningPoints - err := database.DB.Preload("SkillCategory").Where("character_class_id = ?", classID).Find(&points).Error - return points, err -} - -// GetSpellPointsForClass retrieves spell points for a character class -func GetSpellPointsForClass(classID uint) (*ClassSpellPoints, error) { - var points ClassSpellPoints - err := database.DB.Where("character_class_id = ?", classID).First(&points).Error - if err != nil { - if err == gorm.ErrRecordNotFound { - return &ClassSpellPoints{SpellPoints: 0}, nil - } - return nil, err - } - return &points, nil -} - -// GetTypicalSkillsForClass retrieves typical skills for a character class -func GetTypicalSkillsForClass(classID uint) ([]ClassTypicalSkill, error) { - var skills []ClassTypicalSkill - err := database.DB.Preload("Skill").Where("character_class_id = ?", classID).Find(&skills).Error - return skills, err -} - -// GetTypicalSpellsForClass retrieves typical spells for a character class -func GetTypicalSpellsForClass(classID uint) ([]ClassTypicalSpell, error) { - var spells []ClassTypicalSpell - err := database.DB.Preload("Spell").Where("character_class_id = ?", classID).Find(&spells).Error - return spells, err -} diff --git a/backend/models/model_gsmaster.go b/backend/models/model_gsmaster.go index 365a97e..577e2bc 100644 --- a/backend/models/model_gsmaster.go +++ b/backend/models/model_gsmaster.go @@ -54,8 +54,8 @@ type Skill struct { Initialwert int `gorm:"default:5" json:"initialwert"` BasisWert int `gorm:"default:0" json:"basiswert"` Bonuseigenschaft string `json:"bonuseigenschaft,omitempty"` - Improvable bool `gorm:"default:true" json:"improvable"` - InnateSkill bool `gorm:"default:false" json:"innateskill"` + Improvable bool `json:"improvable"` + InnateSkill bool `json:"innateskill"` Category string `json:"category"` Difficulty string `json:"difficulty"` } @@ -136,6 +136,14 @@ func (object *Skill) TableName() string { func (stamm *Skill) Create() error { gameSystem := "midgard" stamm.GameSystem = gameSystem + + // Set default values for boolean fields if not explicitly set + // Note: We cannot rely on GORM defaults anymore since they interfere with copying operations + if !stamm.Improvable && !stamm.InnateSkill { + // If both are false, set Improvable to true as default (most skills are improvable) + stamm.Improvable = true + } + err := database.DB.Transaction(func(tx *gorm.DB) error { // Save the main character record if err := tx.Create(&stamm).Error; err != nil { @@ -250,6 +258,12 @@ func (object *WeaponSkill) TableName() string { func (stamm *WeaponSkill) Create() error { gameSystem := "midgard" stamm.GameSystem = gameSystem + + // Set default values for boolean fields if not explicitly set + if !stamm.Improvable && !stamm.InnateSkill { + stamm.Improvable = true + } + err := database.DB.Transaction(func(tx *gorm.DB) error { // Save the main character record if err := tx.Create(&stamm).Error; err != nil { @@ -649,8 +663,8 @@ func (object *Believe) Save() error { func GetBelievesByActiveSources(gameSystem string) ([]Believe, error) { var believes []Believe err := database.DB. - Joins("LEFT JOIN learning_sources ON gsm_believes.source_id = learning_sources.id"). - Where("gsm_believes.game_system = ? AND (learning_sources.is_active = ? OR gsm_believes.source_id IS NULL)", gameSystem, true). + Joins("LEFT JOIN gsm_lit_sources ON gsm_believes.source_id = gsm_lit_sources.id"). + Where("gsm_believes.game_system = ? AND (gsm_lit_sources.is_active = ? OR gsm_believes.source_id IS NULL)", gameSystem, true). Order("gsm_believes.name ASC"). Find(&believes).Error return believes, err diff --git a/backend/models/model_gsmaster_test.go b/backend/models/model_gsmaster_test.go index 052e660..d152a7e 100644 --- a/backend/models/model_gsmaster_test.go +++ b/backend/models/model_gsmaster_test.go @@ -1707,6 +1707,90 @@ func TestLearnCost_StructFields(t *testing.T) { assert.Equal(t, 2, learnCost.PP) } +// TestSkill_Create_DefaultImprovable verifies that new skills get Improvable=true by default +func TestSkill_Create_DefaultImprovable(t *testing.T) { + database.SetupTestDB(true) + + // Test 1: Create skill without setting Improvable or InnateSkill + skill1 := Skill{ + Name: "Test Skill Default", + GameSystem: "midgard", + Category: "test", + Initialwert: 5, + } + err := skill1.Create() + require.NoError(t, err) + + // Verify it was saved with Improvable=true (default for normal skills) + var savedSkill Skill + err = database.DB.Where("name = ?", "Test Skill Default").First(&savedSkill).Error + require.NoError(t, err) + assert.True(t, savedSkill.Improvable, "Default skill should have Improvable=true") + assert.False(t, savedSkill.InnateSkill, "Default skill should have InnateSkill=false") + + // Test 2: Create skill with explicit Improvable=false and InnateSkill=true + skill2 := Skill{ + Name: "Test Innate Skill", + GameSystem: "midgard", + Category: "test", + Initialwert: 5, + Improvable: false, + InnateSkill: true, + } + err = skill2.Create() + require.NoError(t, err) + + // Verify explicit values were preserved + var savedSkill2 Skill + err = database.DB.Where("name = ?", "Test Innate Skill").First(&savedSkill2).Error + require.NoError(t, err) + assert.False(t, savedSkill2.Improvable, "Innate skill should have Improvable=false") + assert.True(t, savedSkill2.InnateSkill, "Innate skill should have InnateSkill=true") + + // Test 3: Create skill with explicit Improvable=true + skill3 := Skill{ + Name: "Test Explicit Improvable", + GameSystem: "midgard", + Category: "test", + Initialwert: 5, + Improvable: true, + InnateSkill: false, + } + err = skill3.Create() + require.NoError(t, err) + + // Verify explicit values were preserved + var savedSkill3 Skill + err = database.DB.Where("name = ?", "Test Explicit Improvable").First(&savedSkill3).Error + require.NoError(t, err) + assert.True(t, savedSkill3.Improvable, "Explicit improvable skill should have Improvable=true") + assert.False(t, savedSkill3.InnateSkill, "Explicit improvable skill should have InnateSkill=false") +} + +// TestWeaponSkill_Create_DefaultImprovable verifies that new weapon skills get Improvable=true by default +func TestWeaponSkill_Create_DefaultImprovable(t *testing.T) { + database.SetupTestDB(true) + + // Test 1: Create weapon skill without setting Improvable or InnateSkill + weaponSkill := WeaponSkill{ + Skill: Skill{ + Name: "Test Weapon Skill", + GameSystem: "midgard", + Category: "weapon", + Initialwert: 5, + }, + } + err := weaponSkill.Create() + require.NoError(t, err) + + // Verify it was saved with Improvable=true + var savedSkill WeaponSkill + err = database.DB.Where("name = ?", "Test Weapon Skill").First(&savedSkill).Error + require.NoError(t, err) + assert.True(t, savedSkill.Improvable, "Default weapon skill should have Improvable=true") + assert.False(t, savedSkill.InnateSkill, "Default weapon skill should have InnateSkill=false") +} + // ============================================================================= // Additional Benchmark Tests // ============================================================================= diff --git a/backend/models/model_learning_costs.go b/backend/models/model_learning_costs.go index 9e642b5..3d078c4 100644 --- a/backend/models/model_learning_costs.go +++ b/backend/models/model_learning_costs.go @@ -182,11 +182,13 @@ type AuditLogEntry struct { // TableName-Methoden für GORM func (Source) TableName() string { - return "learning_sources" + //learning_sources + return "gsm_lit_sources" } func (CharacterClass) TableName() string { - return "learning_character_classes" + //learning_character_classes + return "gsm_character_classes" } func (SkillCategory) TableName() string { @@ -479,8 +481,8 @@ func GetSourcesByGameSystem(gameSystem string) ([]Source, error) { func GetSkillsByActiveSources(gameSystem string) ([]Skill, error) { var skills []Skill err := database.DB. - Joins("LEFT JOIN learning_sources ON gsm_skills.source_id = learning_sources.id"). - Where("gsm_skills.game_system = ? AND (learning_sources.is_active = ? OR gsm_skills.source_id IS NULL)", gameSystem, true). + Joins("LEFT JOIN gsm_lit_sources ON gsm_skills.source_id = gsm_lit_sources.id"). + Where("gsm_skills.game_system = ? AND (gsm_lit_sourcesis_active = ? OR gsm_skills.source_id IS NULL)", gameSystem, true). Find(&skills).Error return skills, err } @@ -489,8 +491,8 @@ func GetSkillsByActiveSources(gameSystem string) ([]Skill, error) { func GetSpellsByActiveSources(gameSystem string) ([]Spell, error) { var spells []Spell err := database.DB. - Joins("LEFT JOIN learning_sources ON gsm_spells.source_id = learning_sources.id"). - Where("gsm_spells.game_system = ? AND (learning_sources.is_active = ? OR gsm_spells.source_id IS NULL)", gameSystem, true). + Joins("LEFT JOIN gsm_lit_sources ON gsm_spells.source_id = gsm_lit_sources.id"). + Where("gsm_spells.game_system = ? AND (gsm_lit_sources.is_active = ? OR gsm_spells.source_id IS NULL)", gameSystem, true). Find(&spells).Error return spells, err } @@ -499,8 +501,8 @@ func GetSpellsByActiveSources(gameSystem string) ([]Spell, error) { func GetCharacterClassesByActiveSources(gameSystem string) ([]CharacterClass, error) { var classes []CharacterClass err := database.DB. - Joins("LEFT JOIN learning_sources ON learning_character_classes.source_id = learning_sources.id"). - Where("learning_character_classes.game_system = ? AND (learning_sources.is_active = ? OR learning_character_classes.source_id IS NULL)", gameSystem, true). + Joins("LEFT JOIN gsm_lit_sources ON gsm_character_classes.source_id = gsm_lit_sources.id"). + Where("gsm_character_classes.game_system = ? AND (gsm_lit_sources.is_active = ? OR gsm_character_classes.source_id IS NULL)", gameSystem, true). Find(&classes).Error return classes, err } diff --git a/backend/models/model_skills.go b/backend/models/model_skills.go index adfe282..21af76d 100644 --- a/backend/models/model_skills.go +++ b/backend/models/model_skills.go @@ -34,15 +34,15 @@ type SkZauber struct { } func (object *SkFertigkeit) TableName() string { - dbPrefix := "skill" + dbPrefix := "char" return dbPrefix + "_" + "skills" } func (object *SkWaffenfertigkeit) TableName() string { - dbPrefix := "skill" + dbPrefix := "char" return dbPrefix + "_" + "weaponskills" } func (object *SkZauber) TableName() string { - dbPrefix := "skill" + dbPrefix := "char" return dbPrefix + "_" + "spells" } @@ -67,7 +67,7 @@ func (object *SkWaffenfertigkeit) GetSkillByName() *Skill { } func (object *SkFertigkeit) GetCategory() string { - // Always fetch category from gsmaster, ignoring the category field in skill_skills + // Always fetch category from gsmaster, ignoring the category field in char_skills var gsmsk Skill gsmsk.First(object.Name) if gsmsk.ID == 0 { diff --git a/backend/models/model_skills_test.go b/backend/models/model_skills_test.go index abddc09..0b6563e 100644 --- a/backend/models/model_skills_test.go +++ b/backend/models/model_skills_test.go @@ -36,7 +36,7 @@ func createTestSkill(name string) *Skill { func TestSkFertigkeit_TableName(t *testing.T) { skill := SkFertigkeit{} - expected := "skill_skills" + expected := "char_skills" actual := skill.TableName() assert.Equal(t, expected, actual) } @@ -176,7 +176,7 @@ func TestSkFertigkeit_StructTags(t *testing.T) { func TestSkWaffenfertigkeit_TableName(t *testing.T) { weaponSkill := SkWaffenfertigkeit{} - expected := "skill_weaponskills" + expected := "char_weaponskills" actual := weaponSkill.TableName() assert.Equal(t, expected, actual) } @@ -283,7 +283,7 @@ func TestSkAngeboreneFertigkeit_Inheritance(t *testing.T) { func TestSkZauber_TableName(t *testing.T) { spell := SkZauber{} - expected := "skill_spells" + expected := "char_spells" actual := spell.TableName() assert.Equal(t, expected, actual) } @@ -417,14 +417,14 @@ func TestTableNames_Consistency(t *testing.T) { weaponSkill := SkWaffenfertigkeit{} spell := SkZauber{} - assert.Equal(t, "skill_skills", skill.TableName()) - assert.Equal(t, "skill_weaponskills", weaponSkill.TableName()) - assert.Equal(t, "skill_spells", spell.TableName()) + assert.Equal(t, "char_skills", skill.TableName()) + assert.Equal(t, "char_weaponskills", weaponSkill.TableName()) + assert.Equal(t, "char_spells", spell.TableName()) - // All table names should start with "skill_" - assert.Contains(t, skill.TableName(), "skill_") - assert.Contains(t, weaponSkill.TableName(), "skill_") - assert.Contains(t, spell.TableName(), "skill_") + // All table names should start with "char_" + assert.Contains(t, skill.TableName(), "char_") + assert.Contains(t, weaponSkill.TableName(), "char_") + assert.Contains(t, spell.TableName(), "char_") } func TestSkFertigkeit_EdgeCases(t *testing.T) { diff --git a/backend/models/populate_class_learning_points.go b/backend/models/populate_class_learning_points.go index a49ba30..5cad8a2 100644 --- a/backend/models/populate_class_learning_points.go +++ b/backend/models/populate_class_learning_points.go @@ -335,7 +335,7 @@ func PopulateClassLearningPointsData() error { continue } - learningPoints := ClassLearningPoints{ + learningPoints := ClassCategoryLearningPoints{ CharacterClassID: charClass.ID, SkillCategoryID: category.ID, Points: points, diff --git a/backend/transfer/database.go b/backend/transfer/database.go index 8d31473..d6a9baa 100644 --- a/backend/transfer/database.go +++ b/backend/transfer/database.go @@ -53,10 +53,10 @@ type DatabaseExport struct { GsmContainers []models.Container `json:"gsm_containers"` GsmTransportations []models.Transportation `json:"gsm_transportations"` GsmBelieves []models.Believe `json:"gsm_believes"` + Sources []models.Source `json:"gsm_lit_sources"` + CharacterClasses []models.CharacterClass `json:"gsm_character_classes"` // Learning data - Sources []models.Source `json:"learning_sources"` - CharacterClasses []models.CharacterClass `json:"learning_character_classes"` SkillCategories []models.SkillCategory `json:"learning_skill_categories"` SkillDifficulties []models.SkillDifficulty `json:"learning_skill_difficulties"` SpellSchools []models.SpellSchool `json:"learning_spell_schools"`