renamed tables for a name more matching the purpose

This commit is contained in:
2026-01-12 16:36:35 +01:00
parent f83faad432
commit 5e7acb8099
15 changed files with 389 additions and 152 deletions
+1
View File
@@ -37,6 +37,7 @@
./backend$ go test ./... -v 2>&1 |grep FAIL ./backend$ go test ./... -v 2>&1 |grep FAIL
* API endpunkte für Export/Import aus Commit 2dcb4e00faaf316b98eb28e83cc5137bf0d1385d * 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 * 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 ## Refaktor
* Export Import Module neu grupieren * Export Import Module neu grupieren
+1 -1
View File
@@ -12,7 +12,7 @@ The export/import mechanism allows exporting all master data from the `model_gsm
## Supported Entities ## Supported Entities
### From `model_learning_costs.go`: ### 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 - **SkillCategories** (`learning_skill_categories`) - Skill classification categories
- **SkillDifficulties** (`learning_skill_difficulties`) - Difficulty levels - **SkillDifficulties** (`learning_skill_difficulties`) - Difficulty levels
- **SkillCategoryDifficulties** (`learning_skill_category_difficulties`) - Relationship between skills, categories, and difficulties with learning costs - **SkillCategoryDifficulties** (`learning_skill_category_difficulties`) - Relationship between skills, categories, and difficulties with learning costs
+2 -2
View File
@@ -41,7 +41,7 @@ func ValidateLearningCostsData() error {
query string query string
expected int 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 Categories", "SELECT COUNT(*) FROM learning_skill_categories", 10},
{"Skill Difficulties", "SELECT COUNT(*) FROM learning_skill_difficulties", 4}, {"Skill Difficulties", "SELECT COUNT(*) FROM learning_skill_difficulties", 4},
{"Spell Schools", "SELECT COUNT(*) FROM learning_spell_schools", 10}, {"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 // Zähle Einträge in verschiedenen Tabellen
tables := []string{ tables := []string{
"learning_character_classes", "gsm_character_classes",
"learning_skill_categories", "learning_skill_categories",
"learning_skill_difficulties", "learning_skill_difficulties",
"learning_spell_schools", "learning_spell_schools",
+16 -17
View File
@@ -219,7 +219,7 @@ func copyMariaDBToSQLite(mariaDB, sqliteDB *gorm.DB) error {
&models.SkillCategoryDifficulty{}, &models.SkillCategoryDifficulty{},
&models.WeaponSkillCategoryDifficulty{}, &models.WeaponSkillCategoryDifficulty{},
&models.SkillImprovementCost{}, &models.SkillImprovementCost{},
&models.ClassLearningPoints{}, &models.ClassCategoryLearningPoints{},
&models.ClassSpellPoints{}, &models.ClassSpellPoints{},
&models.ClassTypicalSkill{}, &models.ClassTypicalSkill{},
&models.ClassTypicalSpell{}, &models.ClassTypicalSpell{},
@@ -330,25 +330,24 @@ func copyTableData(sourceDB, targetDB *gorm.DB, model interface{}) error {
return err return err
} }
// Get the actual records from reflection // Get the records for iteration
records := recordsPtr.Elem().Interface() recordsVal := recordsPtr.Elem()
recordsLen := recordsPtr.Elem().Len() if recordsVal.Len() == 0 {
if recordsLen == 0 {
logger.Debug("Keine weiteren Datensätze für %s", tableName) logger.Debug("Keine weiteren Datensätze für %s", tableName)
break break
} }
// Batch in SQLite einfügen mit Konflikt-Behandlung // Batch in SQLite einfügen
// Verwende Clauses.OnConflict um bestehende Datensätze zu ersetzen // Use Save() instead of Create() to avoid GORM applying default values to zero values (e.g., false for booleans)
if err := targetDB.Model(model).Clauses(clause.OnConflict{ for i := 0; i < recordsVal.Len(); i++ {
UpdateAll: true, record := recordsVal.Index(i).Addr().Interface()
}).Create(records).Error; err != nil { if err := targetDB.Save(record).Error; err != nil {
logger.Error("Fehler beim Einfügen von Batch %d für %s: %s", batchNum, tableName, err.Error()) logger.Error("Fehler beim Speichern von Datensatz in Batch %d für %s: %s", batchNum, tableName, err.Error())
return err 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) 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.SkillCategoryDifficulty{}, // Jetzt nach Skills
&models.WeaponSkillCategoryDifficulty{}, &models.WeaponSkillCategoryDifficulty{},
&models.SkillImprovementCost{}, &models.SkillImprovementCost{},
&models.ClassLearningPoints{}, &models.ClassCategoryLearningPoints{},
&models.ClassSpellPoints{}, &models.ClassSpellPoints{},
&models.ClassTypicalSkill{}, &models.ClassTypicalSkill{},
&models.ClassTypicalSpell{}, &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) return fmt.Errorf("failed to read batch from source: %w", err)
} }
records = batch records = batch
case *models.ClassLearningPoints: case *models.ClassCategoryLearningPoints:
var batch []models.ClassLearningPoints var batch []models.ClassCategoryLearningPoints
if err := sourceDB.Limit(batchSize).Offset(offset).Find(&batch).Error; err != nil { if err := sourceDB.Limit(batchSize).Offset(offset).Find(&batch).Error; err != nil {
return fmt.Errorf("failed to read batch from source: %w", err) return fmt.Errorf("failed to read batch from source: %w", err)
} }
@@ -1155,7 +1154,7 @@ func clearMariaDBData(db *gorm.DB) error {
&models.ClassTypicalSpell{}, &models.ClassTypicalSpell{},
&models.ClassTypicalSkill{}, &models.ClassTypicalSkill{},
&models.ClassSpellPoints{}, &models.ClassSpellPoints{},
&models.ClassLearningPoints{}, &models.ClassCategoryLearningPoints{},
// GSMaster Basis-Daten // GSMaster Basis-Daten
&models.Believe{}, &models.Believe{},
+139
View File
@@ -1,9 +1,15 @@
package maintenance package maintenance
import ( import (
"bamort/database"
"bamort/models" "bamort/models"
"bamort/user" "bamort/user"
"testing" "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 // 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)) 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 // getModelTypeName returns the type name of a model
func getModelTypeName(model interface{}) string { func getModelTypeName(model interface{}) string {
switch model.(type) { switch model.(type) {
+1 -1
View File
@@ -157,7 +157,7 @@ func learningMigrateStructure(db ...*gorm.DB) error {
&SkillCategoryDifficulty{}, &SkillCategoryDifficulty{},
&WeaponSkillCategoryDifficulty{}, &WeaponSkillCategoryDifficulty{},
&SkillImprovementCost{}, &SkillImprovementCost{},
&ClassLearningPoints{}, &ClassCategoryLearningPoints{},
&ClassSpellPoints{}, &ClassSpellPoints{},
&ClassTypicalSkill{}, &ClassTypicalSkill{},
&ClassTypicalSpell{}, &ClassTypicalSpell{},
@@ -1,6 +1,7 @@
package models package models
import ( import (
"bamort/database"
"bamort/user" "bamort/user"
"encoding/json" "encoding/json"
"time" "time"
@@ -87,6 +88,70 @@ type CharacterCreationSpell struct {
type CharacterCreationSkills []CharacterCreationSkill type CharacterCreationSkills []CharacterCreationSkill
type CharacterCreationSpells []CharacterCreationSpell 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 // JSON scanning methods for Skills slice
func (s *CharacterCreationSkills) Scan(value interface{}) error { func (s *CharacterCreationSkills) Scan(value interface{}) error {
if value == nil { 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 err := db.Where("user_id = ? AND expires_at > ?", userID, time.Now()).Find(&sessions).Error
return sessions, err 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
}
@@ -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
}
+18 -4
View File
@@ -54,8 +54,8 @@ type Skill struct {
Initialwert int `gorm:"default:5" json:"initialwert"` Initialwert int `gorm:"default:5" json:"initialwert"`
BasisWert int `gorm:"default:0" json:"basiswert"` BasisWert int `gorm:"default:0" json:"basiswert"`
Bonuseigenschaft string `json:"bonuseigenschaft,omitempty"` Bonuseigenschaft string `json:"bonuseigenschaft,omitempty"`
Improvable bool `gorm:"default:true" json:"improvable"` Improvable bool `json:"improvable"`
InnateSkill bool `gorm:"default:false" json:"innateskill"` InnateSkill bool `json:"innateskill"`
Category string `json:"category"` Category string `json:"category"`
Difficulty string `json:"difficulty"` Difficulty string `json:"difficulty"`
} }
@@ -136,6 +136,14 @@ func (object *Skill) TableName() string {
func (stamm *Skill) Create() error { func (stamm *Skill) Create() error {
gameSystem := "midgard" gameSystem := "midgard"
stamm.GameSystem = gameSystem 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 { err := database.DB.Transaction(func(tx *gorm.DB) error {
// Save the main character record // Save the main character record
if err := tx.Create(&stamm).Error; err != nil { if err := tx.Create(&stamm).Error; err != nil {
@@ -250,6 +258,12 @@ func (object *WeaponSkill) TableName() string {
func (stamm *WeaponSkill) Create() error { func (stamm *WeaponSkill) Create() error {
gameSystem := "midgard" gameSystem := "midgard"
stamm.GameSystem = gameSystem 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 { err := database.DB.Transaction(func(tx *gorm.DB) error {
// Save the main character record // Save the main character record
if err := tx.Create(&stamm).Error; err != nil { if err := tx.Create(&stamm).Error; err != nil {
@@ -649,8 +663,8 @@ func (object *Believe) Save() error {
func GetBelievesByActiveSources(gameSystem string) ([]Believe, error) { func GetBelievesByActiveSources(gameSystem string) ([]Believe, error) {
var believes []Believe var believes []Believe
err := database.DB. err := database.DB.
Joins("LEFT JOIN learning_sources ON gsm_believes.source_id = learning_sources.id"). Joins("LEFT JOIN gsm_lit_sources ON gsm_believes.source_id = gsm_lit_sources.id").
Where("gsm_believes.game_system = ? AND (learning_sources.is_active = ? OR gsm_believes.source_id IS NULL)", gameSystem, true). 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"). Order("gsm_believes.name ASC").
Find(&believes).Error Find(&believes).Error
return believes, err return believes, err
+84
View File
@@ -1707,6 +1707,90 @@ func TestLearnCost_StructFields(t *testing.T) {
assert.Equal(t, 2, learnCost.PP) 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 // Additional Benchmark Tests
// ============================================================================= // =============================================================================
+10 -8
View File
@@ -182,11 +182,13 @@ type AuditLogEntry struct {
// TableName-Methoden für GORM // TableName-Methoden für GORM
func (Source) TableName() string { func (Source) TableName() string {
return "learning_sources" //learning_sources
return "gsm_lit_sources"
} }
func (CharacterClass) TableName() string { func (CharacterClass) TableName() string {
return "learning_character_classes" //learning_character_classes
return "gsm_character_classes"
} }
func (SkillCategory) TableName() string { func (SkillCategory) TableName() string {
@@ -479,8 +481,8 @@ func GetSourcesByGameSystem(gameSystem string) ([]Source, error) {
func GetSkillsByActiveSources(gameSystem string) ([]Skill, error) { func GetSkillsByActiveSources(gameSystem string) ([]Skill, error) {
var skills []Skill var skills []Skill
err := database.DB. err := database.DB.
Joins("LEFT JOIN learning_sources ON gsm_skills.source_id = learning_sources.id"). Joins("LEFT JOIN gsm_lit_sources ON gsm_skills.source_id = gsm_lit_sources.id").
Where("gsm_skills.game_system = ? AND (learning_sources.is_active = ? OR gsm_skills.source_id IS NULL)", gameSystem, true). Where("gsm_skills.game_system = ? AND (gsm_lit_sourcesis_active = ? OR gsm_skills.source_id IS NULL)", gameSystem, true).
Find(&skills).Error Find(&skills).Error
return skills, err return skills, err
} }
@@ -489,8 +491,8 @@ func GetSkillsByActiveSources(gameSystem string) ([]Skill, error) {
func GetSpellsByActiveSources(gameSystem string) ([]Spell, error) { func GetSpellsByActiveSources(gameSystem string) ([]Spell, error) {
var spells []Spell var spells []Spell
err := database.DB. err := database.DB.
Joins("LEFT JOIN learning_sources ON gsm_spells.source_id = learning_sources.id"). Joins("LEFT JOIN gsm_lit_sources ON gsm_spells.source_id = gsm_lit_sources.id").
Where("gsm_spells.game_system = ? AND (learning_sources.is_active = ? OR gsm_spells.source_id IS NULL)", gameSystem, true). Where("gsm_spells.game_system = ? AND (gsm_lit_sources.is_active = ? OR gsm_spells.source_id IS NULL)", gameSystem, true).
Find(&spells).Error Find(&spells).Error
return spells, err return spells, err
} }
@@ -499,8 +501,8 @@ func GetSpellsByActiveSources(gameSystem string) ([]Spell, error) {
func GetCharacterClassesByActiveSources(gameSystem string) ([]CharacterClass, error) { func GetCharacterClassesByActiveSources(gameSystem string) ([]CharacterClass, error) {
var classes []CharacterClass var classes []CharacterClass
err := database.DB. err := database.DB.
Joins("LEFT JOIN learning_sources ON learning_character_classes.source_id = learning_sources.id"). Joins("LEFT JOIN gsm_lit_sources ON gsm_character_classes.source_id = gsm_lit_sources.id").
Where("learning_character_classes.game_system = ? AND (learning_sources.is_active = ? OR learning_character_classes.source_id IS NULL)", gameSystem, true). 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 Find(&classes).Error
return classes, err return classes, err
} }
+4 -4
View File
@@ -34,15 +34,15 @@ type SkZauber struct {
} }
func (object *SkFertigkeit) TableName() string { func (object *SkFertigkeit) TableName() string {
dbPrefix := "skill" dbPrefix := "char"
return dbPrefix + "_" + "skills" return dbPrefix + "_" + "skills"
} }
func (object *SkWaffenfertigkeit) TableName() string { func (object *SkWaffenfertigkeit) TableName() string {
dbPrefix := "skill" dbPrefix := "char"
return dbPrefix + "_" + "weaponskills" return dbPrefix + "_" + "weaponskills"
} }
func (object *SkZauber) TableName() string { func (object *SkZauber) TableName() string {
dbPrefix := "skill" dbPrefix := "char"
return dbPrefix + "_" + "spells" return dbPrefix + "_" + "spells"
} }
@@ -67,7 +67,7 @@ func (object *SkWaffenfertigkeit) GetSkillByName() *Skill {
} }
func (object *SkFertigkeit) GetCategory() string { 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 var gsmsk Skill
gsmsk.First(object.Name) gsmsk.First(object.Name)
if gsmsk.ID == 0 { if gsmsk.ID == 0 {
+10 -10
View File
@@ -36,7 +36,7 @@ func createTestSkill(name string) *Skill {
func TestSkFertigkeit_TableName(t *testing.T) { func TestSkFertigkeit_TableName(t *testing.T) {
skill := SkFertigkeit{} skill := SkFertigkeit{}
expected := "skill_skills" expected := "char_skills"
actual := skill.TableName() actual := skill.TableName()
assert.Equal(t, expected, actual) assert.Equal(t, expected, actual)
} }
@@ -176,7 +176,7 @@ func TestSkFertigkeit_StructTags(t *testing.T) {
func TestSkWaffenfertigkeit_TableName(t *testing.T) { func TestSkWaffenfertigkeit_TableName(t *testing.T) {
weaponSkill := SkWaffenfertigkeit{} weaponSkill := SkWaffenfertigkeit{}
expected := "skill_weaponskills" expected := "char_weaponskills"
actual := weaponSkill.TableName() actual := weaponSkill.TableName()
assert.Equal(t, expected, actual) assert.Equal(t, expected, actual)
} }
@@ -283,7 +283,7 @@ func TestSkAngeboreneFertigkeit_Inheritance(t *testing.T) {
func TestSkZauber_TableName(t *testing.T) { func TestSkZauber_TableName(t *testing.T) {
spell := SkZauber{} spell := SkZauber{}
expected := "skill_spells" expected := "char_spells"
actual := spell.TableName() actual := spell.TableName()
assert.Equal(t, expected, actual) assert.Equal(t, expected, actual)
} }
@@ -417,14 +417,14 @@ func TestTableNames_Consistency(t *testing.T) {
weaponSkill := SkWaffenfertigkeit{} weaponSkill := SkWaffenfertigkeit{}
spell := SkZauber{} spell := SkZauber{}
assert.Equal(t, "skill_skills", skill.TableName()) assert.Equal(t, "char_skills", skill.TableName())
assert.Equal(t, "skill_weaponskills", weaponSkill.TableName()) assert.Equal(t, "char_weaponskills", weaponSkill.TableName())
assert.Equal(t, "skill_spells", spell.TableName()) assert.Equal(t, "char_spells", spell.TableName())
// All table names should start with "skill_" // All table names should start with "char_"
assert.Contains(t, skill.TableName(), "skill_") assert.Contains(t, skill.TableName(), "char_")
assert.Contains(t, weaponSkill.TableName(), "skill_") assert.Contains(t, weaponSkill.TableName(), "char_")
assert.Contains(t, spell.TableName(), "skill_") assert.Contains(t, spell.TableName(), "char_")
} }
func TestSkFertigkeit_EdgeCases(t *testing.T) { func TestSkFertigkeit_EdgeCases(t *testing.T) {
@@ -335,7 +335,7 @@ func PopulateClassLearningPointsData() error {
continue continue
} }
learningPoints := ClassLearningPoints{ learningPoints := ClassCategoryLearningPoints{
CharacterClassID: charClass.ID, CharacterClassID: charClass.ID,
SkillCategoryID: category.ID, SkillCategoryID: category.ID,
Points: points, Points: points,
+2 -2
View File
@@ -53,10 +53,10 @@ type DatabaseExport struct {
GsmContainers []models.Container `json:"gsm_containers"` GsmContainers []models.Container `json:"gsm_containers"`
GsmTransportations []models.Transportation `json:"gsm_transportations"` GsmTransportations []models.Transportation `json:"gsm_transportations"`
GsmBelieves []models.Believe `json:"gsm_believes"` GsmBelieves []models.Believe `json:"gsm_believes"`
Sources []models.Source `json:"gsm_lit_sources"`
CharacterClasses []models.CharacterClass `json:"gsm_character_classes"`
// Learning data // Learning data
Sources []models.Source `json:"learning_sources"`
CharacterClasses []models.CharacterClass `json:"learning_character_classes"`
SkillCategories []models.SkillCategory `json:"learning_skill_categories"` SkillCategories []models.SkillCategory `json:"learning_skill_categories"`
SkillDifficulties []models.SkillDifficulty `json:"learning_skill_difficulties"` SkillDifficulties []models.SkillDifficulty `json:"learning_skill_difficulties"`
SpellSchools []models.SpellSchool `json:"learning_spell_schools"` SpellSchools []models.SpellSchool `json:"learning_spell_schools"`