Learncost frontend (#42)

* introduced central package  registry by package init function
* dynamic registration of routes, model, migrations and initializers.
* setting a docker compose project name to prevent shutdown of other containers with the same (composer)name
* ai documentation
* app template
* Create tests for ALL API entpoints in ALL packages Based on current data. Ensure that all API endpoints used in frontend are tested. These tests are crucial for the next refactoring tasks.
* adopting agent instructions for a more consistent coding style
* added desired module layout and debugging information
* Fix All Failing tests All failing tests are fixed now that makes the refactoring more easy since all tests must pass
* restored routes for maintenance
* added common translations
* added new tests for API Endpoint
* Merge branch 'separate_business_logic'
* added lern and skill improvement cost editing
* Set Docker image tag when building to prevent rebuild when nothing has changed
* add and remove PP for Weaponskill fixed
* add and remove PP for same named skills fixed
* add new task
This commit is contained in:
Bardioc26
2026-05-01 18:15:31 +02:00
committed by GitHub
parent 261a6294cb
commit 042a1d4773
293 changed files with 17411 additions and 1540 deletions
+309
View File
@@ -0,0 +1,309 @@
package importer
import (
"bamort/bmrt/models"
"encoding/csv"
"fmt"
"os"
"strconv"
"strings"
)
// sourceIDCache stores cached source ID lookups to avoid repeated database queries
var sourceIDCache = make(map[string]uint)
// ClearSourceCache clears the cached source ID lookups
// Useful for testing or when working with different databases
func ClearSourceCache() {
sourceIDCache = make(map[string]uint)
}
// lookupSourceID retrieves the source ID from the database based on the source code
// Uses caching to avoid repeated database calls
// If the source doesn't exist, it creates a new one automatically
func lookupSourceID(sourceCode string) (uint, error) {
if sourceCode == "" {
return 0, fmt.Errorf("source code is empty")
}
// Check cache first
if sourceID, cached := sourceIDCache[sourceCode]; cached {
return sourceID, nil
}
// Not in cache, look up from database
var source models.Source
if err := source.FirstByCode(sourceCode); err != nil {
// Source not found, create it automatically
newSource := models.Source{
Code: sourceCode,
Name: sourceCode, // Use code as name initially
GameSystemId: models.GetGameSystem(0, "").ID, // Default game system
IsActive: true, // Set as active by default
}
if createErr := newSource.Create(); createErr != nil {
return 0, fmt.Errorf("source with code '%s' not found and could not be created: %w", sourceCode, createErr)
}
// Cache the newly created source
sourceIDCache[sourceCode] = newSource.ID
return newSource.ID, nil
}
// Cache the result for future use
sourceIDCache[sourceCode] = source.ID
return source.ID, nil
}
func ImportChar(char CharacterImport) (*models.Char, error) {
return nil, fmt.Errorf("char could not be imported %s", "Weil Wegen Kommt noch")
}
func CheckSkill(fertigkeit *Fertigkeit, autocreate bool) (*models.Skill, error) {
stammF := models.Skill{}
//err := database.DB.First(&stammF, "system=? AND name = ?", gameSystem, fertigkeit.Name).Error
gs := models.GetGameSystem(0, "midgard")
err := stammF.FirstwGS(fertigkeit.Name, gs.ID)
if err == nil {
// Fertigkeit found
return &stammF, nil
}
if !autocreate {
return nil, fmt.Errorf("does not exist in Fertigkeit importer")
}
stammF.GameSystemId = models.GetGameSystem(0, "midgard").ID
stammF.Name = fertigkeit.Name
if stammF.Name != "Sprache" {
stammF.Beschreibung = fertigkeit.Beschreibung
}
if fertigkeit.Fertigkeitswert < 12 {
stammF.Initialwert = 5
} else {
stammF.Initialwert = 12
}
stammF.Bonuseigenschaft = "keine"
stammF.Quelle = fertigkeit.Quelle
//fmt.Println(stammF)
err = stammF.Create()
if err != nil {
// Fertigkeit found
return nil, err
}
//err = database.DB.First(&stammF, "system=? AND name = ?", gameSystem, fertigkeit.Name).Error
err = stammF.FirstwGS(fertigkeit.Name, gs.ID)
if err != nil {
// Fertigkeit found
return nil, err
}
return &stammF, nil
}
func CheckSpell(zauber *Zauber, autocreate bool) (*models.Spell, error) {
stammF := models.Spell{}
//err := database.DB.First(&stammF, "system=? AND name = ?", gameSystem, zauber.Name).Error
gs := models.GetGameSystem(0, "midgard")
err := stammF.FirstwGS(zauber.Name, gs.ID)
if err == nil {
// zauber found
return &stammF, nil
}
if !autocreate {
return nil, fmt.Errorf("does not exist in zauber importer")
}
stammF.GameSystemId = models.GetGameSystem(0, "midgard").ID
stammF.Name = zauber.Name
stammF.Beschreibung = zauber.Beschreibung
stammF.AP = "1"
stammF.Stufe = 1
stammF.Wirkungsziel = "Zauberer"
stammF.Reichweite = "15 m"
stammF.Quelle = zauber.Quelle
//fmt.Println(stammF)
err = stammF.Create()
if err != nil {
// spell found
return nil, err
}
//err = database.DB.First(&stammF, "system=? AND name = ?", gameSystem, zauber.Name).Error
err = stammF.FirstwGS(zauber.Name, gs.ID)
if err != nil {
// spell found
return nil, err
}
return &stammF, nil
}
func ImportCsv2Spell(filepath string) error {
// Open the CSV file
file, err := os.Open(filepath)
if err != nil {
return fmt.Errorf("error opening file %s: %w", filepath, err)
}
defer file.Close()
// Create CSV reader
reader := csv.NewReader(file)
// Read all records
records, err := reader.ReadAll()
if err != nil {
return fmt.Errorf("error reading CSV file: %w", err)
}
if len(records) == 0 {
return fmt.Errorf("CSV file is empty")
}
// Get headers from first row
headers := records[0]
// Create a map for field name mapping to handle case variations
fieldMap := make(map[string]int)
for i, header := range headers {
// Normalize header names for mapping
normalizedHeader := strings.ToLower(strings.TrimSpace(header))
fieldMap[normalizedHeader] = i
}
// Process each record (skip header row)
for i, record := range records[1:] {
if len(record) == 0 {
continue // Skip empty rows
}
// Create spell struct
spell := models.Spell{
GameSystemId: models.GetGameSystem(0, "midgard").ID, // Default value
}
// Map CSV fields to struct fields
if idx, exists := fieldMap["name"]; exists && idx < len(record) {
spell.Name = strings.TrimSpace(record[idx])
}
if idx, exists := fieldMap["beschreibung"]; exists && idx < len(record) {
spell.Beschreibung = strings.TrimSpace(record[idx])
}
if idx, exists := fieldMap["quelle"]; exists && idx < len(record) {
quelleCode := strings.ToUpper(strings.TrimSpace(record[idx]))
spell.Quelle = quelleCode
// Look up source_id from database based on quelle code using cached lookup
if quelleCode != "" {
if sourceID, err := lookupSourceID(quelleCode); err == nil {
spell.SourceID = sourceID
}
// If source lookup fails, keep the existing source_id from CSV if present
}
}
if idx, exists := fieldMap["game_system"]; exists && idx < len(record) && strings.TrimSpace(record[idx]) != "" {
//spell.GameSystem = strings.ToLower(strings.TrimSpace(record[idx]))
spell.GameSystemId = models.GetGameSystem(0, strings.ToLower(strings.TrimSpace(record[idx]))).ID
}
/*
if idx, exists := fieldMap["source_id"]; exists && idx < len(record) {
if sourceID, err := strconv.Atoi(strings.TrimSpace(record[idx])); err == nil {
spell.SourceID = uint(sourceID)
}
}
*/
if idx, exists := fieldMap["page_number"]; exists && idx < len(record) {
if pageNum, err := strconv.Atoi(strings.TrimSpace(record[idx])); err == nil {
spell.PageNumber = pageNum
}
}
if idx, exists := fieldMap["bonus"]; exists && idx < len(record) {
if bonus, err := strconv.Atoi(strings.TrimSpace(record[idx])); err == nil {
spell.Bonus = bonus
}
}
if idx, exists := fieldMap["stufe"]; exists && idx < len(record) {
if level, err := strconv.Atoi(strings.TrimSpace(record[idx])); err == nil {
spell.Stufe = level
}
}
if idx, exists := fieldMap["ap"]; exists && idx < len(record) {
spell.AP = strings.TrimSpace(record[idx])
}
if idx, exists := fieldMap["art"]; exists && idx < len(record) {
spell.Art = strings.TrimSpace(record[idx])
}
if idx, exists := fieldMap["zauberdauer"]; exists && idx < len(record) {
spell.Zauberdauer = strings.TrimSpace(record[idx])
}
if idx, exists := fieldMap["reichweite"]; exists && idx < len(record) {
spell.Reichweite = strings.TrimSpace(record[idx])
}
if idx, exists := fieldMap["wirkungsziel"]; exists && idx < len(record) {
spell.Wirkungsziel = strings.TrimSpace(record[idx])
}
if idx, exists := fieldMap["wirkungsbereich"]; exists && idx < len(record) {
spell.Wirkungsbereich = strings.TrimSpace(record[idx])
}
if idx, exists := fieldMap["wirkungsdauer"]; exists && idx < len(record) {
spell.Wirkungsdauer = strings.TrimSpace(record[idx])
}
if idx, exists := fieldMap["ursprung"]; exists && idx < len(record) {
spell.Ursprung = strings.TrimSpace(record[idx])
}
if idx, exists := fieldMap["category"]; exists && idx < len(record) {
spell.Category = strings.TrimSpace(record[idx])
}
if idx, exists := fieldMap["learning_category"]; exists && idx < len(record) {
spell.LearningCategory = strings.TrimSpace(record[idx])
}
// Skip if name is empty
if spell.Name == "" {
continue
}
// Try to find existing spell by name
existingSpell := models.Spell{}
err := existingSpell.FirstwGS(spell.Name, spell.GameSystemId)
if err == nil {
// Spell exists, update it
existingSpell.Beschreibung = spell.Beschreibung
existingSpell.Quelle = spell.Quelle
existingSpell.GameSystem = spell.GameSystem
// Update SourceID if we found one from quelle lookup
if spell.SourceID != 0 {
existingSpell.SourceID = spell.SourceID
}
if spell.PageNumber != 0 {
existingSpell.PageNumber = spell.PageNumber
}
existingSpell.Bonus = spell.Bonus
existingSpell.Stufe = spell.Stufe
existingSpell.AP = spell.AP
existingSpell.Art = spell.Art
existingSpell.Zauberdauer = spell.Zauberdauer
existingSpell.Reichweite = spell.Reichweite
existingSpell.Wirkungsziel = spell.Wirkungsziel
existingSpell.Wirkungsbereich = spell.Wirkungsbereich
existingSpell.Wirkungsdauer = spell.Wirkungsdauer
existingSpell.Ursprung = spell.Ursprung
existingSpell.Category = spell.Category
existingSpell.LearningCategory = spell.LearningCategory
err = existingSpell.Save()
if err != nil {
return fmt.Errorf("error updating spell '%s' at row %d: %w", spell.Name, i+2, err)
}
} else {
// Spell doesn't exist, create new one
err = spell.Create()
if err != nil {
return fmt.Errorf("error creating spell '%s' at row %d: %w", spell.Name, i+2, err)
}
}
}
return nil
}