renamed tables for a name more matching the purpose
This commit is contained in:
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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{},
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -157,7 +157,7 @@ func learningMigrateStructure(db ...*gorm.DB) error {
|
||||
&SkillCategoryDifficulty{},
|
||||
&WeaponSkillCategoryDifficulty{},
|
||||
&SkillImprovementCost{},
|
||||
&ClassLearningPoints{},
|
||||
&ClassCategoryLearningPoints{},
|
||||
&ClassSpellPoints{},
|
||||
&ClassTypicalSkill{},
|
||||
&ClassTypicalSpell{},
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
// =============================================================================
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -335,7 +335,7 @@ func PopulateClassLearningPointsData() error {
|
||||
continue
|
||||
}
|
||||
|
||||
learningPoints := ClassLearningPoints{
|
||||
learningPoints := ClassCategoryLearningPoints{
|
||||
CharacterClassID: charClass.ID,
|
||||
SkillCategoryID: category.ID,
|
||||
Points: points,
|
||||
|
||||
@@ -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"`
|
||||
|
||||
Reference in New Issue
Block a user