Merge pull request #14 from Bardioc26/skills_improvement
Improve maintenance dialogs and fixed some minor bugs added an exporting/importing for Game system master data
This commit is contained in:
@@ -35,6 +35,7 @@
|
|||||||
dbURL = "bamort:bG4)efozrc@tcp(192.168.0.5:3306)/bamort?charset=utf8mb4&parseTime=True&loc=Local"
|
dbURL = "bamort:bG4)efozrc@tcp(192.168.0.5:3306)/bamort?charset=utf8mb4&parseTime=True&loc=Local"
|
||||||
* fehlgeschlagene Tests ausbessern
|
* fehlgeschlagene Tests ausbessern
|
||||||
./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
|
||||||
|
|
||||||
## Refaktor
|
## Refaktor
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,216 @@
|
|||||||
|
# Master Data Export/Import
|
||||||
|
|
||||||
|
## Overview
|
||||||
|
|
||||||
|
The export/import mechanism allows exporting all master data from the `model_gsmaster` and `model_learning_costs` modules to JSON files and importing them back. The exported data is ID-independent, using natural keys (name + game_system) to identify records, making it suitable for:
|
||||||
|
|
||||||
|
- Migrating data between environments
|
||||||
|
- Version controlling master data
|
||||||
|
- Manually editing game data
|
||||||
|
- Sharing/distributing game systems
|
||||||
|
|
||||||
|
## Supported Entities
|
||||||
|
|
||||||
|
### From `model_learning_costs.go`:
|
||||||
|
- **Sources** (`learning_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
|
||||||
|
|
||||||
|
### From `model_gsmaster.go`:
|
||||||
|
- **Skills** (`gsm_skills`) - Character skills
|
||||||
|
- **Spells** (`gsm_spells`) - Magic spells
|
||||||
|
|
||||||
|
## Excluded Entities
|
||||||
|
|
||||||
|
The following are NOT exported/imported:
|
||||||
|
- `AuditLogEntry` - Audit logs (transient data)
|
||||||
|
- `BamortBase` - Base character data (character-specific)
|
||||||
|
- `BamortCharTrait` - Character traits (character-specific)
|
||||||
|
- `Magisch` - Character magic data (character-specific)
|
||||||
|
- `LookupList` - System lookup lists (system config)
|
||||||
|
|
||||||
|
## File Format
|
||||||
|
|
||||||
|
All data is exported to JSON files with indentation for easy reading and editing:
|
||||||
|
|
||||||
|
```json
|
||||||
|
[
|
||||||
|
{
|
||||||
|
"name": "Skill Name",
|
||||||
|
"game_system": "midgard",
|
||||||
|
"source_code": "KOD",
|
||||||
|
...
|
||||||
|
}
|
||||||
|
]
|
||||||
|
```
|
||||||
|
|
||||||
|
## Natural Keys
|
||||||
|
|
||||||
|
Instead of database IDs, the following natural keys are used to identify records:
|
||||||
|
|
||||||
|
| Entity | Natural Key |
|
||||||
|
|--------|------------|
|
||||||
|
| Source | `code` |
|
||||||
|
| Skill | `name` + `game_system` |
|
||||||
|
| Spell | `name` + `game_system` |
|
||||||
|
| SkillCategory | `name` + `game_system` |
|
||||||
|
| SkillDifficulty | `name` + `game_system` |
|
||||||
|
| SkillCategoryDifficulty | `skill_name` + `skill_system` + `category_name` + `category_system` + `difficulty_name` + `difficulty_system` |
|
||||||
|
|
||||||
|
## Usage
|
||||||
|
|
||||||
|
### Export
|
||||||
|
|
||||||
|
```go
|
||||||
|
import "bamort/gsmaster"
|
||||||
|
|
||||||
|
// Export specific entity types
|
||||||
|
err := gsmaster.ExportSources("/path/to/output")
|
||||||
|
err := gsmaster.ExportSkills("/path/to/output")
|
||||||
|
err := gsmaster.ExportSpells("/path/to/output")
|
||||||
|
err := gsmaster.ExportSkillCategories("/path/to/output")
|
||||||
|
err := gsmaster.ExportSkillDifficulties("/path/to/output")
|
||||||
|
err := gsmaster.ExportSkillCategoryDifficulties("/path/to/output")
|
||||||
|
|
||||||
|
// Export all master data at once
|
||||||
|
err := gsmaster.ExportAll("/path/to/output")
|
||||||
|
```
|
||||||
|
|
||||||
|
### Import
|
||||||
|
|
||||||
|
```go
|
||||||
|
import "bamort/gsmaster"
|
||||||
|
|
||||||
|
// Import specific entity types
|
||||||
|
err := gsmaster.ImportSources("/path/to/input")
|
||||||
|
err := gsmaster.ImportSkills("/path/to/input")
|
||||||
|
err := gsmaster.ImportSpells("/path/to/input")
|
||||||
|
err := gsmaster.ImportSkillCategories("/path/to/input")
|
||||||
|
err := gsmaster.ImportSkillDifficulties("/path/to/input")
|
||||||
|
err := gsmaster.ImportSkillCategoryDifficulties("/path/to/input")
|
||||||
|
|
||||||
|
// Import all master data at once
|
||||||
|
err := gsmaster.ImportAll("/path/to/input")
|
||||||
|
```
|
||||||
|
|
||||||
|
## Import Behavior
|
||||||
|
|
||||||
|
The import mechanism follows an "upsert" pattern:
|
||||||
|
|
||||||
|
1. **Check if record exists** using natural keys
|
||||||
|
2. If **not found**: Create new record
|
||||||
|
3. If **found**: Update existing record with imported values
|
||||||
|
|
||||||
|
This allows for:
|
||||||
|
- Importing new data
|
||||||
|
- Updating existing data
|
||||||
|
- Safe re-import of previously exported data
|
||||||
|
|
||||||
|
## Dependency Order
|
||||||
|
|
||||||
|
When using `ExportAll()` and `ImportAll()`, entities are processed in dependency order:
|
||||||
|
|
||||||
|
**Export/Import Order:**
|
||||||
|
1. Sources (no dependencies)
|
||||||
|
2. SkillCategories (depends on Sources)
|
||||||
|
3. SkillDifficulties (no dependencies)
|
||||||
|
4. Skills (depends on Sources)
|
||||||
|
5. SkillCategoryDifficulties (depends on Skills, Categories, Difficulties)
|
||||||
|
6. Spells (depends on Sources)
|
||||||
|
|
||||||
|
## File Names
|
||||||
|
|
||||||
|
Each entity type is exported to its own file:
|
||||||
|
|
||||||
|
| Entity | Filename |
|
||||||
|
|--------|----------|
|
||||||
|
| Sources | `sources.json` |
|
||||||
|
| Skills | `skills.json` |
|
||||||
|
| Spells | `spells.json` |
|
||||||
|
| SkillCategories | `skill_categories.json` |
|
||||||
|
| SkillDifficulties | `skill_difficulties.json` |
|
||||||
|
| SkillCategoryDifficulties | `skill_category_difficulties.json` |
|
||||||
|
|
||||||
|
## Example Workflow
|
||||||
|
|
||||||
|
### Exporting Data
|
||||||
|
|
||||||
|
```go
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bamort/gsmaster"
|
||||||
|
"log"
|
||||||
|
)
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
outputDir := "./exported_data"
|
||||||
|
|
||||||
|
if err := gsmaster.ExportAll(outputDir); err != nil {
|
||||||
|
log.Fatalf("Export failed: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
log.Println("All master data exported to", outputDir)
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Editing Exported Data
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Edit the JSON files manually
|
||||||
|
vim exported_data/skills.json
|
||||||
|
```
|
||||||
|
|
||||||
|
### Importing Modified Data
|
||||||
|
|
||||||
|
```go
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bamort/gsmaster"
|
||||||
|
"bamort/database"
|
||||||
|
"log"
|
||||||
|
)
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
// Initialize database connection
|
||||||
|
database.InitDB()
|
||||||
|
|
||||||
|
inputDir := "./exported_data"
|
||||||
|
|
||||||
|
if err := gsmaster.ImportAll(inputDir); err != nil {
|
||||||
|
log.Fatalf("Import failed: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
log.Println("All master data imported from", inputDir)
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Error Handling
|
||||||
|
|
||||||
|
All export/import functions return errors that should be checked:
|
||||||
|
|
||||||
|
- **Export errors**: Usually file system issues (permissions, disk space)
|
||||||
|
- **Import errors**: Can be:
|
||||||
|
- File not found
|
||||||
|
- Invalid JSON format
|
||||||
|
- Missing dependencies (e.g., referenced source doesn't exist)
|
||||||
|
- Database constraint violations
|
||||||
|
|
||||||
|
## Testing
|
||||||
|
|
||||||
|
The export/import mechanism is fully tested with TDD. See `gsmaster/export_import_test.go` for comprehensive test coverage including:
|
||||||
|
|
||||||
|
- Export creates valid JSON files
|
||||||
|
- Import creates new records
|
||||||
|
- Import updates existing records
|
||||||
|
- Relationships are correctly restored using natural keys
|
||||||
|
- Full export/import cycle works correctly
|
||||||
|
|
||||||
|
## Notes
|
||||||
|
|
||||||
|
- **No Handlers/Routes**: This functionality is intentionally not exposed as API endpoints. Use it programmatically or via CLI tools.
|
||||||
|
- **ID Independence**: Exported data does not contain database IDs, making it portable across different database instances.
|
||||||
|
- **Idempotent**: Import can be run multiple times safely - it will update existing records rather than creating duplicates.
|
||||||
|
- **Transaction Safety**: Each import operation should ideally be wrapped in a database transaction for atomicity.
|
||||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,594 @@
|
|||||||
|
package gsmaster
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bamort/database"
|
||||||
|
"bamort/models"
|
||||||
|
"fmt"
|
||||||
|
"path/filepath"
|
||||||
|
|
||||||
|
"gorm.io/gorm"
|
||||||
|
)
|
||||||
|
|
||||||
|
// ExportSkillImprovementCosts exports all skill improvement costs to a JSON file
|
||||||
|
func ExportSkillImprovementCosts(outputDir string) error {
|
||||||
|
var costs []models.SkillImprovementCost
|
||||||
|
if err := database.DB.Preload("SkillCategoryDifficulty.Skill").
|
||||||
|
Preload("SkillCategoryDifficulty.SkillCategory").
|
||||||
|
Preload("SkillCategoryDifficulty.SkillDifficulty").
|
||||||
|
Find(&costs).Error; err != nil {
|
||||||
|
return fmt.Errorf("failed to fetch skill improvement costs: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
exportable := make([]ExportableSkillImprovementCost, 0, len(costs))
|
||||||
|
for _, cost := range costs {
|
||||||
|
// Skip records with incomplete relationships
|
||||||
|
if cost.SkillCategoryDifficulty.Skill.Name == "" ||
|
||||||
|
cost.SkillCategoryDifficulty.SkillCategory.Name == "" ||
|
||||||
|
cost.SkillCategoryDifficulty.SkillDifficulty.Name == "" {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
exportable = append(exportable, ExportableSkillImprovementCost{
|
||||||
|
SkillName: cost.SkillCategoryDifficulty.Skill.Name,
|
||||||
|
SkillSystem: cost.SkillCategoryDifficulty.Skill.GameSystem,
|
||||||
|
CategoryName: cost.SkillCategoryDifficulty.SkillCategory.Name,
|
||||||
|
CategorySystem: cost.SkillCategoryDifficulty.SkillCategory.GameSystem,
|
||||||
|
DifficultyName: cost.SkillCategoryDifficulty.SkillDifficulty.Name,
|
||||||
|
DifficultySystem: cost.SkillCategoryDifficulty.SkillDifficulty.GameSystem,
|
||||||
|
CurrentLevel: cost.CurrentLevel,
|
||||||
|
TERequired: cost.TERequired,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
return writeJSON(filepath.Join(outputDir, "skill_improvement_costs.json"), exportable)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ImportSkillImprovementCosts imports skill improvement costs from a JSON file
|
||||||
|
func ImportSkillImprovementCosts(inputDir string) error {
|
||||||
|
var exportable []ExportableSkillImprovementCost
|
||||||
|
if err := readJSON(filepath.Join(inputDir, "skill_improvement_costs.json"), &exportable); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Build lookup maps using helpers
|
||||||
|
skillMap := buildSkillMap()
|
||||||
|
categoryMap := buildCategoryMap()
|
||||||
|
difficultyMap := buildDifficultyMap()
|
||||||
|
|
||||||
|
for _, exp := range exportable {
|
||||||
|
// Find skill ID
|
||||||
|
skillID, ok := skillMap[exp.SkillSystem][exp.SkillName]
|
||||||
|
if !ok {
|
||||||
|
return fmt.Errorf("skill not found: %s (%s)", exp.SkillName, exp.SkillSystem)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Find category ID
|
||||||
|
categoryID, ok := categoryMap[exp.CategorySystem][exp.CategoryName]
|
||||||
|
if !ok {
|
||||||
|
return fmt.Errorf("category not found: %s (%s)", exp.CategoryName, exp.CategorySystem)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Find difficulty ID
|
||||||
|
difficultyID, ok := difficultyMap[exp.DifficultySystem][exp.DifficultyName]
|
||||||
|
if !ok {
|
||||||
|
return fmt.Errorf("difficulty not found: %s (%s)", exp.DifficultyName, exp.DifficultySystem)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Find SkillCategoryDifficulty
|
||||||
|
var scd models.SkillCategoryDifficulty
|
||||||
|
if err := database.DB.Where("skill_id = ? AND skill_category_id = ? AND skill_difficulty_id = ?",
|
||||||
|
skillID, categoryID, difficultyID).First(&scd).Error; err != nil {
|
||||||
|
return fmt.Errorf("skill category difficulty not found for %s/%s/%s: %w",
|
||||||
|
exp.SkillName, exp.CategoryName, exp.DifficultyName, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Find or create SkillImprovementCost
|
||||||
|
var cost models.SkillImprovementCost
|
||||||
|
result := database.DB.Where("skill_category_difficulty_id = ? AND current_level = ?",
|
||||||
|
scd.ID, exp.CurrentLevel).First(&cost)
|
||||||
|
|
||||||
|
if result.Error == gorm.ErrRecordNotFound {
|
||||||
|
cost = models.SkillImprovementCost{
|
||||||
|
SkillCategoryDifficultyID: scd.ID,
|
||||||
|
CurrentLevel: exp.CurrentLevel,
|
||||||
|
TERequired: exp.TERequired,
|
||||||
|
}
|
||||||
|
if err := database.DB.Create(&cost).Error; err != nil {
|
||||||
|
return fmt.Errorf("failed to create skill improvement cost: %w", err)
|
||||||
|
}
|
||||||
|
} else if result.Error != nil {
|
||||||
|
return fmt.Errorf("failed to query skill improvement cost: %w", result.Error)
|
||||||
|
} else {
|
||||||
|
cost.TERequired = exp.TERequired
|
||||||
|
if err := database.DB.Save(&cost).Error; err != nil {
|
||||||
|
return fmt.Errorf("failed to update skill improvement cost: %w", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// ExportWeaponSkills exports all weapon skills to a JSON file
|
||||||
|
func ExportWeaponSkills(outputDir string) error {
|
||||||
|
var skills []models.WeaponSkill
|
||||||
|
if err := database.DB.Find(&skills).Error; err != nil {
|
||||||
|
return fmt.Errorf("failed to fetch weapon skills: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
sourceMap := buildSourceMap()
|
||||||
|
|
||||||
|
exportable := make([]ExportableWeaponSkill, len(skills))
|
||||||
|
for i, skill := range skills {
|
||||||
|
exportable[i] = ExportableWeaponSkill{
|
||||||
|
Name: skill.Name,
|
||||||
|
GameSystem: skill.GameSystem,
|
||||||
|
Beschreibung: skill.Beschreibung,
|
||||||
|
SourceCode: sourceMap[skill.SourceID],
|
||||||
|
PageNumber: skill.PageNumber,
|
||||||
|
Initialwert: skill.Initialwert,
|
||||||
|
BasisWert: skill.BasisWert,
|
||||||
|
Bonuseigenschaft: skill.Bonuseigenschaft,
|
||||||
|
Improvable: skill.Improvable,
|
||||||
|
InnateSkill: skill.InnateSkill,
|
||||||
|
Category: skill.Category,
|
||||||
|
Difficulty: skill.Difficulty,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return writeJSON(filepath.Join(outputDir, "weapon_skills.json"), exportable)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ImportWeaponSkills imports weapon skills from a JSON file
|
||||||
|
func ImportWeaponSkills(inputDir string) error {
|
||||||
|
var exportable []ExportableWeaponSkill
|
||||||
|
if err := readJSON(filepath.Join(inputDir, "weapon_skills.json"), &exportable); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
sourceMap := buildSourceMapReverse()
|
||||||
|
|
||||||
|
for _, exp := range exportable {
|
||||||
|
var skill models.WeaponSkill
|
||||||
|
result := database.DB.Where("name = ? AND game_system = ?", exp.Name, exp.GameSystem).First(&skill)
|
||||||
|
|
||||||
|
sourceID := sourceMap[exp.SourceCode]
|
||||||
|
|
||||||
|
if result.Error == gorm.ErrRecordNotFound {
|
||||||
|
skill = models.WeaponSkill{
|
||||||
|
Skill: models.Skill{
|
||||||
|
Name: exp.Name,
|
||||||
|
GameSystem: exp.GameSystem,
|
||||||
|
Beschreibung: exp.Beschreibung,
|
||||||
|
SourceID: sourceID,
|
||||||
|
PageNumber: exp.PageNumber,
|
||||||
|
Initialwert: exp.Initialwert,
|
||||||
|
BasisWert: exp.BasisWert,
|
||||||
|
Bonuseigenschaft: exp.Bonuseigenschaft,
|
||||||
|
Improvable: exp.Improvable,
|
||||||
|
InnateSkill: exp.InnateSkill,
|
||||||
|
Category: exp.Category,
|
||||||
|
Difficulty: exp.Difficulty,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
if err := database.DB.Create(&skill).Error; err != nil {
|
||||||
|
return fmt.Errorf("failed to create weapon skill %s: %w", exp.Name, err)
|
||||||
|
}
|
||||||
|
} else if result.Error != nil {
|
||||||
|
return fmt.Errorf("failed to query weapon skill %s: %w", exp.Name, result.Error)
|
||||||
|
} else {
|
||||||
|
skill.Beschreibung = exp.Beschreibung
|
||||||
|
skill.SourceID = sourceID
|
||||||
|
skill.PageNumber = exp.PageNumber
|
||||||
|
skill.Initialwert = exp.Initialwert
|
||||||
|
skill.BasisWert = exp.BasisWert
|
||||||
|
skill.Bonuseigenschaft = exp.Bonuseigenschaft
|
||||||
|
skill.Improvable = exp.Improvable
|
||||||
|
skill.InnateSkill = exp.InnateSkill
|
||||||
|
skill.Category = exp.Category
|
||||||
|
skill.Difficulty = exp.Difficulty
|
||||||
|
|
||||||
|
if err := database.DB.Save(&skill).Error; err != nil {
|
||||||
|
return fmt.Errorf("failed to update weapon skill %s: %w", exp.Name, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// ExportEquipment exports all equipment to a JSON file
|
||||||
|
func ExportEquipment(outputDir string) error {
|
||||||
|
var equipment []models.Equipment
|
||||||
|
if err := database.DB.Find(&equipment).Error; err != nil {
|
||||||
|
return fmt.Errorf("failed to fetch equipment: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
sourceMap := buildSourceMap()
|
||||||
|
|
||||||
|
exportable := make([]ExportableEquipment, len(equipment))
|
||||||
|
for i, eq := range equipment {
|
||||||
|
exportable[i] = ExportableEquipment{
|
||||||
|
Name: eq.Name,
|
||||||
|
GameSystem: eq.GameSystem,
|
||||||
|
Beschreibung: eq.Beschreibung,
|
||||||
|
SourceCode: sourceMap[eq.SourceID],
|
||||||
|
PageNumber: eq.PageNumber,
|
||||||
|
Gewicht: eq.Gewicht,
|
||||||
|
Wert: eq.Wert,
|
||||||
|
PersonalItem: eq.PersonalItem,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return writeJSON(filepath.Join(outputDir, "equipment.json"), exportable)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ImportEquipment imports equipment from a JSON file
|
||||||
|
func ImportEquipment(inputDir string) error {
|
||||||
|
var exportable []ExportableEquipment
|
||||||
|
if err := readJSON(filepath.Join(inputDir, "equipment.json"), &exportable); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
sourceMap := buildSourceMapReverse()
|
||||||
|
|
||||||
|
for _, exp := range exportable {
|
||||||
|
var eq models.Equipment
|
||||||
|
result := database.DB.Where("name = ? AND game_system = ?", exp.Name, exp.GameSystem).First(&eq)
|
||||||
|
|
||||||
|
sourceID := sourceMap[exp.SourceCode]
|
||||||
|
|
||||||
|
if result.Error == gorm.ErrRecordNotFound {
|
||||||
|
eq = models.Equipment{
|
||||||
|
Name: exp.Name,
|
||||||
|
GameSystem: exp.GameSystem,
|
||||||
|
Beschreibung: exp.Beschreibung,
|
||||||
|
SourceID: sourceID,
|
||||||
|
PageNumber: exp.PageNumber,
|
||||||
|
Gewicht: exp.Gewicht,
|
||||||
|
Wert: exp.Wert,
|
||||||
|
PersonalItem: exp.PersonalItem,
|
||||||
|
}
|
||||||
|
if err := database.DB.Create(&eq).Error; err != nil {
|
||||||
|
return fmt.Errorf("failed to create equipment %s: %w", exp.Name, err)
|
||||||
|
}
|
||||||
|
} else if result.Error != nil {
|
||||||
|
return fmt.Errorf("failed to query equipment %s: %w", exp.Name, result.Error)
|
||||||
|
} else {
|
||||||
|
eq.Beschreibung = exp.Beschreibung
|
||||||
|
eq.SourceID = sourceID
|
||||||
|
eq.PageNumber = exp.PageNumber
|
||||||
|
eq.Gewicht = exp.Gewicht
|
||||||
|
eq.Wert = exp.Wert
|
||||||
|
eq.PersonalItem = exp.PersonalItem
|
||||||
|
|
||||||
|
if err := database.DB.Save(&eq).Error; err != nil {
|
||||||
|
return fmt.Errorf("failed to update equipment %s: %w", exp.Name, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// ExportWeapons exports all weapons to a JSON file
|
||||||
|
func ExportWeapons(outputDir string) error {
|
||||||
|
var weapons []models.Weapon
|
||||||
|
if err := database.DB.Find(&weapons).Error; err != nil {
|
||||||
|
return fmt.Errorf("failed to fetch weapons: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
sourceMap := buildSourceMap()
|
||||||
|
|
||||||
|
exportable := make([]ExportableWeapon, len(weapons))
|
||||||
|
for i, weapon := range weapons {
|
||||||
|
exportable[i] = ExportableWeapon{
|
||||||
|
Name: weapon.Name,
|
||||||
|
GameSystem: weapon.GameSystem,
|
||||||
|
Beschreibung: weapon.Beschreibung,
|
||||||
|
SourceCode: sourceMap[weapon.SourceID],
|
||||||
|
PageNumber: weapon.PageNumber,
|
||||||
|
Gewicht: weapon.Gewicht,
|
||||||
|
Wert: weapon.Wert,
|
||||||
|
PersonalItem: weapon.PersonalItem,
|
||||||
|
SkillRequired: weapon.SkillRequired,
|
||||||
|
Damage: weapon.Damage,
|
||||||
|
RangeNear: weapon.RangeNear,
|
||||||
|
RangeMiddle: weapon.RangeMiddle,
|
||||||
|
RangeFar: weapon.RangeFar,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return writeJSON(filepath.Join(outputDir, "weapons.json"), exportable)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ImportWeapons imports weapons from a JSON file
|
||||||
|
func ImportWeapons(inputDir string) error {
|
||||||
|
var exportable []ExportableWeapon
|
||||||
|
if err := readJSON(filepath.Join(inputDir, "weapons.json"), &exportable); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
sourceMap := buildSourceMapReverse()
|
||||||
|
|
||||||
|
for _, exp := range exportable {
|
||||||
|
var weapon models.Weapon
|
||||||
|
result := database.DB.Where("name = ? AND game_system = ?", exp.Name, exp.GameSystem).First(&weapon)
|
||||||
|
|
||||||
|
sourceID := sourceMap[exp.SourceCode]
|
||||||
|
|
||||||
|
if result.Error == gorm.ErrRecordNotFound {
|
||||||
|
weapon = models.Weapon{
|
||||||
|
Equipment: models.Equipment{
|
||||||
|
Name: exp.Name,
|
||||||
|
GameSystem: exp.GameSystem,
|
||||||
|
Beschreibung: exp.Beschreibung,
|
||||||
|
SourceID: sourceID,
|
||||||
|
PageNumber: exp.PageNumber,
|
||||||
|
Gewicht: exp.Gewicht,
|
||||||
|
Wert: exp.Wert,
|
||||||
|
PersonalItem: exp.PersonalItem,
|
||||||
|
},
|
||||||
|
SkillRequired: exp.SkillRequired,
|
||||||
|
Damage: exp.Damage,
|
||||||
|
RangeNear: exp.RangeNear,
|
||||||
|
RangeMiddle: exp.RangeMiddle,
|
||||||
|
RangeFar: exp.RangeFar,
|
||||||
|
}
|
||||||
|
if err := database.DB.Create(&weapon).Error; err != nil {
|
||||||
|
return fmt.Errorf("failed to create weapon %s: %w", exp.Name, err)
|
||||||
|
}
|
||||||
|
} else if result.Error != nil {
|
||||||
|
return fmt.Errorf("failed to query weapon %s: %w", exp.Name, result.Error)
|
||||||
|
} else {
|
||||||
|
weapon.Beschreibung = exp.Beschreibung
|
||||||
|
weapon.SourceID = sourceID
|
||||||
|
weapon.PageNumber = exp.PageNumber
|
||||||
|
weapon.Gewicht = exp.Gewicht
|
||||||
|
weapon.Wert = exp.Wert
|
||||||
|
weapon.PersonalItem = exp.PersonalItem
|
||||||
|
weapon.SkillRequired = exp.SkillRequired
|
||||||
|
weapon.Damage = exp.Damage
|
||||||
|
weapon.RangeNear = exp.RangeNear
|
||||||
|
weapon.RangeMiddle = exp.RangeMiddle
|
||||||
|
weapon.RangeFar = exp.RangeFar
|
||||||
|
|
||||||
|
if err := database.DB.Save(&weapon).Error; err != nil {
|
||||||
|
return fmt.Errorf("failed to update weapon %s: %w", exp.Name, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// ExportContainers exports all containers to a JSON file
|
||||||
|
func ExportContainers(outputDir string) error {
|
||||||
|
var containers []models.Container
|
||||||
|
if err := database.DB.Find(&containers).Error; err != nil {
|
||||||
|
return fmt.Errorf("failed to fetch containers: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
sourceMap := buildSourceMap()
|
||||||
|
|
||||||
|
exportable := make([]ExportableContainer, len(containers))
|
||||||
|
for i, container := range containers {
|
||||||
|
exportable[i] = ExportableContainer{
|
||||||
|
Name: container.Name,
|
||||||
|
GameSystem: container.GameSystem,
|
||||||
|
Beschreibung: container.Beschreibung,
|
||||||
|
SourceCode: sourceMap[container.SourceID],
|
||||||
|
PageNumber: container.PageNumber,
|
||||||
|
Gewicht: container.Gewicht,
|
||||||
|
Wert: container.Wert,
|
||||||
|
PersonalItem: container.PersonalItem,
|
||||||
|
Tragkraft: container.Tragkraft,
|
||||||
|
Volumen: container.Volumen,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return writeJSON(filepath.Join(outputDir, "containers.json"), exportable)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ImportContainers imports containers from a JSON file
|
||||||
|
func ImportContainers(inputDir string) error {
|
||||||
|
var exportable []ExportableContainer
|
||||||
|
if err := readJSON(filepath.Join(inputDir, "containers.json"), &exportable); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
sourceMap := buildSourceMapReverse()
|
||||||
|
|
||||||
|
for _, exp := range exportable {
|
||||||
|
var container models.Container
|
||||||
|
result := database.DB.Where("name = ? AND game_system = ?", exp.Name, exp.GameSystem).First(&container)
|
||||||
|
|
||||||
|
sourceID := sourceMap[exp.SourceCode]
|
||||||
|
|
||||||
|
if result.Error == gorm.ErrRecordNotFound {
|
||||||
|
container = models.Container{
|
||||||
|
Equipment: models.Equipment{
|
||||||
|
Name: exp.Name,
|
||||||
|
GameSystem: exp.GameSystem,
|
||||||
|
Beschreibung: exp.Beschreibung,
|
||||||
|
SourceID: sourceID,
|
||||||
|
PageNumber: exp.PageNumber,
|
||||||
|
Gewicht: exp.Gewicht,
|
||||||
|
Wert: exp.Wert,
|
||||||
|
PersonalItem: exp.PersonalItem,
|
||||||
|
},
|
||||||
|
Tragkraft: exp.Tragkraft,
|
||||||
|
Volumen: exp.Volumen,
|
||||||
|
}
|
||||||
|
if err := database.DB.Create(&container).Error; err != nil {
|
||||||
|
return fmt.Errorf("failed to create container %s: %w", exp.Name, err)
|
||||||
|
}
|
||||||
|
} else if result.Error != nil {
|
||||||
|
return fmt.Errorf("failed to query container %s: %w", exp.Name, result.Error)
|
||||||
|
} else {
|
||||||
|
container.Beschreibung = exp.Beschreibung
|
||||||
|
container.SourceID = sourceID
|
||||||
|
container.PageNumber = exp.PageNumber
|
||||||
|
container.Gewicht = exp.Gewicht
|
||||||
|
container.Wert = exp.Wert
|
||||||
|
container.PersonalItem = exp.PersonalItem
|
||||||
|
container.Tragkraft = exp.Tragkraft
|
||||||
|
container.Volumen = exp.Volumen
|
||||||
|
|
||||||
|
if err := database.DB.Save(&container).Error; err != nil {
|
||||||
|
return fmt.Errorf("failed to update container %s: %w", exp.Name, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// ExportTransportation exports all transportation to a JSON file
|
||||||
|
func ExportTransportation(outputDir string) error {
|
||||||
|
var transportation []models.Transportation
|
||||||
|
if err := database.DB.Find(&transportation).Error; err != nil {
|
||||||
|
return fmt.Errorf("failed to fetch transportation: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
sourceMap := buildSourceMap()
|
||||||
|
|
||||||
|
exportable := make([]ExportableTransportation, len(transportation))
|
||||||
|
for i, trans := range transportation {
|
||||||
|
exportable[i] = ExportableTransportation{
|
||||||
|
Name: trans.Name,
|
||||||
|
GameSystem: trans.GameSystem,
|
||||||
|
Beschreibung: trans.Beschreibung,
|
||||||
|
SourceCode: sourceMap[trans.SourceID],
|
||||||
|
PageNumber: trans.PageNumber,
|
||||||
|
Gewicht: trans.Gewicht,
|
||||||
|
Wert: trans.Wert,
|
||||||
|
PersonalItem: trans.PersonalItem,
|
||||||
|
Tragkraft: trans.Tragkraft,
|
||||||
|
Volumen: trans.Volumen,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return writeJSON(filepath.Join(outputDir, "transportation.json"), exportable)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ImportTransportation imports transportation from a JSON file
|
||||||
|
func ImportTransportation(inputDir string) error {
|
||||||
|
var exportable []ExportableTransportation
|
||||||
|
if err := readJSON(filepath.Join(inputDir, "transportation.json"), &exportable); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
sourceMap := buildSourceMapReverse()
|
||||||
|
|
||||||
|
for _, exp := range exportable {
|
||||||
|
var trans models.Transportation
|
||||||
|
result := database.DB.Where("name = ? AND game_system = ?", exp.Name, exp.GameSystem).First(&trans)
|
||||||
|
|
||||||
|
sourceID := sourceMap[exp.SourceCode]
|
||||||
|
|
||||||
|
if result.Error == gorm.ErrRecordNotFound {
|
||||||
|
trans = models.Transportation{
|
||||||
|
Container: models.Container{
|
||||||
|
Equipment: models.Equipment{
|
||||||
|
Name: exp.Name,
|
||||||
|
GameSystem: exp.GameSystem,
|
||||||
|
Beschreibung: exp.Beschreibung,
|
||||||
|
SourceID: sourceID,
|
||||||
|
PageNumber: exp.PageNumber,
|
||||||
|
Gewicht: exp.Gewicht,
|
||||||
|
Wert: exp.Wert,
|
||||||
|
PersonalItem: exp.PersonalItem,
|
||||||
|
},
|
||||||
|
Tragkraft: exp.Tragkraft,
|
||||||
|
Volumen: exp.Volumen,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
if err := database.DB.Create(&trans).Error; err != nil {
|
||||||
|
return fmt.Errorf("failed to create transportation %s: %w", exp.Name, err)
|
||||||
|
}
|
||||||
|
} else if result.Error != nil {
|
||||||
|
return fmt.Errorf("failed to query transportation %s: %w", exp.Name, result.Error)
|
||||||
|
} else {
|
||||||
|
trans.Beschreibung = exp.Beschreibung
|
||||||
|
trans.SourceID = sourceID
|
||||||
|
trans.PageNumber = exp.PageNumber
|
||||||
|
trans.Gewicht = exp.Gewicht
|
||||||
|
trans.Wert = exp.Wert
|
||||||
|
trans.PersonalItem = exp.PersonalItem
|
||||||
|
trans.Tragkraft = exp.Tragkraft
|
||||||
|
trans.Volumen = exp.Volumen
|
||||||
|
|
||||||
|
if err := database.DB.Save(&trans).Error; err != nil {
|
||||||
|
return fmt.Errorf("failed to update transportation %s: %w", exp.Name, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// ExportBelieves exports all beliefs to a JSON file
|
||||||
|
func ExportBelieves(outputDir string) error {
|
||||||
|
var believes []models.Believe
|
||||||
|
if err := database.DB.Find(&believes).Error; err != nil {
|
||||||
|
return fmt.Errorf("failed to fetch believes: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
sourceMap := buildSourceMap()
|
||||||
|
|
||||||
|
exportable := make([]ExportableBelieve, len(believes))
|
||||||
|
for i, believe := range believes {
|
||||||
|
exportable[i] = ExportableBelieve{
|
||||||
|
Name: believe.Name,
|
||||||
|
GameSystem: believe.GameSystem,
|
||||||
|
Beschreibung: believe.Beschreibung,
|
||||||
|
SourceCode: sourceMap[believe.SourceID],
|
||||||
|
PageNumber: believe.PageNumber,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return writeJSON(filepath.Join(outputDir, "believes.json"), exportable)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ImportBelieves imports believes from a JSON file
|
||||||
|
func ImportBelieves(inputDir string) error {
|
||||||
|
var exportable []ExportableBelieve
|
||||||
|
if err := readJSON(filepath.Join(inputDir, "believes.json"), &exportable); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
sourceMap := buildSourceMapReverse()
|
||||||
|
|
||||||
|
for _, exp := range exportable {
|
||||||
|
var believe models.Believe
|
||||||
|
result := database.DB.Where("name = ? AND game_system = ?", exp.Name, exp.GameSystem).First(&believe)
|
||||||
|
|
||||||
|
sourceID := sourceMap[exp.SourceCode]
|
||||||
|
|
||||||
|
if result.Error == gorm.ErrRecordNotFound {
|
||||||
|
believe = models.Believe{
|
||||||
|
Name: exp.Name,
|
||||||
|
GameSystem: exp.GameSystem,
|
||||||
|
Beschreibung: exp.Beschreibung,
|
||||||
|
SourceID: sourceID,
|
||||||
|
PageNumber: exp.PageNumber,
|
||||||
|
}
|
||||||
|
if err := database.DB.Create(&believe).Error; err != nil {
|
||||||
|
return fmt.Errorf("failed to create believe %s: %w", exp.Name, err)
|
||||||
|
}
|
||||||
|
} else if result.Error != nil {
|
||||||
|
return fmt.Errorf("failed to query believe %s: %w", exp.Name, result.Error)
|
||||||
|
} else {
|
||||||
|
believe.Beschreibung = exp.Beschreibung
|
||||||
|
believe.SourceID = sourceID
|
||||||
|
believe.PageNumber = exp.PageNumber
|
||||||
|
|
||||||
|
if err := database.DB.Save(&believe).Error; err != nil {
|
||||||
|
return fmt.Errorf("failed to update believe %s: %w", exp.Name, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
@@ -0,0 +1,167 @@
|
|||||||
|
package gsmaster
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bamort/database"
|
||||||
|
"bamort/models"
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"gorm.io/gorm"
|
||||||
|
)
|
||||||
|
|
||||||
|
// LookupMap builders - reusable functions to build ID<->Code/Name maps
|
||||||
|
|
||||||
|
// buildSourceMap creates a map from source ID to source code
|
||||||
|
func buildSourceMap() map[uint]string {
|
||||||
|
var sources []models.Source
|
||||||
|
database.DB.Find(&sources)
|
||||||
|
sourceMap := make(map[uint]string)
|
||||||
|
for _, s := range sources {
|
||||||
|
sourceMap[s.ID] = s.Code
|
||||||
|
}
|
||||||
|
return sourceMap
|
||||||
|
}
|
||||||
|
|
||||||
|
// buildSourceMapReverse creates a map from source code to source ID
|
||||||
|
func buildSourceMapReverse() map[string]uint {
|
||||||
|
var sources []models.Source
|
||||||
|
database.DB.Find(&sources)
|
||||||
|
sourceMap := make(map[string]uint)
|
||||||
|
for _, s := range sources {
|
||||||
|
sourceMap[s.Code] = s.ID
|
||||||
|
}
|
||||||
|
return sourceMap
|
||||||
|
}
|
||||||
|
|
||||||
|
// buildCategoryMap creates a nested map: game_system -> name -> id
|
||||||
|
func buildCategoryMap() map[string]map[string]uint {
|
||||||
|
var categories []models.SkillCategory
|
||||||
|
database.DB.Find(&categories)
|
||||||
|
categoryMap := make(map[string]map[string]uint)
|
||||||
|
for _, c := range categories {
|
||||||
|
if categoryMap[c.GameSystem] == nil {
|
||||||
|
categoryMap[c.GameSystem] = make(map[string]uint)
|
||||||
|
}
|
||||||
|
categoryMap[c.GameSystem][c.Name] = c.ID
|
||||||
|
}
|
||||||
|
return categoryMap
|
||||||
|
}
|
||||||
|
|
||||||
|
// buildDifficultyMap creates a nested map: game_system -> name -> id
|
||||||
|
func buildDifficultyMap() map[string]map[string]uint {
|
||||||
|
var difficulties []models.SkillDifficulty
|
||||||
|
database.DB.Find(&difficulties)
|
||||||
|
difficultyMap := make(map[string]map[string]uint)
|
||||||
|
for _, d := range difficulties {
|
||||||
|
if difficultyMap[d.GameSystem] == nil {
|
||||||
|
difficultyMap[d.GameSystem] = make(map[string]uint)
|
||||||
|
}
|
||||||
|
difficultyMap[d.GameSystem][d.Name] = d.ID
|
||||||
|
}
|
||||||
|
return difficultyMap
|
||||||
|
}
|
||||||
|
|
||||||
|
// buildCharacterClassMap creates a map from character class code to ID
|
||||||
|
func buildCharacterClassMap() map[string]uint {
|
||||||
|
var classes []models.CharacterClass
|
||||||
|
database.DB.Find(&classes)
|
||||||
|
classMap := make(map[string]uint)
|
||||||
|
for _, c := range classes {
|
||||||
|
classMap[c.Code] = c.ID
|
||||||
|
}
|
||||||
|
return classMap
|
||||||
|
}
|
||||||
|
|
||||||
|
// buildSpellSchoolMap creates a nested map: game_system -> name -> id
|
||||||
|
func buildSpellSchoolMap() map[string]map[string]uint {
|
||||||
|
var schools []models.SpellSchool
|
||||||
|
database.DB.Find(&schools)
|
||||||
|
schoolMap := make(map[string]map[string]uint)
|
||||||
|
for _, s := range schools {
|
||||||
|
if schoolMap[s.GameSystem] == nil {
|
||||||
|
schoolMap[s.GameSystem] = make(map[string]uint)
|
||||||
|
}
|
||||||
|
schoolMap[s.GameSystem][s.Name] = s.ID
|
||||||
|
}
|
||||||
|
return schoolMap
|
||||||
|
}
|
||||||
|
|
||||||
|
// buildSkillMap creates a nested map: game_system -> name -> id
|
||||||
|
func buildSkillMap() map[string]map[string]uint {
|
||||||
|
var skills []models.Skill
|
||||||
|
database.DB.Find(&skills)
|
||||||
|
skillMap := make(map[string]map[string]uint)
|
||||||
|
for _, s := range skills {
|
||||||
|
if skillMap[s.GameSystem] == nil {
|
||||||
|
skillMap[s.GameSystem] = make(map[string]uint)
|
||||||
|
}
|
||||||
|
skillMap[s.GameSystem][s.Name] = s.ID
|
||||||
|
}
|
||||||
|
return skillMap
|
||||||
|
}
|
||||||
|
|
||||||
|
// buildWeaponSkillMap creates a nested map: game_system -> name -> id
|
||||||
|
func buildWeaponSkillMap() map[string]map[string]uint {
|
||||||
|
var weaponSkills []models.WeaponSkill
|
||||||
|
database.DB.Find(&weaponSkills)
|
||||||
|
weaponSkillMap := make(map[string]map[string]uint)
|
||||||
|
for _, ws := range weaponSkills {
|
||||||
|
if weaponSkillMap[ws.GameSystem] == nil {
|
||||||
|
weaponSkillMap[ws.GameSystem] = make(map[string]uint)
|
||||||
|
}
|
||||||
|
weaponSkillMap[ws.GameSystem][ws.Name] = ws.ID
|
||||||
|
}
|
||||||
|
return weaponSkillMap
|
||||||
|
}
|
||||||
|
|
||||||
|
// Generic import helper for entities with name + game_system natural key
|
||||||
|
type ImportConfig struct {
|
||||||
|
EntityName string // For error messages, e.g., "skill category"
|
||||||
|
}
|
||||||
|
|
||||||
|
// findOrCreateByNameAndSystem is a helper for import operations
|
||||||
|
// It looks up an entity by name and game_system, creates if not found
|
||||||
|
func findOrCreateByNameAndSystem(
|
||||||
|
name string,
|
||||||
|
gameSystem string,
|
||||||
|
model interface{},
|
||||||
|
entityName string,
|
||||||
|
) error {
|
||||||
|
result := database.DB.Where("name = ? AND game_system = ?", name, gameSystem).First(model)
|
||||||
|
|
||||||
|
if result.Error == gorm.ErrRecordNotFound {
|
||||||
|
if err := database.DB.Create(model).Error; err != nil {
|
||||||
|
return fmt.Errorf("failed to create %s %s: %w", entityName, name, err)
|
||||||
|
}
|
||||||
|
} else if result.Error != nil {
|
||||||
|
return fmt.Errorf("failed to query %s %s: %w", entityName, name, result.Error)
|
||||||
|
} else {
|
||||||
|
if err := database.DB.Save(model).Error; err != nil {
|
||||||
|
return fmt.Errorf("failed to update %s %s: %w", entityName, name, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// findOrCreateByCode is a helper for import operations with code as natural key
|
||||||
|
func findOrCreateByCode(
|
||||||
|
code string,
|
||||||
|
model interface{},
|
||||||
|
entityName string,
|
||||||
|
) error {
|
||||||
|
result := database.DB.Where("code = ?", code).First(model)
|
||||||
|
|
||||||
|
if result.Error == gorm.ErrRecordNotFound {
|
||||||
|
if err := database.DB.Create(model).Error; err != nil {
|
||||||
|
return fmt.Errorf("failed to create %s %s: %w", entityName, code, err)
|
||||||
|
}
|
||||||
|
} else if result.Error != nil {
|
||||||
|
return fmt.Errorf("failed to query %s %s: %w", entityName, code, result.Error)
|
||||||
|
} else {
|
||||||
|
if err := database.DB.Save(model).Error; err != nil {
|
||||||
|
return fmt.Errorf("failed to update %s %s: %w", entityName, code, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
@@ -0,0 +1,914 @@
|
|||||||
|
package gsmaster
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bamort/database"
|
||||||
|
"bamort/models"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
"gorm.io/gorm"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestExportSkills(t *testing.T) {
|
||||||
|
setupTestEnvironment(t)
|
||||||
|
database.SetupTestDB()
|
||||||
|
|
||||||
|
// Create test data
|
||||||
|
source := getOrCreateSource("KOD", "Kodex")
|
||||||
|
skill := models.Skill{
|
||||||
|
Name: "Schwimmen",
|
||||||
|
GameSystem: "midgard",
|
||||||
|
Beschreibung: "Schwimmen im Wasser",
|
||||||
|
Initialwert: 12,
|
||||||
|
BasisWert: 0,
|
||||||
|
Bonuseigenschaft: "Gw",
|
||||||
|
Improvable: true,
|
||||||
|
InnateSkill: false,
|
||||||
|
SourceID: source.ID,
|
||||||
|
PageNumber: 42,
|
||||||
|
}
|
||||||
|
database.DB.Create(&skill)
|
||||||
|
|
||||||
|
// Export skills
|
||||||
|
tmpDir := t.TempDir()
|
||||||
|
err := ExportSkills(tmpDir)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("ExportSkills failed: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Verify file exists
|
||||||
|
exportFile := filepath.Join(tmpDir, "skills.json")
|
||||||
|
if _, err := os.Stat(exportFile); os.IsNotExist(err) {
|
||||||
|
t.Fatalf("Export file not created: %s", exportFile)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestImportSkills(t *testing.T) {
|
||||||
|
setupTestEnvironment(t)
|
||||||
|
database.SetupTestDB()
|
||||||
|
|
||||||
|
// Create source that will be referenced
|
||||||
|
source := getOrCreateSource("KOD", "Kodex")
|
||||||
|
|
||||||
|
// Export first
|
||||||
|
tmpDir := t.TempDir()
|
||||||
|
skill := models.Skill{
|
||||||
|
Name: "Klettern",
|
||||||
|
GameSystem: "midgard",
|
||||||
|
Beschreibung: "Klettern an Wänden",
|
||||||
|
Initialwert: 10,
|
||||||
|
BasisWert: 0,
|
||||||
|
Bonuseigenschaft: "Gw",
|
||||||
|
Improvable: true,
|
||||||
|
InnateSkill: false,
|
||||||
|
SourceID: source.ID,
|
||||||
|
PageNumber: 50,
|
||||||
|
}
|
||||||
|
database.DB.Create(&skill)
|
||||||
|
|
||||||
|
err := ExportSkills(tmpDir)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("ExportSkills failed: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Delete the skill
|
||||||
|
database.DB.Delete(&skill)
|
||||||
|
|
||||||
|
// Import back
|
||||||
|
err = ImportSkills(tmpDir)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("ImportSkills failed: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Verify skill was imported
|
||||||
|
var importedSkill models.Skill
|
||||||
|
err = database.DB.Where("name = ? AND game_system = ?", "Klettern", "midgard").First(&importedSkill).Error
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Imported skill not found: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if importedSkill.Beschreibung != "Klettern an Wänden" {
|
||||||
|
t.Errorf("Expected beschreibung 'Klettern an Wänden', got '%s'", importedSkill.Beschreibung)
|
||||||
|
}
|
||||||
|
|
||||||
|
if importedSkill.Initialwert != 10 {
|
||||||
|
t.Errorf("Expected initialwert 10, got %d", importedSkill.Initialwert)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestImportSkillsUpdate(t *testing.T) {
|
||||||
|
setupTestEnvironment(t)
|
||||||
|
database.SetupTestDB()
|
||||||
|
|
||||||
|
source := getOrCreateSource("KOD", "Kodex")
|
||||||
|
|
||||||
|
// Create existing skill
|
||||||
|
skill := models.Skill{
|
||||||
|
Name: "Reiten",
|
||||||
|
GameSystem: "midgard",
|
||||||
|
Beschreibung: "Alte Beschreibung",
|
||||||
|
Initialwert: 8,
|
||||||
|
SourceID: source.ID,
|
||||||
|
PageNumber: 30,
|
||||||
|
}
|
||||||
|
database.DB.Create(&skill)
|
||||||
|
|
||||||
|
// Export, modify, and re-import
|
||||||
|
tmpDir := t.TempDir()
|
||||||
|
err := ExportSkills(tmpDir)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("ExportSkills failed: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update skill manually
|
||||||
|
skill.Beschreibung = "Neue Beschreibung"
|
||||||
|
skill.Initialwert = 12
|
||||||
|
database.DB.Save(&skill)
|
||||||
|
|
||||||
|
// Export again with updated values
|
||||||
|
err = ExportSkills(tmpDir)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("ExportSkills failed: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Reset to old values
|
||||||
|
skill.Beschreibung = "Alte Beschreibung"
|
||||||
|
skill.Initialwert = 8
|
||||||
|
database.DB.Save(&skill)
|
||||||
|
|
||||||
|
// Import should update to exported values
|
||||||
|
err = ImportSkills(tmpDir)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("ImportSkills failed: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Verify update
|
||||||
|
var updatedSkill models.Skill
|
||||||
|
err = database.DB.Where("name = ? AND game_system = ?", "Reiten", "midgard").First(&updatedSkill).Error
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Updated skill not found: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if updatedSkill.Beschreibung != "Neue Beschreibung" {
|
||||||
|
t.Errorf("Expected updated beschreibung 'Neue Beschreibung', got '%s'", updatedSkill.Beschreibung)
|
||||||
|
}
|
||||||
|
|
||||||
|
if updatedSkill.Initialwert != 12 {
|
||||||
|
t.Errorf("Expected updated initialwert 12, got %d", updatedSkill.Initialwert)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestExportImportSources(t *testing.T) {
|
||||||
|
setupTestEnvironment(t)
|
||||||
|
database.SetupTestDB()
|
||||||
|
|
||||||
|
// Create test source
|
||||||
|
source := models.Source{
|
||||||
|
Code: "ARK",
|
||||||
|
Name: "Arkanum",
|
||||||
|
GameSystem: "midgard",
|
||||||
|
IsActive: true,
|
||||||
|
}
|
||||||
|
database.DB.Create(&source)
|
||||||
|
|
||||||
|
// Export
|
||||||
|
tmpDir := t.TempDir()
|
||||||
|
err := ExportSources(tmpDir)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("ExportSources failed: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Delete
|
||||||
|
database.DB.Delete(&source)
|
||||||
|
|
||||||
|
// Import
|
||||||
|
err = ImportSources(tmpDir)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("ImportSources failed: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Verify
|
||||||
|
var imported models.Source
|
||||||
|
err = database.DB.Where("code = ?", "ARK").First(&imported).Error
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Imported source not found: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if imported.Name != "Arkanum" {
|
||||||
|
t.Errorf("Expected name 'Arkanum', got '%s'", imported.Name)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestExportImportSkillCategoryDifficulty(t *testing.T) {
|
||||||
|
setupTestEnvironment(t)
|
||||||
|
database.SetupTestDB()
|
||||||
|
|
||||||
|
// Create dependencies
|
||||||
|
source := getOrCreateSource("KOD", "Kodex")
|
||||||
|
skill := models.Skill{
|
||||||
|
Name: "Tanzen",
|
||||||
|
GameSystem: "midgard",
|
||||||
|
SourceID: source.ID,
|
||||||
|
}
|
||||||
|
database.DB.Create(&skill)
|
||||||
|
|
||||||
|
category := getOrCreateCategory("Alltag", source.ID)
|
||||||
|
difficulty := getOrCreateDifficulty("leicht")
|
||||||
|
|
||||||
|
// Create relationship
|
||||||
|
scd := models.SkillCategoryDifficulty{
|
||||||
|
SkillID: skill.ID,
|
||||||
|
SkillCategoryID: category.ID,
|
||||||
|
SkillDifficultyID: difficulty.ID,
|
||||||
|
LearnCost: 5,
|
||||||
|
SCategory: category.Name,
|
||||||
|
SDifficulty: difficulty.Name,
|
||||||
|
}
|
||||||
|
database.DB.Create(&scd)
|
||||||
|
|
||||||
|
// Export
|
||||||
|
tmpDir := t.TempDir()
|
||||||
|
err := ExportSkillCategoryDifficulties(tmpDir)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("ExportSkillCategoryDifficulties failed: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Delete relationship
|
||||||
|
database.DB.Delete(&scd)
|
||||||
|
|
||||||
|
// Import
|
||||||
|
err = ImportSkillCategoryDifficulties(tmpDir)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("ImportSkillCategoryDifficulties failed: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Verify relationship was recreated
|
||||||
|
var imported models.SkillCategoryDifficulty
|
||||||
|
err = database.DB.Where("skill_id = ? AND skill_category_id = ?", skill.ID, category.ID).First(&imported).Error
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Imported relationship not found: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if imported.LearnCost != 5 {
|
||||||
|
t.Errorf("Expected learn_cost 5, got %d", imported.LearnCost)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestExportImportSkillCategories(t *testing.T) {
|
||||||
|
setupTestEnvironment(t)
|
||||||
|
database.SetupTestDB()
|
||||||
|
|
||||||
|
// Create test data
|
||||||
|
source := getOrCreateSource("TEST_SC", "Test Source")
|
||||||
|
category := models.SkillCategory{
|
||||||
|
Name: "TestCategory",
|
||||||
|
GameSystem: "midgard",
|
||||||
|
SourceID: source.ID,
|
||||||
|
}
|
||||||
|
database.DB.Create(&category)
|
||||||
|
|
||||||
|
// Export
|
||||||
|
tempDir := t.TempDir()
|
||||||
|
err := ExportSkillCategories(tempDir)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("ExportSkillCategories failed: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Verify file was created
|
||||||
|
filename := filepath.Join(tempDir, "skill_categories.json")
|
||||||
|
if _, err := os.Stat(filename); os.IsNotExist(err) {
|
||||||
|
t.Fatalf("Export file not created: %s", filename)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Delete the category
|
||||||
|
database.DB.Unscoped().Delete(&category)
|
||||||
|
|
||||||
|
// Import
|
||||||
|
err = ImportSkillCategories(tempDir)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("ImportSkillCategories failed: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Verify the category was recreated
|
||||||
|
var imported models.SkillCategory
|
||||||
|
result := database.DB.Where("name = ? AND game_system = ?", "TestCategory", "midgard").First(&imported)
|
||||||
|
if result.Error != nil {
|
||||||
|
t.Fatalf("Category not found after import: %v", result.Error)
|
||||||
|
}
|
||||||
|
|
||||||
|
if imported.SourceID != source.ID {
|
||||||
|
t.Errorf("Expected SourceID %d, got %d", source.ID, imported.SourceID)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestExportImportSkillDifficulties(t *testing.T) {
|
||||||
|
setupTestEnvironment(t)
|
||||||
|
database.SetupTestDB()
|
||||||
|
|
||||||
|
// Create test data
|
||||||
|
difficulty := models.SkillDifficulty{
|
||||||
|
Name: "TestDifficulty",
|
||||||
|
GameSystem: "midgard",
|
||||||
|
}
|
||||||
|
database.DB.Create(&difficulty)
|
||||||
|
|
||||||
|
// Export
|
||||||
|
tempDir := t.TempDir()
|
||||||
|
err := ExportSkillDifficulties(tempDir)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("ExportSkillDifficulties failed: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Verify file was created
|
||||||
|
filename := filepath.Join(tempDir, "skill_difficulties.json")
|
||||||
|
if _, err := os.Stat(filename); os.IsNotExist(err) {
|
||||||
|
t.Fatalf("Export file not created: %s", filename)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Delete the difficulty
|
||||||
|
database.DB.Unscoped().Delete(&difficulty)
|
||||||
|
|
||||||
|
// Import
|
||||||
|
err = ImportSkillDifficulties(tempDir)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("ImportSkillDifficulties failed: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Verify the difficulty was recreated
|
||||||
|
var imported models.SkillDifficulty
|
||||||
|
result := database.DB.Where("name = ? AND game_system = ?", "TestDifficulty", "midgard").First(&imported)
|
||||||
|
if result.Error != nil {
|
||||||
|
t.Fatalf("Difficulty not found after import: %v", result.Error)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestExportImportSpells(t *testing.T) {
|
||||||
|
setupTestEnvironment(t)
|
||||||
|
database.SetupTestDB()
|
||||||
|
|
||||||
|
// Create test data
|
||||||
|
source := getOrCreateSource("TEST_SP", "Test Spell Source")
|
||||||
|
spell := models.Spell{
|
||||||
|
Name: "TestSpell",
|
||||||
|
GameSystem: "midgard",
|
||||||
|
Beschreibung: "Test description",
|
||||||
|
SourceID: source.ID,
|
||||||
|
PageNumber: 42,
|
||||||
|
Bonus: 5,
|
||||||
|
Stufe: 3,
|
||||||
|
AP: "2",
|
||||||
|
Art: "Gestenzauber",
|
||||||
|
Zauberdauer: "10 sec",
|
||||||
|
Reichweite: "10m",
|
||||||
|
Wirkungsziel: "Person",
|
||||||
|
Wirkungsbereich: "1 Person",
|
||||||
|
Wirkungsdauer: "1h",
|
||||||
|
Ursprung: "Elben",
|
||||||
|
Category: "normal",
|
||||||
|
LearningCategory: "default",
|
||||||
|
}
|
||||||
|
database.DB.Create(&spell)
|
||||||
|
|
||||||
|
// Export
|
||||||
|
tempDir := t.TempDir()
|
||||||
|
err := ExportSpells(tempDir)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("ExportSpells failed: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Verify file was created
|
||||||
|
filename := filepath.Join(tempDir, "spells.json")
|
||||||
|
if _, err := os.Stat(filename); os.IsNotExist(err) {
|
||||||
|
t.Fatalf("Export file not created: %s", filename)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Modify the spell
|
||||||
|
spell.Beschreibung = "Old description"
|
||||||
|
spell.Bonus = 3
|
||||||
|
database.DB.Save(&spell)
|
||||||
|
|
||||||
|
// Import (should update)
|
||||||
|
err = ImportSpells(tempDir)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("ImportSpells failed: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Verify the spell was updated
|
||||||
|
var imported models.Spell
|
||||||
|
result := database.DB.Where("name = ? AND game_system = ?", "TestSpell", "midgard").First(&imported)
|
||||||
|
if result.Error != nil {
|
||||||
|
t.Fatalf("Spell not found after import: %v", result.Error)
|
||||||
|
}
|
||||||
|
|
||||||
|
if imported.Beschreibung != "Test description" {
|
||||||
|
t.Errorf("Expected description 'Test description', got '%s'", imported.Beschreibung)
|
||||||
|
}
|
||||||
|
if imported.Bonus != 5 {
|
||||||
|
t.Errorf("Expected bonus 5, got %d", imported.Bonus)
|
||||||
|
}
|
||||||
|
if imported.Stufe != 3 {
|
||||||
|
t.Errorf("Expected level 3, got %d", imported.Stufe)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestExportImportAll(t *testing.T) {
|
||||||
|
setupTestEnvironment(t)
|
||||||
|
database.SetupTestDB()
|
||||||
|
|
||||||
|
// Create test data
|
||||||
|
source := getOrCreateSource("TEST_ALL", "Test All Source")
|
||||||
|
|
||||||
|
category := models.SkillCategory{Name: "AllCategory", GameSystem: "midgard", SourceID: source.ID}
|
||||||
|
database.DB.Create(&category)
|
||||||
|
|
||||||
|
difficulty := models.SkillDifficulty{Name: "AllDifficulty", GameSystem: "midgard"}
|
||||||
|
database.DB.Create(&difficulty)
|
||||||
|
|
||||||
|
skill := models.Skill{
|
||||||
|
Name: "AllSkill",
|
||||||
|
GameSystem: "midgard",
|
||||||
|
SourceID: source.ID,
|
||||||
|
Initialwert: 10,
|
||||||
|
}
|
||||||
|
database.DB.Create(&skill)
|
||||||
|
|
||||||
|
spell := models.Spell{
|
||||||
|
Name: "AllSpell",
|
||||||
|
GameSystem: "midgard",
|
||||||
|
SourceID: source.ID,
|
||||||
|
Stufe: 2,
|
||||||
|
}
|
||||||
|
database.DB.Create(&spell)
|
||||||
|
|
||||||
|
// Export all
|
||||||
|
tempDir := t.TempDir()
|
||||||
|
err := ExportAll(tempDir)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("ExportAll failed: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Verify all files were created
|
||||||
|
files := []string{
|
||||||
|
"sources.json",
|
||||||
|
"character_classes.json",
|
||||||
|
"skill_categories.json",
|
||||||
|
"skill_difficulties.json",
|
||||||
|
"spell_schools.json",
|
||||||
|
"skills.json",
|
||||||
|
"skill_category_difficulties.json",
|
||||||
|
"spells.json",
|
||||||
|
"class_category_ep_costs.json",
|
||||||
|
"class_spell_school_ep_costs.json",
|
||||||
|
"spell_level_le_costs.json",
|
||||||
|
"skill_improvement_costs.json",
|
||||||
|
"weapon_skills.json",
|
||||||
|
"equipment.json",
|
||||||
|
"weapons.json",
|
||||||
|
"containers.json",
|
||||||
|
"transportation.json",
|
||||||
|
"believes.json",
|
||||||
|
}
|
||||||
|
for _, file := range files {
|
||||||
|
filename := filepath.Join(tempDir, file)
|
||||||
|
if _, err := os.Stat(filename); os.IsNotExist(err) {
|
||||||
|
t.Errorf("Export file not created: %s", filename)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Delete all test data
|
||||||
|
database.DB.Unscoped().Delete(&spell)
|
||||||
|
database.DB.Unscoped().Delete(&skill)
|
||||||
|
database.DB.Unscoped().Delete(&difficulty)
|
||||||
|
database.DB.Unscoped().Delete(&category)
|
||||||
|
// Don't delete source to avoid FK constraints
|
||||||
|
|
||||||
|
// Import all
|
||||||
|
err = ImportAll(tempDir)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("ImportAll failed: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Verify all data was recreated
|
||||||
|
var importedCategory models.SkillCategory
|
||||||
|
if err := database.DB.Where("name = ? AND game_system = ?", "AllCategory", "midgard").First(&importedCategory).Error; err != nil {
|
||||||
|
t.Errorf("Category not found after import: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
var importedDifficulty models.SkillDifficulty
|
||||||
|
if err := database.DB.Where("name = ? AND game_system = ?", "AllDifficulty", "midgard").First(&importedDifficulty).Error; err != nil {
|
||||||
|
t.Errorf("Difficulty not found after import: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
var importedSkill models.Skill
|
||||||
|
if err := database.DB.Where("name = ? AND game_system = ?", "AllSkill", "midgard").First(&importedSkill).Error; err != nil {
|
||||||
|
t.Errorf("Skill not found after import: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
var importedSpell models.Spell
|
||||||
|
if err := database.DB.Where("name = ? AND game_system = ?", "AllSpell", "midgard").First(&importedSpell).Error; err != nil {
|
||||||
|
t.Errorf("Spell not found after import: %v", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestExportAll_live(t *testing.T) {
|
||||||
|
setupTestEnvironment(t)
|
||||||
|
database.SetupTestDB(false)
|
||||||
|
|
||||||
|
// Export all
|
||||||
|
tempDir := t.TempDir()
|
||||||
|
err := ExportAll(tempDir)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("ExportAll failed: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Verify all files were created
|
||||||
|
files := []string{"sources.json", "skill_categories.json", "skill_difficulties.json", "skills.json", "spells.json", "skill_category_difficulties.json"}
|
||||||
|
for _, file := range files {
|
||||||
|
filename := filepath.Join(tempDir, file)
|
||||||
|
if _, err := os.Stat(filename); os.IsNotExist(err) {
|
||||||
|
t.Errorf("Export file not created: %s", filename)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
assert.Equal(t, len(files), 6)
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestExportImportWeaponSkills(t *testing.T) {
|
||||||
|
setupTestEnvironment(t)
|
||||||
|
database.SetupTestDB()
|
||||||
|
|
||||||
|
source := getOrCreateSource("TEST_WS", "Test Weapon Source")
|
||||||
|
weaponSkill := models.WeaponSkill{
|
||||||
|
Skill: models.Skill{
|
||||||
|
Name: "Langschwert",
|
||||||
|
GameSystem: "midgard",
|
||||||
|
Beschreibung: "Langschwert Waffenfertigkeiten",
|
||||||
|
SourceID: source.ID,
|
||||||
|
PageNumber: 50,
|
||||||
|
Initialwert: 10,
|
||||||
|
BasisWert: 5,
|
||||||
|
Bonuseigenschaft: "St",
|
||||||
|
Improvable: true,
|
||||||
|
InnateSkill: false,
|
||||||
|
Category: "Waffen",
|
||||||
|
Difficulty: "normal",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
database.DB.Create(&weaponSkill)
|
||||||
|
|
||||||
|
tempDir := t.TempDir()
|
||||||
|
err := ExportWeaponSkills(tempDir)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("ExportWeaponSkills failed: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
filename := filepath.Join(tempDir, "weapon_skills.json")
|
||||||
|
if _, err := os.Stat(filename); os.IsNotExist(err) {
|
||||||
|
t.Fatalf("Export file not created: %s", filename)
|
||||||
|
}
|
||||||
|
|
||||||
|
database.DB.Unscoped().Delete(&weaponSkill)
|
||||||
|
|
||||||
|
err = ImportWeaponSkills(tempDir)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("ImportWeaponSkills failed: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
var imported models.WeaponSkill
|
||||||
|
result := database.DB.Where("name = ? AND game_system = ?", "Langschwert", "midgard").First(&imported)
|
||||||
|
if result.Error != nil {
|
||||||
|
t.Fatalf("Weapon skill not found after import: %v", result.Error)
|
||||||
|
}
|
||||||
|
|
||||||
|
assert.Equal(t, "Langschwert Waffenfertigkeiten", imported.Beschreibung)
|
||||||
|
assert.Equal(t, 10, imported.Initialwert)
|
||||||
|
assert.Equal(t, 5, imported.BasisWert)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestExportImportEquipment(t *testing.T) {
|
||||||
|
setupTestEnvironment(t)
|
||||||
|
database.SetupTestDB()
|
||||||
|
|
||||||
|
source := getOrCreateSource("TEST_EQ", "Test Equipment Source")
|
||||||
|
equipment := models.Equipment{
|
||||||
|
Name: "Seil",
|
||||||
|
GameSystem: "midgard",
|
||||||
|
Beschreibung: "10m langes Hanfseil",
|
||||||
|
SourceID: source.ID,
|
||||||
|
PageNumber: 75,
|
||||||
|
Gewicht: 2.5,
|
||||||
|
Wert: 15.0,
|
||||||
|
PersonalItem: false,
|
||||||
|
}
|
||||||
|
database.DB.Create(&equipment)
|
||||||
|
|
||||||
|
tempDir := t.TempDir()
|
||||||
|
err := ExportEquipment(tempDir)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("ExportEquipment failed: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
filename := filepath.Join(tempDir, "equipment.json")
|
||||||
|
if _, err := os.Stat(filename); os.IsNotExist(err) {
|
||||||
|
t.Fatalf("Export file not created: %s", filename)
|
||||||
|
}
|
||||||
|
|
||||||
|
equipment.Wert = 10.0
|
||||||
|
database.DB.Save(&equipment)
|
||||||
|
|
||||||
|
err = ImportEquipment(tempDir)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("ImportEquipment failed: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
var imported models.Equipment
|
||||||
|
result := database.DB.Where("name = ? AND game_system = ?", "Seil", "midgard").First(&imported)
|
||||||
|
if result.Error != nil {
|
||||||
|
t.Fatalf("Equipment not found after import: %v", result.Error)
|
||||||
|
}
|
||||||
|
|
||||||
|
assert.Equal(t, 15.0, imported.Wert)
|
||||||
|
assert.Equal(t, 2.5, imported.Gewicht)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestExportImportWeapons(t *testing.T) {
|
||||||
|
setupTestEnvironment(t)
|
||||||
|
database.SetupTestDB()
|
||||||
|
|
||||||
|
source := getOrCreateSource("TEST_WP", "Test Weapon Source")
|
||||||
|
weapon := models.Weapon{
|
||||||
|
Equipment: models.Equipment{
|
||||||
|
Name: "Kurzschwert",
|
||||||
|
GameSystem: "midgard",
|
||||||
|
Beschreibung: "Einhändiges Kurzschwert",
|
||||||
|
SourceID: source.ID,
|
||||||
|
PageNumber: 80,
|
||||||
|
Gewicht: 1.5,
|
||||||
|
Wert: 50.0,
|
||||||
|
PersonalItem: false,
|
||||||
|
},
|
||||||
|
SkillRequired: "Langschwert",
|
||||||
|
Damage: "1W6+1",
|
||||||
|
RangeNear: 0,
|
||||||
|
RangeMiddle: 0,
|
||||||
|
RangeFar: 0,
|
||||||
|
}
|
||||||
|
database.DB.Create(&weapon)
|
||||||
|
|
||||||
|
tempDir := t.TempDir()
|
||||||
|
err := ExportWeapons(tempDir)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("ExportWeapons failed: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
filename := filepath.Join(tempDir, "weapons.json")
|
||||||
|
if _, err := os.Stat(filename); os.IsNotExist(err) {
|
||||||
|
t.Fatalf("Export file not created: %s", filename)
|
||||||
|
}
|
||||||
|
|
||||||
|
weapon.Damage = "1W6"
|
||||||
|
database.DB.Save(&weapon)
|
||||||
|
|
||||||
|
err = ImportWeapons(tempDir)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("ImportWeapons failed: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
var imported models.Weapon
|
||||||
|
result := database.DB.Where("name = ? AND game_system = ?", "Kurzschwert", "midgard").First(&imported)
|
||||||
|
if result.Error != nil {
|
||||||
|
t.Fatalf("Weapon not found after import: %v", result.Error)
|
||||||
|
}
|
||||||
|
|
||||||
|
assert.Equal(t, "1W6+1", imported.Damage)
|
||||||
|
assert.Equal(t, "Langschwert", imported.SkillRequired)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestExportImportContainers(t *testing.T) {
|
||||||
|
setupTestEnvironment(t)
|
||||||
|
database.SetupTestDB()
|
||||||
|
|
||||||
|
source := getOrCreateSource("TEST_CT", "Test Container Source")
|
||||||
|
container := models.Container{
|
||||||
|
Equipment: models.Equipment{
|
||||||
|
Name: "Rucksack",
|
||||||
|
GameSystem: "midgard",
|
||||||
|
Beschreibung: "Großer Lederrucksack",
|
||||||
|
SourceID: source.ID,
|
||||||
|
PageNumber: 85,
|
||||||
|
Gewicht: 1.0,
|
||||||
|
Wert: 20.0,
|
||||||
|
PersonalItem: false,
|
||||||
|
},
|
||||||
|
Tragkraft: 30.0,
|
||||||
|
Volumen: 50.0,
|
||||||
|
}
|
||||||
|
database.DB.Create(&container)
|
||||||
|
|
||||||
|
tempDir := t.TempDir()
|
||||||
|
err := ExportContainers(tempDir)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("ExportContainers failed: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
filename := filepath.Join(tempDir, "containers.json")
|
||||||
|
if _, err := os.Stat(filename); os.IsNotExist(err) {
|
||||||
|
t.Fatalf("Export file not created: %s", filename)
|
||||||
|
}
|
||||||
|
|
||||||
|
container.Tragkraft = 25.0
|
||||||
|
database.DB.Save(&container)
|
||||||
|
|
||||||
|
err = ImportContainers(tempDir)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("ImportContainers failed: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
var imported models.Container
|
||||||
|
result := database.DB.Where("name = ? AND game_system = ?", "Rucksack", "midgard").First(&imported)
|
||||||
|
if result.Error != nil {
|
||||||
|
t.Fatalf("Container not found after import: %v", result.Error)
|
||||||
|
}
|
||||||
|
|
||||||
|
assert.Equal(t, 30.0, imported.Tragkraft)
|
||||||
|
assert.Equal(t, 50.0, imported.Volumen)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestExportImportTransportation(t *testing.T) {
|
||||||
|
setupTestEnvironment(t)
|
||||||
|
database.SetupTestDB()
|
||||||
|
|
||||||
|
source := getOrCreateSource("TEST_TR", "Test Transport Source")
|
||||||
|
transportation := models.Transportation{
|
||||||
|
Container: models.Container{
|
||||||
|
Equipment: models.Equipment{
|
||||||
|
Name: "Pferdewagen",
|
||||||
|
GameSystem: "midgard",
|
||||||
|
Beschreibung: "Zweirädriger Wagen",
|
||||||
|
SourceID: source.ID,
|
||||||
|
PageNumber: 90,
|
||||||
|
Gewicht: 100.0,
|
||||||
|
Wert: 200.0,
|
||||||
|
PersonalItem: false,
|
||||||
|
},
|
||||||
|
Tragkraft: 500.0,
|
||||||
|
Volumen: 1000.0,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
database.DB.Create(&transportation)
|
||||||
|
|
||||||
|
tempDir := t.TempDir()
|
||||||
|
err := ExportTransportation(tempDir)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("ExportTransportation failed: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
filename := filepath.Join(tempDir, "transportation.json")
|
||||||
|
if _, err := os.Stat(filename); os.IsNotExist(err) {
|
||||||
|
t.Fatalf("Export file not created: %s", filename)
|
||||||
|
}
|
||||||
|
|
||||||
|
transportation.Tragkraft = 450.0
|
||||||
|
database.DB.Save(&transportation)
|
||||||
|
|
||||||
|
err = ImportTransportation(tempDir)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("ImportTransportation failed: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
var imported models.Transportation
|
||||||
|
result := database.DB.Where("name = ? AND game_system = ?", "Pferdewagen", "midgard").First(&imported)
|
||||||
|
if result.Error != nil {
|
||||||
|
t.Fatalf("Transportation not found after import: %v", result.Error)
|
||||||
|
}
|
||||||
|
|
||||||
|
assert.Equal(t, 500.0, imported.Tragkraft)
|
||||||
|
assert.Equal(t, 1000.0, imported.Volumen)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestExportImportBelieves(t *testing.T) {
|
||||||
|
setupTestEnvironment(t)
|
||||||
|
database.SetupTestDB()
|
||||||
|
|
||||||
|
source := getOrCreateSource("TEST_BL", "Test Believe Source")
|
||||||
|
believe := models.Believe{
|
||||||
|
Name: "Kirche des Lichts",
|
||||||
|
GameSystem: "midgard",
|
||||||
|
Beschreibung: "Hauptreligion in Valian",
|
||||||
|
SourceID: source.ID,
|
||||||
|
PageNumber: 95,
|
||||||
|
}
|
||||||
|
database.DB.Create(&believe)
|
||||||
|
|
||||||
|
tempDir := t.TempDir()
|
||||||
|
err := ExportBelieves(tempDir)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("ExportBelieves failed: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
filename := filepath.Join(tempDir, "believes.json")
|
||||||
|
if _, err := os.Stat(filename); os.IsNotExist(err) {
|
||||||
|
t.Fatalf("Export file not created: %s", filename)
|
||||||
|
}
|
||||||
|
|
||||||
|
believe.Beschreibung = "Alte Beschreibung"
|
||||||
|
database.DB.Save(&believe)
|
||||||
|
|
||||||
|
err = ImportBelieves(tempDir)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("ImportBelieves failed: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
var imported models.Believe
|
||||||
|
result := database.DB.Where("name = ? AND game_system = ?", "Kirche des Lichts", "midgard").First(&imported)
|
||||||
|
if result.Error != nil {
|
||||||
|
t.Fatalf("Believe not found after import: %v", result.Error)
|
||||||
|
}
|
||||||
|
|
||||||
|
assert.Equal(t, "Hauptreligion in Valian", imported.Beschreibung)
|
||||||
|
assert.Equal(t, 95, imported.PageNumber)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestExportImportSkillImprovementCosts(t *testing.T) {
|
||||||
|
setupTestEnvironment(t)
|
||||||
|
database.SetupTestDB()
|
||||||
|
|
||||||
|
// Create dependencies that already exist in test DB
|
||||||
|
// Use existing skill, category, difficulty from test database
|
||||||
|
var skill models.Skill
|
||||||
|
if err := database.DB.Where("name = ?", "Abrichten").First(&skill).Error; err != nil {
|
||||||
|
t.Skip("Test skill not found in database, skipping test")
|
||||||
|
}
|
||||||
|
|
||||||
|
var category models.SkillCategory
|
||||||
|
if err := database.DB.First(&category).Error; err != nil {
|
||||||
|
t.Skip("No skill category found in database, skipping test")
|
||||||
|
}
|
||||||
|
|
||||||
|
var difficulty models.SkillDifficulty
|
||||||
|
if err := database.DB.First(&difficulty).Error; err != nil {
|
||||||
|
t.Skip("No skill difficulty found in database, skipping test")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Find or create SkillCategoryDifficulty
|
||||||
|
var scd models.SkillCategoryDifficulty
|
||||||
|
err := database.DB.Where("skill_id = ? AND skill_category_id = ? AND skill_difficulty_id = ?",
|
||||||
|
skill.ID, category.ID, difficulty.ID).First(&scd).Error
|
||||||
|
|
||||||
|
if err == gorm.ErrRecordNotFound {
|
||||||
|
scd = models.SkillCategoryDifficulty{
|
||||||
|
SkillID: skill.ID,
|
||||||
|
SkillCategoryID: category.ID,
|
||||||
|
SkillDifficultyID: difficulty.ID,
|
||||||
|
LearnCost: 10,
|
||||||
|
SCategory: category.Name,
|
||||||
|
SDifficulty: difficulty.Name,
|
||||||
|
}
|
||||||
|
database.DB.Create(&scd)
|
||||||
|
} else if err != nil {
|
||||||
|
t.Fatalf("Failed to query SkillCategoryDifficulty: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create SkillImprovementCost
|
||||||
|
improvementCost := models.SkillImprovementCost{
|
||||||
|
SkillCategoryDifficultyID: scd.ID,
|
||||||
|
CurrentLevel: 15, // Use unique level to avoid conflicts
|
||||||
|
TERequired: 5,
|
||||||
|
}
|
||||||
|
database.DB.Create(&improvementCost)
|
||||||
|
|
||||||
|
// Export
|
||||||
|
tempDir := t.TempDir()
|
||||||
|
err = ExportSkillImprovementCosts(tempDir)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("ExportSkillImprovementCosts failed: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
filename := filepath.Join(tempDir, "skill_improvement_costs.json")
|
||||||
|
if _, err := os.Stat(filename); os.IsNotExist(err) {
|
||||||
|
t.Fatalf("Export file not created: %s", filename)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Modify the record
|
||||||
|
improvementCost.TERequired = 7
|
||||||
|
database.DB.Save(&improvementCost)
|
||||||
|
|
||||||
|
// Import should restore original value
|
||||||
|
err = ImportSkillImprovementCosts(tempDir)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("ImportSkillImprovementCosts failed: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
var imported models.SkillImprovementCost
|
||||||
|
result := database.DB.Where("skill_category_difficulty_id = ? AND current_level = ?", scd.ID, 15).First(&imported)
|
||||||
|
if result.Error != nil {
|
||||||
|
t.Fatalf("SkillImprovementCost not found after import: %v", result.Error)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Should be restored to original value from export
|
||||||
|
assert.Equal(t, 5, imported.TERequired)
|
||||||
|
assert.Equal(t, 15, imported.CurrentLevel)
|
||||||
|
}
|
||||||
@@ -12,7 +12,8 @@ func RegisterRoutes(r *gin.RouterGroup) {
|
|||||||
{
|
{
|
||||||
maintGrp.GET("", GetMasterData)
|
maintGrp.GET("", GetMasterData)
|
||||||
maintGrp.GET("/skills", GetMDSkills)
|
maintGrp.GET("/skills", GetMDSkills)
|
||||||
maintGrp.GET("/skills-enhanced", GetEnhancedMDSkills) // New enhanced endpoint
|
maintGrp.GET("/skills-enhanced", GetEnhancedMDSkills) // New enhanced endpoint
|
||||||
|
maintGrp.POST("/skills-enhanced", CreateEnhancedMDSkill) // Create new skill
|
||||||
maintGrp.GET("/skills/:id", GetMDSkill)
|
maintGrp.GET("/skills/:id", GetMDSkill)
|
||||||
maintGrp.GET("/skills-enhanced/:id", GetEnhancedMDSkill) // New enhanced endpoint
|
maintGrp.GET("/skills-enhanced/:id", GetEnhancedMDSkill) // New enhanced endpoint
|
||||||
maintGrp.PUT("/skills/:id", UpdateMDSkill)
|
maintGrp.PUT("/skills/:id", UpdateMDSkill)
|
||||||
|
|||||||
@@ -0,0 +1,149 @@
|
|||||||
|
package gsmaster
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bamort/database"
|
||||||
|
"bamort/models"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestCreateSkillWithCategories(t *testing.T) {
|
||||||
|
setupTestEnvironment(t)
|
||||||
|
database.SetupTestDB()
|
||||||
|
|
||||||
|
// Create test dependencies
|
||||||
|
source := getOrCreateSource("TSTCRT", "TestCreate")
|
||||||
|
category := getOrCreateCategory("Alltag", source.ID)
|
||||||
|
difficulty := getOrCreateDifficulty("normal")
|
||||||
|
|
||||||
|
// Prepare create request
|
||||||
|
req := SkillUpdateRequest{
|
||||||
|
Skill: models.Skill{
|
||||||
|
Name: "Neue Fertigkeit",
|
||||||
|
GameSystem: "midgard",
|
||||||
|
Beschreibung: "Test Fertigkeit",
|
||||||
|
Initialwert: 5,
|
||||||
|
BasisWert: 0,
|
||||||
|
Bonuseigenschaft: "In",
|
||||||
|
Improvable: true,
|
||||||
|
InnateSkill: false,
|
||||||
|
SourceID: source.ID,
|
||||||
|
PageNumber: 42,
|
||||||
|
},
|
||||||
|
CategoryDifficulties: []CategoryDifficultyPair{
|
||||||
|
{
|
||||||
|
CategoryID: category.ID,
|
||||||
|
DifficultyID: difficulty.ID,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test creating new skill
|
||||||
|
skillID, err := CreateSkillWithCategories(req)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("CreateSkillWithCategories failed: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if skillID == 0 {
|
||||||
|
t.Fatalf("Expected non-zero skill ID, got 0")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Verify skill was created
|
||||||
|
var skill models.Skill
|
||||||
|
if err := database.DB.First(&skill, skillID).Error; err != nil {
|
||||||
|
t.Fatalf("Failed to retrieve created skill: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if skill.Name != "Neue Fertigkeit" {
|
||||||
|
t.Errorf("Expected name 'Neue Fertigkeit', got '%s'", skill.Name)
|
||||||
|
}
|
||||||
|
|
||||||
|
if skill.Initialwert != 5 {
|
||||||
|
t.Errorf("Expected initialwert 5, got %d", skill.Initialwert)
|
||||||
|
}
|
||||||
|
|
||||||
|
if skill.BasisWert != 0 {
|
||||||
|
t.Errorf("Expected basiswert 0, got %d", skill.BasisWert)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Verify category-difficulty relationship
|
||||||
|
var scd models.SkillCategoryDifficulty
|
||||||
|
if err := database.DB.Where("skill_id = ?", skillID).First(&scd).Error; err != nil {
|
||||||
|
t.Fatalf("Failed to retrieve skill category difficulty: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if scd.SkillCategoryID != category.ID {
|
||||||
|
t.Errorf("Expected category ID %d, got %d", category.ID, scd.SkillCategoryID)
|
||||||
|
}
|
||||||
|
|
||||||
|
if scd.SkillDifficultyID != difficulty.ID {
|
||||||
|
t.Errorf("Expected difficulty ID %d, got %d", difficulty.ID, scd.SkillDifficultyID)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestCreateSkillWithMultipleCategories(t *testing.T) {
|
||||||
|
setupTestEnvironment(t)
|
||||||
|
database.SetupTestDB()
|
||||||
|
|
||||||
|
// Create test dependencies
|
||||||
|
source := getOrCreateSource("TSTMLT", "TestMultiple")
|
||||||
|
category1 := getOrCreateCategory("Körper", source.ID)
|
||||||
|
category2 := getOrCreateCategory("Geist", source.ID)
|
||||||
|
difficulty1 := getOrCreateDifficulty("leicht")
|
||||||
|
difficulty2 := getOrCreateDifficulty("schwer")
|
||||||
|
|
||||||
|
// Prepare create request with multiple categories
|
||||||
|
req := SkillUpdateRequest{
|
||||||
|
Skill: models.Skill{
|
||||||
|
Name: "Multi-Kategorie Fertigkeit",
|
||||||
|
GameSystem: "midgard",
|
||||||
|
Initialwert: 10,
|
||||||
|
Improvable: true,
|
||||||
|
SourceID: source.ID,
|
||||||
|
},
|
||||||
|
CategoryDifficulties: []CategoryDifficultyPair{
|
||||||
|
{
|
||||||
|
CategoryID: category1.ID,
|
||||||
|
DifficultyID: difficulty1.ID,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
CategoryID: category2.ID,
|
||||||
|
DifficultyID: difficulty2.ID,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test creating skill with multiple categories
|
||||||
|
skillID, err := CreateSkillWithCategories(req)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("CreateSkillWithCategories failed: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Verify both category-difficulty relationships exist
|
||||||
|
var scds []models.SkillCategoryDifficulty
|
||||||
|
if err := database.DB.Where("skill_id = ?", skillID).Find(&scds).Error; err != nil {
|
||||||
|
t.Fatalf("Failed to retrieve skill category difficulties: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(scds) != 2 {
|
||||||
|
t.Fatalf("Expected 2 category-difficulty relationships, got %d", len(scds))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestCreateSkillValidation(t *testing.T) {
|
||||||
|
setupTestEnvironment(t)
|
||||||
|
database.SetupTestDB()
|
||||||
|
|
||||||
|
// Test creating skill without name
|
||||||
|
req := SkillUpdateRequest{
|
||||||
|
Skill: models.Skill{
|
||||||
|
GameSystem: "midgard",
|
||||||
|
Initialwert: 5,
|
||||||
|
},
|
||||||
|
CategoryDifficulties: []CategoryDifficultyPair{},
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err := CreateSkillWithCategories(req)
|
||||||
|
if err == nil {
|
||||||
|
t.Error("Expected error when creating skill without name, got nil")
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -95,12 +95,77 @@ type CategoryDifficultyPair struct {
|
|||||||
LearnCost int `json:"learn_cost,omitempty"`
|
LearnCost int `json:"learn_cost,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// CreateSkillWithCategories creates a new skill with category-difficulty relationships
|
||||||
|
func CreateSkillWithCategories(req SkillUpdateRequest) (uint, error) {
|
||||||
|
// Validate required fields
|
||||||
|
if req.Skill.Name == "" {
|
||||||
|
return 0, fmt.Errorf("skill name is required")
|
||||||
|
}
|
||||||
|
|
||||||
|
var skillID uint
|
||||||
|
|
||||||
|
// Start transaction
|
||||||
|
err := database.DB.Transaction(func(tx *gorm.DB) error {
|
||||||
|
// Create skill
|
||||||
|
if err := tx.Create(&req.Skill).Error; err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
skillID = req.Skill.ID
|
||||||
|
|
||||||
|
// Create category-difficulty relationships
|
||||||
|
for _, cd := range req.CategoryDifficulties {
|
||||||
|
// Get category and difficulty names for denormalized fields
|
||||||
|
var category models.SkillCategory
|
||||||
|
if err := tx.First(&category, cd.CategoryID).Error; err != nil {
|
||||||
|
return fmt.Errorf("category not found: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
var difficulty models.SkillDifficulty
|
||||||
|
if err := tx.First(&difficulty, cd.DifficultyID).Error; err != nil {
|
||||||
|
return fmt.Errorf("difficulty not found: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
learnCost := cd.LearnCost
|
||||||
|
if learnCost == 0 {
|
||||||
|
// Use default based on difficulty
|
||||||
|
learnCost = getDefaultLearnCost(difficulty.Name)
|
||||||
|
}
|
||||||
|
|
||||||
|
scd := models.SkillCategoryDifficulty{
|
||||||
|
SkillID: skillID,
|
||||||
|
SkillCategoryID: cd.CategoryID,
|
||||||
|
SkillDifficultyID: cd.DifficultyID,
|
||||||
|
LearnCost: learnCost,
|
||||||
|
SCategory: category.Name,
|
||||||
|
SDifficulty: difficulty.Name,
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := tx.Create(&scd).Error; err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return skillID, nil
|
||||||
|
}
|
||||||
|
|
||||||
// UpdateSkillWithCategories updates a skill and its category-difficulty relationships
|
// UpdateSkillWithCategories updates a skill and its category-difficulty relationships
|
||||||
func UpdateSkillWithCategories(skillID uint, req SkillUpdateRequest) error {
|
func UpdateSkillWithCategories(skillID uint, req SkillUpdateRequest) error {
|
||||||
// Start transaction
|
// Start transaction
|
||||||
return database.DB.Transaction(func(tx *gorm.DB) error {
|
return database.DB.Transaction(func(tx *gorm.DB) error {
|
||||||
// Update skill basic info
|
// Update skill basic info - use Select to explicitly include boolean fields
|
||||||
if err := tx.Model(&models.Skill{}).Where("id = ?", skillID).Updates(req.Skill).Error; err != nil {
|
// This ensures false values are also updated (GORM skips zero values by default in Updates)
|
||||||
|
if err := tx.Model(&models.Skill{}).Where("id = ?", skillID).
|
||||||
|
Select("name", "beschreibung", "game_system", "initialwert", "basis_wert",
|
||||||
|
"bonuseigenschaft", "improvable", "innate_skill", "source_id", "page_number").
|
||||||
|
Updates(req.Skill).Error; err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -240,3 +305,28 @@ func UpdateEnhancedMDSkill(c *gin.Context) {
|
|||||||
|
|
||||||
c.JSON(http.StatusOK, skill)
|
c.JSON(http.StatusOK, skill)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// CreateEnhancedMDSkill creates a new skill with categories
|
||||||
|
func CreateEnhancedMDSkill(c *gin.Context) {
|
||||||
|
var req SkillUpdateRequest
|
||||||
|
if err := c.ShouldBindJSON(&req); err != nil {
|
||||||
|
respondWithError(c, http.StatusBadRequest, "Invalid request: "+err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create the skill
|
||||||
|
skillID, err := CreateSkillWithCategories(req)
|
||||||
|
if err != nil {
|
||||||
|
respondWithError(c, http.StatusInternalServerError, "Failed to create skill: "+err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Return created skill
|
||||||
|
skill, err := GetSkillWithCategories(skillID)
|
||||||
|
if err != nil {
|
||||||
|
respondWithError(c, http.StatusInternalServerError, "Failed to retrieve created skill")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
c.JSON(http.StatusCreated, skill)
|
||||||
|
}
|
||||||
|
|||||||
@@ -292,6 +292,73 @@ func TestUpdateSkillWithCategories(t *testing.T) {
|
|||||||
t.Error("Expected to find 'Alltag/normal' category after update")
|
t.Error("Expected to find 'Alltag/normal' category after update")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
func TestUpdateSkillBooleanFields(t *testing.T) {
|
||||||
|
setupTestEnvironment(t)
|
||||||
|
database.SetupTestDB()
|
||||||
|
|
||||||
|
// Create test data with improvable=true and innateskill=false
|
||||||
|
source := getOrCreateSource("TSTBOOL", "TestBoolean")
|
||||||
|
skill := models.Skill{
|
||||||
|
Name: "TestBooleanSkill",
|
||||||
|
GameSystem: "midgard",
|
||||||
|
Initialwert: 5,
|
||||||
|
Improvable: true,
|
||||||
|
InnateSkill: false,
|
||||||
|
SourceID: source.ID,
|
||||||
|
}
|
||||||
|
database.DB.Create(&skill)
|
||||||
|
|
||||||
|
category := getOrCreateCategory("Alltag", source.ID)
|
||||||
|
difficulty := getOrCreateDifficulty("normal")
|
||||||
|
|
||||||
|
scd := models.SkillCategoryDifficulty{
|
||||||
|
SkillID: skill.ID,
|
||||||
|
SkillCategoryID: category.ID,
|
||||||
|
SkillDifficultyID: difficulty.ID,
|
||||||
|
LearnCost: 10,
|
||||||
|
SCategory: category.Name,
|
||||||
|
SDifficulty: difficulty.Name,
|
||||||
|
}
|
||||||
|
database.DB.Create(&scd)
|
||||||
|
|
||||||
|
// Update to set improvable=false and innateskill=true
|
||||||
|
updateReq := SkillUpdateRequest{
|
||||||
|
Skill: models.Skill{
|
||||||
|
ID: skill.ID,
|
||||||
|
Name: "TestBooleanSkill",
|
||||||
|
GameSystem: "midgard",
|
||||||
|
Initialwert: 5,
|
||||||
|
Improvable: false, // Change to false
|
||||||
|
InnateSkill: true, // Change to true
|
||||||
|
SourceID: source.ID,
|
||||||
|
},
|
||||||
|
CategoryDifficulties: []CategoryDifficultyPair{
|
||||||
|
{
|
||||||
|
CategoryID: category.ID,
|
||||||
|
DifficultyID: difficulty.ID,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
err := UpdateSkillWithCategories(skill.ID, updateReq)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("UpdateSkillWithCategories failed: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Verify boolean fields were updated correctly
|
||||||
|
var updatedSkill models.Skill
|
||||||
|
if err := database.DB.First(&updatedSkill, skill.ID).Error; err != nil {
|
||||||
|
t.Fatalf("Failed to retrieve updated skill: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if updatedSkill.Improvable != false {
|
||||||
|
t.Errorf("Expected improvable to be false, got %v", updatedSkill.Improvable)
|
||||||
|
}
|
||||||
|
|
||||||
|
if updatedSkill.InnateSkill != true {
|
||||||
|
t.Errorf("Expected innateskill to be true, got %v", updatedSkill.InnateSkill)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func TestGetDefaultLearnCost(t *testing.T) {
|
func TestGetDefaultLearnCost(t *testing.T) {
|
||||||
tests := []struct {
|
tests := []struct {
|
||||||
|
|||||||
@@ -52,6 +52,7 @@ type Skill struct {
|
|||||||
SourceID uint `gorm:"index" json:"source_id,omitempty"` // Verweis auf strukturierte Quelle
|
SourceID uint `gorm:"index" json:"source_id,omitempty"` // Verweis auf strukturierte Quelle
|
||||||
PageNumber int `json:"page_number,omitempty"` // Seitenzahl im Quellenbuch
|
PageNumber int `json:"page_number,omitempty"` // Seitenzahl im Quellenbuch
|
||||||
Initialwert int `gorm:"default:5" json:"initialwert"`
|
Initialwert int `gorm:"default:5" json:"initialwert"`
|
||||||
|
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 `gorm:"default:true" json:"improvable"`
|
||||||
InnateSkill bool `gorm:"default:false" json:"innateskill"`
|
InnateSkill bool `gorm:"default:false" json:"innateskill"`
|
||||||
@@ -127,51 +128,6 @@ type Believe struct {
|
|||||||
PageNumber int `json:"page_number,omitempty"` // Seitenzahl im Quellenbuch
|
PageNumber int `json:"page_number,omitempty"` // Seitenzahl im Quellenbuch
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
|
||||||
func (object *LookupList) Create() error {
|
|
||||||
gameSystem := "midgard"
|
|
||||||
object.GameSystem = gameSystem
|
|
||||||
err := database.DB.Transaction(func(tx *gorm.DB) error {
|
|
||||||
// Save the main character record
|
|
||||||
if err := tx.Create(&object).Error; err != nil {
|
|
||||||
return fmt.Errorf("failed to save Lookup: %w", err)
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
})
|
|
||||||
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
func (object *LookupList) First(value string) error {
|
|
||||||
gameSystem := "midgard"
|
|
||||||
err := database.DB.First(&object, "game_system=? AND name!='Placeholder' AND name = ?", gameSystem, value).Error
|
|
||||||
if err != nil {
|
|
||||||
// zauber found
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (object *LookupList) FirstId(value uint) error {
|
|
||||||
gameSystem := "midgard"
|
|
||||||
err := database.DB.First(&object, "game_system=? AND name!='Placeholder' AND id = ?", gameSystem, value).Error
|
|
||||||
if err != nil {
|
|
||||||
// zauber found
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (object *LookupList) Save() error {
|
|
||||||
err := database.DB.Save(&object).Error
|
|
||||||
if err != nil {
|
|
||||||
// zauber found
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
*/
|
|
||||||
|
|
||||||
func (object *Skill) TableName() string {
|
func (object *Skill) TableName() string {
|
||||||
dbPrefix := "gsm"
|
dbPrefix := "gsm"
|
||||||
return dbPrefix + "_" + "skills"
|
return dbPrefix + "_" + "skills"
|
||||||
|
|||||||
@@ -0,0 +1,49 @@
|
|||||||
|
package models
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestSkillBasisWert(t *testing.T) {
|
||||||
|
// Test that BasisWert defaults to 0 in master skill data
|
||||||
|
skill := Skill{
|
||||||
|
Name: "TestSkill",
|
||||||
|
GameSystem: "midgard",
|
||||||
|
Initialwert: 5,
|
||||||
|
}
|
||||||
|
|
||||||
|
// BasisWert should default to 0
|
||||||
|
if skill.BasisWert != 0 {
|
||||||
|
t.Errorf("Expected BasisWert to default to 0, got %d", skill.BasisWert)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestSkillBasisWertSet(t *testing.T) {
|
||||||
|
// Test that BasisWert can be set in master skill data
|
||||||
|
skill := Skill{
|
||||||
|
Name: "TestSkill",
|
||||||
|
GameSystem: "midgard",
|
||||||
|
Initialwert: 5,
|
||||||
|
BasisWert: 3,
|
||||||
|
}
|
||||||
|
|
||||||
|
if skill.BasisWert != 3 {
|
||||||
|
t.Errorf("Expected BasisWert to be 3, got %d", skill.BasisWert)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestWeaponSkillBasisWert(t *testing.T) {
|
||||||
|
// Test that BasisWert works for WeaponSkill (inherited from Skill)
|
||||||
|
weaponSkill := WeaponSkill{
|
||||||
|
Skill: Skill{
|
||||||
|
Name: "TestWeaponSkill",
|
||||||
|
GameSystem: "midgard",
|
||||||
|
Initialwert: 5,
|
||||||
|
BasisWert: 2,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
if weaponSkill.BasisWert != 2 {
|
||||||
|
t.Errorf("Expected BasisWert to be 2, got %d", weaponSkill.BasisWert)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -6,6 +6,7 @@ type SkFertigkeit struct {
|
|||||||
BamortCharTrait
|
BamortCharTrait
|
||||||
Beschreibung string `json:"beschreibung"`
|
Beschreibung string `json:"beschreibung"`
|
||||||
Fertigkeitswert int `json:"fertigkeitswert"`
|
Fertigkeitswert int `json:"fertigkeitswert"`
|
||||||
|
BasisWert int `json:"basiswert"`
|
||||||
Bonus int `json:"bonus,omitempty"`
|
Bonus int `json:"bonus,omitempty"`
|
||||||
Pp int `json:"pp,omitempty"` //Praxispunkte
|
Pp int `json:"pp,omitempty"` //Praxispunkte
|
||||||
Bemerkung string `json:"bemerkung"`
|
Bemerkung string `json:"bemerkung"`
|
||||||
|
|||||||
@@ -0,0 +1,61 @@
|
|||||||
|
package models
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestSkFertigkeitBasisWert(t *testing.T) {
|
||||||
|
// Test that BasisWert defaults to 0
|
||||||
|
skill := SkFertigkeit{
|
||||||
|
Fertigkeitswert: 10,
|
||||||
|
}
|
||||||
|
skill.Name = "TestSkill"
|
||||||
|
|
||||||
|
// BasisWert should default to 0
|
||||||
|
if skill.BasisWert != 0 {
|
||||||
|
t.Errorf("Expected BasisWert to default to 0, got %d", skill.BasisWert)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestSkFertigkeitBasisWertSet(t *testing.T) {
|
||||||
|
// Test that BasisWert can be set
|
||||||
|
skill := SkFertigkeit{
|
||||||
|
Fertigkeitswert: 10,
|
||||||
|
BasisWert: 5,
|
||||||
|
}
|
||||||
|
skill.Name = "TestSkill"
|
||||||
|
|
||||||
|
if skill.BasisWert != 5 {
|
||||||
|
t.Errorf("Expected BasisWert to be 5, got %d", skill.BasisWert)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestSkWaffenfertigkeitBasisWert(t *testing.T) {
|
||||||
|
// Test that BasisWert works for SkWaffenfertigkeit (inherited from SkFertigkeit)
|
||||||
|
weaponSkill := SkWaffenfertigkeit{
|
||||||
|
SkFertigkeit: SkFertigkeit{
|
||||||
|
Fertigkeitswert: 8,
|
||||||
|
BasisWert: 3,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
weaponSkill.Name = "TestWeaponSkill"
|
||||||
|
|
||||||
|
if weaponSkill.BasisWert != 3 {
|
||||||
|
t.Errorf("Expected BasisWert to be 3, got %d", weaponSkill.BasisWert)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestSkAngeboreneFertigkeitBasisWert(t *testing.T) {
|
||||||
|
// Test that BasisWert works for SkAngeboreneFertigkeit (inherited from SkFertigkeit)
|
||||||
|
innateSkill := SkAngeboreneFertigkeit{
|
||||||
|
SkFertigkeit: SkFertigkeit{
|
||||||
|
Fertigkeitswert: 12,
|
||||||
|
BasisWert: 7,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
innateSkill.Name = "TestInnateSkill"
|
||||||
|
|
||||||
|
if innateSkill.BasisWert != 7 {
|
||||||
|
t.Errorf("Expected BasisWert to be 7, got %d", innateSkill.BasisWert)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -7,6 +7,7 @@
|
|||||||
v-model="searchTerm"
|
v-model="searchTerm"
|
||||||
:placeholder="`${$t('search')} ${$t('Skill')}...`"
|
:placeholder="`${$t('search')} ${$t('Skill')}...`"
|
||||||
/>
|
/>
|
||||||
|
<button @click="startCreate" class="btn-primary">{{ $t('newSkill') }}</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -69,6 +70,7 @@
|
|||||||
</th>
|
</th>
|
||||||
<th class="cd-table-header">{{ $t('skill.difficulty') }}</th>
|
<th class="cd-table-header">{{ $t('skill.difficulty') }}</th>
|
||||||
<th class="cd-table-header">{{ $t('skill.initialwert') }}</th>
|
<th class="cd-table-header">{{ $t('skill.initialwert') }}</th>
|
||||||
|
<th class="cd-table-header">{{ $t('skill.basiswert') }}</th>
|
||||||
<th class="cd-table-header">{{ $t('skill.improvable') }}</th>
|
<th class="cd-table-header">{{ $t('skill.improvable') }}</th>
|
||||||
<th class="cd-table-header">{{ $t('skill.innateskill') }}</th>
|
<th class="cd-table-header">{{ $t('skill.innateskill') }}</th>
|
||||||
<th class="cd-table-header">{{ $t('skill.description') }}</th>
|
<th class="cd-table-header">{{ $t('skill.description') }}</th>
|
||||||
@@ -79,6 +81,117 @@
|
|||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody>
|
<tbody>
|
||||||
|
<!-- Create New Skill Row -->
|
||||||
|
<tr v-if="creatingNew">
|
||||||
|
<td>New</td>
|
||||||
|
<td colspan="11">
|
||||||
|
<!-- Create form -->
|
||||||
|
<div class="edit-form">
|
||||||
|
<div class="edit-row">
|
||||||
|
<div class="edit-field">
|
||||||
|
<label>{{ $t('skill.name') }}:</label>
|
||||||
|
<input v-model="editedItem.name" />
|
||||||
|
</div>
|
||||||
|
<div class="edit-field">
|
||||||
|
<label>{{ $t('skill.initialwert') }}:</label>
|
||||||
|
<input v-model.number="editedItem.initialwert" type="number" style="width:60px;" />
|
||||||
|
</div>
|
||||||
|
<div class="edit-field">
|
||||||
|
<label>{{ $t('skill.basiswert') }}:</label>
|
||||||
|
<input v-model.number="editedItem.basiswert" type="number" style="width:60px;" />
|
||||||
|
</div>
|
||||||
|
<div class="edit-field">
|
||||||
|
<label>{{ $t('skill.bonusskill') }}:</label>
|
||||||
|
<select v-model="editedItem.bonuseigenschaft" style="width:80px;">
|
||||||
|
<option value="">-</option>
|
||||||
|
<option value="St">St</option>
|
||||||
|
<option value="Gs">Gs</option>
|
||||||
|
<option value="Gw">Gw</option>
|
||||||
|
<option value="Ko">Ko</option>
|
||||||
|
<option value="In">In</option>
|
||||||
|
<option value="Zt">Zt</option>
|
||||||
|
<option value="Au">Au</option>
|
||||||
|
<option value="pA">pA</option>
|
||||||
|
<option value="Wk">Wk</option>
|
||||||
|
<option value="B">B</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="edit-row">
|
||||||
|
<div class="edit-field">
|
||||||
|
<label>{{ $t('skill.improvable') }}:</label>
|
||||||
|
<input type="checkbox" v-model="editedItem.improvable" />
|
||||||
|
</div>
|
||||||
|
<div class="edit-field">
|
||||||
|
<label>{{ $t('skill.innateskill') }}:</label>
|
||||||
|
<input type="checkbox" v-model="editedItem.innateskill" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="edit-row">
|
||||||
|
<div class="edit-field full-width">
|
||||||
|
<label>{{ $t('skill.description') }}:</label>
|
||||||
|
<input v-model="editedItem.beschreibung" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="edit-row">
|
||||||
|
<div class="edit-field">
|
||||||
|
<label>{{ $t('skill.quelle') }}:</label>
|
||||||
|
<select v-model="editedItem.sourceCode" style="width:100px;">
|
||||||
|
<option v-for="source in availableSources" :key="source.code" :value="source.code">
|
||||||
|
{{ source.code }}
|
||||||
|
</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
<div class="edit-field">
|
||||||
|
<label>{{ $t('skill.page') || 'Page' }}:</label>
|
||||||
|
<input v-model.number="editedItem.page_number" type="number" style="width:60px;" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="edit-row">
|
||||||
|
<div class="edit-field full-width">
|
||||||
|
<label>{{ $t('skill.categories') || 'Categories' }}:</label>
|
||||||
|
<div class="category-checkboxes">
|
||||||
|
<div v-for="category in mdata.categories" :key="category.id" class="category-checkbox">
|
||||||
|
<input
|
||||||
|
type="checkbox"
|
||||||
|
:value="category.id"
|
||||||
|
v-model="editedItem.selectedCategories"
|
||||||
|
@change="onCategoryToggle(category.id)"
|
||||||
|
/>
|
||||||
|
<label>{{ category.name }}</label>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="edit-row">
|
||||||
|
<div class="edit-field full-width">
|
||||||
|
<label>{{ $t('skill.difficulty') || 'Difficulty' }}:</label>
|
||||||
|
<div class="difficulty-selects">
|
||||||
|
<div v-for="catId in editedItem.selectedCategories" :key="catId" class="difficulty-select">
|
||||||
|
<span>{{ getCategoryName(catId) }}:</span>
|
||||||
|
<select v-model="editedItem.categoryDifficulties[catId]" style="width:120px;">
|
||||||
|
<option v-for="diff in mdata.difficulties" :key="diff.id" :value="diff.id">
|
||||||
|
{{ diff.name }}
|
||||||
|
</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="edit-actions">
|
||||||
|
<button @click="saveCreate" class="btn-save">{{ $t('createSkill') }}</button>
|
||||||
|
<button @click="cancelCreate" class="btn-cancel">Cancel</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
|
||||||
<template v-for="(dtaItem, index) in filteredAndSortedSkills" :key="dtaItem.id">
|
<template v-for="(dtaItem, index) in filteredAndSortedSkills" :key="dtaItem.id">
|
||||||
<!-- Display Mode -->
|
<!-- Display Mode -->
|
||||||
<tr v-if="editingIndex !== index">
|
<tr v-if="editingIndex !== index">
|
||||||
@@ -87,6 +200,7 @@
|
|||||||
<td>{{ dtaItem.name || '-' }}</td>
|
<td>{{ dtaItem.name || '-' }}</td>
|
||||||
<td>{{ formatDifficulties(dtaItem.difficulties) }}</td>
|
<td>{{ formatDifficulties(dtaItem.difficulties) }}</td>
|
||||||
<td>{{ dtaItem.initialwert || '0' }}</td>
|
<td>{{ dtaItem.initialwert || '0' }}</td>
|
||||||
|
<td>{{ dtaItem.basiswert || '0' }}</td>
|
||||||
<td><input type="checkbox" :checked="dtaItem.improvable" disabled /></td>
|
<td><input type="checkbox" :checked="dtaItem.improvable" disabled /></td>
|
||||||
<td><input type="checkbox" :checked="dtaItem.innateskill" disabled /></td>
|
<td><input type="checkbox" :checked="dtaItem.innateskill" disabled /></td>
|
||||||
<td>{{ dtaItem.beschreibung || '-' }}</td>
|
<td>{{ dtaItem.beschreibung || '-' }}</td>
|
||||||
@@ -113,6 +227,10 @@
|
|||||||
<label>{{ $t('skill.initialwert') }}:</label>
|
<label>{{ $t('skill.initialwert') }}:</label>
|
||||||
<input v-model.number="editedItem.initialwert" type="number" style="width:60px;" />
|
<input v-model.number="editedItem.initialwert" type="number" style="width:60px;" />
|
||||||
</div>
|
</div>
|
||||||
|
<div class="edit-field">
|
||||||
|
<label>{{ $t('skill.basiswert') }}:</label>
|
||||||
|
<input v-model.number="editedItem.basiswert" type="number" style="width:60px;" />
|
||||||
|
</div>
|
||||||
<div class="edit-field">
|
<div class="edit-field">
|
||||||
<label>{{ $t('skill.bonusskill') }}:</label>
|
<label>{{ $t('skill.bonusskill') }}:</label>
|
||||||
<select v-model="editedItem.bonuseigenschaft" style="width:80px;">
|
<select v-model="editedItem.bonuseigenschaft" style="width:80px;">
|
||||||
@@ -183,7 +301,7 @@
|
|||||||
|
|
||||||
<div class="edit-row">
|
<div class="edit-row">
|
||||||
<div class="edit-field full-width">
|
<div class="edit-field full-width">
|
||||||
<label>{{ $t('skill.difficulties') || 'Difficulties' }}:</label>
|
<label>{{ $t('skill.difficulty') || 'Difficulty' }}:</label>
|
||||||
<div class="difficulty-selects">
|
<div class="difficulty-selects">
|
||||||
<div v-for="catId in editedItem.selectedCategories" :key="catId" class="difficulty-select">
|
<div v-for="catId in editedItem.selectedCategories" :key="catId" class="difficulty-select">
|
||||||
<span>{{ getCategoryName(catId) }}:</span>
|
<span>{{ getCategoryName(catId) }}:</span>
|
||||||
@@ -240,6 +358,7 @@ export default {
|
|||||||
sortAsc: true,
|
sortAsc: true,
|
||||||
editingIndex: -1,
|
editingIndex: -1,
|
||||||
editedItem: null,
|
editedItem: null,
|
||||||
|
creatingNew: false,
|
||||||
filterCategory: '',
|
filterCategory: '',
|
||||||
filterDifficulty: '',
|
filterDifficulty: '',
|
||||||
filterImprovable: '',
|
filterImprovable: '',
|
||||||
@@ -440,6 +559,7 @@ export default {
|
|||||||
beschreibung: this.editedItem.beschreibung,
|
beschreibung: this.editedItem.beschreibung,
|
||||||
game_system: this.editedItem.game_system || 'midgard',
|
game_system: this.editedItem.game_system || 'midgard',
|
||||||
initialwert: this.editedItem.initialwert,
|
initialwert: this.editedItem.initialwert,
|
||||||
|
basiswert: this.editedItem.basiswert || 0,
|
||||||
bonuseigenschaft: this.editedItem.bonuseigenschaft,
|
bonuseigenschaft: this.editedItem.bonuseigenschaft,
|
||||||
improvable: this.editedItem.improvable,
|
improvable: this.editedItem.improvable,
|
||||||
innateskill: this.editedItem.innateskill,
|
innateskill: this.editedItem.innateskill,
|
||||||
@@ -485,6 +605,76 @@ export default {
|
|||||||
this.filterImprovable = ''
|
this.filterImprovable = ''
|
||||||
this.filterInnateskill = ''
|
this.filterInnateskill = ''
|
||||||
this.filterBonuseigenschaft = ''
|
this.filterBonuseigenschaft = ''
|
||||||
|
},
|
||||||
|
startCreate() {
|
||||||
|
// Initialize new skill object with defaults
|
||||||
|
this.editedItem = {
|
||||||
|
name: '',
|
||||||
|
beschreibung: '',
|
||||||
|
game_system: 'midgard',
|
||||||
|
initialwert: 5,
|
||||||
|
basiswert: 0,
|
||||||
|
bonuseigenschaft: '',
|
||||||
|
improvable: true,
|
||||||
|
innateskill: false,
|
||||||
|
sourceCode: this.availableSources.length > 0 ? this.availableSources[0].code : '',
|
||||||
|
page_number: 0,
|
||||||
|
selectedCategories: [],
|
||||||
|
categoryDifficulties: {}
|
||||||
|
}
|
||||||
|
this.creatingNew = true
|
||||||
|
},
|
||||||
|
async saveCreate() {
|
||||||
|
try {
|
||||||
|
// Validate required fields
|
||||||
|
if (!this.editedItem.name) {
|
||||||
|
alert('Skill name is required')
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Find source ID from code
|
||||||
|
const source = this.availableSources.find(s => s.code === this.editedItem.sourceCode)
|
||||||
|
|
||||||
|
// Build category_difficulties array
|
||||||
|
const categoryDifficulties = this.editedItem.selectedCategories.map(catId => ({
|
||||||
|
category_id: catId,
|
||||||
|
difficulty_id: this.editedItem.categoryDifficulties[catId]
|
||||||
|
}))
|
||||||
|
|
||||||
|
const createData = {
|
||||||
|
name: this.editedItem.name,
|
||||||
|
beschreibung: this.editedItem.beschreibung,
|
||||||
|
game_system: this.editedItem.game_system || 'midgard',
|
||||||
|
initialwert: this.editedItem.initialwert,
|
||||||
|
basiswert: this.editedItem.basiswert || 0,
|
||||||
|
bonuseigenschaft: this.editedItem.bonuseigenschaft,
|
||||||
|
improvable: this.editedItem.improvable,
|
||||||
|
innateskill: this.editedItem.innateskill,
|
||||||
|
source_id: source ? source.id : null,
|
||||||
|
page_number: this.editedItem.page_number || 0,
|
||||||
|
category_difficulties: categoryDifficulties
|
||||||
|
}
|
||||||
|
|
||||||
|
const response = await API.post(
|
||||||
|
'/api/maintenance/skills-enhanced',
|
||||||
|
createData
|
||||||
|
)
|
||||||
|
|
||||||
|
// Add the new skill to the list
|
||||||
|
this.enhancedSkills.push(response.data)
|
||||||
|
|
||||||
|
// Hide the create dialog
|
||||||
|
this.creatingNew = false
|
||||||
|
this.editedItem = null
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Failed to create skill:', error)
|
||||||
|
alert('Failed to create skill: ' + (error.response?.data?.error || error.message))
|
||||||
|
// Don't close dialog on error so user can fix the issue
|
||||||
|
}
|
||||||
|
},
|
||||||
|
cancelCreate() {
|
||||||
|
this.creatingNew = false
|
||||||
|
this.editedItem = null
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -107,7 +107,10 @@ export default {
|
|||||||
improvable:'verbesserbar',
|
improvable:'verbesserbar',
|
||||||
category:'Kategorie',
|
category:'Kategorie',
|
||||||
initialwert:'Startwert',
|
initialwert:'Startwert',
|
||||||
|
basiswert:'Basiswert (ungelernt)',
|
||||||
difficulty:'Schwierigkeit',
|
difficulty:'Schwierigkeit',
|
||||||
|
page:'Seite',
|
||||||
|
categories:'Kategorien',
|
||||||
},
|
},
|
||||||
spell:{
|
spell:{
|
||||||
id:'ID',
|
id:'ID',
|
||||||
@@ -232,6 +235,8 @@ export default {
|
|||||||
},
|
},
|
||||||
search:'Suche',
|
search:'Suche',
|
||||||
Skill:'Fertigkeit',
|
Skill:'Fertigkeit',
|
||||||
|
newSkill:'Neue Fertigkeit',
|
||||||
|
createSkill:'Fertigkeit erstellen',
|
||||||
common: {
|
common: {
|
||||||
loading: 'Laden...',
|
loading: 'Laden...',
|
||||||
cancel: 'Abbrechen',
|
cancel: 'Abbrechen',
|
||||||
|
|||||||
@@ -104,6 +104,9 @@ export default {
|
|||||||
improvable:'improvable',
|
improvable:'improvable',
|
||||||
category:'Category',
|
category:'Category',
|
||||||
initialwert:'Initial value',
|
initialwert:'Initial value',
|
||||||
|
basiswert:'Base value (untrained)',
|
||||||
|
page:'Page',
|
||||||
|
categories:'Categories',
|
||||||
},
|
},
|
||||||
spell:{
|
spell:{
|
||||||
id:'ID',
|
id:'ID',
|
||||||
@@ -124,7 +127,7 @@ export default {
|
|||||||
quelle:'Source',
|
quelle:'Source',
|
||||||
import: 'Import',
|
import: 'Import',
|
||||||
selectCsv: 'select CSV',
|
selectCsv: 'select CSV',
|
||||||
system: 'System'
|
system: 'System',
|
||||||
},
|
},
|
||||||
spells: {
|
spells: {
|
||||||
learn: {
|
learn: {
|
||||||
@@ -228,6 +231,8 @@ export default {
|
|||||||
},
|
},
|
||||||
search:'Suche',
|
search:'Suche',
|
||||||
Skill:'Fertigkeit',
|
Skill:'Fertigkeit',
|
||||||
|
newSkill:'New Skill',
|
||||||
|
createSkill:'Create Skill',
|
||||||
common: {
|
common: {
|
||||||
loading: 'Laden...',
|
loading: 'Laden...',
|
||||||
cancel: 'Abbrechen',
|
cancel: 'Abbrechen',
|
||||||
|
|||||||
@@ -15,7 +15,7 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="action-links">
|
<div class="action-links">
|
||||||
<router-link to="/login" class="btn btn-primary">
|
<router-link to="/login" class="btn btn-primary" :class="{ disabled: !isBackendAvailable }" :event="isBackendAvailable ? 'click' : ''">
|
||||||
{{ $t('landing.login') }}
|
{{ $t('landing.login') }}
|
||||||
</router-link>
|
</router-link>
|
||||||
<a :href="githubUrl" target="_blank" rel="noopener noreferrer" class="btn btn-secondary">
|
<a :href="githubUrl" target="_blank" rel="noopener noreferrer" class="btn btn-secondary">
|
||||||
@@ -43,12 +43,28 @@ export default {
|
|||||||
frontendCommit: getGitCommit(),
|
frontendCommit: getGitCommit(),
|
||||||
backendVersion: "Loading...",
|
backendVersion: "Loading...",
|
||||||
backendCommit: "Loading...",
|
backendCommit: "Loading...",
|
||||||
githubUrl: "https://github.com/Bardioc26/bamort"
|
githubUrl: "https://github.com/Bardioc26/bamort",
|
||||||
|
retryCount: 0,
|
||||||
|
maxRetries: 24,
|
||||||
|
retryInterval: null
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
mounted() {
|
mounted() {
|
||||||
this.fetchBackendVersion()
|
this.fetchBackendVersion()
|
||||||
},
|
},
|
||||||
|
beforeUnmount() {
|
||||||
|
if (this.retryInterval) {
|
||||||
|
clearInterval(this.retryInterval)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
isBackendAvailable() {
|
||||||
|
return this.backendVersion !== "Loading..." &&
|
||||||
|
this.backendVersion !== "Unavailable" &&
|
||||||
|
this.backendVersion !== "Unreachable" &&
|
||||||
|
this.backendVersion !== "Unknown"
|
||||||
|
}
|
||||||
|
},
|
||||||
methods: {
|
methods: {
|
||||||
async fetchBackendVersion() {
|
async fetchBackendVersion() {
|
||||||
try {
|
try {
|
||||||
@@ -58,11 +74,29 @@ export default {
|
|||||||
if (response.data) {
|
if (response.data) {
|
||||||
this.backendVersion = response.data.version || "Unknown"
|
this.backendVersion = response.data.version || "Unknown"
|
||||||
this.backendCommit = response.data.gitCommit || "Unknown"
|
this.backendCommit = response.data.gitCommit || "Unknown"
|
||||||
|
if (this.retryInterval) {
|
||||||
|
clearInterval(this.retryInterval)
|
||||||
|
this.retryInterval = null
|
||||||
|
}
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.warn("Could not fetch backend version:", error)
|
console.warn("Could not fetch backend version:", error)
|
||||||
this.backendVersion = "Unavailable"
|
this.backendVersion = "Unavailable"
|
||||||
this.backendCommit = "N/A"
|
this.backendCommit = "N/A"
|
||||||
|
|
||||||
|
if (this.retryCount < this.maxRetries && !this.retryInterval) {
|
||||||
|
this.retryInterval = setInterval(() => {
|
||||||
|
this.retryCount++
|
||||||
|
if (this.retryCount >= this.maxRetries) {
|
||||||
|
clearInterval(this.retryInterval)
|
||||||
|
this.retryInterval = null
|
||||||
|
console.warn("Max retries reached for backend version")
|
||||||
|
this.backendVersion = "Unreachable"
|
||||||
|
return
|
||||||
|
}
|
||||||
|
this.fetchBackendVersion()
|
||||||
|
}, 5000)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -69,8 +69,12 @@ echo "1. Review changes: git diff"
|
|||||||
if [ "$BACKEND_VERSION" = "$FRONTEND_VERSION" ]; then
|
if [ "$BACKEND_VERSION" = "$FRONTEND_VERSION" ]; then
|
||||||
echo "2. Commit changes: git commit -am 'Bump version to $BACKEND_VERSION'"
|
echo "2. Commit changes: git commit -am 'Bump version to $BACKEND_VERSION'"
|
||||||
echo "3. Tag release: git tag v$BACKEND_VERSION"
|
echo "3. Tag release: git tag v$BACKEND_VERSION"
|
||||||
|
git tag v$BACKEND_VERSION
|
||||||
else
|
else
|
||||||
echo "2. Commit changes: git commit -am 'Bump backend to $BACKEND_VERSION, frontend to $FRONTEND_VERSION'"
|
echo "2. Commit changes: git commit -am 'Bump backend to $BACKEND_VERSION, frontend to $FRONTEND_VERSION'"
|
||||||
echo "3. Tag releases: git tag backend-v$BACKEND_VERSION && git tag frontend-v$FRONTEND_VERSION"
|
echo "3. Tag releases: git tag backend-v$BACKEND_VERSION && git tag frontend-v$FRONTEND_VERSION"
|
||||||
|
git tag backend-v$BACKEND_VERSION
|
||||||
|
git tag frontend-v$FRONTEND_VERSION
|
||||||
|
git tag v$BACKEND_VERSION -m "Backend version $BACKEND_VERSION, Frontend version $FRONTEND_VERSION"
|
||||||
fi
|
fi
|
||||||
echo "4. Push: git push && git push --tags"
|
echo "4. Push: git push && git push --tags"
|
||||||
|
|||||||
Reference in New Issue
Block a user