mirror of
https://git.rwth-aachen.de/acs/public/villas/web-backend-go/
synced 2025-03-30 00:00:12 +01:00
Add file upload mechanism
This commit is contained in:
parent
4dfa4fd5f4
commit
75f8ef280d
5 changed files with 177 additions and 40 deletions
|
@ -10,12 +10,14 @@ import (
|
||||||
func ProvideErrorResponse(c *gin.Context, err error) bool {
|
func ProvideErrorResponse(c *gin.Context, err error) bool {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if err == gorm.ErrRecordNotFound {
|
if err == gorm.ErrRecordNotFound {
|
||||||
|
errormsg := "Record not Found in DB: " + err.Error()
|
||||||
c.JSON(http.StatusNotFound, gin.H{
|
c.JSON(http.StatusNotFound, gin.H{
|
||||||
"error": "No files found in DB",
|
"error": errormsg,
|
||||||
})
|
})
|
||||||
} else {
|
} else {
|
||||||
|
errormsg := "Error on DB Query or transaction: " + err.Error()
|
||||||
c.JSON(http.StatusInternalServerError, gin.H{
|
c.JSON(http.StatusInternalServerError, gin.H{
|
||||||
"error": "Error on DB Query or transaction",
|
"error": errormsg,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
return true // Error
|
return true // Error
|
||||||
|
|
|
@ -4,16 +4,12 @@ import (
|
||||||
"strconv"
|
"strconv"
|
||||||
|
|
||||||
"net/http"
|
"net/http"
|
||||||
|
|
||||||
"github.com/gin-gonic/gin"
|
"github.com/gin-gonic/gin"
|
||||||
"github.com/gin-gonic/gin/json"
|
|
||||||
|
|
||||||
"git.rwth-aachen.de/acs/public/villas/villasweb-backend-go/common"
|
"git.rwth-aachen.de/acs/public/villas/villasweb-backend-go/common"
|
||||||
)
|
)
|
||||||
|
|
||||||
func FilesRegister(r *gin.RouterGroup) {
|
func FilesRegister(r *gin.RouterGroup) {
|
||||||
r.GET("/", filesReadEp)
|
r.GET("/", filesReadEp)
|
||||||
r.POST("/", fileRegistrationEp) // NEW in API
|
|
||||||
r.PUT("/:FileID", fileUpdateEp) // NEW in API
|
r.PUT("/:FileID", fileUpdateEp) // NEW in API
|
||||||
r.GET("/:FileID", fileReadEp)
|
r.GET("/:FileID", fileReadEp)
|
||||||
r.DELETE("/:FileID", fileDeleteEp)
|
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) {
|
func fileUpdateEp(c *gin.Context) {
|
||||||
c.JSON(http.StatusOK, gin.H{
|
c.JSON(http.StatusOK, gin.H{
|
||||||
|
|
|
@ -38,16 +38,3 @@ func FindFile(userID int, fileID string) ( common.File, error) {
|
||||||
return file, err
|
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
|
|
||||||
|
|
||||||
}
|
|
|
@ -2,7 +2,12 @@ package simulation
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/gin-gonic/gin"
|
"github.com/gin-gonic/gin"
|
||||||
|
"fmt"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
"path/filepath"
|
||||||
|
"strconv"
|
||||||
|
|
||||||
|
"git.rwth-aachen.de/acs/public/villas/villasweb-backend-go/common"
|
||||||
)
|
)
|
||||||
|
|
||||||
func SimulationsRegister(r *gin.RouterGroup) {
|
func SimulationsRegister(r *gin.RouterGroup) {
|
||||||
|
@ -11,6 +16,7 @@ func SimulationsRegister(r *gin.RouterGroup) {
|
||||||
r.PUT("/:SimulationID", simulationUpdateEp)
|
r.PUT("/:SimulationID", simulationUpdateEp)
|
||||||
r.GET("/:SimulationID", simulationReadEp)
|
r.GET("/:SimulationID", simulationReadEp)
|
||||||
r.DELETE("/:SimulationID", simulationDeleteEp)
|
r.DELETE("/:SimulationID", simulationDeleteEp)
|
||||||
|
r.POST ("/:SimulationID/models/:SimulationModelID/file", fileRegistrationEp) // NEW
|
||||||
}
|
}
|
||||||
|
|
||||||
func simulationsReadEp(c *gin.Context) {
|
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.",
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
|
@ -1,6 +1,11 @@
|
||||||
package simulation
|
package simulation
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"io"
|
||||||
|
"mime/multipart"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
|
||||||
"git.rwth-aachen.de/acs/public/villas/villasweb-backend-go/common"
|
"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
|
err := db.Model(user).Related(&simulations, "Simulations").Error
|
||||||
return simulations, len(simulations), err
|
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
|
||||||
|
|
||||||
|
}
|
Loading…
Add table
Reference in a new issue