diff --git a/common/dberrorhandling.go b/common/dberrorhandling.go index 31c2f5c..12112aa 100644 --- a/common/dberrorhandling.go +++ b/common/dberrorhandling.go @@ -10,12 +10,14 @@ import ( func ProvideErrorResponse(c *gin.Context, err error) bool { if err != nil { if err == gorm.ErrRecordNotFound { + errormsg := "Record not Found in DB: " + err.Error() c.JSON(http.StatusNotFound, gin.H{ - "error": "No files found in DB", + "error": errormsg, }) } else { + errormsg := "Error on DB Query or transaction: " + err.Error() c.JSON(http.StatusInternalServerError, gin.H{ - "error": "Error on DB Query or transaction", + "error": errormsg, }) } return true // Error diff --git a/routes/file/fileEndpoints.go b/routes/file/fileEndpoints.go index 65a1938..fbd020e 100644 --- a/routes/file/fileEndpoints.go +++ b/routes/file/fileEndpoints.go @@ -4,16 +4,12 @@ import ( "strconv" "net/http" - "github.com/gin-gonic/gin" - "github.com/gin-gonic/gin/json" - "git.rwth-aachen.de/acs/public/villas/villasweb-backend-go/common" ) func FilesRegister(r *gin.RouterGroup) { r.GET("/", filesReadEp) - r.POST("/", fileRegistrationEp) // NEW in API r.PUT("/:FileID", fileUpdateEp) // NEW in API r.GET("/:FileID", fileReadEp) r.DELETE("/:FileID", fileDeleteEp) @@ -32,28 +28,7 @@ func filesReadEp(c *gin.Context) { } -func fileRegistrationEp(c *gin.Context) { - var m map[string]interface{} - decoder := json.NewDecoder(c.Request.Body) - defer c.Request.Body.Close() - - if err := decoder.Decode(&m); err != nil { - c.JSON(http.StatusBadRequest, gin.H{ - "error": "Bad request. Invalid body.", - }) - return; - } - - // Database query - err := AddFile(m) - - if common.ProvideErrorResponse(c, err) == false { - c.JSON(http.StatusOK, gin.H{ - "message": "OK.", - }) - } -} func fileUpdateEp(c *gin.Context) { c.JSON(http.StatusOK, gin.H{ diff --git a/routes/file/fileQueries.go b/routes/file/fileQueries.go index 8efaab5..96e8760 100644 --- a/routes/file/fileQueries.go +++ b/routes/file/fileQueries.go @@ -37,17 +37,4 @@ func FindFile(userID int, fileID string) ( common.File, error) { return file, err -} - -func AddFile(m map[string]interface{}) error { - - // TODO deserialize m (JSON file object) to data struct File - - // TODO we need the user here as well to be able to create the association in the DB - - // TODO add deserialized File to DB - - var err error - return err - } \ No newline at end of file diff --git a/routes/simulation/simulationEndpoints.go b/routes/simulation/simulationEndpoints.go index 00a6c73..e49aae8 100644 --- a/routes/simulation/simulationEndpoints.go +++ b/routes/simulation/simulationEndpoints.go @@ -2,7 +2,12 @@ package simulation import ( "github.com/gin-gonic/gin" + "fmt" "net/http" + "path/filepath" + "strconv" + + "git.rwth-aachen.de/acs/public/villas/villasweb-backend-go/common" ) func SimulationsRegister(r *gin.RouterGroup) { @@ -11,6 +16,7 @@ func SimulationsRegister(r *gin.RouterGroup) { r.PUT("/:SimulationID", simulationUpdateEp) r.GET("/:SimulationID", simulationReadEp) r.DELETE("/:SimulationID", simulationDeleteEp) + r.POST ("/:SimulationID/models/:SimulationModelID/file", fileRegistrationEp) // NEW } func simulationsReadEp(c *gin.Context) { @@ -45,3 +51,66 @@ func simulationDeleteEp(c *gin.Context) { }) } +func fileRegistrationEp(c *gin.Context) { + + // TODO Check if file upload is ok for this user or simulation (user or simulation exists) + var widgetID_s = c.Param("WidgetID") + var widgetID_i int + var simulationmodelID_s = c.Param("SimulationModelID") + var simulationmodelID_i int + + if widgetID_s != "" { + widgetID_i, _ = strconv.Atoi(widgetID_s) + } else { + widgetID_i = -1 + } + + if simulationmodelID_s != "" { + simulationmodelID_i, _ = strconv.Atoi(simulationmodelID_s) + } else { + simulationmodelID_i = -1 + } + + if simulationmodelID_i == -1 && widgetID_i == -1 { + errormsg := fmt.Sprintf("Bad request. Did not provide simulation model ID or widget ID for file") + c.JSON(http.StatusBadRequest, gin.H{ + "error": errormsg, + }) + return; + } + + // Extract file from POST request form + file, err := c.FormFile("file") + if err != nil { + errormsg := fmt.Sprintf("Bad request. Get form error: %s", err.Error()) + c.JSON(http.StatusBadRequest, gin.H{ + "error": errormsg, + }) + return; + } + + // Obtain properties of file + filetype := file.Header.Get("Content-Type") // TODO make sure this is properly set in file header + filename := filepath.Base(file.Filename) + foldername := "files/testfolder" //TODO replace this placeholder with systematic foldername (e.g. simulation ID) + size := file.Size + + // Save file to local disc (NOT DB!) + err = SaveFile(file, filename, foldername, uint(size)) + if err != nil { + errormsg := fmt.Sprintf("Internal Server Error. Error saving file: %s", err.Error()) + c.JSON(http.StatusInternalServerError, gin.H{ + "error": errormsg, + }) + return + } + + // Add File object with parameters to DB + err = AddFile(filename, foldername, filetype, uint(size), widgetID_i, simulationmodelID_i ) + if common.ProvideErrorResponse(c, err) == false { + c.JSON(http.StatusOK, gin.H{ + "message": "OK.", + }) + } + +} diff --git a/routes/simulation/simulationQueries.go b/routes/simulation/simulationQueries.go index 58b88ce..2b7ecf7 100644 --- a/routes/simulation/simulationQueries.go +++ b/routes/simulation/simulationQueries.go @@ -1,6 +1,11 @@ package simulation import ( + "io" + "mime/multipart" + "os" + "path/filepath" + "git.rwth-aachen.de/acs/public/villas/villasweb-backend-go/common" ) @@ -17,3 +22,102 @@ func FindUserSimulations(user *common.User) ([]common.Simulation, int, error) { err := db.Model(user).Related(&simulations, "Simulations").Error return simulations, len(simulations), err } + + +func AddFile(filename string, foldername string, filetype string, size uint, widgetID int, simulationmodelID int ) error { + + filesavepath := filepath.Join(foldername, filename) + + // get last modify time of target file + fileinfo, err := os.Stat(filesavepath) + if err != nil { + return err + } + modTime := fileinfo.ModTime() + + // Create file object for Database + var fileObj common.File + fileObj.Size = uint(size) + fileObj.Type = filetype + fileObj.Path = filesavepath + fileObj.Date = modTime + fileObj.Name = filename + fileObj.ImageHeight = 0 + fileObj.ImageWidth = 0 + + // Check if file shall be associated with Widget or Simulation Model + db := common.GetDB() + if widgetID != -1 { + // associate to Widget + var widget common.Widget + err := db.First(&widget, widgetID).Error + + if err != nil { + return err + } + err = db.Model(&widget).Association("Files").Append(&fileObj).Error + } + if simulationmodelID != -1 { + // associate to Simulation Model + var model common.SimulationModel + err := db.First(&model, simulationmodelID).Error + if err != nil { + return err + } + err = db.Model(&model).Association("Files").Append(&fileObj).Error + } + + return err +} + +func SaveFile(file *multipart.FileHeader, filename string, foldername string, size uint, ) error { + + filesavepath := filepath.Join(foldername, filename) + + // Ensure folder with name foldername exists + err := os.MkdirAll(foldername, os.ModePerm) + if err != nil { + // TODO error handling + return err + } + + fileTarget , errcreate := os.OpenFile(filesavepath, os.O_RDWR|os.O_CREATE|os.O_EXCL, 0666) + if errcreate != nil { + // TODO error handling: File could not be created + return errcreate + } + defer fileTarget.Close() + + + // Save file to target path + uploadedFile, erropen := file.Open() + if erropen != nil { + // TODO error handling + return erropen + } + defer uploadedFile.Close() + + var uploadContent = make([]byte, size) + for { + + n, err := uploadedFile.Read(uploadContent) + if err != nil && err != io.EOF { + // TODO error handling + return err + } + + if n == 0 { + break + } + + _, err = fileTarget.Write(uploadContent[:n]) + if err != nil { + // TODO error handling + return err + } + + } + + return err + +} \ No newline at end of file