diff --git a/routes/result/result_endpoints.go b/routes/result/result_endpoints.go index ccb734b..c1e44e9 100644 --- a/routes/result/result_endpoints.go +++ b/routes/result/result_endpoints.go @@ -1,8 +1,32 @@ +/** Result package, endpoints. +* +* @author Sonja Happ +* @copyright 2014-2019, Institute for Automation of Complex Power Systems, EONERC +* @license GNU General Public License (version 3) +* +* VILLASweb-backend-go +* +* This program is free software: you can redistribute it and/or modify +* it under the terms of the GNU General Public License as published by +* the Free Software Foundation, either version 3 of the License, or +* any later version. +* +* This program is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +* GNU General Public License for more details. +* +* You should have received a copy of the GNU General Public License +* along with this program. If not, see . +*********************************************************************************/ + package result import ( + "fmt" "git.rwth-aachen.de/acs/public/villas/web-backend-go/database" "git.rwth-aachen.de/acs/public/villas/web-backend-go/helper" + "git.rwth-aachen.de/acs/public/villas/web-backend-go/routes/file" "git.rwth-aachen.de/acs/public/villas/web-backend-go/routes/scenario" "github.com/gin-gonic/gin" "net/http" @@ -15,7 +39,6 @@ func RegisterResultEndpoints(r *gin.RouterGroup) { r.GET("/:resultID", getResult) r.DELETE("/:resultID", deleteResult) r.POST("/:resultID/file", addResultFile) - r.GET("/:resultID/file/:fileID", getResultFile) r.DELETE("/:resultID/file/:fileID", deleteResultFile) } @@ -33,16 +56,16 @@ func RegisterResultEndpoints(r *gin.RouterGroup) { // @Security Bearer func getResults(c *gin.Context) { - ok, scenario := scenario.CheckPermissions(c, database.Read, "query", -1) + ok, sco := scenario.CheckPermissions(c, database.Read, "query", -1) if !ok { return } db := database.GetDB() - var result []database.Result - err := db.Order("ID asc").Model(scenario).Related(&result, "Results").Error + var results []database.Result + err := db.Order("ID asc").Model(sco).Related(&results, "Results").Error if !helper.DBError(c, err) { - c.JSON(http.StatusOK, gin.H{"result": result}) + c.JSON(http.StatusOK, gin.H{"results": results}) } } @@ -109,7 +132,7 @@ func addResult(c *gin.Context) { // @Security Bearer func updateResult(c *gin.Context) { - ok, oldResult := CheckPermissions(c, database.Update, "path", -1) + ok, oldResult := checkPermissions(c, database.Update, "path", -1) if !ok { return } @@ -151,7 +174,7 @@ func updateResult(c *gin.Context) { // @Security Bearer func getResult(c *gin.Context) { - ok, result := CheckPermissions(c, database.Read, "path", -1) + ok, result := checkPermissions(c, database.Read, "path", -1) if !ok { return } @@ -160,7 +183,7 @@ func getResult(c *gin.Context) { } // deleteResult godoc -// @Summary Delete a Result +// @Summary Delete a Result incl. all result files // @ID deleteResult // @Tags results // @Produce json @@ -173,7 +196,13 @@ func getResult(c *gin.Context) { // @Router /results/{resultID} [delete] // @Security Bearer func deleteResult(c *gin.Context) { - ok, result := CheckPermissions(c, database.Delete, "path", -1) + ok, result := checkPermissions(c, database.Delete, "path", -1) + if !ok { + return + } + + // Check if user is allowed to modify scenario associated with result + ok, _ = scenario.CheckPermissions(c, database.Update, "body", int(result.ScenarioID)) if !ok { return } @@ -210,49 +239,37 @@ func deleteResult(c *gin.Context) { // @Router /results/{resultID}/file [post] // @Security Bearer func addResultFile(c *gin.Context) { - ok, _ := CheckPermissions(c, database.Update, "path", -1) + ok, result := checkPermissions(c, database.Update, "path", -1) if !ok { return } - // TODO check permissions of scenario first (file will be added to scenario) - - // TODO add file to DB, associate with scenario and add file ID to result - -} - -// getResultFile godoc -// @Summary Download a result file -// @ID getResultFile -// @Tags results -// @Produce text/plain -// @Produce text/csv -// @Produce application/gzip -// @Produce application/x-gtar -// @Produce application/x-tar -// @Produce application/x-ustar -// @Produce application/zip -// @Produce application/msexcel -// @Produce application/xml -// @Produce application/x-bag -// @Success 200 {object} docs.ResponseFile "File that was requested" -// @Failure 400 {object} docs.ResponseError "Bad request" -// @Failure 404 {object} docs.ResponseError "Not found" -// @Failure 422 {object} docs.ResponseError "Unprocessable entity" -// @Failure 500 {object} docs.ResponseError "Internal server error" -// @Param resultID path int true "Result ID" -// @Param fileID path int true "ID of the file to download" -// @Router /results/{resultID}/file/{fileID} [get] -// @Security Bearer -func getResultFile(c *gin.Context) { - - // check access - ok, _ := CheckPermissions(c, database.Read, "path", -1) + // Check if user is allowed to modify scenario associated with result + ok, sco := scenario.CheckPermissions(c, database.Update, "body", int(result.ScenarioID)) if !ok { return } - // TODO download result file + // Extract file from POST request form + file_header, err := c.FormFile("file") + if err != nil { + helper.BadRequestError(c, fmt.Sprintf("Get form error: %s", err.Error())) + return + } + + // save result file to DB and associate it with scenario + var newFile file.File + err = newFile.Register(file_header, sco.ID) + if helper.DBError(c, err) { + return + } + + // add file ID to ResultFileIDs of Result + err = result.addResultFileID(newFile.File.ID) + if !helper.DBError(c, err) { + c.JSON(http.StatusOK, gin.H{"result": result.Result}) + } + } // deleteResultFile godoc @@ -270,12 +287,34 @@ func getResultFile(c *gin.Context) { // @Router /results/{resultID}/file/{fileID} [delete] // @Security Bearer func deleteResultFile(c *gin.Context) { - // TODO check access to scenario (file deletion) first // check access - ok, _ := CheckPermissions(c, database.Update, "path", -1) + ok, result := checkPermissions(c, database.Update, "path", -1) if !ok { return } + ok, f := file.CheckPermissions(c, database.Delete) + if !ok { + return + } + + // Check if user is allowed to modify scenario associated with result + ok, _ = scenario.CheckPermissions(c, database.Update, "body", int(result.ScenarioID)) + if !ok { + return + } + + // remove file ID from ResultFileIDs of Result + err := result.removeResultFileID(f.ID) + if helper.DBError(c, err) { + return + } + + // Delete the file + err = f.Delete() + if !helper.DBError(c, err) { + c.JSON(http.StatusOK, gin.H{"result": result.Result}) + } + } diff --git a/routes/result/result_methods.go b/routes/result/result_methods.go index 48fb528..8512e7f 100644 --- a/routes/result/result_methods.go +++ b/routes/result/result_methods.go @@ -1,8 +1,32 @@ +/** Result package, methods. +* +* @author Sonja Happ +* @copyright 2014-2019, Institute for Automation of Complex Power Systems, EONERC +* @license GNU General Public License (version 3) +* +* VILLASweb-backend-go +* +* This program is free software: you can redistribute it and/or modify +* it under the terms of the GNU General Public License as published by +* the Free Software Foundation, either version 3 of the License, or +* any later version. +* +* This program is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +* GNU General Public License for more details. +* +* You should have received a copy of the GNU General Public License +* along with this program. If not, see . +*********************************************************************************/ + package result import ( "git.rwth-aachen.de/acs/public/villas/web-backend-go/database" + "git.rwth-aachen.de/acs/public/villas/web-backend-go/routes/file" "git.rwth-aachen.de/acs/public/villas/web-backend-go/routes/scenario" + "log" ) type Result struct { @@ -69,7 +93,56 @@ func (r *Result) delete() error { // remove association between Result and Scenario err = db.Model(&sco).Association("Results").Delete(r).Error - // TODO delete Result + files (if any) + // Delete result files + for id, _ := range r.ResultFileIDs { + var f file.File + err := f.ByID(uint(id)) + if err != nil { + log.Println("Unable to delete file with ID ", id, err) + continue + } + err = f.Delete() + if err != nil { + return err + } + } + + // Delete result + err = db.Delete(r).Error + + return err +} + +func (r *Result) addResultFileID(fileID uint) error { + + oldResultFileIDs := r.ResultFileIDs + newResultFileIDs := append(oldResultFileIDs, int64(fileID)) + + db := database.GetDB() + + err := db.Model(r).Updates(map[string]interface{}{ + "ResultFileIDs": newResultFileIDs, + }).Error + + return err + +} + +func (r *Result) removeResultFileID(fileID uint) error { + oldResultFileIDs := r.ResultFileIDs + var newResultFileIDs []int64 + + for _, id := range oldResultFileIDs { + if id != int64(fileID) { + newResultFileIDs = append(newResultFileIDs, id) + } + } + + db := database.GetDB() + + err := db.Model(r).Updates(map[string]interface{}{ + "ResultFileIDs": newResultFileIDs, + }).Error return err } diff --git a/routes/result/result_middleware.go b/routes/result/result_middleware.go index 70a199b..7656087 100644 --- a/routes/result/result_middleware.go +++ b/routes/result/result_middleware.go @@ -1,3 +1,25 @@ +/** Result package, middleware. +* +* @author Sonja Happ +* @copyright 2014-2019, Institute for Automation of Complex Power Systems, EONERC +* @license GNU General Public License (version 3) +* +* VILLASweb-backend-go +* +* This program is free software: you can redistribute it and/or modify +* it under the terms of the GNU General Public License as published by +* the Free Software Foundation, either version 3 of the License, or +* any later version. +* +* This program is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +* GNU General Public License for more details. +* +* You should have received a copy of the GNU General Public License +* along with this program. If not, see . +*********************************************************************************/ + package result import ( @@ -8,7 +30,7 @@ import ( "github.com/gin-gonic/gin" ) -func CheckPermissions(c *gin.Context, operation database.CRUD, resultIDSource string, resultIDBody int) (bool, Result) { +func checkPermissions(c *gin.Context, operation database.CRUD, resultIDSource string, resultIDBody int) (bool, Result) { var result Result diff --git a/routes/result/result_test.go b/routes/result/result_test.go index 2705049..47dfba9 100644 --- a/routes/result/result_test.go +++ b/routes/result/result_test.go @@ -1 +1,120 @@ +/** Result package, testing. +* +* @author Sonja Happ +* @copyright 2014-2019, Institute for Automation of Complex Power Systems, EONERC +* @license GNU General Public License (version 3) +* +* VILLASweb-backend-go +* +* This program is free software: you can redistribute it and/or modify +* it under the terms of the GNU General Public License as published by +* the Free Software Foundation, either version 3 of the License, or +* any later version. +* +* This program is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +* GNU General Public License for more details. +* +* You should have received a copy of the GNU General Public License +* along with this program. If not, see . +*********************************************************************************/ + package result + +import ( + "fmt" + "git.rwth-aachen.de/acs/public/villas/web-backend-go/configuration" + "git.rwth-aachen.de/acs/public/villas/web-backend-go/database" + "git.rwth-aachen.de/acs/public/villas/web-backend-go/helper" + "git.rwth-aachen.de/acs/public/villas/web-backend-go/routes/file" + "git.rwth-aachen.de/acs/public/villas/web-backend-go/routes/scenario" + "git.rwth-aachen.de/acs/public/villas/web-backend-go/routes/user" + "github.com/gin-gonic/gin" + "github.com/jinzhu/gorm/dialects/postgres" + "os" + "testing" +) + +var router *gin.Engine + +type ScenarioRequest struct { + Name string `json:"name,omitempty"` + Running bool `json:"running,omitempty"` + StartParameters postgres.Jsonb `json:"startParameters,omitempty"` +} + +func addScenario() (scenarioID uint) { + + // authenticate as admin + token, _ := helper.AuthenticateForTest(router, + "/api/authenticate", "POST", helper.AdminCredentials) + + // authenticate as normal user + token, _ = helper.AuthenticateForTest(router, + "/api/authenticate", "POST", helper.UserACredentials) + + // POST $newScenario + newScenario := ScenarioRequest{ + Name: helper.ScenarioA.Name, + Running: helper.ScenarioA.Running, + StartParameters: helper.ScenarioA.StartParameters, + } + _, resp, _ := helper.TestEndpoint(router, token, + "/api/scenarios", "POST", helper.KeyModels{"scenario": newScenario}) + + // Read newScenario's ID from the response + newScenarioID, _ := helper.GetResponseID(resp) + + // add the guest user to the new scenario + _, resp, _ = helper.TestEndpoint(router, token, + fmt.Sprintf("/api/scenarios/%v/user?username=User_C", newScenarioID), "PUT", nil) + + return uint(newScenarioID) +} + +func TestMain(m *testing.M) { + err := configuration.InitConfig() + if err != nil { + panic(m) + } + err = database.InitDB(configuration.GolbalConfig) + if err != nil { + panic(m) + } + defer database.DBpool.Close() + + router = gin.Default() + api := router.Group("/api") + + user.RegisterAuthenticate(api.Group("/authenticate")) + api.Use(user.Authentication(true)) + // scenario endpoints required here to first add a scenario to the DB + scenario.RegisterScenarioEndpoints(api.Group("/scenarios")) + // file endpoints required to download result file + file.RegisterFileEndpoints(api.Group("/files")) + + RegisterResultEndpoints(api.Group("/results")) + + os.Exit(m.Run()) +} + +func TestGetAllResultsOfScenario(t *testing.T) { + +} + +func TestAddGetUpdateResult(t *testing.T) { + +} + +func TestDeleteResult(t *testing.T) { + +} + +func TestAddResultFile(t *testing.T) { + +} + +func TestDeleteResultFile(t *testing.T) { + +} diff --git a/routes/result/result_validators.go b/routes/result/result_validators.go index 1f4da71..28e84a1 100644 --- a/routes/result/result_validators.go +++ b/routes/result/result_validators.go @@ -1,3 +1,25 @@ +/** Result package, validators. +* +* @author Sonja Happ +* @copyright 2014-2019, Institute for Automation of Complex Power Systems, EONERC +* @license GNU General Public License (version 3) +* +* VILLASweb-backend-go +* +* This program is free software: you can redistribute it and/or modify +* it under the terms of the GNU General Public License as published by +* the Free Software Foundation, either version 3 of the License, or +* any later version. +* +* This program is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +* GNU General Public License for more details. +* +* You should have received a copy of the GNU General Public License +* along with this program. If not, see . +*********************************************************************************/ + package result import (