mirror of
https://git.rwth-aachen.de/acs/public/villas/web-backend-go/
synced 2025-03-30 00:00:12 +01:00
working on file endpoints; testing to be completed, other tests to be checked
This commit is contained in:
parent
cde0ee01c2
commit
fc91ebc64c
11 changed files with 521 additions and 182 deletions
|
@ -117,8 +117,12 @@ func DummyPopulateDB(test_db *gorm.DB) {
|
||||||
|
|
||||||
file_A := File{Name: "File_A"}
|
file_A := File{Name: "File_A"}
|
||||||
file_B := File{Name: "File_B"}
|
file_B := File{Name: "File_B"}
|
||||||
|
file_C := File{Name: "File_C"}
|
||||||
|
file_D := File{Name: "File_D"}
|
||||||
checkErr(test_db.Create(&file_A).Error)
|
checkErr(test_db.Create(&file_A).Error)
|
||||||
checkErr(test_db.Create(&file_B).Error)
|
checkErr(test_db.Create(&file_B).Error)
|
||||||
|
checkErr(test_db.Create(&file_C).Error)
|
||||||
|
checkErr(test_db.Create(&file_D).Error)
|
||||||
|
|
||||||
simn_A := Simulation{Name: "Simulation_A"}
|
simn_A := Simulation{Name: "Simulation_A"}
|
||||||
simn_B := Simulation{Name: "Simulation_B"}
|
simn_B := Simulation{Name: "Simulation_B"}
|
||||||
|
@ -186,8 +190,8 @@ func DummyPopulateDB(test_db *gorm.DB) {
|
||||||
checkErr(test_db.Model(&mo_A).Association("OutputMapping").Append(&outSig_B).Error)
|
checkErr(test_db.Model(&mo_A).Association("OutputMapping").Append(&outSig_B).Error)
|
||||||
|
|
||||||
// SimulationModel HM Files
|
// SimulationModel HM Files
|
||||||
checkErr(test_db.Model(&mo_A).Association("Files").Append(&file_A).Error)
|
checkErr(test_db.Model(&mo_A).Association("Files").Append(&file_C).Error)
|
||||||
checkErr(test_db.Model(&mo_A).Association("Files").Append(&file_B).Error)
|
checkErr(test_db.Model(&mo_A).Association("Files").Append(&file_D).Error)
|
||||||
|
|
||||||
// Simulator HM SimulationModels
|
// Simulator HM SimulationModels
|
||||||
checkErr(test_db.Model(&simr_A).Association("SimulationModels").Append(&mo_A).Error)
|
checkErr(test_db.Model(&simr_A).Association("SimulationModels").Append(&mo_A).Error)
|
||||||
|
|
|
@ -1,9 +1,5 @@
|
||||||
package common
|
package common
|
||||||
|
|
||||||
import (
|
|
||||||
"time"
|
|
||||||
)
|
|
||||||
|
|
||||||
// User data model
|
// User data model
|
||||||
type User struct {
|
type User struct {
|
||||||
// ID of user
|
// ID of user
|
||||||
|
@ -155,7 +151,7 @@ type File struct {
|
||||||
// Name of file
|
// Name of file
|
||||||
Name string `gorm:"not null"`
|
Name string `gorm:"not null"`
|
||||||
// Path at which file is saved at server side
|
// Path at which file is saved at server side
|
||||||
Path string `gorm:"not null"`
|
//Path string `gorm:"not null"`
|
||||||
// Type of file (MIME type)
|
// Type of file (MIME type)
|
||||||
Type string `gorm:"not null"`
|
Type string `gorm:"not null"`
|
||||||
// Size of file (in byte)
|
// Size of file (in byte)
|
||||||
|
@ -165,9 +161,11 @@ type File struct {
|
||||||
// Width of image (only needed in case of image)
|
// Width of image (only needed in case of image)
|
||||||
ImageWidth uint
|
ImageWidth uint
|
||||||
// Last modification time of file
|
// Last modification time of file
|
||||||
Date time.Time
|
Date string
|
||||||
// ID of model to which file belongs
|
// ID of model to which file belongs
|
||||||
SimulationModelID uint `gorm:""`
|
SimulationModelID uint
|
||||||
// ID of widget to which file belongs
|
// ID of widget to which file belongs
|
||||||
WidgetID uint `gorm:""`
|
WidgetID uint
|
||||||
|
// TODO Add file itself here??
|
||||||
|
FileData []byte `gorm:"column:FileData"`
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,9 +1,5 @@
|
||||||
package common
|
package common
|
||||||
|
|
||||||
import (
|
|
||||||
"time"
|
|
||||||
)
|
|
||||||
|
|
||||||
type UserResponse struct {
|
type UserResponse struct {
|
||||||
Username string `json:"Username"`
|
Username string `json:"Username"`
|
||||||
Role string `json:"Role"`
|
Role string `json:"Role"`
|
||||||
|
@ -65,12 +61,13 @@ type WidgetResponse struct {
|
||||||
type FileResponse struct {
|
type FileResponse struct {
|
||||||
Name string `json:"Name"`
|
Name string `json:"Name"`
|
||||||
ID uint `json:"ID"`
|
ID uint `json:"ID"`
|
||||||
Path string `json:"Path"`
|
|
||||||
Type string `json:"Type"`
|
Type string `json:"Type"`
|
||||||
Size uint `json:"Size"`
|
Size uint `json:"Size"`
|
||||||
H uint `json:"ImageHeight"`
|
H uint `json:"ImageHeight"`
|
||||||
W uint `json:"ImageWidth"`
|
W uint `json:"ImageWidth"`
|
||||||
Date time.Time `json:"Date"`
|
Date string `json:"Date"`
|
||||||
|
WidgetID uint `json:"WidgetID"`
|
||||||
|
SimulationModelID uint `json:"SimulationModelID"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type SignalResponse struct {
|
type SignalResponse struct {
|
||||||
|
@ -142,3 +139,11 @@ type ResponseMsgSimulators struct {
|
||||||
type ResponseMsgSimulator struct {
|
type ResponseMsgSimulator struct {
|
||||||
Simulator SimulatorResponse `json:"simulator"`
|
Simulator SimulatorResponse `json:"simulator"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type ResponseMsgFiles struct {
|
||||||
|
Files []FileResponse `json:"files"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type ResponseMsgFile struct {
|
||||||
|
File FileResponse `json:"file"`
|
||||||
|
}
|
||||||
|
|
|
@ -21,6 +21,7 @@ const ModelVisualization = ModelName("visualization")
|
||||||
const ModelWidget = ModelName("widget")
|
const ModelWidget = ModelName("widget")
|
||||||
const ModelSimulationModel = ModelName("simulationmodel")
|
const ModelSimulationModel = ModelName("simulationmodel")
|
||||||
const ModelSignal = ModelName("signal")
|
const ModelSignal = ModelName("signal")
|
||||||
|
const ModelFile = ModelName("file")
|
||||||
|
|
||||||
type CRUD string
|
type CRUD string
|
||||||
|
|
||||||
|
@ -54,6 +55,7 @@ var Roles = RoleActions{
|
||||||
ModelWidget: crud,
|
ModelWidget: crud,
|
||||||
ModelVisualization: crud,
|
ModelVisualization: crud,
|
||||||
ModelSignal: crud,
|
ModelSignal: crud,
|
||||||
|
ModelFile: crud,
|
||||||
},
|
},
|
||||||
"User": {
|
"User": {
|
||||||
ModelUser: _ru_,
|
ModelUser: _ru_,
|
||||||
|
@ -63,6 +65,7 @@ var Roles = RoleActions{
|
||||||
ModelWidget: crud,
|
ModelWidget: crud,
|
||||||
ModelVisualization: crud,
|
ModelVisualization: crud,
|
||||||
ModelSignal: crud,
|
ModelSignal: crud,
|
||||||
|
ModelFile: crud,
|
||||||
},
|
},
|
||||||
"Guest": {
|
"Guest": {
|
||||||
ModelSimulation: _r__,
|
ModelSimulation: _r__,
|
||||||
|
@ -72,6 +75,7 @@ var Roles = RoleActions{
|
||||||
ModelSimulator: _r__,
|
ModelSimulator: _r__,
|
||||||
ModelUser: _ru_,
|
ModelUser: _ru_,
|
||||||
ModelSignal: _r__,
|
ModelSignal: _r__,
|
||||||
|
ModelFile: _r__,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -248,12 +248,14 @@ func (self *FileSerializerNoAssoc) Response() FileResponse {
|
||||||
response := FileResponse{
|
response := FileResponse{
|
||||||
Name: self.Name,
|
Name: self.Name,
|
||||||
ID: self.ID,
|
ID: self.ID,
|
||||||
Path: self.Path,
|
//Path: self.Path,
|
||||||
Type: self.Type,
|
Type: self.Type,
|
||||||
Size: self.Size,
|
Size: self.Size,
|
||||||
H: self.ImageHeight,
|
H: self.ImageHeight,
|
||||||
W: self.ImageWidth,
|
W: self.ImageWidth,
|
||||||
// Date
|
Date: self.Date,
|
||||||
|
WidgetID: self.WidgetID,
|
||||||
|
SimulationModelID: self.SimulationModelID,
|
||||||
}
|
}
|
||||||
return response
|
return response
|
||||||
}
|
}
|
||||||
|
|
|
@ -8,6 +8,8 @@ import (
|
||||||
"github.com/gin-gonic/gin"
|
"github.com/gin-gonic/gin"
|
||||||
|
|
||||||
"git.rwth-aachen.de/acs/public/villas/villasweb-backend-go/common"
|
"git.rwth-aachen.de/acs/public/villas/villasweb-backend-go/common"
|
||||||
|
"git.rwth-aachen.de/acs/public/villas/villasweb-backend-go/routes/simulationmodel"
|
||||||
|
"git.rwth-aachen.de/acs/public/villas/villasweb-backend-go/routes/widget"
|
||||||
)
|
)
|
||||||
|
|
||||||
func RegisterFileEndpoints(r *gin.RouterGroup) {
|
func RegisterFileEndpoints(r *gin.RouterGroup) {
|
||||||
|
@ -28,8 +30,8 @@ func RegisterFileEndpoints(r *gin.RouterGroup) {
|
||||||
// @Failure 403 "Access forbidden."
|
// @Failure 403 "Access forbidden."
|
||||||
// @Failure 404 "Not found"
|
// @Failure 404 "Not found"
|
||||||
// @Failure 500 "Internal server error"
|
// @Failure 500 "Internal server error"
|
||||||
// @Param originType query string true "Set to model for files of model, set to widget for files of widget"
|
// @Param objectType query string true "Set to model for files of model, set to widget for files of widget"
|
||||||
// @Param originID query int true "ID of either model or widget of which files are requested"
|
// @Param objectID query int true "ID of either model or widget of which files are requested"
|
||||||
// @Router /files [get]
|
// @Router /files [get]
|
||||||
func getFiles(c *gin.Context) {
|
func getFiles(c *gin.Context) {
|
||||||
|
|
||||||
|
@ -51,15 +53,33 @@ func getFiles(c *gin.Context) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//Check access
|
||||||
|
var ok bool
|
||||||
|
var m simulationmodel.SimulationModel
|
||||||
|
var w widget.Widget
|
||||||
|
if objectType == "model" {
|
||||||
|
ok, m = simulationmodel.CheckPermissions(c, common.Read, "body", objectID)
|
||||||
|
if !ok {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
ok, w = widget.CheckPermissions(c, common.Read, objectID)
|
||||||
|
if !ok {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// get meta data of files
|
||||||
db := common.GetDB()
|
db := common.GetDB()
|
||||||
|
|
||||||
var files []common.File
|
var files []common.File
|
||||||
if objectType == "model" {
|
if objectType == "model" {
|
||||||
err = db.Where("ModelID", objectID).Find(&files).Error
|
err = db.Order("ID asc").Model(&m).Related(&files, "Files").Error
|
||||||
if common.ProvideErrorResponse(c, err) {
|
if common.ProvideErrorResponse(c, err) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
err = db.Where("WidgetID", objectID).Find(&files).Error
|
err = db.Order("ID asc").Model(&w).Related(&files, "Files").Error
|
||||||
if common.ProvideErrorResponse(c, err) {
|
if common.ProvideErrorResponse(c, err) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
@ -93,15 +113,6 @@ func getFiles(c *gin.Context) {
|
||||||
// @Param objectID query int true "ID of either model or widget of which files are requested"
|
// @Param objectID query int true "ID of either model or widget of which files are requested"
|
||||||
// @Router /files [post]
|
// @Router /files [post]
|
||||||
func addFile(c *gin.Context) {
|
func addFile(c *gin.Context) {
|
||||||
// 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
|
|
||||||
}
|
|
||||||
|
|
||||||
objectType := c.Request.URL.Query().Get("objectType")
|
objectType := c.Request.URL.Query().Get("objectType")
|
||||||
if objectType != "model" && objectType != "widget" {
|
if objectType != "model" && objectType != "widget" {
|
||||||
|
@ -121,6 +132,30 @@ func addFile(c *gin.Context) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Check access
|
||||||
|
var ok bool
|
||||||
|
if objectType == "model" {
|
||||||
|
ok, _ = simulationmodel.CheckPermissions(c, common.Create, "body", objectID)
|
||||||
|
if !ok {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
ok, _ = widget.CheckPermissions(c, common.Create, objectID)
|
||||||
|
if !ok {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Extract file from POST 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
|
||||||
|
}
|
||||||
|
|
||||||
var newFile File
|
var newFile File
|
||||||
err = newFile.register(file_header, objectType, uint(objectID))
|
err = newFile.register(file_header, objectType, uint(objectID))
|
||||||
if common.ProvideErrorResponse(c, err) == false {
|
if common.ProvideErrorResponse(c, err) == false {
|
||||||
|
@ -149,22 +184,16 @@ func addFile(c *gin.Context) {
|
||||||
// @Router /files/{fileID} [get]
|
// @Router /files/{fileID} [get]
|
||||||
func getFile(c *gin.Context) {
|
func getFile(c *gin.Context) {
|
||||||
|
|
||||||
fileID, err := common.GetFileID(c)
|
// check access
|
||||||
if err != nil {
|
ok, f := checkPermissions(c, common.Read)
|
||||||
|
if !ok {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
var f File
|
err := f.download(c)
|
||||||
err = f.byID(uint(fileID))
|
|
||||||
if common.ProvideErrorResponse(c, err) {
|
if common.ProvideErrorResponse(c, err) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
f.download(c)
|
|
||||||
|
|
||||||
c.JSON(http.StatusOK, gin.H{
|
|
||||||
"message": "OK.",
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// updateFile godoc
|
// updateFile godoc
|
||||||
|
@ -188,6 +217,12 @@ func getFile(c *gin.Context) {
|
||||||
// @Router /files/{fileID} [put]
|
// @Router /files/{fileID} [put]
|
||||||
func updateFile(c *gin.Context) {
|
func updateFile(c *gin.Context) {
|
||||||
|
|
||||||
|
// check access
|
||||||
|
ok, f := checkPermissions(c, common.Update)
|
||||||
|
if !ok {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
// Extract file from PUT request form
|
// Extract file from PUT request form
|
||||||
err := c.Request.ParseForm()
|
err := c.Request.ParseForm()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -207,17 +242,6 @@ func updateFile(c *gin.Context) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
fileID, err := common.GetFileID(c)
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
var f File
|
|
||||||
err = f.byID(uint(fileID))
|
|
||||||
if common.ProvideErrorResponse(c, err) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
err = f.update(file_header)
|
err = f.update(file_header)
|
||||||
if common.ProvideErrorResponse(c, err) == false {
|
if common.ProvideErrorResponse(c, err) == false {
|
||||||
c.JSON(http.StatusOK, gin.H{
|
c.JSON(http.StatusOK, gin.H{
|
||||||
|
@ -239,7 +263,17 @@ func updateFile(c *gin.Context) {
|
||||||
// @Param fileID path int true "ID of the file to update"
|
// @Param fileID path int true "ID of the file to update"
|
||||||
// @Router /files/{fileID} [delete]
|
// @Router /files/{fileID} [delete]
|
||||||
func deleteFile(c *gin.Context) {
|
func deleteFile(c *gin.Context) {
|
||||||
|
|
||||||
|
// check access
|
||||||
|
ok, f := checkPermissions(c, common.Delete)
|
||||||
|
if !ok {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
err := f.delete()
|
||||||
|
if common.ProvideErrorResponse(c, err) == false {
|
||||||
c.JSON(http.StatusOK, gin.H{
|
c.JSON(http.StatusOK, gin.H{
|
||||||
"message": "Not implemented.",
|
"message": "OK.",
|
||||||
})
|
})
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,13 +2,12 @@ package file
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"github.com/gin-gonic/gin"
|
||||||
|
"io/ioutil"
|
||||||
"mime/multipart"
|
"mime/multipart"
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"strconv"
|
"time"
|
||||||
|
|
||||||
"github.com/gin-gonic/gin"
|
|
||||||
|
|
||||||
"git.rwth-aachen.de/acs/public/villas/villasweb-backend-go/common"
|
"git.rwth-aachen.de/acs/public/villas/villasweb-backend-go/common"
|
||||||
"git.rwth-aachen.de/acs/public/villas/villasweb-backend-go/routes/simulationmodel"
|
"git.rwth-aachen.de/acs/public/villas/villasweb-backend-go/routes/simulationmodel"
|
||||||
|
@ -38,29 +37,40 @@ func (f *File) byID(id uint) error {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (f *File) save() error {
|
func (f *File) save() error {
|
||||||
|
|
||||||
// get last modify time of target file
|
|
||||||
fileinfo, err := os.Stat(f.Path)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("error stat on file: %s", err.Error())
|
|
||||||
}
|
|
||||||
f.Date = fileinfo.ModTime()
|
|
||||||
f.ImageWidth = 0
|
|
||||||
f.ImageHeight = 0
|
|
||||||
|
|
||||||
db := common.GetDB()
|
db := common.GetDB()
|
||||||
err = db.Create(f).Error
|
err := db.Create(f).Error
|
||||||
|
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (f *File) download(c *gin.Context) error {
|
||||||
|
|
||||||
|
err := ioutil.WriteFile(f.Name, f.FileData, 0644)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("file could not be temporarily created on server disk: %s", err.Error())
|
||||||
|
}
|
||||||
|
defer os.Remove(f.Name)
|
||||||
|
//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="+f.Name)
|
||||||
|
//c.Header("Content-Type", contentType)
|
||||||
|
c.File(f.Name)
|
||||||
|
|
||||||
|
return nil
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
func (f *File) register(fileHeader *multipart.FileHeader, objectType string, objectID uint) error {
|
func (f *File) register(fileHeader *multipart.FileHeader, objectType string, objectID uint) error {
|
||||||
|
|
||||||
// Obtain properties of file
|
// Obtain properties of file
|
||||||
f.Type = fileHeader.Header.Get("Content-Type")
|
f.Type = fileHeader.Header.Get("Content-Type")
|
||||||
f.Name = filepath.Base(fileHeader.Filename)
|
f.Name = filepath.Base(fileHeader.Filename)
|
||||||
f.Path = filepath.Join(getFolderName(objectType, objectID), f.Name)
|
//f.Path = filepath.Join(getFolderName(objectType, objectID), f.Name)
|
||||||
f.Size = uint(fileHeader.Size)
|
f.Size = uint(fileHeader.Size)
|
||||||
|
f.Date = time.Now().String()
|
||||||
|
f.ImageWidth = 0 // TODO: do we need this?
|
||||||
|
f.ImageHeight = 0 // TODO: do we need this?
|
||||||
|
|
||||||
var m simulationmodel.SimulationModel
|
var m simulationmodel.SimulationModel
|
||||||
var w widget.Widget
|
var w widget.Widget
|
||||||
|
@ -68,12 +78,16 @@ func (f *File) register(fileHeader *multipart.FileHeader, objectType string, obj
|
||||||
if objectType == "model" {
|
if objectType == "model" {
|
||||||
// check if model exists
|
// check if model exists
|
||||||
err = m.ByID(objectID)
|
err = m.ByID(objectID)
|
||||||
|
f.WidgetID = 0
|
||||||
|
f.SimulationModelID = objectID
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
// check if widget exists
|
// check if widget exists
|
||||||
|
f.WidgetID = objectID
|
||||||
|
f.SimulationModelID = 0
|
||||||
err = w.ByID(uint(objectID))
|
err = w.ByID(uint(objectID))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
|
@ -81,12 +95,21 @@ func (f *File) register(fileHeader *multipart.FileHeader, objectType string, obj
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Save file to local disc (NOT DB!)
|
// set file data
|
||||||
err = f.modifyFileOnDisc(fileHeader, true)
|
fileContent, err := fileHeader.Open()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("File could not be saved/ modified on disk: ", err.Error())
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
f.FileData, err = ioutil.ReadAll(fileContent)
|
||||||
|
defer fileContent.Close()
|
||||||
|
|
||||||
|
// Save file to local disc (NOT DB!)
|
||||||
|
//err = f.modifyFileOnDisc(fileHeader, true)
|
||||||
|
//if err != nil {
|
||||||
|
// return fmt.Errorf("File could not be saved/ modified on disk: ", err.Error())
|
||||||
|
//}
|
||||||
|
|
||||||
// Add File object with parameters to DB
|
// Add File object with parameters to DB
|
||||||
err = f.save()
|
err = f.save()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -95,12 +118,14 @@ func (f *File) register(fileHeader *multipart.FileHeader, objectType string, obj
|
||||||
|
|
||||||
// Create association to model or widget
|
// Create association to model or widget
|
||||||
if objectType == "model" {
|
if objectType == "model" {
|
||||||
err = f.addToModel(m)
|
db := common.GetDB()
|
||||||
|
err := db.Model(&m).Association("Files").Append(f).Error
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
err = f.addToWidget(w)
|
db := common.GetDB()
|
||||||
|
err := db.Model(&w).Association("Files").Append(f).Error
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
@ -110,99 +135,94 @@ func (f *File) register(fileHeader *multipart.FileHeader, objectType string, obj
|
||||||
}
|
}
|
||||||
|
|
||||||
func (f *File) update(fileHeader *multipart.FileHeader) error {
|
func (f *File) update(fileHeader *multipart.FileHeader) error {
|
||||||
err := f.modifyFileOnDisc(fileHeader, false)
|
|
||||||
|
// set file data
|
||||||
|
fileContent, err := fileHeader.Open()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fileData, err := ioutil.ReadAll(fileContent)
|
||||||
|
fmt.Println("File content: ", string(fileData))
|
||||||
|
defer fileContent.Close()
|
||||||
|
|
||||||
|
//err := f.modifyFileOnDisc(fileHeader, false)
|
||||||
|
//if err != nil {
|
||||||
|
// return err
|
||||||
|
//}
|
||||||
|
|
||||||
db := common.GetDB()
|
db := common.GetDB()
|
||||||
err = db.Model(f).Update("Size", fileHeader.Size).Error
|
err = db.Model(f).Updates(map[string]interface{}{"Size": fileHeader.Size,
|
||||||
|
"FileData": fileData,
|
||||||
|
"Date": time.Now().String()}).Error
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
func (f *File) modifyFileOnDisc(fileHeader *multipart.FileHeader, createFile bool) error {
|
func (f *File) delete() error {
|
||||||
|
return nil
|
||||||
//filesavepath := filepath.Join(foldername, filename)
|
|
||||||
var err error
|
|
||||||
|
|
||||||
if createFile {
|
|
||||||
// Ensure folder with name foldername exists
|
|
||||||
err = os.MkdirAll(f.Path, os.ModePerm)
|
|
||||||
} else {
|
|
||||||
// test if file exists
|
|
||||||
_, err = os.Stat(f.Path)
|
|
||||||
}
|
|
||||||
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(f.Path, open_options, 0666)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
defer fileTarget.Close()
|
|
||||||
|
|
||||||
// Save file to target path
|
|
||||||
uploadedFile, err := fileHeader.Open()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
defer uploadedFile.Close()
|
|
||||||
|
|
||||||
var uploadContent = make([]byte, f.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 (f *File) download(c *gin.Context) {
|
//func (f *File) modifyFileOnDisc(fileHeader *multipart.FileHeader, createFile bool) error {
|
||||||
|
//
|
||||||
|
// //filesavepath := filepath.Join(foldername, filename)
|
||||||
|
// var err error
|
||||||
|
//
|
||||||
|
// if createFile {
|
||||||
|
// // Ensure folder with name foldername exists
|
||||||
|
// err = os.MkdirAll(f.Path, os.ModePerm)
|
||||||
|
// } else {
|
||||||
|
// // test if file exists
|
||||||
|
// _, err = os.Stat(f.Path)
|
||||||
|
// }
|
||||||
|
// 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(f.Path, open_options, 0666)
|
||||||
|
// if err != nil {
|
||||||
|
// return err
|
||||||
|
// }
|
||||||
|
// defer fileTarget.Close()
|
||||||
|
//
|
||||||
|
// // Save file to target path
|
||||||
|
// uploadedFile, err := fileHeader.Open()
|
||||||
|
// if err != nil {
|
||||||
|
// return err
|
||||||
|
// }
|
||||||
|
// defer uploadedFile.Close()
|
||||||
|
//
|
||||||
|
// var uploadContent = make([]byte, f.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
|
||||||
|
//}
|
||||||
|
|
||||||
//Seems this headers needed for some browsers (for example without this headers Chrome will download files as txt)
|
//func getFolderName(objectType string, objectID uint) string {
|
||||||
c.Header("Content-Description", "File Transfer")
|
// base_foldername := "files/"
|
||||||
c.Header("Content-Transfer-Encoding", "binary")
|
//
|
||||||
c.Header("Content-Disposition", "attachment; filename="+f.Name)
|
// foldername := base_foldername + objectType + "_" + strconv.Itoa(int(objectID)) + "/"
|
||||||
//c.Header("Content-Type", contentType)
|
// return foldername
|
||||||
c.File(f.Path)
|
//}
|
||||||
}
|
|
||||||
|
|
||||||
func (f *File) addToModel(model simulationmodel.SimulationModel) error {
|
|
||||||
db := common.GetDB()
|
|
||||||
err := db.Model(&model).Association("Files").Append(f).Error
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
func (f *File) addToWidget(widget widget.Widget) error {
|
|
||||||
db := common.GetDB()
|
|
||||||
err := db.Model(&widget).Association("Files").Append(f).Error
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
func getFolderName(objectType string, objectID uint) string {
|
|
||||||
base_foldername := "files/"
|
|
||||||
|
|
||||||
foldername := base_foldername + objectType + "_" + strconv.Itoa(int(objectID)) + "/"
|
|
||||||
return foldername
|
|
||||||
}
|
|
||||||
|
|
50
routes/file/fileMiddleware.go
Normal file
50
routes/file/fileMiddleware.go
Normal file
|
@ -0,0 +1,50 @@
|
||||||
|
package file
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"git.rwth-aachen.de/acs/public/villas/villasweb-backend-go/common"
|
||||||
|
"git.rwth-aachen.de/acs/public/villas/villasweb-backend-go/routes/simulationmodel"
|
||||||
|
"git.rwth-aachen.de/acs/public/villas/villasweb-backend-go/routes/widget"
|
||||||
|
"github.com/gin-gonic/gin"
|
||||||
|
"net/http"
|
||||||
|
"strconv"
|
||||||
|
)
|
||||||
|
|
||||||
|
func checkPermissions(c *gin.Context, operation common.CRUD) (bool, File) {
|
||||||
|
|
||||||
|
var f File
|
||||||
|
|
||||||
|
err := common.ValidateRole(c, common.ModelFile, operation)
|
||||||
|
if err != nil {
|
||||||
|
c.JSON(http.StatusUnprocessableEntity, "Access denied (role validation failed).")
|
||||||
|
return false, f
|
||||||
|
}
|
||||||
|
|
||||||
|
fileID, err := strconv.Atoi(c.Param("fileID"))
|
||||||
|
if err != nil {
|
||||||
|
errormsg := fmt.Sprintf("Bad request. No or incorrect format of fileID path parameter")
|
||||||
|
c.JSON(http.StatusBadRequest, gin.H{
|
||||||
|
"error": errormsg,
|
||||||
|
})
|
||||||
|
return false, f
|
||||||
|
}
|
||||||
|
|
||||||
|
err = f.byID(uint(fileID))
|
||||||
|
if common.ProvideErrorResponse(c, err) {
|
||||||
|
return false, f
|
||||||
|
}
|
||||||
|
|
||||||
|
if f.SimulationModelID > 0 {
|
||||||
|
ok, _ := simulationmodel.CheckPermissions(c, operation, "body", int(f.SimulationModelID))
|
||||||
|
if !ok {
|
||||||
|
return false, f
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
ok, _ := widget.CheckPermissions(c, operation, int(f.WidgetID))
|
||||||
|
if !ok {
|
||||||
|
return false, f
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return true, f
|
||||||
|
}
|
217
routes/file/file_test.go
Normal file
217
routes/file/file_test.go
Normal file
|
@ -0,0 +1,217 @@
|
||||||
|
package file
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"git.rwth-aachen.de/acs/public/villas/villasweb-backend-go/common"
|
||||||
|
"git.rwth-aachen.de/acs/public/villas/villasweb-backend-go/routes/user"
|
||||||
|
"github.com/gin-gonic/gin"
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
"io"
|
||||||
|
"io/ioutil"
|
||||||
|
"mime/multipart"
|
||||||
|
"net/http"
|
||||||
|
"net/http/httptest"
|
||||||
|
"os"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
var token string
|
||||||
|
var filecontent = "This is my testfile"
|
||||||
|
var filecontent_update = "This is my updated testfile with a dot at the end."
|
||||||
|
var filename = "testfile.txt"
|
||||||
|
var filename_update = "testfileupdate.txt"
|
||||||
|
|
||||||
|
type credentials struct {
|
||||||
|
Username string `json:"username"`
|
||||||
|
Password string `json:"password"`
|
||||||
|
}
|
||||||
|
|
||||||
|
var cred = credentials{
|
||||||
|
Username: "User_A",
|
||||||
|
Password: "abc123",
|
||||||
|
}
|
||||||
|
|
||||||
|
var msgOK = common.ResponseMsg{
|
||||||
|
Message: "OK.",
|
||||||
|
}
|
||||||
|
|
||||||
|
var FileA = common.FileResponse{
|
||||||
|
Name: "File_A",
|
||||||
|
ID: 1,
|
||||||
|
WidgetID: 1,
|
||||||
|
}
|
||||||
|
|
||||||
|
var FileB = common.FileResponse{
|
||||||
|
Name: "File_B",
|
||||||
|
ID: 2,
|
||||||
|
WidgetID: 1,
|
||||||
|
}
|
||||||
|
|
||||||
|
var myFiles = []common.FileResponse{
|
||||||
|
FileA,
|
||||||
|
FileB,
|
||||||
|
}
|
||||||
|
|
||||||
|
var msgFiles = common.ResponseMsgFiles{
|
||||||
|
Files: myFiles,
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test /models endpoints
|
||||||
|
func TestSignalEndpoints(t *testing.T) {
|
||||||
|
|
||||||
|
db := common.DummyInitDB()
|
||||||
|
defer db.Close()
|
||||||
|
common.DummyPopulateDB(db)
|
||||||
|
|
||||||
|
// create a testfile in local folder
|
||||||
|
c1 := []byte(filecontent)
|
||||||
|
c2 := []byte(filecontent_update)
|
||||||
|
err := ioutil.WriteFile(filename, c1, 0644)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
err = ioutil.WriteFile(filename_update, c2, 0644)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
router := gin.Default()
|
||||||
|
api := router.Group("/api")
|
||||||
|
|
||||||
|
// All endpoints require authentication except when someone wants to
|
||||||
|
// login (POST /authenticate)
|
||||||
|
user.VisitorAuthenticate(api.Group("/authenticate"))
|
||||||
|
|
||||||
|
api.Use(user.Authentication(true))
|
||||||
|
|
||||||
|
RegisterFileEndpoints(api.Group("/files"))
|
||||||
|
|
||||||
|
credjson, err := json.Marshal(cred)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
msgOKjson, err := json.Marshal(msgOK)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
msgFilesjson, err := json.Marshal(msgFiles)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
token = common.AuthenticateForTest(t, router, "/api/authenticate", "POST", credjson, 200)
|
||||||
|
|
||||||
|
// test GET files
|
||||||
|
common.TestEndpoint(t, router, token, "/api/files?objectID=1&objectType=widget", "GET", nil, 200, string(msgFilesjson))
|
||||||
|
|
||||||
|
// test POST files
|
||||||
|
bodyBuf := &bytes.Buffer{}
|
||||||
|
bodyWriter := multipart.NewWriter(bodyBuf)
|
||||||
|
fileWriter, err := bodyWriter.CreateFormFile("file", "testuploadfile.txt")
|
||||||
|
if err != nil {
|
||||||
|
fmt.Println("error writing to buffer")
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// open file handle
|
||||||
|
fh, err := os.Open(filename)
|
||||||
|
if err != nil {
|
||||||
|
fmt.Println("error opening file")
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
defer fh.Close()
|
||||||
|
|
||||||
|
// io copy
|
||||||
|
_, err = io.Copy(fileWriter, fh)
|
||||||
|
if err != nil {
|
||||||
|
fmt.Println("error on IO copy")
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
contentType := bodyWriter.FormDataContentType()
|
||||||
|
bodyWriter.Close()
|
||||||
|
|
||||||
|
w := httptest.NewRecorder()
|
||||||
|
req, err := http.NewRequest("POST", "/api/files?objectID=1&objectType=widget", bodyBuf)
|
||||||
|
req.Header.Add("Authorization", "Bearer "+token)
|
||||||
|
req.Header.Set("Content-Type", contentType)
|
||||||
|
if err != nil {
|
||||||
|
fmt.Println("error creating post request")
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
router.ServeHTTP(w, req)
|
||||||
|
|
||||||
|
assert.Equal(t, 200, w.Code)
|
||||||
|
fmt.Println(w.Body.String())
|
||||||
|
assert.Equal(t, string(msgOKjson), w.Body.String())
|
||||||
|
|
||||||
|
// test GET files/:fileID
|
||||||
|
common.TestEndpoint(t, router, token, "/api/files/5", "GET", nil, 200, filecontent)
|
||||||
|
//common.TestEndpoint(t, router, token, "/api/files?objectID=1&objectType=widget", "GET", nil, 200, string(msgFilesjson))
|
||||||
|
|
||||||
|
// test PUT files/:fileID
|
||||||
|
bodyBuf_update := &bytes.Buffer{}
|
||||||
|
bodyWriter_update := multipart.NewWriter(bodyBuf_update)
|
||||||
|
fileWriter_update, err := bodyWriter_update.CreateFormFile("file", "testuploadfile.txt")
|
||||||
|
if err != nil {
|
||||||
|
fmt.Println("error writing to buffer")
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// open file handle
|
||||||
|
fh_update, err := os.Open(filename_update)
|
||||||
|
if err != nil {
|
||||||
|
fmt.Println("error opening file")
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
defer fh_update.Close()
|
||||||
|
|
||||||
|
// io copy
|
||||||
|
_, err = io.Copy(fileWriter_update, fh_update)
|
||||||
|
if err != nil {
|
||||||
|
fmt.Println("error on IO copy")
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
contentType_update := bodyWriter_update.FormDataContentType()
|
||||||
|
bodyWriter_update.Close()
|
||||||
|
w_update := httptest.NewRecorder()
|
||||||
|
req_update, err := http.NewRequest("PUT", "/api/files/5", bodyBuf_update)
|
||||||
|
req_update.Header.Add("Authorization", "Bearer "+token)
|
||||||
|
req_update.Header.Set("Content-Type", contentType_update)
|
||||||
|
if err != nil {
|
||||||
|
fmt.Println("error creating post request")
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
router.ServeHTTP(w_update, req_update)
|
||||||
|
|
||||||
|
assert.Equal(t, 200, w_update.Code)
|
||||||
|
fmt.Println(w_update.Body.String())
|
||||||
|
assert.Equal(t, string(msgOKjson), w_update.Body.String())
|
||||||
|
|
||||||
|
common.TestEndpoint(t, router, token, "/api/files/5", "GET", nil, 200, filecontent_update)
|
||||||
|
|
||||||
|
// test DELETE files/:fileID
|
||||||
|
//common.TestEndpoint(t, router, token, "/api/files/3", "DELETE", nil, 200, string(msgOKjson))
|
||||||
|
//common.TestEndpoint(t, router, token, "/api/files?originID=1&originType=widget", "GET", nil, 200, string(msgFilesjson))
|
||||||
|
|
||||||
|
// TODO add testing for other return codes
|
||||||
|
|
||||||
|
// clean up temporary file
|
||||||
|
err = os.Remove(filename)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
err = os.Remove(filename_update)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -104,7 +104,7 @@ func addWidget(c *gin.Context) {
|
||||||
// @Router /widgets/{widgetID} [put]
|
// @Router /widgets/{widgetID} [put]
|
||||||
func updateWidget(c *gin.Context) {
|
func updateWidget(c *gin.Context) {
|
||||||
|
|
||||||
ok, w := CheckPermissions(c, common.Update)
|
ok, w := CheckPermissions(c, common.Update, -1)
|
||||||
if !ok {
|
if !ok {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
@ -141,7 +141,7 @@ func updateWidget(c *gin.Context) {
|
||||||
// @Router /widgets/{widgetID} [get]
|
// @Router /widgets/{widgetID} [get]
|
||||||
func getWidget(c *gin.Context) {
|
func getWidget(c *gin.Context) {
|
||||||
|
|
||||||
ok, w := CheckPermissions(c, common.Read)
|
ok, w := CheckPermissions(c, common.Read, -1)
|
||||||
if !ok {
|
if !ok {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
@ -166,7 +166,7 @@ func getWidget(c *gin.Context) {
|
||||||
// @Router /widgets/{widgetID} [delete]
|
// @Router /widgets/{widgetID} [delete]
|
||||||
func deleteWidget(c *gin.Context) {
|
func deleteWidget(c *gin.Context) {
|
||||||
|
|
||||||
ok, w := CheckPermissions(c, common.Delete)
|
ok, w := CheckPermissions(c, common.Delete, -1)
|
||||||
if !ok {
|
if !ok {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
|
@ -11,7 +11,7 @@ import (
|
||||||
"git.rwth-aachen.de/acs/public/villas/villasweb-backend-go/routes/visualization"
|
"git.rwth-aachen.de/acs/public/villas/villasweb-backend-go/routes/visualization"
|
||||||
)
|
)
|
||||||
|
|
||||||
func CheckPermissions(c *gin.Context, operation common.CRUD) (bool, Widget) {
|
func CheckPermissions(c *gin.Context, operation common.CRUD, widgetIDBody int) (bool, Widget) {
|
||||||
|
|
||||||
var w Widget
|
var w Widget
|
||||||
|
|
||||||
|
@ -21,7 +21,9 @@ func CheckPermissions(c *gin.Context, operation common.CRUD) (bool, Widget) {
|
||||||
return false, w
|
return false, w
|
||||||
}
|
}
|
||||||
|
|
||||||
widgetID, err := strconv.Atoi(c.Param("widgetID"))
|
var widgetID int
|
||||||
|
if widgetIDBody < 0 {
|
||||||
|
widgetID, err = strconv.Atoi(c.Param("widgetID"))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
errormsg := fmt.Sprintf("Bad request. No or incorrect format of widgetID path parameter")
|
errormsg := fmt.Sprintf("Bad request. No or incorrect format of widgetID path parameter")
|
||||||
c.JSON(http.StatusBadRequest, gin.H{
|
c.JSON(http.StatusBadRequest, gin.H{
|
||||||
|
@ -29,6 +31,9 @@ func CheckPermissions(c *gin.Context, operation common.CRUD) (bool, Widget) {
|
||||||
})
|
})
|
||||||
return false, w
|
return false, w
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
widgetID = widgetIDBody
|
||||||
|
}
|
||||||
|
|
||||||
err = w.ByID(uint(widgetID))
|
err = w.ByID(uint(widgetID))
|
||||||
if common.ProvideErrorResponse(c, err) {
|
if common.ProvideErrorResponse(c, err) {
|
||||||
|
|
Loading…
Add table
Reference in a new issue