logging, environment und Datenbank connection

This commit is contained in:
2025-08-11 07:46:53 +02:00
parent 8874084a07
commit 4df7376e33
9 changed files with 315 additions and 71 deletions
+3 -10
View File
@@ -20,8 +20,8 @@ import (
// @BasePath /
// @schemes http
func main() {
// Konfiguration laden
cfg := config.LoadConfig()
// Verwende die globale Konfigurationsvariable (bereits in config.init() geladen)
cfg := config.Cfg
// Logger konfigurieren
logger.SetDebugMode(cfg.DebugMode)
@@ -38,6 +38,7 @@ func main() {
logger.Info("Bamort Server wird gestartet...")
logger.Debug("Debug-Modus ist aktiviert")
logger.Info("Environment: %s", cfg.Environment)
logger.Info("testingDB Set: %s", cfg.DevTesting)
logger.Info("Server Port: %s", cfg.ServerPort)
// Gin-Modus basierend auf Environment setzen
@@ -54,14 +55,6 @@ func main() {
database.ConnectDatabase()
logger.Info("Datenbankverbindung erfolgreich")
// Migrate Audit-Log table
logger.Debug("Führe Audit-Log Migration durch...")
if err := character.MigrateAuditLog(); err != nil {
logger.Error("Fehler bei Audit-Log Migration: %s", err.Error())
panic("Failed to migrate audit log table: " + err.Error())
}
logger.Debug("Audit-Log Migration erfolgreich")
r := gin.Default()
router.SetupGin(r)
+47 -5
View File
@@ -2,6 +2,7 @@ package config
import (
"bufio"
"fmt"
"os"
"strconv"
"strings"
@@ -23,7 +24,16 @@ type Config struct {
// Environment
Environment string
Testing string // "yes" or "no", used to determine if we are in a test environment
DevTesting string // "yes" or "no", used to determine if we are in a test environment
}
// Cfg ist die globale Konfigurationsvariable
// Sie wird beim Programmstart automatisch geladen
var Cfg *Config
// init lädt die Konfiguration einmal beim Programmstart
func init() {
Cfg = LoadConfig()
}
// defaultConfig gibt die Standard-Konfiguration zurück
@@ -35,7 +45,7 @@ func defaultConfig() *Config {
DebugMode: false,
LogLevel: "INFO",
Environment: "production",
Testing: "no", // Default to "no", can be overridden in tests
DevTesting: "no", // Default to "no", can be overridden in tests
}
}
@@ -46,6 +56,11 @@ func LoadConfig() *Config {
config := defaultConfig()
// Debug: Zeige geladene Umgebungsvariablen
fmt.Printf("DEBUG LoadConfig - ENVIRONMENT aus ENV: '%s'\n", os.Getenv("ENVIRONMENT"))
fmt.Printf("DEBUG LoadConfig - TESTING aus ENV: '%s'\n", os.Getenv("DEVTESTING"))
fmt.Printf("DEBUG LoadConfig - DATABASE_TYPE aus ENV: '%s'\n", os.Getenv("DATABASE_TYPE"))
// Server Port
if port := os.Getenv("PORT"); port != "" {
config.ServerPort = port
@@ -88,12 +103,17 @@ func LoadConfig() *Config {
}
}
// Testing in Development
if testing := os.Getenv("TESTING"); testing != "" {
config.Testing = strings.ToLower(testing)
if testing := os.Getenv("DEVTESTING"); testing != "" {
config.DevTesting = strings.ToLower(testing)
fmt.Printf("DEBUG LoadConfig - DEVTESTING gefunden: '%s' -> DevTesting: '%s'\n", testing, config.DevTesting)
} else {
config.Testing = "no" // Default to "no"
config.DevTesting = "no" // Default to "no"
fmt.Printf("DEBUG LoadConfig - DEVTESTING nicht gefunden, setze DevTesting auf 'no'\n")
}
fmt.Printf("DEBUG LoadConfig - Finale Config: Environment='%s', DevTesting='%s', DatabaseType='%s'\n",
config.Environment, config.DevTesting, config.DatabaseType)
return config
}
@@ -103,21 +123,28 @@ func loadEnvFile() {
for _, envFile := range envFiles {
if _, err := os.Stat(envFile); err == nil {
fmt.Printf("DEBUG loadEnvFile - Lade .env-Datei: %s\n", envFile)
loadEnvFileContent(envFile)
} else {
fmt.Printf("DEBUG loadEnvFile - .env-Datei nicht gefunden: %s\n", envFile)
}
}
}
// loadEnvFileContent lädt den Inhalt einer .env-Datei
func loadEnvFileContent(filename string) {
fmt.Printf("DEBUG loadEnvFileContent - Öffne Datei: %s\n", filename)
file, err := os.Open(filename)
if err != nil {
fmt.Printf("DEBUG loadEnvFileContent - Fehler beim Öffnen von %s: %v\n", filename, err)
return
}
defer file.Close()
scanner := bufio.NewScanner(file)
lineNum := 0
for scanner.Scan() {
lineNum++
line := strings.TrimSpace(scanner.Text())
// Überspringe leere Zeilen und Kommentare
@@ -134,14 +161,29 @@ func loadEnvFileContent(filename string) {
key := strings.TrimSpace(parts[0])
value := strings.TrimSpace(parts[1])
// Behandle Kommentare am Ende der Zeile (nur wenn nicht in Anführungszeichen)
if !strings.HasPrefix(value, `"`) && !strings.HasPrefix(value, `'`) {
// Suche nach Kommentar am Ende der Zeile
if commentPos := strings.Index(value, "#"); commentPos > 0 {
// Entferne Kommentar und Leerzeichen davor
value = strings.TrimSpace(value[:commentPos])
}
}
// Entferne Anführungszeichen falls vorhanden
value = strings.Trim(value, `"'`)
fmt.Printf("DEBUG loadEnvFileContent - Zeile %d: %s='%s' (nach Kommentar-Behandlung)\n", lineNum, key, value)
// Setze die Umgebungsvariable nur, wenn sie noch nicht gesetzt ist
if os.Getenv(key) == "" {
os.Setenv(key, value)
fmt.Printf("DEBUG loadEnvFileContent - Setze ENV %s='%s'\n", key, value)
} else {
fmt.Printf("DEBUG loadEnvFileContent - ENV %s bereits gesetzt, überspringe\n", key)
}
}
fmt.Printf("DEBUG loadEnvFileContent - Datei %s vollständig verarbeitet (%d Zeilen)\n", filename, lineNum)
}
// IsDevelopment prüft, ob die Anwendung im Development-Modus läuft
+12 -5
View File
@@ -40,11 +40,18 @@ var (
)
func ConnectDatabase() *gorm.DB {
// Konfiguration laden um zu entscheiden, welche Datenbank verwendet werden soll
cfg := config.LoadConfig()
// Verwende die globale Konfigurationsvariable
cfg := config.Cfg
logger.Debug("Datenbankverbindung - Environment: '%s', DevTesting: '%s'", cfg.Environment, cfg.DevTesting)
// Debug: Explizite Bedingungsauswertung
envIsTest := cfg.Environment == "test"
devTestingIsYes := cfg.DevTesting == "yes"
logger.Debug("Bedingungsauswertung - Environment == 'test': %v, DevTesting == 'yes': %v", envIsTest, devTestingIsYes)
// In Test-Umgebung verwende Test-DB, sonst die konfigurierte Datenbank
if cfg.Environment == "test" || cfg.Testing == "yes" {
if envIsTest || devTestingIsYes {
logger.Debug("Test-Umgebung erkannt, verwende Test-Datenbank")
SetupTestDB()
} else {
@@ -55,8 +62,8 @@ func ConnectDatabase() *gorm.DB {
return DB
}
func ConnectDatabaseOrig() *gorm.DB {
// Konfiguration laden
cfg := config.LoadConfig()
// Verwende die globale Konfigurationsvariable
cfg := config.Cfg
logger.Debug("Datenbank-Konfiguration geladen: Type=%s, URL=%s", cfg.DatabaseType, cfg.DatabaseURL)
+62 -3
View File
@@ -1,6 +1,7 @@
package database
import (
"bamort/logger"
"io"
"os"
"path/filepath"
@@ -14,19 +15,29 @@ var testdbTempDir string
// copyFile copies a file from src to dst
func copyFile(src, dst string) error {
logger.Debug("copyFile: Kopiere Datei von %s nach %s", src, dst)
sourceFile, err := os.Open(src)
if err != nil {
logger.Error("copyFile: Fehler beim Öffnen der Quelldatei %s: %s", src, err.Error())
return err
}
defer sourceFile.Close()
destFile, err := os.Create(dst)
if err != nil {
logger.Error("copyFile: Fehler beim Erstellen der Zieldatei %s: %s", dst, err.Error())
return err
}
defer destFile.Close()
_, err = io.Copy(destFile, sourceFile)
copied, err := io.Copy(destFile, sourceFile)
if err != nil {
logger.Error("copyFile: Fehler beim Kopieren der Datei: %s", err.Error())
return err
}
logger.Debug("copyFile: Erfolgreich %d Bytes kopiert von %s nach %s", copied, src, dst)
return err
}
@@ -38,47 +49,95 @@ func copyFile(src, dst string) error {
// Parameters:
// - opts[0]: isTestDb (bool) - whether to use precopied SQLite (true) or persistent (Live) MariaDB (false)
func SetupTestDB(opts ...bool) {
logger.Debug("SetupTestDB aufgerufen")
isTestDb = true
if len(opts) > 0 {
isTestDb = opts[0]
logger.Debug("SetupTestDB: isTestDb Parameter überschrieben auf %t", isTestDb)
}
logger.Debug("SetupTestDB: Verwende Test-Datenbank: %t", isTestDb)
if DB == nil {
logger.Debug("SetupTestDB: DB ist nil, erstelle neue Datenbankverbindung")
var db *gorm.DB
if isTestDb {
logger.Info("SetupTestDB: Erstelle SQLite Test-Datenbank")
testdbTempDir, err := os.MkdirTemp("", "bamort-test-")
if err != nil {
logger.Error("SetupTestDB: Fehler beim Erstellen des temporären Verzeichnisses: %s", err.Error())
panic("failed to create temporary directory: " + err.Error())
}
logger.Debug("SetupTestDB: Temporäres Verzeichnis erstellt: %s", testdbTempDir)
targetFile := filepath.Join(testdbTempDir, "test_backup.db")
logger.Debug("SetupTestDB: Ziel-Datei: %s", targetFile)
logger.Debug("SetupTestDB: Quelle-Datei: %s", PreparedTestDB)
err = copyFile(PreparedTestDB, targetFile)
if err != nil {
logger.Error("SetupTestDB: Fehler beim Kopieren der Test-Datenbank: %s", err.Error())
panic("failed to copy prepared test database: " + err.Error())
}
logger.Info("SetupTestDB: Test-Datenbank erfolgreich kopiert")
db, err = gorm.Open(sqlite.Open(targetFile), &gorm.Config{})
if err != nil {
logger.Error("SetupTestDB: Fehler beim Verbinden mit der Test-Datenbank: %s", err.Error())
panic("failed to connect to the test database: " + err.Error())
}
logger.Info("SetupTestDB: Erfolgreich mit SQLite Test-Datenbank verbunden")
//defer os.RemoveAll(testdbTempDir)
} else {
logger.Info("SetupTestDB: Verwende Live-Datenbank (MariaDB)")
//* //testing with persistent MariaDB
db = ConnectDatabase()
if db == nil {
logger.Error("SetupTestDB: Fehler beim Verbinden mit der Live-Datenbank")
panic("failed to connect to the live database")
}
logger.Info("SetupTestDB: Erfolgreich mit Live-Datenbank verbunden")
}
DB = db
logger.Info("SetupTestDB: Datenbankverbindung erfolgreich eingerichtet")
} else {
logger.Debug("SetupTestDB: DB bereits initialisiert, überspringe Setup")
}
}
func ResetTestDB() {
logger.Debug("ResetTestDB aufgerufen")
if isTestDb {
logger.Debug("ResetTestDB: Verwende Test-Datenbank, führe Cleanup durch")
sqlDB, err := DB.DB()
if err == nil {
logger.Debug("ResetTestDB: Schließe Datenbankverbindung")
sqlDB.Close()
DB = nil
os.RemoveAll(testdbTempDir)
logger.Debug("ResetTestDB: DB auf nil gesetzt")
if testdbTempDir != "" {
logger.Debug("ResetTestDB: Lösche temporäres Verzeichnis: %s", testdbTempDir)
err = os.RemoveAll(testdbTempDir)
if err != nil {
logger.Error("ResetTestDB: Fehler beim Löschen des temporären Verzeichnisses: %s", err.Error())
} else {
logger.Info("ResetTestDB: Temporäres Verzeichnis erfolgreich gelöscht")
}
testdbTempDir = ""
}
} else {
logger.Error("ResetTestDB: Fehler beim Abrufen der SQL-Datenbank: %s", err.Error())
}
} else {
logger.Debug("ResetTestDB: Verwende Live-Datenbank, überspringe Cleanup")
}
logger.Info("ResetTestDB: Cleanup abgeschlossen")
}
+125 -27
View File
@@ -1,7 +1,9 @@
package maintenance
import (
"bamort/config"
"bamort/database"
"bamort/logger"
"bamort/models"
"bamort/user"
"fmt"
@@ -28,35 +30,54 @@ func respondWithError(c *gin.Context, status int, message string) {
// migrateAllStructures migrates all database structures to the provided database
func migrateAllStructures(db *gorm.DB) error {
logger.Debug("Starte Migration aller Datenbankstrukturen...")
// Migrate all structures in the correct order
logger.Debug("Migriere Datenbankstrukturen...")
if err := database.MigrateStructure(db); err != nil {
logger.Error("Fehler beim Migrieren der Datenbankstrukturen: %s", err.Error())
return fmt.Errorf("failed to migrate database structures: %w", err)
}
logger.Debug("Migriere Benutzerstrukturen...")
if err := user.MigrateStructure(db); err != nil {
logger.Error("Fehler beim Migrieren der Benutzerstrukturen: %s", err.Error())
return fmt.Errorf("failed to migrate user structures: %w", err)
}
logger.Debug("Migriere GSMaster-Strukturen...")
if err := models.MigrateStructure(db); err != nil {
logger.Error("Fehler beim Migrieren der GSMaster-Strukturen: %s", err.Error())
return fmt.Errorf("failed to migrate gsmaster structures: %w", err)
}
/*if err := importer.MigrateStructure(db); err != nil {
return fmt.Errorf("failed to migrate importer structures: %w", err)
}*/
logger.Info("Migration aller Datenbankstrukturen erfolgreich abgeschlossen")
return nil
}
func migrateDataIfNeeded(db *gorm.DB) error {
logger.Debug("Starte Datenmigration falls erforderlich...")
// Kopiere categorie nach learning_category für Spells, wenn learning_category leer ist
logger.Debug("Migriere Spell Learning Categories...")
err := migrateSpellLearningCategories(db)
if err != nil {
logger.Error("Fehler beim Migrieren der Spell Learning Categories: %s", err.Error())
return fmt.Errorf("failed to migrate spell learning categories: %w", err)
}
logger.Info("Datenmigration erfolgreich abgeschlossen")
return nil
}
// migrateSpellLearningCategories kopiert categorie-Werte in learning_category wenn diese leer sind
func migrateSpellLearningCategories(db *gorm.DB) error {
logger.Debug("Starte Migration der Spell Learning Categories...")
// SQL-Statement um categorie nach learning_category zu kopieren, wo learning_category leer oder NULL ist
sql := `
UPDATE gsm_spells
@@ -66,34 +87,46 @@ func migrateSpellLearningCategories(db *gorm.DB) error {
AND category != ''
`
logger.Debug("Führe SQL-Update aus: %s", strings.ReplaceAll(sql, "\n", " "))
result := db.Exec(sql)
if result.Error != nil {
logger.Error("Fehler beim SQL-Update der Spell Learning Categories: %s", result.Error.Error())
return fmt.Errorf("failed to update spell learning categories: %w", result.Error)
}
// Log der Anzahl der aktualisierten Datensätze
if result.RowsAffected > 0 {
logger.Info("Updated %d spell records with learning_category from categorie", result.RowsAffected)
fmt.Printf("Updated %d spell records with learning_category from categorie\n", result.RowsAffected)
} else {
logger.Debug("Keine Spell-Datensätze benötigten ein Update der learning_category")
}
return nil
}
func MakeTestdataFromLive(c *gin.Context) {
logger.Info("Starte Testdaten-Erstellung aus Live-Datenbank...")
liveDB := database.ConnectDatabase()
if liveDB == nil {
logger.Error("Fehler beim Verbinden mit der Live-Datenbank")
respondWithError(c, http.StatusInternalServerError, "Failed to connect to live database")
return
}
logger.Debug("Erfolgreich mit Live-Datenbank verbunden")
// Live-Datenbank in SQLite-Datei kopieren
backupFile := preparedTestDB
logger.Info("Kopiere Live-Datenbank nach: %s", backupFile)
err := copyLiveDatabaseToFile(liveDB, backupFile)
if err != nil {
logger.Error("Fehler beim Kopieren der Datenbank: %s", err.Error())
respondWithError(c, http.StatusInternalServerError, fmt.Sprintf("Failed to copy database: %v", err))
return
}
logger.Info("Live-Datenbank erfolgreich in Datei kopiert: %s", backupFile)
c.JSON(http.StatusOK, gin.H{
"message": "Live database copied to file successfully",
"test_data_file": backupFile,
@@ -107,47 +140,64 @@ func CopyLiveDatabaseToFile(liveDB *gorm.DB, targetFile string) error {
// copyLiveDatabaseToFile kopiert die MariaDB-Datenbank in eine SQLite-Datei
func copyLiveDatabaseToFile(liveDB *gorm.DB, targetFile string) error {
logger.Debug("Starte Kopiervorgang von Live-DB nach SQLite-Datei: %s", targetFile)
// Verzeichnis erstellen falls es nicht existiert
dir := filepath.Dir(targetFile)
logger.Debug("Erstelle Zielverzeichnis falls erforderlich: %s", dir)
if err := os.MkdirAll(dir, 0755); err != nil {
logger.Error("Fehler beim Erstellen des Verzeichnisses %s: %s", dir, err.Error())
return fmt.Errorf("failed to create directory: %w", err)
}
// Backup der existierenden Datei erstellen
if _, err := os.Stat(targetFile); err == nil {
backupFile := targetFile + ".backup"
logger.Debug("Existierende Datei gefunden, erstelle Backup: %s", backupFile)
os.Remove(backupFile) // Alte Backup entfernen
if err := os.Rename(targetFile, backupFile); err != nil {
logger.Error("Fehler beim Erstellen des Backups %s: %s", backupFile, err.Error())
return fmt.Errorf("failed to backup existing file: %w", err)
}
logger.Debug("Backup erfolgreich erstellt")
}
// SQLite-Zieldatenbank erstellen
logger.Debug("Erstelle neue SQLite-Zieldatenbank: %s", targetFile)
targetDB, err := gorm.Open(sqlite.Open(targetFile), &gorm.Config{})
if err != nil {
logger.Error("Fehler beim Erstellen der SQLite-Zieldatenbank: %s", err.Error())
return fmt.Errorf("failed to create target SQLite database: %w", err)
}
defer func() {
if sqlDB, err := targetDB.DB(); err == nil {
logger.Debug("Schließe SQLite-Datenbankverbindung")
sqlDB.Close()
}
}()
// Strukturen in SQLite-DB migrieren
logger.Debug("Migriere Strukturen in SQLite-Datenbank...")
if err := migrateAllStructures(targetDB); err != nil {
logger.Error("Fehler beim Migrieren der Strukturen in SQLite: %s", err.Error())
return fmt.Errorf("failed to migrate structures to SQLite: %w", err)
}
// Daten von MariaDB zu SQLite kopieren
logger.Info("Kopiere Daten von MariaDB zu SQLite...")
if err := copyMariaDBToSQLite(liveDB, targetDB); err != nil {
logger.Error("Fehler beim Kopieren der Daten von MariaDB zu SQLite: %s", err.Error())
return fmt.Errorf("failed to copy data from MariaDB to SQLite: %w", err)
}
logger.Info("Kopiervorgang erfolgreich abgeschlossen")
return nil
}
// copyMariaDBToSQLite kopiert alle Daten von MariaDB zu SQLite
func copyMariaDBToSQLite(mariaDB, sqliteDB *gorm.DB) error {
logger.Debug("Starte Kopiervorgang aller Daten von MariaDB zu SQLite...")
// Vollständige Liste aller Strukturen mit GORM-Tags in der richtigen Reihenfolge
// (Basis-Tabellen zuerst wegen Foreign Key-Abhängigkeiten)
tables := []interface{}{
@@ -207,43 +257,62 @@ func copyMariaDBToSQLite(mariaDB, sqliteDB *gorm.DB) error {
// SkillLearningInfo, SpellLearningInfo, CharList, FeChar, etc.
}
for _, model := range tables {
logger.Info("Kopiere Daten für %d Tabellen...", len(tables))
for i, model := range tables {
logger.Debug("Kopiere Tabelle %d/%d: %T", i+1, len(tables), model)
if err := copyTableData(mariaDB, sqliteDB, model); err != nil {
logger.Error("Fehler beim Kopieren der Tabellendaten für %T: %s", model, err.Error())
return fmt.Errorf("failed to copy table data for %T: %w", model, err)
}
}
logger.Info("Alle Tabellendaten erfolgreich kopiert")
return nil
}
// copyTableData kopiert alle Daten einer Tabelle von MariaDB zu SQLite
func copyTableData(sourceDB, targetDB *gorm.DB, model interface{}) error {
tableName := fmt.Sprintf("%T", model)
logger.Debug("Starte Kopiervorgang für Tabelle: %s", tableName)
// Anzahl der Datensätze prüfen
var count int64
err := sourceDB.Model(model).Count(&count).Error
if err != nil {
// If table doesn't exist, skip silently (useful for testing with partial schemas)
if isTableNotExistError(err) {
logger.Debug("Tabelle %s existiert nicht in der Quelle, überspringe", tableName)
return nil
}
logger.Error("Fehler beim Zählen der Datensätze für %s: %s", tableName, err.Error())
return err
}
if count == 0 {
logger.Debug("Tabelle %s ist leer, keine Daten zu kopieren", tableName)
return nil // Keine Daten zu kopieren
}
logger.Debug("Kopiere %d Datensätze für Tabelle %s", count, tableName)
// Daten in Blöcken kopieren (für große Tabellen)
batchSize := 100
totalBatches := (int(count) + batchSize - 1) / batchSize
for offset := 0; offset < int(count); offset += batchSize {
batchNum := (offset / batchSize) + 1
logger.Debug("Kopiere Batch %d/%d für %s (Offset: %d, Limit: %d)", batchNum, totalBatches, tableName, offset, batchSize)
var records []map[string]interface{}
// Batch aus MariaDB lesen
if err := sourceDB.Model(model).Offset(offset).Limit(batchSize).Find(&records).Error; err != nil {
logger.Error("Fehler beim Lesen von Batch %d für %s: %s", batchNum, tableName, err.Error())
return err
}
if len(records) == 0 {
logger.Debug("Keine weiteren Datensätze für %s", tableName)
break
}
@@ -252,10 +321,14 @@ func copyTableData(sourceDB, targetDB *gorm.DB, model interface{}) error {
if err := targetDB.Model(model).Clauses(clause.OnConflict{
UpdateAll: true,
}).Create(&records).Error; err != nil {
logger.Error("Fehler beim Einfügen von Batch %d für %s: %s", batchNum, tableName, err.Error())
return err
}
logger.Debug("Batch %d/%d für %s erfolgreich kopiert (%d Datensätze)", batchNum, totalBatches, tableName, len(records))
}
logger.Info("Tabelle %s erfolgreich kopiert (%d Datensätze total)", tableName, count)
return nil
}
@@ -269,23 +342,32 @@ func isTableNotExistError(err error) bool {
// LoadPredefinedTestDataFromFile loads predefined test data from a specific file into the provided database
func LoadPredefinedTestDataFromFile(targetDB *gorm.DB, dataFile string) error {
logger.Debug("Lade vordefinierte Testdaten aus Datei: %s", dataFile)
// Check if file exists
if _, err := os.Stat(dataFile); os.IsNotExist(err) {
logger.Error("Vordefinierte Testdaten-Datei nicht gefunden: %s", dataFile)
return fmt.Errorf("predefined test data file not found: %s", dataFile)
}
logger.Debug("Testdaten-Datei existiert: %s", dataFile)
// Migrate structures to target DB
logger.Debug("Migriere Strukturen in Zieldatenbank...")
err := migrateAllStructures(targetDB)
if err != nil {
logger.Error("Fehler beim Migrieren der Strukturen: %s", err.Error())
return fmt.Errorf("failed to migrate structures: %w", err)
}
// Copy data from file database to target database
logger.Info("Kopiere Testdaten in Zieldatenbank...")
err = copyDataFromFileToMemory(dataFile, targetDB)
if err != nil {
logger.Error("Fehler beim Kopieren der Testdaten: %s", err.Error())
return fmt.Errorf("failed to copy test data to database: %w", err)
}
logger.Info("Vordefinierte Testdaten erfolgreich geladen")
return nil
}
@@ -327,31 +409,44 @@ func LoadPredefinedTestData(c *gin.Context) {
// copyDataFromFileToMemory copies data from a SQLite file to an in-memory database
func copyDataFromFileToMemory(sourceFile string, targetDB *gorm.DB) error {
logger.Debug("Kopiere Daten von SQLite-Datei in Memory-Datenbank: %s", sourceFile)
// Copy all tables using ATTACH and INSERT
attachSQL := fmt.Sprintf("ATTACH DATABASE '%s' AS source", sourceFile)
logger.Debug("Hänge Quell-Datenbank an: %s", attachSQL)
if err := targetDB.Exec(attachSQL).Error; err != nil {
logger.Error("Fehler beim Anhängen der Quell-Datenbank: %s", err.Error())
return fmt.Errorf("failed to attach source database: %w", err)
}
// Get list of tables from source database
logger.Debug("Ermittle Tabellenliste aus Quell-Datenbank...")
var tables []string
if err := targetDB.Raw("SELECT name FROM source.sqlite_master WHERE type='table' AND name NOT LIKE 'sqlite_%'").Scan(&tables).Error; err != nil {
logger.Error("Fehler beim Ermitteln der Tabellenliste: %s", err.Error())
return fmt.Errorf("failed to get table list: %w", err)
}
logger.Info("Gefundene Tabellen zum Kopieren: %d (%v)", len(tables), tables)
// Copy each table
for _, table := range tables {
for i, table := range tables {
logger.Debug("Kopiere Tabelle %d/%d: %s", i+1, len(tables), table)
copySQL := fmt.Sprintf("INSERT OR REPLACE INTO main.%s SELECT * FROM source.%s", table, table)
if err := targetDB.Exec(copySQL).Error; err != nil {
logger.Error("Fehler beim Kopieren der Tabelle %s: %s", table, err.Error())
return fmt.Errorf("failed to copy table %s: %w", table, err)
}
logger.Debug("Tabelle %s erfolgreich kopiert", table)
}
// Detach the source database
logger.Debug("Löse Quell-Datenbank-Verbindung...")
if err := targetDB.Exec("DETACH DATABASE source").Error; err != nil {
logger.Error("Fehler beim Lösen der Quell-Datenbank-Verbindung: %s", err.Error())
return fmt.Errorf("failed to detach source database: %w", err)
}
logger.Info("Daten erfolgreich von Datei in Memory-Datenbank kopiert")
return nil
}
@@ -385,51 +480,54 @@ func getTestDataStatistics(db *gorm.DB) (map[string]int64, error) {
}
func SetupCheck(c *gin.Context) {
logger.Info("Starte Setup-Check...")
db := database.ConnectDatabase()
if db == nil {
logger.Error("Fehler beim Verbinden mit der Datenbank für Setup-Check")
respondWithError(c, http.StatusInternalServerError, "Failed to connect to DataBase")
return
}
logger.Debug("Erfolgreich mit Datenbank für Setup-Check verbunden")
logger.Debug("Führe Strukturmigration durch...")
err := migrateAllStructures(db)
if err != nil {
logger.Error("Fehler bei der Strukturmigration: %s", err.Error())
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
return
}
logger.Debug("Führe Datenmigration durch...")
err = migrateDataIfNeeded(db)
if err != nil {
logger.Error("Fehler bei der Datenmigration: %s", err.Error())
c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to migrate data: " + err.Error()})
return
}
logger.Info("Setup-Check erfolgreich abgeschlossen")
c.JSON(http.StatusOK, gin.H{"message": "Setup Check OK"})
}
/*
// InitializeLearningCosts initialisiert das Lernkosten-System
// Wird danach nicht mehr benötigt
func InitializeLearningCosts(c *gin.Context) {
err := gsmaster.InitializeLearningCostsSystem()
if err != nil {
respondWithError(c, http.StatusInternalServerError, fmt.Sprintf("Failed to initialize learning costs: %v", err))
func ReconnectDataBase(c *gin.Context) {
logger.Info("Führe Datenbank-Reconnect durch...")
db := database.ConnectDatabase()
if db == nil {
logger.Error("Fehler beim Reconnect zur Datenbank")
respondWithError(c, http.StatusInternalServerError, "Failed to reconnect to DataBase")
return
}
// Validierung
if err := gsmaster.ValidateLearningCostsData(); err != nil {
respondWithError(c, http.StatusInternalServerError, fmt.Sprintf("Learning costs initialized but validation failed: %v", err))
return
}
// Zusammenfassung
summary, err := gsmaster.GetLearningCostsSummary()
if err != nil {
respondWithError(c, http.StatusInternalServerError, fmt.Sprintf("Failed to get summary: %v", err))
return
}
c.JSON(http.StatusOK, gin.H{
"message": "Learning costs system initialized successfully",
"summary": summary,
})
logger.Info("Datenbank-Reconnect erfolgreich")
c.JSON(http.StatusOK, gin.H{"message": "Database reconnected successfully"})
}
func ReloadENV(c *gin.Context) {
logger.Info("Starte Reload der Umgebungsvariablen...")
// Reload the environment variables
config.LoadConfig()
c.JSON(http.StatusOK, gin.H{"message": "Environment variables reloaded successfully"})
}
*/
+2
View File
@@ -8,6 +8,8 @@ func RegisterRoutes(r *gin.RouterGroup) {
charGrp := r.Group("/maintenance")
charGrp.GET("/setupcheck", SetupCheck)
charGrp.GET("/mktestdata", MakeTestdataFromLive)
charGrp.GET("/reconndb", ReconnectDataBase) // Datenbank neu verbinden
charGrp.GET("/reloadenv", ReloadENV)
/*
//nur zur einmaligen Ausführung, um das Lernkosten-System zu initialisieren
charGrp.POST("/initialize-learning-costs", InitializeLearningCosts)
-20
View File
@@ -1,20 +0,0 @@
package main
import (
"bamort/database"
"bamort/models"
"fmt"
)
func main() {
fmt.Println("Starte Migration...")
database.ConnectDatabase()
err := database.DB.AutoMigrate(&models.CharacterCreationSession{})
if err != nil {
fmt.Printf("Migration Fehler: %v\n", err)
} else {
fmt.Println("Migration erfolgreich!")
}
}
+18
View File
@@ -0,0 +1,18 @@
package main
import (
"bamort/config"
"fmt"
)
func main() {
// Teste, ob die globale Konfigurationsvariable funktioniert
fmt.Printf("Globale Konfiguration:\n")
fmt.Printf("Environment: %s\n", config.Cfg.Environment)
fmt.Printf("DatabaseType: %s\n", config.Cfg.DatabaseType)
fmt.Printf("DatabaseURL: %s\n", config.Cfg.DatabaseURL)
fmt.Printf("ServerPort: %s\n", config.Cfg.ServerPort)
fmt.Printf("DebugMode: %v\n", config.Cfg.DebugMode)
fmt.Printf("LogLevel: %s\n", config.Cfg.LogLevel)
fmt.Printf("Testing: %s\n", config.Cfg.DevTesting)
}
+46 -1
View File
@@ -6,6 +6,7 @@ Add handlers for user registration and login:
package user
import (
"bamort/logger"
"crypto/md5"
"encoding/hex"
"fmt"
@@ -21,25 +22,37 @@ func respondWithError(c *gin.Context, status int, message string) {
}
func RegisterUser(c *gin.Context) {
logger.Debug("Starte Benutzerregistrierung...")
var user User
if err := c.ShouldBindJSON(&user); err != nil {
logger.Error("Fehler beim Parsen der Registrierungsdaten: %s", err.Error())
respondWithError(c, http.StatusBadRequest, err.Error())
return
}
logger.Debug("Registriere Benutzer: %s", user.Username)
//fmt.Printf("User input: '%s'", user.PasswordHash)
//hashedPassword, _ := bcrypt.GenerateFromPassword([]byte(user.PasswordHash), bcrypt.DefaultCost)
hashedPassword := md5.Sum([]byte(user.PasswordHash))
user.PasswordHash = hex.EncodeToString(hashedPassword[:])
logger.Debug("Passwort-Hash erstellt für Benutzer: %s", user.Username)
//fmt.Printf("pwdh: %s", user.PasswordHash)
if err := user.Create(); err != nil {
logger.Error("Fehler beim Erstellen des Benutzers %s: %s", user.Username, err.Error())
respondWithError(c, http.StatusInternalServerError, fmt.Sprintf("Failed to create user: %s", err))
return
}
logger.Info("Benutzer erfolgreich registriert: %s (ID: %d)", user.Username, user.UserID)
//fmt.Printf(" ___ pwdh2: %s", user.PasswordHash)
c.JSON(http.StatusCreated, gin.H{"message": "User registered successfully:"})
}
func GenerateToken(u *User) string {
logger.Debug("Generiere Token für Benutzer: %s (ID: %d)", u.Username, u.UserID)
//u.Username + "lkiuztrew" + u.CreatedAt.String()
tx := md5.Sum([]byte(u.Username + u.CreatedAt.String()))
// Convert hash to raw string
@@ -48,9 +61,13 @@ func GenerateToken(u *User) string {
idm := "." + fmt.Sprintf("%d", u.UserID) + ":"
// Insert the character
token := hashString[:pos] + string(idm) + hashString[pos:]
logger.Debug("Token erfolgreich generiert für Benutzer: %s", u.Username)
return token
}
func CheckToken(token string) *User {
logger.Debug("Prüfe Token-Gültigkeit...")
//fmt.Print("CheckToken1: " + token)
var u User
var err error
@@ -58,6 +75,7 @@ func CheckToken(token string) *User {
userid := 0
// Check if a `.` is at position 7 (zero-indexed)
if len(token) > pos && token[pos] == '.' {
logger.Debug("Token-Format erkannt, extrahiere Benutzer-ID...")
//fmt.Print("CheckToken2: " + token + "\n")
// Find the next `:` after the `.`
colonPos := strings.Index(token[pos+1:], ":") // Start searching after position 7
@@ -69,34 +87,44 @@ func CheckToken(token string) *User {
userid, err = strconv.Atoi(uu)
//fmt.Printf("Extracted UserID: %v \n", userid)
if err != nil {
logger.Error("Fehler beim Parsen der Benutzer-ID aus Token: %s", err.Error())
//fmt.Print("CheckToken4: " + err.Error() + "\n")
return nil
}
logger.Debug("Benutzer-ID aus Token extrahiert: %d", userid)
} else {
logger.Debug("Token-Format ungültig: Kein ':' nach '.' gefunden")
//fmt.Print("CheckToken5: not found\n")
return nil
}
} else {
logger.Debug("Token-Format ungültig: Kein '.' an erwarteter Position")
//fmt.Print("CheckToken6: not found\n")
return nil
}
if userid > 0 {
logger.Debug("Lade Benutzer mit ID: %d", userid)
//fmt.Printf("CheckToken6-1: userid %v\n", userid)
//fmt.Printf("CheckToken6-1: userid %v\n", uint(userid))
err := u.FirstId(uint(userid))
if err != nil {
logger.Error("Benutzer mit ID %d nicht gefunden: %s", userid, err.Error())
//fmt.Printf("CheckToken7: not found error %s\n", err.Error())
return nil
}
logger.Debug("Benutzer gefunden und Token validiert: %s (ID: %d)", u.Username, u.UserID)
//fmt.Printf("CheckToken8: found:%s \n", u.Username)
return &u
}
logger.Debug("Token-Validierung fehlgeschlagen: Ungültige Benutzer-ID")
//fmt.Print("CheckToken9: not found\n")
return nil
}
func LoginUser(c *gin.Context) {
logger.Debug("Starte Benutzer-Anmeldung...")
var user User
var input struct {
Username string `json:"username"`
@@ -104,19 +132,25 @@ func LoginUser(c *gin.Context) {
}
if err := c.ShouldBindJSON(&input); err != nil {
logger.Error("Fehler beim Parsen der Login-Daten: %s", err.Error())
respondWithError(c, http.StatusBadRequest, err.Error())
return
}
logger.Debug("Login-Versuch für Benutzer: %s", input.Username)
//if err := database.DB.Where("username = ?", input.Username).First(&user).Error; err != nil {
if err := user.First(input.Username); err != nil {
logger.Warn("Login fehlgeschlagen - Benutzer nicht gefunden: %s", input.Username)
respondWithError(c, http.StatusUnauthorized, fmt.Sprintf("Invalid username. or password %v", input))
return
}
logger.Debug("Benutzer gefunden, prüfe Passwort für: %s", input.Username)
hashedPassword := md5.Sum([]byte(input.Password))
fmt.Printf("pwdh: %s", hex.EncodeToString(hashedPassword[:]))
if user.PasswordHash != hex.EncodeToString(hashedPassword[:]) {
logger.Warn("Login fehlgeschlagen - Ungültiges Passwort für Benutzer: %s", input.Username)
respondWithError(c, http.StatusUnauthorized, fmt.Sprintf("Invalid username. or password. %s %s", input.Password, hex.EncodeToString(hashedPassword[:])))
return
}
@@ -127,26 +161,37 @@ func LoginUser(c *gin.Context) {
}
*/
c.JSON(http.StatusOK, gin.H{"message": "Login successful", "token": GenerateToken(&user)})
logger.Info("Login erfolgreich für Benutzer: %s (ID: %d)", user.Username, user.UserID)
token := GenerateToken(&user)
logger.Debug("Login-Token generiert für Benutzer: %s", user.Username)
c.JSON(http.StatusOK, gin.H{"message": "Login successful", "token": token})
}
// Apply middleware to protected routes
func AuthMiddleware() gin.HandlerFunc {
return func(c *gin.Context) {
logger.Debug("Prüfe Authentifizierung für Request: %s %s", c.Request.Method, c.Request.URL.Path)
token := c.GetHeader("Authorization")
if token == "" {
logger.Warn("Authentifizierung fehlgeschlagen - Kein Authorization-Header für %s %s", c.Request.Method, c.Request.URL.Path)
respondWithError(c, http.StatusUnauthorized, "Unauthorized")
c.Abort()
return
}
logger.Debug("Authorization-Header gefunden, prüfe Token...")
user := CheckToken(token)
if user == nil {
logger.Warn("Authentifizierung fehlgeschlagen - Ungültiger Token für %s %s", c.Request.Method, c.Request.URL.Path)
respondWithError(c, http.StatusUnauthorized, "Unauthorized.")
c.Abort()
return
}
logger.Debug("Authentifizierung erfolgreich für Benutzer: %s (ID: %d) - %s %s", user.Username, user.UserID, c.Request.Method, c.Request.URL.Path)
// Set user information in context
c.Set("userID", user.UserID)
c.Set("username", user.Username)