2025-01-04 21:29:33 +01:00
package importer
import (
2025-08-20 23:51:29 +02:00
"bamort/database"
2026-05-01 18:15:31 +02:00
"bamort/bmrt/models"
2025-01-04 21:29:33 +01:00
"fmt"
"net/http"
2025-08-20 23:51:29 +02:00
"os"
2025-01-04 21:29:33 +01:00
"path/filepath"
"strings"
"github.com/gin-gonic/gin"
)
2025-07-24 07:39:43 +02:00
// Helper function for error responses
func respondWithError ( c * gin . Context , status int , message string ) {
c . JSON ( status , gin . H { "error" : message } )
}
2025-01-04 21:29:33 +01:00
// 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 {
2025-07-24 07:39:43 +02:00
respondWithError ( c , http . StatusBadRequest , "file_vtt is required" )
2025-01-04 21:29:33 +01:00
return
}
if ! isValidFileType ( file_vtt . Filename ) {
2025-07-24 07:39:43 +02:00
respondWithError ( c , http . StatusBadRequest , "File1 must be a .csv or .json file" )
2025-01-04 21:29:33 +01:00
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 ) {
2025-07-24 07:39:43 +02:00
respondWithError ( c , http . StatusBadRequest , "File2 must be a .csv or .json file" )
2025-01-04 21:29:33 +01:00
return
}
// Save File 1
err := c . SaveUploadedFile ( file_vtt , vttFileName )
if err != nil {
2025-07-24 07:39:43 +02:00
respondWithError ( c , http . StatusInternalServerError , "Failed to save file_vtt" )
2025-01-04 21:29:33 +01:00
return
}
// Save File 2 if provided
if err2 == nil {
err := c . SaveUploadedFile ( file_csv , csvFileName )
if err != nil {
2025-07-24 07:39:43 +02:00
respondWithError ( c , http . StatusInternalServerError , "Failed to save file_csv" )
2025-01-04 21:29:33 +01:00
return
}
}
2026-02-02 15:04:05 +01:00
userID := c . GetUint ( "userID" )
character , err3 := ImportVTTJSON ( vttFileName , userID )
2025-01-04 21:29:33 +01:00
if err3 != nil {
2025-07-24 07:39:43 +02:00
respondWithError ( c , http . StatusInternalServerError , fmt . Sprintf ( "Failed to Import Character from file %s" , err3 . Error ( ) ) )
2025-01-04 21:29:33 +01:00
return
}
if character . ID < 1 {
2025-07-24 07:39:43 +02:00
respondWithError ( c , http . StatusInternalServerError , "Failed to Import Character from file ID is < 1" )
2025-01-04 21:29:33 +01:00
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
}
2025-08-20 23:51:29 +02:00
// 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
2025-08-21 00:08:49 +02:00
// @Accept multipart/form-data
2025-08-20 23:51:29 +02:00
// @Produce json
2025-08-21 00:08:49 +02:00
// @Param file formData file true "CSV file to import"
2025-08-20 23:51:29 +02:00
// @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 ) {
2025-08-21 00:08:49 +02:00
// Try to get file from multipart form first
file , err := c . FormFile ( "file" )
var filePath string
2025-08-20 23:51:29 +02:00
2025-08-21 00:08:49 +02:00
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
}
2025-08-20 23:51:29 +02:00
}
2025-08-21 00:08:49 +02:00
// 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
}
2025-08-20 23:51:29 +02:00
}
// Clear source cache before import to ensure fresh data
ClearSourceCache ( )
// Perform the import
2025-08-21 00:08:49 +02:00
err = ImportCsv2Spell ( filePath )
2025-08-20 23:51:29 +02:00
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" ,
2025-08-21 00:08:49 +02:00
"file" : filepath . Base ( filePath ) ,
2025-08-20 23:51:29 +02:00
} )
return
}
c . JSON ( http . StatusOK , gin . H {
"success" : true ,
"message" : "Spells imported successfully" ,
2025-08-21 00:08:49 +02:00
"file" : filepath . Base ( filePath ) ,
2025-08-20 23:51:29 +02:00
"total_spells" : spellCount ,
} )
}
2025-12-29 18:13:24 +01:00
// 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 )
}