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:
@@ -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
|
||||
}
|
||||
Reference in New Issue
Block a user