042a1d4773
* 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
414 lines
12 KiB
Go
414 lines
12 KiB
Go
package importer
|
|
|
|
import (
|
|
"bamort/database"
|
|
"bamort/bmrt/models"
|
|
"fmt"
|
|
"net/http"
|
|
"os"
|
|
"path/filepath"
|
|
"strings"
|
|
|
|
"github.com/gin-gonic/gin"
|
|
)
|
|
|
|
// Helper function for error responses
|
|
func respondWithError(c *gin.Context, status int, message string) {
|
|
c.JSON(status, gin.H{"error": message})
|
|
}
|
|
|
|
// Upload files
|
|
func UploadFiles(c *gin.Context) {
|
|
// Get files from the request
|
|
file_vtt, err1 := c.FormFile("file_vtt")
|
|
file_csv, err2 := c.FormFile("file_csv")
|
|
if err1 != nil {
|
|
respondWithError(c, http.StatusBadRequest, "file_vtt is required")
|
|
return
|
|
}
|
|
if !isValidFileType(file_vtt.Filename) {
|
|
respondWithError(c, http.StatusBadRequest, "File1 must be a .csv or .json file")
|
|
return
|
|
}
|
|
|
|
vttFileName := fmt.Sprintf("./uploads/%s", file_vtt.Filename)
|
|
csvFileName := "./uploads/default.csv"
|
|
if file_csv != nil {
|
|
csvFileName = fmt.Sprintf("./uploads/%s", file_csv.Filename)
|
|
}
|
|
|
|
// Validate file2 if provided
|
|
if file_csv != nil && !isValidFileType(file_csv.Filename) {
|
|
respondWithError(c, http.StatusBadRequest, "File2 must be a .csv or .json file")
|
|
return
|
|
}
|
|
|
|
// Save File 1
|
|
err := c.SaveUploadedFile(file_vtt, vttFileName)
|
|
if err != nil {
|
|
respondWithError(c, http.StatusInternalServerError, "Failed to save file_vtt")
|
|
return
|
|
}
|
|
|
|
// Save File 2 if provided
|
|
if err2 == nil {
|
|
err := c.SaveUploadedFile(file_csv, csvFileName)
|
|
if err != nil {
|
|
respondWithError(c, http.StatusInternalServerError, "Failed to save file_csv")
|
|
return
|
|
}
|
|
}
|
|
userID := c.GetUint("userID")
|
|
|
|
character, err3 := ImportVTTJSON(vttFileName, userID)
|
|
if err3 != nil {
|
|
respondWithError(c, http.StatusInternalServerError, fmt.Sprintf("Failed to Import Character from file %s", err3.Error()))
|
|
return
|
|
}
|
|
if character.ID < 1 {
|
|
respondWithError(c, http.StatusInternalServerError, "Failed to Import Character from file ID is < 1")
|
|
return
|
|
}
|
|
|
|
c.JSON(http.StatusOK, gin.H{
|
|
"message": "Character imported successfully",
|
|
"character": character,
|
|
})
|
|
}
|
|
|
|
func isValidFileType(filename string) bool {
|
|
allowedExtensions := []string{".csv", ".json"}
|
|
ext := strings.ToLower(filepath.Ext(filename))
|
|
for _, allowedExt := range allowedExtensions {
|
|
if ext == allowedExt {
|
|
return true
|
|
}
|
|
}
|
|
return false
|
|
}
|
|
|
|
// ImportSpellCSVHandler handles HTTP requests to import spell data from CSV files
|
|
// @Summary Import spells from CSV file
|
|
// @Description Imports spell data from a CSV file into the database. The CSV file should contain spell information with headers matching the database fields.
|
|
// @Tags importer
|
|
// @Accept multipart/form-data
|
|
// @Produce json
|
|
// @Param file formData file true "CSV file to import"
|
|
// @Success 200 {object} map[string]interface{} "Import successful"
|
|
// @Failure 400 {object} map[string]interface{} "Bad request - missing file parameter, file not found, or invalid file type"
|
|
// @Failure 500 {object} map[string]interface{} "Internal server error - import failed"
|
|
// @Router /api/importer/spells/csv [post]
|
|
func ImportSpellCSVHandler(c *gin.Context) {
|
|
// Try to get file from multipart form first
|
|
file, err := c.FormFile("file")
|
|
var filePath string
|
|
|
|
if err != nil {
|
|
// Fallback to query parameter for backward compatibility
|
|
filePath = c.Query("file")
|
|
if filePath == "" {
|
|
c.JSON(http.StatusBadRequest, gin.H{
|
|
"error": "Missing file parameter",
|
|
"message": "Please provide a CSV file via multipart upload or file path using the 'file' parameter",
|
|
})
|
|
return
|
|
}
|
|
|
|
// Validate file exists and has proper extension
|
|
if _, err := os.Stat(filePath); os.IsNotExist(err) {
|
|
c.JSON(http.StatusBadRequest, gin.H{
|
|
"error": "File not found",
|
|
"message": fmt.Sprintf("File '%s' does not exist", filePath),
|
|
})
|
|
return
|
|
}
|
|
} else {
|
|
// Handle uploaded file
|
|
// Check file extension
|
|
ext := strings.ToLower(filepath.Ext(file.Filename))
|
|
if ext != ".csv" {
|
|
c.JSON(http.StatusBadRequest, gin.H{
|
|
"error": "Invalid file type",
|
|
"message": "Only CSV files are supported",
|
|
})
|
|
return
|
|
}
|
|
|
|
// Create uploads directory if it doesn't exist
|
|
uploadDir := "./uploads"
|
|
if _, err := os.Stat(uploadDir); os.IsNotExist(err) {
|
|
if err := os.MkdirAll(uploadDir, 0755); err != nil {
|
|
c.JSON(http.StatusInternalServerError, gin.H{
|
|
"error": "Failed to create upload directory",
|
|
"message": err.Error(),
|
|
})
|
|
return
|
|
}
|
|
}
|
|
|
|
// Save the uploaded file
|
|
filePath = filepath.Join(uploadDir, file.Filename)
|
|
if err := c.SaveUploadedFile(file, filePath); err != nil {
|
|
c.JSON(http.StatusInternalServerError, gin.H{
|
|
"error": "Failed to save uploaded file",
|
|
"message": err.Error(),
|
|
})
|
|
return
|
|
}
|
|
}
|
|
|
|
// Check file extension for query parameter path
|
|
if file == nil {
|
|
ext := strings.ToLower(filepath.Ext(filePath))
|
|
if ext != ".csv" {
|
|
c.JSON(http.StatusBadRequest, gin.H{
|
|
"error": "Invalid file type",
|
|
"message": "Only CSV files are supported",
|
|
})
|
|
return
|
|
}
|
|
}
|
|
|
|
// Clear source cache before import to ensure fresh data
|
|
ClearSourceCache()
|
|
|
|
// Perform the import
|
|
err = ImportCsv2Spell(filePath)
|
|
if err != nil {
|
|
c.JSON(http.StatusInternalServerError, gin.H{
|
|
"error": "Import failed",
|
|
"message": err.Error(),
|
|
})
|
|
return
|
|
}
|
|
|
|
// Count imported spells for response
|
|
var spellCount int64
|
|
if countErr := database.DB.Model(&models.Spell{}).Count(&spellCount).Error; countErr != nil {
|
|
// If count fails, just report success without count
|
|
c.JSON(http.StatusOK, gin.H{
|
|
"success": true,
|
|
"message": "Spells imported successfully",
|
|
"file": filepath.Base(filePath),
|
|
})
|
|
return
|
|
}
|
|
|
|
c.JSON(http.StatusOK, gin.H{
|
|
"success": true,
|
|
"message": "Spells imported successfully",
|
|
"file": filepath.Base(filePath),
|
|
"total_spells": spellCount,
|
|
})
|
|
}
|
|
|
|
// ExportCharacterVTTHandler exports a character to VTT JSON format
|
|
// @Summary Export character to VTT JSON format
|
|
// @Description Exports a character to VTT JSON format for use in other systems
|
|
// @Tags importer
|
|
// @Produce json
|
|
// @Param id path int true "Character ID"
|
|
// @Success 200 {object} CharacterImport "Export successful"
|
|
// @Failure 400 {object} map[string]interface{} "Bad request - invalid character ID"
|
|
// @Failure 404 {object} map[string]interface{} "Character not found"
|
|
// @Failure 500 {object} map[string]interface{} "Internal server error - export failed"
|
|
// @Router /api/importer/export/vtt/{id} [get]
|
|
func ExportCharacterVTTHandler(c *gin.Context) {
|
|
// Get character ID from URL parameter
|
|
charID := c.Param("id")
|
|
if charID == "" {
|
|
respondWithError(c, http.StatusBadRequest, "Character ID is required")
|
|
return
|
|
}
|
|
|
|
// Load character from database
|
|
var char models.Char
|
|
err := database.DB.Preload("Eigenschaften").
|
|
Preload("Fertigkeiten").
|
|
Preload("Waffenfertigkeiten").
|
|
Preload("Zauber").
|
|
Preload("Waffen").
|
|
Preload("Ausruestung").
|
|
Preload("Behaeltnisse").
|
|
Preload("Transportmittel").
|
|
First(&char, charID).Error
|
|
|
|
if err != nil {
|
|
respondWithError(c, http.StatusNotFound, "Character not found")
|
|
return
|
|
}
|
|
|
|
// Export to VTT format
|
|
vttChar, err := ExportCharToVTT(&char)
|
|
if err != nil {
|
|
respondWithError(c, http.StatusInternalServerError, fmt.Sprintf("Failed to export character: %s", err.Error()))
|
|
return
|
|
}
|
|
|
|
// Return as JSON
|
|
c.JSON(http.StatusOK, vttChar)
|
|
}
|
|
|
|
// ExportCharacterVTTFileHandler exports a character to VTT JSON file
|
|
// @Summary Export character to VTT JSON file
|
|
// @Description Exports a character to VTT JSON file and returns it as a download
|
|
// @Tags importer
|
|
// @Produce json
|
|
// @Param id path int true "Character ID"
|
|
// @Success 200 {file} file "VTT JSON file"
|
|
// @Failure 400 {object} map[string]interface{} "Bad request - invalid character ID"
|
|
// @Failure 404 {object} map[string]interface{} "Character not found"
|
|
// @Failure 500 {object} map[string]interface{} "Internal server error - export failed"
|
|
// @Router /api/importer/export/vtt/{id}/file [get]
|
|
func ExportCharacterVTTFileHandler(c *gin.Context) {
|
|
// Get character ID from URL parameter
|
|
charID := c.Param("id")
|
|
if charID == "" {
|
|
respondWithError(c, http.StatusBadRequest, "Character ID is required")
|
|
return
|
|
}
|
|
|
|
// Load character from database
|
|
var char models.Char
|
|
err := database.DB.Preload("Eigenschaften").
|
|
Preload("Fertigkeiten").
|
|
Preload("Waffenfertigkeiten").
|
|
Preload("Zauber").
|
|
Preload("Waffen").
|
|
Preload("Ausruestung").
|
|
Preload("Behaeltnisse").
|
|
Preload("Transportmittel").
|
|
First(&char, charID).Error
|
|
|
|
if err != nil {
|
|
respondWithError(c, http.StatusNotFound, "Character not found")
|
|
return
|
|
}
|
|
|
|
// Create temp file
|
|
tempFile, err := os.CreateTemp("", fmt.Sprintf("vtt_export_%s_*.json", char.Name))
|
|
if err != nil {
|
|
respondWithError(c, http.StatusInternalServerError, "Failed to create temp file")
|
|
return
|
|
}
|
|
defer os.Remove(tempFile.Name())
|
|
tempFile.Close()
|
|
|
|
// Export to file
|
|
err = ExportCharToVTTFile(&char, tempFile.Name())
|
|
if err != nil {
|
|
respondWithError(c, http.StatusInternalServerError, fmt.Sprintf("Failed to export character: %s", err.Error()))
|
|
return
|
|
}
|
|
|
|
// Send file as download
|
|
filename := fmt.Sprintf("%s_vtt_export.json", char.Name)
|
|
c.FileAttachment(tempFile.Name(), filename)
|
|
}
|
|
|
|
// ExportSpellsCSVHandler exports spell master data to CSV file
|
|
// @Summary Export spells to CSV file
|
|
// @Description Exports spell master data to CSV format
|
|
// @Tags importer
|
|
// @Produce text/csv
|
|
// @Param game_system query string false "Game system filter (e.g., 'midgard')"
|
|
// @Success 200 {file} file "CSV file"
|
|
// @Failure 500 {object} map[string]interface{} "Internal server error - export failed"
|
|
// @Router /api/importer/export/spells/csv [get]
|
|
func ExportSpellsCSVHandler(c *gin.Context) {
|
|
gameSystem := c.Query("game_system")
|
|
|
|
// Load spells from database
|
|
var spells []models.Spell
|
|
query := database.DB
|
|
if gameSystem != "" {
|
|
query = query.Where("game_system = ?", gameSystem)
|
|
}
|
|
err := query.Find(&spells).Error
|
|
|
|
if err != nil {
|
|
respondWithError(c, http.StatusInternalServerError, "Failed to load spells")
|
|
return
|
|
}
|
|
|
|
// Create temp file
|
|
tempFile, err := os.CreateTemp("", "spells_export_*.csv")
|
|
if err != nil {
|
|
respondWithError(c, http.StatusInternalServerError, "Failed to create temp file")
|
|
return
|
|
}
|
|
defer os.Remove(tempFile.Name())
|
|
tempFile.Close()
|
|
|
|
// Export to CSV
|
|
err = ExportSpellsToCSV(spells, tempFile.Name())
|
|
if err != nil {
|
|
respondWithError(c, http.StatusInternalServerError, fmt.Sprintf("Failed to export spells: %s", err.Error()))
|
|
return
|
|
}
|
|
|
|
// Send file as download
|
|
filename := "spells_export.csv"
|
|
if gameSystem != "" {
|
|
filename = fmt.Sprintf("spells_%s_export.csv", gameSystem)
|
|
}
|
|
c.FileAttachment(tempFile.Name(), filename)
|
|
}
|
|
|
|
// ExportCharacterCSVHandler exports a character to CSV file
|
|
// @Summary Export character to CSV file
|
|
// @Description Exports a character to CSV format (MOAM-compatible)
|
|
// @Tags importer
|
|
// @Produce text/csv
|
|
// @Param id path int true "Character ID"
|
|
// @Success 200 {file} file "CSV file"
|
|
// @Failure 400 {object} map[string]interface{} "Bad request - invalid character ID"
|
|
// @Failure 404 {object} map[string]interface{} "Character not found"
|
|
// @Failure 500 {object} map[string]interface{} "Internal server error - export failed"
|
|
// @Router /api/importer/export/csv/{id} [get]
|
|
func ExportCharacterCSVHandler(c *gin.Context) {
|
|
// Get character ID from URL parameter
|
|
charID := c.Param("id")
|
|
if charID == "" {
|
|
respondWithError(c, http.StatusBadRequest, "Character ID is required")
|
|
return
|
|
}
|
|
|
|
// Load character from database
|
|
var char models.Char
|
|
err := database.DB.Preload("Eigenschaften").
|
|
Preload("Fertigkeiten").
|
|
Preload("Waffenfertigkeiten").
|
|
Preload("Zauber").
|
|
Preload("Waffen").
|
|
Preload("Ausruestung").
|
|
Preload("Behaeltnisse").
|
|
Preload("Transportmittel").
|
|
First(&char, charID).Error
|
|
|
|
if err != nil {
|
|
respondWithError(c, http.StatusNotFound, "Character not found")
|
|
return
|
|
}
|
|
|
|
// Create temp file
|
|
tempFile, err := os.CreateTemp("", fmt.Sprintf("csv_export_%s_*.csv", char.Name))
|
|
if err != nil {
|
|
respondWithError(c, http.StatusInternalServerError, "Failed to create temp file")
|
|
return
|
|
}
|
|
defer os.Remove(tempFile.Name())
|
|
tempFile.Close()
|
|
|
|
// Export to CSV
|
|
err = ExportCharToCSV(&char, tempFile.Name())
|
|
if err != nil {
|
|
respondWithError(c, http.StatusInternalServerError, fmt.Sprintf("Failed to export character: %s", err.Error()))
|
|
return
|
|
}
|
|
|
|
// Send file as download
|
|
filename := fmt.Sprintf("%s_export.csv", char.Name)
|
|
c.FileAttachment(tempFile.Name(), filename)
|
|
}
|