From 17d88f4c6789a4e3def54bbb02c912e57178c690 Mon Sep 17 00:00:00 2001 From: Sonja Happ Date: Mon, 20 May 2019 16:17:54 +0200 Subject: [PATCH] Work on endpoints involving file management, To be tested, WIP --- routes/file/fileQueries.go | 290 +++++++++++++++++- routes/simulation/simulationEndpoints.go | 71 +---- routes/simulation/simulationQueries.go | 104 ------- .../simulationmodelEnpoints.go | 108 ++++++- 4 files changed, 394 insertions(+), 179 deletions(-) diff --git a/routes/file/fileQueries.go b/routes/file/fileQueries.go index 96e8760..92219b0 100644 --- a/routes/file/fileQueries.go +++ b/routes/file/fileQueries.go @@ -2,8 +2,14 @@ package file import ( "fmt" + "io" + "mime/multipart" + "net/http" + "os" + "path/filepath" "strconv" + "github.com/gin-gonic/gin" _ "github.com/gin-gonic/gin" "git.rwth-aachen.de/acs/public/villas/villasweb-backend-go/common" @@ -28,7 +34,6 @@ func FindUserFiles(user *common.User) ([]common.File, int, error) { } func FindFile(userID int, fileID string) ( common.File, error) { - //TODO Check here if user owns the file var file common.File db := common.GetDB() fileID_i, _ := strconv.Atoi(fileID) @@ -37,4 +42,287 @@ func FindFile(userID int, fileID string) ( common.File, error) { return file, err +} + +func FindFileByPath(path string) (common.File, error) { + var file common.File + db := common.GetDB() + err := db.Where("Path = ?", path).Find(file).Error + + return file, err +} + +func RegisterFile(c *gin.Context, widgetID int, simulationmodelID int, simulationID int){ + + // Extract file from PUT request form + file_header, 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.Header.Get("Content-Type") // TODO make sure this is properly set in file header + filename := filepath.Base(file_header.Filename) + foldername := getFolderName(simulationID, simulationmodelID, widgetID) + size := file_header.Size + + // Save file to local disc (NOT DB!) + err = modifyFileOnDisc(file_header, filename, foldername, uint(size), true) + 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 + saveFileInDB(c, filename, foldername, filetype, uint(size), widgetID, simulationmodelID, true) + +} + +func UpdateFile(c *gin.Context, widgetID int, simulationmodelID int, simulationID int){ + + // Extract file from PUT request form + file_header, 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; + } + + filename := filepath.Base(file_header.Filename) + filetype := file_header.Header.Get("Content-Type") // TODO make sure this is properly set in file header + size := file_header.Size + foldername := getFolderName(simulationID, simulationmodelID, widgetID) + + err = modifyFileOnDisc(file_header, filename, foldername, uint(size), false) + if err != nil { + errormsg := fmt.Sprintf("Internal Server Error. Error saving file: %s", err.Error()) + c.JSON(http.StatusInternalServerError, gin.H{ + "error": errormsg, + }) + return + } + + saveFileInDB(c, filename, foldername, filetype, uint(size), widgetID, simulationmodelID, false) +} + +func ReadFile(c *gin.Context, widgetID int, simulationmodelID int, simulationID int){ + + contentType := c.GetHeader("Content-Type") + + db := common.GetDB() + var fileInDB common.File + if widgetID != -1 { + // get associated Widget + var wdgt common.Widget + err := db.First(&wdgt, simulationmodelID).Error + if common.ProvideErrorResponse(c, err) { + return + } + err = db.Model(&wdgt).Related(&fileInDB).Where("Type = ?", contentType).Error + if common.ProvideErrorResponse(c, err) { + return + } + + } else if simulationmodelID != -1 { + + // get associated Simulation Model + var model common.SimulationModel + err := db.First(&model, simulationmodelID).Error + if common.ProvideErrorResponse(c, err) { + return + } + err = db.Model(&model).Related(&fileInDB).Where("Type = ?", contentType).Error + if common.ProvideErrorResponse(c, err) { + return + } + } + + //Seems this headers needed for some browsers (for example without this headers Chrome will download files as txt) + c.Header("Content-Description", "File Transfer") + c.Header("Content-Transfer-Encoding", "binary") + c.Header("Content-Disposition", "attachment; filename="+fileInDB.Name ) + c.Header("Content-Type", contentType) + c.File(fileInDB.Path) + + c.JSON(http.StatusOK, gin.H{ + "message": "OK.", + }) +} + +func DeleteFile(c *gin.Context, widgetID int, simulationmodelID int, simulationID int){ + // TODO +} + + +func saveFileInDB(c *gin.Context, filename string, foldername string, filetype string, size uint, widgetID int, simulationmodelID int, createObj bool) { + + filesavepath := filepath.Join(foldername, filename) + + // get last modify time of target file + fileinfo, err := os.Stat(filesavepath) + if err != nil { + errormsg := fmt.Sprintf("Internal Server Error. Error stat on file: %s", err.Error()) + c.JSON(http.StatusInternalServerError, gin.H{ + "error": errormsg, + }) + return + } + 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 { + + if createObj { + // associate to Widget + var wdgt common.Widget + err := db.First(&wdgt, widgetID).Error + if common.ProvideErrorResponse(c, err) { + return + } + err = db.Model(&wdgt).Association("Files").Append(&fileObj).Error + } else { + // update file obj in DB + fileInDB, err := FindFileByPath(filesavepath) + if common.ProvideErrorResponse(c, err){ + return + } + + err = db.Model(&fileInDB).Where("Path = ?", filesavepath).Updates(map[string]interface{}{"Size": fileObj.Size, "Date": fileObj.Date, "ImageHeight": fileObj.ImageHeight, "ImageWidth": fileObj.ImageWidth}).Error + + } + + if common.ProvideErrorResponse(c, err) == false { + c.JSON(http.StatusOK, gin.H{ + "message": "OK.", + "fileID": fileObj.ID, + }) + return + } + + } + if simulationmodelID != -1 { + + if createObj { + // associate to Simulation Model + db := common.GetDB() + var model common.SimulationModel + err := db.First(&model, simulationmodelID).Error + if common.ProvideErrorResponse(c, err) { + return + } + err = db.Model(&model).Association("Files").Append(&fileObj).Error + } else { + // update file obj in DB + fileInDB, err := FindFileByPath(filesavepath) + if common.ProvideErrorResponse(c, err){ + return + } + + err = db.Model(&fileInDB).Where("Path = ?", filesavepath).Updates(map[string]interface{}{"Size": fileObj.Size, "Date": fileObj.Date, "ImageHeight": fileObj.ImageHeight, "ImageWidth": fileObj.ImageWidth}).Error + } + + if common.ProvideErrorResponse(c, err) == false { + c.JSON(http.StatusOK, gin.H{ + "message": "OK.", + "fileID": fileObj.ID, + }) + return + } + } +} + +func modifyFileOnDisc(file_header *multipart.FileHeader, filename string, foldername string, size uint, createFile bool) error { + + filesavepath := filepath.Join(foldername, filename) + var err error + + if createFile { + // Ensure folder with name foldername exists + err = os.MkdirAll(foldername, os.ModePerm) + } else { + // test if file exists + _, err = os.Stat(filesavepath) + } + if err != nil { + return err + } + + var open_options int + if createFile { + // create file it not exists, file MUST not exist + open_options = os.O_RDWR|os.O_CREATE|os.O_EXCL + } else { + open_options = os.O_RDWR + } + + fileTarget , err := os.OpenFile(filesavepath, open_options, 0666) + if err != nil { + return err + } + defer fileTarget.Close() + + // Save file to target path + uploadedFile, err := file_header.Open() + if err != nil { + return err + } + defer uploadedFile.Close() + + var uploadContent = make([]byte, size) + for { + + n, err := uploadedFile.Read(uploadContent) + if err != nil && err != io.EOF { + return err + } + + if n == 0 { + break + } + + _, err = fileTarget.Write(uploadContent[:n]) + if err != nil { + return err + } + + } + return err +} + + +func getFolderName(simulationID int, simulationmodelID int, widgetID int) string { + base_foldername := "files/" + elementname := "" + elementid := 0 + if simulationmodelID == -1{ + elementname = "/widget_" + elementid = widgetID + } else { + elementname = "/simulationmodel_" + elementid = simulationmodelID + } + + + foldername := base_foldername + "simulation_"+ string(simulationID) + elementname + string(elementid) + "/" + return foldername } \ No newline at end of file diff --git a/routes/simulation/simulationEndpoints.go b/routes/simulation/simulationEndpoints.go index e49aae8..fc05749 100644 --- a/routes/simulation/simulationEndpoints.go +++ b/routes/simulation/simulationEndpoints.go @@ -1,22 +1,21 @@ 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" + "github.com/gin-gonic/gin" ) func SimulationsRegister(r *gin.RouterGroup) { + + //simulations r.GET("/", simulationsReadEp) r.POST("/", simulationRegistrationEp) 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) { @@ -51,66 +50,18 @@ func simulationDeleteEp(c *gin.Context) { }) } -func fileRegistrationEp(c *gin.Context) { +func GetSimulationID(c *gin.Context) (int, error) { - // 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 + simulationID, err := strconv.Atoi(c.Param("SimulationID")) - 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") + if err != nil { + errormsg := fmt.Sprintf("Bad request. No or incorrect format of simulation ID") c.JSON(http.StatusBadRequest, gin.H{ "error": errormsg, }) - return; + return -1, err + } else { + return simulationID, err + } - - // 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 2b7ecf7..58b88ce 100644 --- a/routes/simulation/simulationQueries.go +++ b/routes/simulation/simulationQueries.go @@ -1,11 +1,6 @@ package simulation import ( - "io" - "mime/multipart" - "os" - "path/filepath" - "git.rwth-aachen.de/acs/public/villas/villasweb-backend-go/common" ) @@ -22,102 +17,3 @@ 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 diff --git a/routes/simulationmodel/simulationmodelEnpoints.go b/routes/simulationmodel/simulationmodelEnpoints.go index 7f41593..868de1e 100644 --- a/routes/simulationmodel/simulationmodelEnpoints.go +++ b/routes/simulationmodel/simulationmodelEnpoints.go @@ -1,18 +1,31 @@ package simulationmodel import ( - "github.com/gin-gonic/gin" + "fmt" "net/http" + "strconv" + + "github.com/gin-gonic/gin" + + "git.rwth-aachen.de/acs/public/villas/villasweb-backend-go/routes/file" + "git.rwth-aachen.de/acs/public/villas/villasweb-backend-go/routes/simulation" ) func SimulationModelsRegister(r *gin.RouterGroup) { - r.GET("/", simulationmodelsReadEp) - r.POST("/", simulationmodelRegistrationEp) - r.PUT("/:SimulationModelID", simulationmodelUpdateEp) - r.GET("/:SimulationModelID", simulationmodelReadEp) - r.DELETE("/:SimulationModelID", simulationmodelDeleteEp) - r.GET("/:SimulationModelID/file", simulationmodelReadFileEp) // NEW in API - r.PUT("/:SimulationModelID/file", simulationmodelUpdateFileEp) // NEW in API + r.GET("/:SimulationID/models/", simulationmodelsReadEp) + r.POST("/:SimulationID/models/", simulationmodelRegistrationEp) + + r.PUT("/:SimulationID/models/:SimulationModelID", simulationmodelUpdateEp) + r.GET("/:SimulationID/models/:SimulationModelID", simulationmodelReadEp) + r.DELETE("/:SimulationID/models/:SimulationModelID", simulationmodelDeleteEp) + + // Files + r.POST ("/:SimulationID/models/:SimulationModelID/file", simulationmodelRegisterFileEp) // NEW in API + r.GET("/:SimulationID/models/:SimulationModelID/file", simulationmodelReadFileEp) // NEW in API + r.PUT("/:SimulationID/models/:SimulationModelID/file", simulationmodelUpdateFileEp) // NEW in API + r.DELETE("/:SimulationID/models/:SimulationModelID/file", simulationmodelDeleteFileEp) // NEW in API + + } func simulationmodelsReadEp(c *gin.Context) { @@ -47,14 +60,81 @@ func simulationmodelDeleteEp(c *gin.Context) { }) } + +func simulationmodelRegisterFileEp(c *gin.Context) { + + simulationID, simulationmodelID, err := getRequestParams(c) + if err != nil{ + return + } + + // Save file locally and register file in DB, HTTP response is set by this method + file.RegisterFile(c,-1, simulationmodelID, simulationID) + +} + func simulationmodelReadFileEp(c *gin.Context) { - c.JSON(http.StatusOK, gin.H{ - "message": "NOT implemented", - }) + + simulationID, simulationmodelID, err := getRequestParams(c) + if err != nil{ + return + } + + // Read file from disk and return in HTTP response, no change to DB + file.ReadFile(c, -1, simulationmodelID, simulationID) } func simulationmodelUpdateFileEp(c *gin.Context) { - c.JSON(http.StatusOK, gin.H{ - "message": "NOT implemented", - }) + + simulationID, simulationmodelID, err := getRequestParams(c) + if err != nil{ + return + } + + // Update file locally and update file entry in DB, HTTP response is set by this method + file.UpdateFile(c,-1, simulationmodelID, simulationID) +} + +func simulationmodelDeleteFileEp(c *gin.Context) { + + simulationID, simulationmodelID, err := getRequestParams(c) + if err != nil{ + return + } + + // Delete file from disk and remove entry from DB, HTTP response is set by this method + file.DeleteFile(c, -1, simulationmodelID, simulationID) + + +} + + +func GetSimulationmodelID(c *gin.Context) (int, error) { + + simulationmodelID, err := strconv.Atoi(c.Param("SimulationModelID")) + + if err != nil { + errormsg := fmt.Sprintf("Bad request. No or incorrect format of simulation model ID") + c.JSON(http.StatusBadRequest, gin.H{ + "error": errormsg, + }) + return -1, err + } else { + return simulationmodelID, err + + } +} + +func getRequestParams(c *gin.Context) (int, int, error){ + simulationID, err := simulation.GetSimulationID(c) + if err != nil{ + return -1, -1, err + } + + simulationmodelID, err := GetSimulationmodelID(c) + if err != nil{ + return -1, -1, err + } + + return simulationID, simulationmodelID, err } \ No newline at end of file