WIP: initial version of result data model, endpoints, methods, validators, middleware #38

This commit is contained in:
Sonja Happ 2020-11-19 17:23:24 +01:00
parent adbef6abda
commit 89311af146
15 changed files with 2311 additions and 4 deletions

View file

@ -93,6 +93,7 @@ func DropTables() {
DBpool.DropTableIfExists(&User{})
DBpool.DropTableIfExists(&Dashboard{})
DBpool.DropTableIfExists(&Widget{})
DBpool.DropTableIfExists(&Result{})
// The following statement deletes the many to many relationship between users and scenarios
DBpool.DropTableIfExists("user_scenarios")
}
@ -107,4 +108,5 @@ func MigrateModels() {
DBpool.AutoMigrate(&User{})
DBpool.AutoMigrate(&Dashboard{})
DBpool.AutoMigrate(&Widget{})
DBpool.AutoMigrate(&Result{})
}

View file

@ -144,6 +144,8 @@ func TestScenarioAssociations(t *testing.T) {
dashboardB := Dashboard{}
fileA := File{}
fileB := File{}
resultA := Result{}
resultB := Result{}
// add scenarios to DB
assert.NoError(t, DBpool.Create(&scenarioA).Error)
@ -165,6 +167,10 @@ func TestScenarioAssociations(t *testing.T) {
assert.NoError(t, DBpool.Create(&fileA).Error)
assert.NoError(t, DBpool.Create(&fileB).Error)
// add results to DB
assert.NoError(t, DBpool.Create(&resultA).Error)
assert.NoError(t, DBpool.Create(&resultB).Error)
// add many-to-many associations between users and scenarios
// User HM Scenarios, Scenario HM Users (Many-to-Many)
assert.NoError(t, DBpool.Model(&scenarioA).Association("Users").Append(&userA).Error)
@ -184,6 +190,10 @@ func TestScenarioAssociations(t *testing.T) {
assert.NoError(t, DBpool.Model(&scenarioA).Association("Dashboards").Append(&dashboardA).Error)
assert.NoError(t, DBpool.Model(&scenarioA).Association("Dashboards").Append(&dashboardB).Error)
// Scenario HM Results
assert.NoError(t, DBpool.Model(&scenarioA).Association("Results").Append(&resultA).Error)
assert.NoError(t, DBpool.Model(&scenarioA).Association("Results").Append(&resultB).Error)
var scenario1 Scenario
assert.NoError(t, DBpool.Find(&scenario1, 1).Error, fmt.Sprintf("Find Scenario with ID=1"))
@ -218,6 +228,14 @@ func TestScenarioAssociations(t *testing.T) {
assert.Fail(t, "Scenario Associations",
"Expected to have %v Files. Has %v.", 2, len(files))
}
// Get results of scenario1
var results []File
assert.NoError(t, DBpool.Model(&scenario1).Related(&results, "Results").Error)
if len(files) != 2 {
assert.Fail(t, "Scenario Associations",
"Expected to have %v Results. Has %v.", 2, len(results))
}
}
func TestICAssociations(t *testing.T) {

View file

@ -32,8 +32,8 @@ import (
// except the json tags that are needed for serializing the models
type Model struct {
ID uint `json:"id,omitempty" gorm:"primary_key:true"`
CreatedAt time.Time `json:"-"`
UpdatedAt time.Time `json:"-"`
CreatedAt time.Time `json:"createdAt"`
UpdatedAt time.Time `json:"updatedAt"`
DeletedAt *time.Time `json:"-" sql:"index"`
}
@ -71,6 +71,8 @@ type Scenario struct {
Dashboards []Dashboard `json:"-" gorm:"foreignkey:ScenarioID" `
// Files that belong to the Scenario (for example images, models, etc.)
Files []File `json:"-" gorm:"foreignkey:ScenarioID"`
// Results that belong to the Scenario
Results []Result `json:"-" gorm:"foreignkey:ScenarioID"`
}
// ComponentConfiguration data model
@ -212,3 +214,16 @@ type File struct {
// Width of an image file in pixels (optional)
ImageWidth int `json:"imageWidth" gorm:"default:0"`
}
// Result data model
type Result struct {
Model
// JSON snapshots of component configurations used to generate results
ConfigSnapshots []postgres.Jsonb `json:"configSnapshots"`
// Description of results
Description string `json:"description"`
// ID of Scenario to which result belongs
ScenarioID uint `json:"scenarioID"`
// File IDs associated with result
ResultFileIDs pq.Int64Array `json:"resultFileIDs" gorm:"type:integer[]"`
}

View file

@ -45,6 +45,7 @@ const ModelWidget = ModelName("widget")
const ModelComponentConfiguration = ModelName("component-configuration")
const ModelSignal = ModelName("signal")
const ModelFile = ModelName("file")
const ModelResult = ModelName("result")
type CRUD string
@ -83,6 +84,7 @@ var Roles = RoleActions{
ModelDashboard: crud,
ModelSignal: crud,
ModelFile: crud,
ModelResult: crud,
},
"User": {
ModelUser: _ru_,
@ -95,6 +97,7 @@ var Roles = RoleActions{
ModelDashboard: crud,
ModelSignal: crud,
ModelFile: crud,
ModelResult: crud,
},
"Guest": {
ModelScenario: _r__,
@ -107,6 +110,7 @@ var Roles = RoleActions{
ModelUsers: none,
ModelSignal: _r__,
ModelFile: _r__,
ModelResult: none,
},
}

View file

@ -1,6 +1,6 @@
// GENERATED BY THE COMMAND ABOVE; DO NOT EDIT
// This file was generated by swaggo/swag at
// 2020-11-11 16:32:47.799676915 +0100 CET m=+0.126448240
// 2020-11-19 17:20:42.626650342 +0100 CET m=+0.093632886
package docs
@ -1439,6 +1439,517 @@ var doc = `{
}
}
},
"/results": {
"get": {
"security": [
{
"Bearer": []
}
],
"produces": [
"application/json"
],
"tags": [
"results"
],
"summary": "Get all results of scenario",
"operationId": "getResults",
"parameters": [
{
"type": "integer",
"description": "Scenario ID",
"name": "scenarioID",
"in": "query",
"required": true
}
],
"responses": {
"200": {
"description": "Results which belong to scenario",
"schema": {
"$ref": "#/definitions/docs.ResponseResults"
}
},
"404": {
"description": "Not found",
"schema": {
"$ref": "#/definitions/docs.ResponseError"
}
},
"422": {
"description": "Unprocessable entity",
"schema": {
"$ref": "#/definitions/docs.ResponseError"
}
},
"500": {
"description": "Internal server error",
"schema": {
"$ref": "#/definitions/docs.ResponseError"
}
}
}
},
"post": {
"security": [
{
"Bearer": []
}
],
"consumes": [
"application/json"
],
"produces": [
"application/json"
],
"tags": [
"results"
],
"summary": "Add a result to a scenario",
"operationId": "addResult",
"parameters": [
{
"description": "Result to be added incl. ID of Scenario",
"name": "inputResult",
"in": "body",
"required": true,
"schema": {
"type": "object",
"$ref": "#/definitions/result.addResultRequest"
}
}
],
"responses": {
"200": {
"description": "Result that was added",
"schema": {
"$ref": "#/definitions/docs.ResponseResult"
}
},
"400": {
"description": "Bad request",
"schema": {
"$ref": "#/definitions/docs.ResponseError"
}
},
"404": {
"description": "Not found",
"schema": {
"$ref": "#/definitions/docs.ResponseError"
}
},
"422": {
"description": "Unprocessable entity",
"schema": {
"$ref": "#/definitions/docs.ResponseError"
}
},
"500": {
"description": "Internal server error",
"schema": {
"$ref": "#/definitions/docs.ResponseError"
}
}
}
}
},
"/results/{resultID}": {
"get": {
"security": [
{
"Bearer": []
}
],
"produces": [
"application/json"
],
"tags": [
"results"
],
"summary": "Get a Result",
"operationId": "getResult",
"parameters": [
{
"type": "integer",
"description": "Result ID",
"name": "resultID",
"in": "path",
"required": true
}
],
"responses": {
"200": {
"description": "Result that was requested",
"schema": {
"$ref": "#/definitions/docs.ResponseResult"
}
},
"400": {
"description": "Bad request",
"schema": {
"$ref": "#/definitions/docs.ResponseError"
}
},
"404": {
"description": "Not found",
"schema": {
"$ref": "#/definitions/docs.ResponseError"
}
},
"422": {
"description": "Unprocessable entity",
"schema": {
"$ref": "#/definitions/docs.ResponseError"
}
},
"500": {
"description": "Internal server error",
"schema": {
"$ref": "#/definitions/docs.ResponseError"
}
}
}
},
"put": {
"security": [
{
"Bearer": []
}
],
"consumes": [
"application/json"
],
"produces": [
"application/json"
],
"tags": [
"results"
],
"summary": "Update a result",
"operationId": "updateResult",
"parameters": [
{
"description": "Result to be updated",
"name": "inputResult",
"in": "body",
"required": true,
"schema": {
"type": "object",
"$ref": "#/definitions/result.updateResultRequest"
}
},
{
"type": "integer",
"description": "Result ID",
"name": "resultID",
"in": "path",
"required": true
}
],
"responses": {
"200": {
"description": "Result that was updated",
"schema": {
"$ref": "#/definitions/docs.ResponseResult"
}
},
"400": {
"description": "Bad request",
"schema": {
"$ref": "#/definitions/docs.ResponseError"
}
},
"404": {
"description": "Not found",
"schema": {
"$ref": "#/definitions/docs.ResponseError"
}
},
"422": {
"description": "Unprocessable entity",
"schema": {
"$ref": "#/definitions/docs.ResponseError"
}
},
"500": {
"description": "Internal server error",
"schema": {
"$ref": "#/definitions/docs.ResponseError"
}
}
}
},
"delete": {
"security": [
{
"Bearer": []
}
],
"produces": [
"application/json"
],
"tags": [
"results"
],
"summary": "Delete a Result",
"operationId": "deleteResult",
"parameters": [
{
"type": "integer",
"description": "Result ID",
"name": "resultID",
"in": "path",
"required": true
}
],
"responses": {
"200": {
"description": "Result that was deleted",
"schema": {
"$ref": "#/definitions/docs.ResponseResult"
}
},
"400": {
"description": "Bad request",
"schema": {
"$ref": "#/definitions/docs.ResponseError"
}
},
"404": {
"description": "Not found",
"schema": {
"$ref": "#/definitions/docs.ResponseError"
}
},
"422": {
"description": "Unprocessable entity",
"schema": {
"$ref": "#/definitions/docs.ResponseError"
}
},
"500": {
"description": "Internal server error",
"schema": {
"$ref": "#/definitions/docs.ResponseError"
}
}
}
}
},
"/results/{resultID}/file": {
"post": {
"security": [
{
"Bearer": []
}
],
"consumes": [
"text/plain",
"text/csv",
"application/gzip",
"application/x-gtar",
"application/x-tar",
"application/x-ustar",
"application/zip",
"application/msexcel",
"application/xml",
"application/x-bag"
],
"produces": [
"application/json"
],
"tags": [
"results"
],
"summary": "Upload a result file to the DB and associate it with scenario and result",
"operationId": "addResultFile",
"parameters": [
{
"type": "file",
"description": "File to be uploaded",
"name": "inputFile",
"in": "formData",
"required": true
},
{
"type": "integer",
"description": "Result ID",
"name": "resultID",
"in": "path",
"required": true
}
],
"responses": {
"200": {
"description": "Result that was updated",
"schema": {
"$ref": "#/definitions/docs.ResponseResult"
}
},
"400": {
"description": "Bad request",
"schema": {
"$ref": "#/definitions/docs.ResponseError"
}
},
"404": {
"description": "Not found",
"schema": {
"$ref": "#/definitions/docs.ResponseError"
}
},
"422": {
"description": "Unprocessable entity",
"schema": {
"$ref": "#/definitions/docs.ResponseError"
}
},
"500": {
"description": "Internal server error",
"schema": {
"$ref": "#/definitions/docs.ResponseError"
}
}
}
}
},
"/results/{resultID}/file/{fileID}": {
"get": {
"security": [
{
"Bearer": []
}
],
"produces": [
"text/plain",
"text/csv",
"application/gzip",
"application/x-gtar",
"application/x-tar",
"application/x-ustar",
"application/zip",
"application/msexcel",
"application/xml",
"application/x-bag"
],
"tags": [
"results"
],
"summary": "Download a result file",
"operationId": "getResultFile",
"parameters": [
{
"type": "integer",
"description": "Result ID",
"name": "resultID",
"in": "path",
"required": true
},
{
"type": "integer",
"description": "ID of the file to download",
"name": "fileID",
"in": "path",
"required": true
}
],
"responses": {
"200": {
"description": "File that was requested",
"schema": {
"$ref": "#/definitions/docs.ResponseFile"
}
},
"400": {
"description": "Bad request",
"schema": {
"$ref": "#/definitions/docs.ResponseError"
}
},
"404": {
"description": "Not found",
"schema": {
"$ref": "#/definitions/docs.ResponseError"
}
},
"422": {
"description": "Unprocessable entity",
"schema": {
"$ref": "#/definitions/docs.ResponseError"
}
},
"500": {
"description": "Internal server error",
"schema": {
"$ref": "#/definitions/docs.ResponseError"
}
}
}
},
"delete": {
"security": [
{
"Bearer": []
}
],
"produces": [
"application/json"
],
"tags": [
"results"
],
"summary": "Delete a result file",
"operationId": "deleteResultFile",
"parameters": [
{
"type": "integer",
"description": "Result ID",
"name": "resultID",
"in": "path",
"required": true
},
{
"type": "integer",
"description": "ID of the file to delete",
"name": "fileID",
"in": "path",
"required": true
}
],
"responses": {
"200": {
"description": "Result for which file was deleted",
"schema": {
"$ref": "#/definitions/docs.ResponseResult"
}
},
"400": {
"description": "Bad request",
"schema": {
"$ref": "#/definitions/docs.ResponseError"
}
},
"404": {
"description": "Not found",
"schema": {
"$ref": "#/definitions/docs.ResponseError"
}
},
"422": {
"description": "Unprocessable entity",
"schema": {
"$ref": "#/definitions/docs.ResponseError"
}
},
"500": {
"description": "Internal server error",
"schema": {
"$ref": "#/definitions/docs.ResponseError"
}
}
}
}
},
"/scenarios": {
"get": {
"security": [
@ -2889,6 +3400,9 @@ var doc = `{
"database.ComponentConfiguration": {
"type": "object",
"properties": {
"createdAt": {
"type": "string"
},
"fileIDs": {
"description": "Array of file IDs used by the component configuration",
"type": "string"
@ -2919,12 +3433,18 @@ var doc = `{
"startParameters": {
"description": "Start parameters of Component Configuration as JSON",
"type": "string"
},
"updatedAt": {
"type": "string"
}
}
},
"database.Dashboard": {
"type": "object",
"properties": {
"createdAt": {
"type": "string"
},
"grid": {
"description": "Grid of dashboard",
"type": "integer"
@ -2943,12 +3463,18 @@ var doc = `{
"scenarioID": {
"description": "ID of scenario to which dashboard belongs",
"type": "integer"
},
"updatedAt": {
"type": "string"
}
}
},
"database.File": {
"type": "object",
"properties": {
"createdAt": {
"type": "string"
},
"date": {
"description": "Last modification time of file",
"type": "string"
@ -2956,6 +3482,14 @@ var doc = `{
"id": {
"type": "integer"
},
"imageHeight": {
"description": "Height of an image file in pixels (optional)",
"type": "integer"
},
"imageWidth": {
"description": "Width of an image file in pixels (optional)",
"type": "integer"
},
"name": {
"description": "Name of file",
"type": "string"
@ -2971,6 +3505,9 @@ var doc = `{
"type": {
"description": "Type of file (MIME type)",
"type": "string"
},
"updatedAt": {
"type": "string"
}
}
},
@ -2985,6 +3522,9 @@ var doc = `{
"description": "Category of IC (simulator, gateway, database, etc.)",
"type": "string"
},
"createdAt": {
"type": "string"
},
"description": {
"description": "Description of the IC",
"type": "string"
@ -3020,6 +3560,9 @@ var doc = `{
"description": "Type of IC (RTDS, VILLASnode, RTDS, etc.)",
"type": "string"
},
"updatedAt": {
"type": "string"
},
"uptime": {
"description": "Uptime of the IC",
"type": "number"
@ -3034,9 +3577,42 @@ var doc = `{
}
}
},
"database.Result": {
"type": "object",
"properties": {
"configSnapshots": {
"description": "JSON snapshots of component configurations used to generate results",
"type": "string"
},
"createdAt": {
"type": "string"
},
"description": {
"description": "Description of results",
"type": "string"
},
"id": {
"type": "integer"
},
"resultFileIDs": {
"description": "File IDs associated with result",
"type": "string"
},
"scenarioID": {
"description": "ID of Scenario to which result belongs",
"type": "integer"
},
"updatedAt": {
"type": "string"
}
}
},
"database.Scenario": {
"type": "object",
"properties": {
"createdAt": {
"type": "string"
},
"id": {
"type": "integer"
},
@ -3051,6 +3627,9 @@ var doc = `{
"startParameters": {
"description": "Start parameters of scenario as JSON",
"type": "string"
},
"updatedAt": {
"type": "string"
}
}
},
@ -3061,6 +3640,9 @@ var doc = `{
"description": "ID of Component Configuration",
"type": "integer"
},
"createdAt": {
"type": "string"
},
"direction": {
"description": "Direction of the signal (in or out)",
"type": "string"
@ -3083,6 +3665,9 @@ var doc = `{
"unit": {
"description": "Unit of Signal",
"type": "string"
},
"updatedAt": {
"type": "string"
}
}
},
@ -3093,6 +3678,9 @@ var doc = `{
"description": "Indicating status of user (false means user is inactive and should not be able to login)",
"type": "boolean"
},
"createdAt": {
"type": "string"
},
"id": {
"type": "integer"
},
@ -3104,6 +3692,9 @@ var doc = `{
"description": "Role of user",
"type": "string"
},
"updatedAt": {
"type": "string"
},
"username": {
"description": "Username of user",
"type": "string"
@ -3113,6 +3704,9 @@ var doc = `{
"database.Widget": {
"type": "object",
"properties": {
"createdAt": {
"type": "string"
},
"customProperties": {
"description": "Custom properties of widget as JSON string",
"type": "string"
@ -3152,6 +3746,9 @@ var doc = `{
"description": "Type of widget",
"type": "string"
},
"updatedAt": {
"type": "string"
},
"width": {
"description": "Width of widget",
"type": "integer"
@ -3279,6 +3876,26 @@ var doc = `{
}
}
},
"docs.ResponseResult": {
"type": "object",
"properties": {
"result": {
"type": "object",
"$ref": "#/definitions/database.Result"
}
}
},
"docs.ResponseResults": {
"type": "object",
"properties": {
"results": {
"type": "array",
"items": {
"$ref": "#/definitions/database.Result"
}
}
}
},
"docs.ResponseScenario": {
"type": "object",
"properties": {
@ -3462,6 +4079,65 @@ var doc = `{
}
}
},
"result.addResultRequest": {
"type": "object",
"properties": {
"result": {
"type": "object",
"$ref": "#/definitions/result.validNewResult"
}
}
},
"result.updateResultRequest": {
"type": "object",
"properties": {
"result": {
"type": "object",
"$ref": "#/definitions/result.validUpdatedResult"
}
}
},
"result.validNewResult": {
"type": "object",
"required": [
"ConfigSnapshots",
"ScenarioID"
],
"properties": {
"ConfigSnapshots": {
"type": "string"
},
"Description": {
"type": "string"
},
"ResultFileIDs": {
"type": "array",
"items": {
"type": "integer"
}
},
"ScenarioID": {
"type": "integer"
}
}
},
"result.validUpdatedResult": {
"type": "object",
"properties": {
"configSnapshots": {
"type": "string"
},
"description": {
"type": "string"
},
"resultFileIDs": {
"type": "array",
"items": {
"type": "integer"
}
}
}
},
"scenario.addScenarioRequest": {
"type": "object",
"properties": {

View file

@ -101,3 +101,11 @@ type ResponseFiles struct {
type ResponseFile struct {
file database.File
}
type ResponseResults struct {
results []database.Result
}
type ResponseResult struct {
result database.Result
}

View file

@ -1422,6 +1422,517 @@
}
}
},
"/results": {
"get": {
"security": [
{
"Bearer": []
}
],
"produces": [
"application/json"
],
"tags": [
"results"
],
"summary": "Get all results of scenario",
"operationId": "getResults",
"parameters": [
{
"type": "integer",
"description": "Scenario ID",
"name": "scenarioID",
"in": "query",
"required": true
}
],
"responses": {
"200": {
"description": "Results which belong to scenario",
"schema": {
"$ref": "#/definitions/docs.ResponseResults"
}
},
"404": {
"description": "Not found",
"schema": {
"$ref": "#/definitions/docs.ResponseError"
}
},
"422": {
"description": "Unprocessable entity",
"schema": {
"$ref": "#/definitions/docs.ResponseError"
}
},
"500": {
"description": "Internal server error",
"schema": {
"$ref": "#/definitions/docs.ResponseError"
}
}
}
},
"post": {
"security": [
{
"Bearer": []
}
],
"consumes": [
"application/json"
],
"produces": [
"application/json"
],
"tags": [
"results"
],
"summary": "Add a result to a scenario",
"operationId": "addResult",
"parameters": [
{
"description": "Result to be added incl. ID of Scenario",
"name": "inputResult",
"in": "body",
"required": true,
"schema": {
"type": "object",
"$ref": "#/definitions/result.addResultRequest"
}
}
],
"responses": {
"200": {
"description": "Result that was added",
"schema": {
"$ref": "#/definitions/docs.ResponseResult"
}
},
"400": {
"description": "Bad request",
"schema": {
"$ref": "#/definitions/docs.ResponseError"
}
},
"404": {
"description": "Not found",
"schema": {
"$ref": "#/definitions/docs.ResponseError"
}
},
"422": {
"description": "Unprocessable entity",
"schema": {
"$ref": "#/definitions/docs.ResponseError"
}
},
"500": {
"description": "Internal server error",
"schema": {
"$ref": "#/definitions/docs.ResponseError"
}
}
}
}
},
"/results/{resultID}": {
"get": {
"security": [
{
"Bearer": []
}
],
"produces": [
"application/json"
],
"tags": [
"results"
],
"summary": "Get a Result",
"operationId": "getResult",
"parameters": [
{
"type": "integer",
"description": "Result ID",
"name": "resultID",
"in": "path",
"required": true
}
],
"responses": {
"200": {
"description": "Result that was requested",
"schema": {
"$ref": "#/definitions/docs.ResponseResult"
}
},
"400": {
"description": "Bad request",
"schema": {
"$ref": "#/definitions/docs.ResponseError"
}
},
"404": {
"description": "Not found",
"schema": {
"$ref": "#/definitions/docs.ResponseError"
}
},
"422": {
"description": "Unprocessable entity",
"schema": {
"$ref": "#/definitions/docs.ResponseError"
}
},
"500": {
"description": "Internal server error",
"schema": {
"$ref": "#/definitions/docs.ResponseError"
}
}
}
},
"put": {
"security": [
{
"Bearer": []
}
],
"consumes": [
"application/json"
],
"produces": [
"application/json"
],
"tags": [
"results"
],
"summary": "Update a result",
"operationId": "updateResult",
"parameters": [
{
"description": "Result to be updated",
"name": "inputResult",
"in": "body",
"required": true,
"schema": {
"type": "object",
"$ref": "#/definitions/result.updateResultRequest"
}
},
{
"type": "integer",
"description": "Result ID",
"name": "resultID",
"in": "path",
"required": true
}
],
"responses": {
"200": {
"description": "Result that was updated",
"schema": {
"$ref": "#/definitions/docs.ResponseResult"
}
},
"400": {
"description": "Bad request",
"schema": {
"$ref": "#/definitions/docs.ResponseError"
}
},
"404": {
"description": "Not found",
"schema": {
"$ref": "#/definitions/docs.ResponseError"
}
},
"422": {
"description": "Unprocessable entity",
"schema": {
"$ref": "#/definitions/docs.ResponseError"
}
},
"500": {
"description": "Internal server error",
"schema": {
"$ref": "#/definitions/docs.ResponseError"
}
}
}
},
"delete": {
"security": [
{
"Bearer": []
}
],
"produces": [
"application/json"
],
"tags": [
"results"
],
"summary": "Delete a Result",
"operationId": "deleteResult",
"parameters": [
{
"type": "integer",
"description": "Result ID",
"name": "resultID",
"in": "path",
"required": true
}
],
"responses": {
"200": {
"description": "Result that was deleted",
"schema": {
"$ref": "#/definitions/docs.ResponseResult"
}
},
"400": {
"description": "Bad request",
"schema": {
"$ref": "#/definitions/docs.ResponseError"
}
},
"404": {
"description": "Not found",
"schema": {
"$ref": "#/definitions/docs.ResponseError"
}
},
"422": {
"description": "Unprocessable entity",
"schema": {
"$ref": "#/definitions/docs.ResponseError"
}
},
"500": {
"description": "Internal server error",
"schema": {
"$ref": "#/definitions/docs.ResponseError"
}
}
}
}
},
"/results/{resultID}/file": {
"post": {
"security": [
{
"Bearer": []
}
],
"consumes": [
"text/plain",
"text/csv",
"application/gzip",
"application/x-gtar",
"application/x-tar",
"application/x-ustar",
"application/zip",
"application/msexcel",
"application/xml",
"application/x-bag"
],
"produces": [
"application/json"
],
"tags": [
"results"
],
"summary": "Upload a result file to the DB and associate it with scenario and result",
"operationId": "addResultFile",
"parameters": [
{
"type": "file",
"description": "File to be uploaded",
"name": "inputFile",
"in": "formData",
"required": true
},
{
"type": "integer",
"description": "Result ID",
"name": "resultID",
"in": "path",
"required": true
}
],
"responses": {
"200": {
"description": "Result that was updated",
"schema": {
"$ref": "#/definitions/docs.ResponseResult"
}
},
"400": {
"description": "Bad request",
"schema": {
"$ref": "#/definitions/docs.ResponseError"
}
},
"404": {
"description": "Not found",
"schema": {
"$ref": "#/definitions/docs.ResponseError"
}
},
"422": {
"description": "Unprocessable entity",
"schema": {
"$ref": "#/definitions/docs.ResponseError"
}
},
"500": {
"description": "Internal server error",
"schema": {
"$ref": "#/definitions/docs.ResponseError"
}
}
}
}
},
"/results/{resultID}/file/{fileID}": {
"get": {
"security": [
{
"Bearer": []
}
],
"produces": [
"text/plain",
"text/csv",
"application/gzip",
"application/x-gtar",
"application/x-tar",
"application/x-ustar",
"application/zip",
"application/msexcel",
"application/xml",
"application/x-bag"
],
"tags": [
"results"
],
"summary": "Download a result file",
"operationId": "getResultFile",
"parameters": [
{
"type": "integer",
"description": "Result ID",
"name": "resultID",
"in": "path",
"required": true
},
{
"type": "integer",
"description": "ID of the file to download",
"name": "fileID",
"in": "path",
"required": true
}
],
"responses": {
"200": {
"description": "File that was requested",
"schema": {
"$ref": "#/definitions/docs.ResponseFile"
}
},
"400": {
"description": "Bad request",
"schema": {
"$ref": "#/definitions/docs.ResponseError"
}
},
"404": {
"description": "Not found",
"schema": {
"$ref": "#/definitions/docs.ResponseError"
}
},
"422": {
"description": "Unprocessable entity",
"schema": {
"$ref": "#/definitions/docs.ResponseError"
}
},
"500": {
"description": "Internal server error",
"schema": {
"$ref": "#/definitions/docs.ResponseError"
}
}
}
},
"delete": {
"security": [
{
"Bearer": []
}
],
"produces": [
"application/json"
],
"tags": [
"results"
],
"summary": "Delete a result file",
"operationId": "deleteResultFile",
"parameters": [
{
"type": "integer",
"description": "Result ID",
"name": "resultID",
"in": "path",
"required": true
},
{
"type": "integer",
"description": "ID of the file to delete",
"name": "fileID",
"in": "path",
"required": true
}
],
"responses": {
"200": {
"description": "Result for which file was deleted",
"schema": {
"$ref": "#/definitions/docs.ResponseResult"
}
},
"400": {
"description": "Bad request",
"schema": {
"$ref": "#/definitions/docs.ResponseError"
}
},
"404": {
"description": "Not found",
"schema": {
"$ref": "#/definitions/docs.ResponseError"
}
},
"422": {
"description": "Unprocessable entity",
"schema": {
"$ref": "#/definitions/docs.ResponseError"
}
},
"500": {
"description": "Internal server error",
"schema": {
"$ref": "#/definitions/docs.ResponseError"
}
}
}
}
},
"/scenarios": {
"get": {
"security": [
@ -2872,6 +3383,9 @@
"database.ComponentConfiguration": {
"type": "object",
"properties": {
"createdAt": {
"type": "string"
},
"fileIDs": {
"description": "Array of file IDs used by the component configuration",
"type": "string"
@ -2902,12 +3416,18 @@
"startParameters": {
"description": "Start parameters of Component Configuration as JSON",
"type": "string"
},
"updatedAt": {
"type": "string"
}
}
},
"database.Dashboard": {
"type": "object",
"properties": {
"createdAt": {
"type": "string"
},
"grid": {
"description": "Grid of dashboard",
"type": "integer"
@ -2926,12 +3446,18 @@
"scenarioID": {
"description": "ID of scenario to which dashboard belongs",
"type": "integer"
},
"updatedAt": {
"type": "string"
}
}
},
"database.File": {
"type": "object",
"properties": {
"createdAt": {
"type": "string"
},
"date": {
"description": "Last modification time of file",
"type": "string"
@ -2939,6 +3465,14 @@
"id": {
"type": "integer"
},
"imageHeight": {
"description": "Height of an image file in pixels (optional)",
"type": "integer"
},
"imageWidth": {
"description": "Width of an image file in pixels (optional)",
"type": "integer"
},
"name": {
"description": "Name of file",
"type": "string"
@ -2954,6 +3488,9 @@
"type": {
"description": "Type of file (MIME type)",
"type": "string"
},
"updatedAt": {
"type": "string"
}
}
},
@ -2968,6 +3505,9 @@
"description": "Category of IC (simulator, gateway, database, etc.)",
"type": "string"
},
"createdAt": {
"type": "string"
},
"description": {
"description": "Description of the IC",
"type": "string"
@ -3003,6 +3543,9 @@
"description": "Type of IC (RTDS, VILLASnode, RTDS, etc.)",
"type": "string"
},
"updatedAt": {
"type": "string"
},
"uptime": {
"description": "Uptime of the IC",
"type": "number"
@ -3017,9 +3560,42 @@
}
}
},
"database.Result": {
"type": "object",
"properties": {
"configSnapshots": {
"description": "JSON snapshots of component configurations used to generate results",
"type": "string"
},
"createdAt": {
"type": "string"
},
"description": {
"description": "Description of results",
"type": "string"
},
"id": {
"type": "integer"
},
"resultFileIDs": {
"description": "File IDs associated with result",
"type": "string"
},
"scenarioID": {
"description": "ID of Scenario to which result belongs",
"type": "integer"
},
"updatedAt": {
"type": "string"
}
}
},
"database.Scenario": {
"type": "object",
"properties": {
"createdAt": {
"type": "string"
},
"id": {
"type": "integer"
},
@ -3034,6 +3610,9 @@
"startParameters": {
"description": "Start parameters of scenario as JSON",
"type": "string"
},
"updatedAt": {
"type": "string"
}
}
},
@ -3044,6 +3623,9 @@
"description": "ID of Component Configuration",
"type": "integer"
},
"createdAt": {
"type": "string"
},
"direction": {
"description": "Direction of the signal (in or out)",
"type": "string"
@ -3066,6 +3648,9 @@
"unit": {
"description": "Unit of Signal",
"type": "string"
},
"updatedAt": {
"type": "string"
}
}
},
@ -3076,6 +3661,9 @@
"description": "Indicating status of user (false means user is inactive and should not be able to login)",
"type": "boolean"
},
"createdAt": {
"type": "string"
},
"id": {
"type": "integer"
},
@ -3087,6 +3675,9 @@
"description": "Role of user",
"type": "string"
},
"updatedAt": {
"type": "string"
},
"username": {
"description": "Username of user",
"type": "string"
@ -3096,6 +3687,9 @@
"database.Widget": {
"type": "object",
"properties": {
"createdAt": {
"type": "string"
},
"customProperties": {
"description": "Custom properties of widget as JSON string",
"type": "string"
@ -3135,6 +3729,9 @@
"description": "Type of widget",
"type": "string"
},
"updatedAt": {
"type": "string"
},
"width": {
"description": "Width of widget",
"type": "integer"
@ -3262,6 +3859,26 @@
}
}
},
"docs.ResponseResult": {
"type": "object",
"properties": {
"result": {
"type": "object",
"$ref": "#/definitions/database.Result"
}
}
},
"docs.ResponseResults": {
"type": "object",
"properties": {
"results": {
"type": "array",
"items": {
"$ref": "#/definitions/database.Result"
}
}
}
},
"docs.ResponseScenario": {
"type": "object",
"properties": {
@ -3445,6 +4062,65 @@
}
}
},
"result.addResultRequest": {
"type": "object",
"properties": {
"result": {
"type": "object",
"$ref": "#/definitions/result.validNewResult"
}
}
},
"result.updateResultRequest": {
"type": "object",
"properties": {
"result": {
"type": "object",
"$ref": "#/definitions/result.validUpdatedResult"
}
}
},
"result.validNewResult": {
"type": "object",
"required": [
"ConfigSnapshots",
"ScenarioID"
],
"properties": {
"ConfigSnapshots": {
"type": "string"
},
"Description": {
"type": "string"
},
"ResultFileIDs": {
"type": "array",
"items": {
"type": "integer"
}
},
"ScenarioID": {
"type": "integer"
}
}
},
"result.validUpdatedResult": {
"type": "object",
"properties": {
"configSnapshots": {
"type": "string"
},
"description": {
"type": "string"
},
"resultFileIDs": {
"type": "array",
"items": {
"type": "integer"
}
}
}
},
"scenario.addScenarioRequest": {
"type": "object",
"properties": {

View file

@ -83,6 +83,8 @@ definitions:
type: object
database.ComponentConfiguration:
properties:
createdAt:
type: string
fileIDs:
description: Array of file IDs used by the component configuration
type: string
@ -106,9 +108,13 @@ definitions:
startParameters:
description: Start parameters of Component Configuration as JSON
type: string
updatedAt:
type: string
type: object
database.Dashboard:
properties:
createdAt:
type: string
grid:
description: Grid of dashboard
type: integer
@ -123,14 +129,24 @@ definitions:
scenarioID:
description: ID of scenario to which dashboard belongs
type: integer
updatedAt:
type: string
type: object
database.File:
properties:
createdAt:
type: string
date:
description: Last modification time of file
type: string
id:
type: integer
imageHeight:
description: Height of an image file in pixels (optional)
type: integer
imageWidth:
description: Width of an image file in pixels (optional)
type: integer
name:
description: Name of file
type: string
@ -143,6 +159,8 @@ definitions:
type:
description: Type of file (MIME type)
type: string
updatedAt:
type: string
type: object
database.InfrastructureComponent:
properties:
@ -152,6 +170,8 @@ definitions:
category:
description: Category of IC (simulator, gateway, database, etc.)
type: string
createdAt:
type: string
description:
description: Description of the IC
type: string
@ -178,6 +198,8 @@ definitions:
type:
description: Type of IC (RTDS, VILLASnode, RTDS, etc.)
type: string
updatedAt:
type: string
uptime:
description: Uptime of the IC
type: number
@ -188,8 +210,31 @@ definitions:
description: WebsocketURL if the IC
type: string
type: object
database.Result:
properties:
configSnapshots:
description: JSON snapshots of component configurations used to generate results
type: string
createdAt:
type: string
description:
description: Description of results
type: string
id:
type: integer
resultFileIDs:
description: File IDs associated with result
type: string
scenarioID:
description: ID of Scenario to which result belongs
type: integer
updatedAt:
type: string
type: object
database.Scenario:
properties:
createdAt:
type: string
id:
type: integer
name:
@ -201,12 +246,16 @@ definitions:
startParameters:
description: Start parameters of scenario as JSON
type: string
updatedAt:
type: string
type: object
database.Signal:
properties:
configID:
description: ID of Component Configuration
type: integer
createdAt:
type: string
direction:
description: Direction of the signal (in or out)
type: string
@ -224,6 +273,8 @@ definitions:
unit:
description: Unit of Signal
type: string
updatedAt:
type: string
type: object
database.User:
properties:
@ -231,6 +282,8 @@ definitions:
description: Indicating status of user (false means user is inactive and should
not be able to login)
type: boolean
createdAt:
type: string
id:
type: integer
mail:
@ -239,12 +292,16 @@ definitions:
role:
description: Role of user
type: string
updatedAt:
type: string
username:
description: Username of user
type: string
type: object
database.Widget:
properties:
createdAt:
type: string
customProperties:
description: Custom properties of widget as JSON string
type: string
@ -274,6 +331,8 @@ definitions:
type:
description: Type of widget
type: string
updatedAt:
type: string
width:
description: Width of widget
type: integer
@ -358,6 +417,19 @@ definitions:
$ref: '#/definitions/database.InfrastructureComponent'
type: array
type: object
docs.ResponseResult:
properties:
result:
$ref: '#/definitions/database.Result'
type: object
type: object
docs.ResponseResults:
properties:
results:
items:
$ref: '#/definitions/database.Result'
type: array
type: object
docs.ResponseScenario:
properties:
scenario:
@ -479,6 +551,45 @@ definitions:
WebsocketURL:
type: string
type: object
result.addResultRequest:
properties:
result:
$ref: '#/definitions/result.validNewResult'
type: object
type: object
result.updateResultRequest:
properties:
result:
$ref: '#/definitions/result.validUpdatedResult'
type: object
type: object
result.validNewResult:
properties:
ConfigSnapshots:
type: string
Description:
type: string
ResultFileIDs:
items:
type: integer
type: array
ScenarioID:
type: integer
required:
- ConfigSnapshots
- ScenarioID
type: object
result.validUpdatedResult:
properties:
configSnapshots:
type: string
description:
type: string
resultFileIDs:
items:
type: integer
type: array
type: object
scenario.addScenarioRequest:
properties:
scenario:
@ -1616,6 +1727,342 @@ paths:
summary: Prometheus metrics endpoint
tags:
- metrics
/results:
get:
operationId: getResults
parameters:
- description: Scenario ID
in: query
name: scenarioID
required: true
type: integer
produces:
- application/json
responses:
"200":
description: Results which belong to scenario
schema:
$ref: '#/definitions/docs.ResponseResults'
"404":
description: Not found
schema:
$ref: '#/definitions/docs.ResponseError'
"422":
description: Unprocessable entity
schema:
$ref: '#/definitions/docs.ResponseError'
"500":
description: Internal server error
schema:
$ref: '#/definitions/docs.ResponseError'
security:
- Bearer: []
summary: Get all results of scenario
tags:
- results
post:
consumes:
- application/json
operationId: addResult
parameters:
- description: Result to be added incl. ID of Scenario
in: body
name: inputResult
required: true
schema:
$ref: '#/definitions/result.addResultRequest'
type: object
produces:
- application/json
responses:
"200":
description: Result that was added
schema:
$ref: '#/definitions/docs.ResponseResult'
"400":
description: Bad request
schema:
$ref: '#/definitions/docs.ResponseError'
"404":
description: Not found
schema:
$ref: '#/definitions/docs.ResponseError'
"422":
description: Unprocessable entity
schema:
$ref: '#/definitions/docs.ResponseError'
"500":
description: Internal server error
schema:
$ref: '#/definitions/docs.ResponseError'
security:
- Bearer: []
summary: Add a result to a scenario
tags:
- results
/results/{resultID}:
delete:
operationId: deleteResult
parameters:
- description: Result ID
in: path
name: resultID
required: true
type: integer
produces:
- application/json
responses:
"200":
description: Result that was deleted
schema:
$ref: '#/definitions/docs.ResponseResult'
"400":
description: Bad request
schema:
$ref: '#/definitions/docs.ResponseError'
"404":
description: Not found
schema:
$ref: '#/definitions/docs.ResponseError'
"422":
description: Unprocessable entity
schema:
$ref: '#/definitions/docs.ResponseError'
"500":
description: Internal server error
schema:
$ref: '#/definitions/docs.ResponseError'
security:
- Bearer: []
summary: Delete a Result
tags:
- results
get:
operationId: getResult
parameters:
- description: Result ID
in: path
name: resultID
required: true
type: integer
produces:
- application/json
responses:
"200":
description: Result that was requested
schema:
$ref: '#/definitions/docs.ResponseResult'
"400":
description: Bad request
schema:
$ref: '#/definitions/docs.ResponseError'
"404":
description: Not found
schema:
$ref: '#/definitions/docs.ResponseError'
"422":
description: Unprocessable entity
schema:
$ref: '#/definitions/docs.ResponseError'
"500":
description: Internal server error
schema:
$ref: '#/definitions/docs.ResponseError'
security:
- Bearer: []
summary: Get a Result
tags:
- results
put:
consumes:
- application/json
operationId: updateResult
parameters:
- description: Result to be updated
in: body
name: inputResult
required: true
schema:
$ref: '#/definitions/result.updateResultRequest'
type: object
- description: Result ID
in: path
name: resultID
required: true
type: integer
produces:
- application/json
responses:
"200":
description: Result that was updated
schema:
$ref: '#/definitions/docs.ResponseResult'
"400":
description: Bad request
schema:
$ref: '#/definitions/docs.ResponseError'
"404":
description: Not found
schema:
$ref: '#/definitions/docs.ResponseError'
"422":
description: Unprocessable entity
schema:
$ref: '#/definitions/docs.ResponseError'
"500":
description: Internal server error
schema:
$ref: '#/definitions/docs.ResponseError'
security:
- Bearer: []
summary: Update a result
tags:
- results
/results/{resultID}/file:
post:
consumes:
- text/plain
- text/csv
- application/gzip
- application/x-gtar
- application/x-tar
- application/x-ustar
- application/zip
- application/msexcel
- application/xml
- application/x-bag
operationId: addResultFile
parameters:
- description: File to be uploaded
in: formData
name: inputFile
required: true
type: file
- description: Result ID
in: path
name: resultID
required: true
type: integer
produces:
- application/json
responses:
"200":
description: Result that was updated
schema:
$ref: '#/definitions/docs.ResponseResult'
"400":
description: Bad request
schema:
$ref: '#/definitions/docs.ResponseError'
"404":
description: Not found
schema:
$ref: '#/definitions/docs.ResponseError'
"422":
description: Unprocessable entity
schema:
$ref: '#/definitions/docs.ResponseError'
"500":
description: Internal server error
schema:
$ref: '#/definitions/docs.ResponseError'
security:
- Bearer: []
summary: Upload a result file to the DB and associate it with scenario and result
tags:
- results
/results/{resultID}/file/{fileID}:
delete:
operationId: deleteResultFile
parameters:
- description: Result ID
in: path
name: resultID
required: true
type: integer
- description: ID of the file to delete
in: path
name: fileID
required: true
type: integer
produces:
- application/json
responses:
"200":
description: Result for which file was deleted
schema:
$ref: '#/definitions/docs.ResponseResult'
"400":
description: Bad request
schema:
$ref: '#/definitions/docs.ResponseError'
"404":
description: Not found
schema:
$ref: '#/definitions/docs.ResponseError'
"422":
description: Unprocessable entity
schema:
$ref: '#/definitions/docs.ResponseError'
"500":
description: Internal server error
schema:
$ref: '#/definitions/docs.ResponseError'
security:
- Bearer: []
summary: Delete a result file
tags:
- results
get:
operationId: getResultFile
parameters:
- description: Result ID
in: path
name: resultID
required: true
type: integer
- description: ID of the file to download
in: path
name: fileID
required: true
type: integer
produces:
- text/plain
- text/csv
- application/gzip
- application/x-gtar
- application/x-tar
- application/x-ustar
- application/zip
- application/msexcel
- application/xml
- application/x-bag
responses:
"200":
description: File that was requested
schema:
$ref: '#/definitions/docs.ResponseFile'
"400":
description: Bad request
schema:
$ref: '#/definitions/docs.ResponseError'
"404":
description: Not found
schema:
$ref: '#/definitions/docs.ResponseError'
"422":
description: Unprocessable entity
schema:
$ref: '#/definitions/docs.ResponseError'
"500":
description: Internal server error
schema:
$ref: '#/definitions/docs.ResponseError'
security:
- Bearer: []
summary: Download a result file
tags:
- results
/scenarios:
get:
operationId: getScenarios

View file

@ -88,8 +88,10 @@ func (d *Dashboard) delete() error {
}
// remove association between Dashboard and Scenario
// Dashboard itself is not deleted from DB, it remains as "dangling"
err = db.Model(&sim).Association("Dashboards").Delete(d).Error
// Dashboard itself is not deleted from DB, it remains as "dangling"
// TODO: delete dashboard and associated widgets
return err
}

View file

@ -39,6 +39,7 @@ import (
"git.rwth-aachen.de/acs/public/villas/web-backend-go/routes/healthz"
infrastructure_component "git.rwth-aachen.de/acs/public/villas/web-backend-go/routes/infrastructure-component"
"git.rwth-aachen.de/acs/public/villas/web-backend-go/routes/metrics"
"git.rwth-aachen.de/acs/public/villas/web-backend-go/routes/result"
"git.rwth-aachen.de/acs/public/villas/web-backend-go/routes/scenario"
"git.rwth-aachen.de/acs/public/villas/web-backend-go/routes/signal"
"git.rwth-aachen.de/acs/public/villas/web-backend-go/routes/user"
@ -68,6 +69,7 @@ func RegisterEndpoints(router *gin.Engine, api *gin.RouterGroup) {
file.RegisterFileEndpoints(api.Group("/files"))
user.RegisterUserEndpoints(api.Group("/users"))
infrastructure_component.RegisterICEndpoints(api.Group("/ic"))
result.RegisterResultEndpoints(api.Group("/results"))
router.GET("swagger/*any", ginSwagger.WrapHandler(swaggerFiles.Handler))

View file

@ -0,0 +1,281 @@
package result
import (
"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/scenario"
"github.com/gin-gonic/gin"
"net/http"
)
func RegisterResultEndpoints(r *gin.RouterGroup) {
r.GET("", getResults)
r.POST("", addResult)
r.PUT("/:resultID", updateResult)
r.GET("/:resultID", getResult)
r.DELETE("/:scenarioID", deleteResult)
r.POST("/:resultID/file", addResultFile)
r.GET("/:resultID/file/:fileID", getResultFile)
r.DELETE("/:resultID/file/:fileID", deleteResultFile)
}
// getResults godoc
// @Summary Get all results of scenario
// @ID getResults
// @Produce json
// @Tags results
// @Success 200 {object} docs.ResponseResults "Results which belong to scenario"
// @Failure 404 {object} docs.ResponseError "Not found"
// @Failure 422 {object} docs.ResponseError "Unprocessable entity"
// @Failure 500 {object} docs.ResponseError "Internal server error"
// @Param scenarioID query int true "Scenario ID"
// @Router /results [get]
// @Security Bearer
func getResults(c *gin.Context) {
ok, scenario := 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
if !helper.DBError(c, err) {
c.JSON(http.StatusOK, gin.H{"result": result})
}
}
// addResult godoc
// @Summary Add a result to a scenario
// @ID addResult
// @Accept json
// @Produce json
// @Tags results
// @Success 200 {object} docs.ResponseResult "Result that was added"
// @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 inputResult body result.addResultRequest true "Result to be added incl. ID of Scenario"
// @Router /results [post]
// @Security Bearer
func addResult(c *gin.Context) {
// bind request to JSON
var req addResultRequest
if err := c.ShouldBindJSON(&req); err != nil {
helper.BadRequestError(c, err.Error())
return
}
// Validate the request
if err := req.validate(); err != nil {
helper.UnprocessableEntityError(c, err.Error())
return
}
// Create the new result from the request
newResult := req.createResult()
// Check if user is allowed to modify scenario specified in request
ok, _ := scenario.CheckPermissions(c, database.Update, "body", int(newResult.ScenarioID))
if !ok {
return
}
// add result to DB and add association to scenario
err := newResult.addToScenario()
if !helper.DBError(c, err) {
c.JSON(http.StatusOK, gin.H{"result": newResult.Result})
}
}
// updateResult godoc
// @Summary Update a result
// @ID updateResult
// @Tags results
// @Accept json
// @Produce json
// @Success 200 {object} docs.ResponseResult "Result that was updated"
// @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 inputResult body result.updateResultRequest true "Result to be updated"
// @Param resultID path int true "Result ID"
// @Router /results/{resultID} [put]
// @Security Bearer
func updateResult(c *gin.Context) {
ok, oldResult := CheckPermissions(c, database.Update, "path", -1)
if !ok {
return
}
var req updateResultRequest
if err := c.ShouldBindJSON(&req); err != nil {
helper.BadRequestError(c, err.Error())
return
}
// Validate the request
if err := req.Result.validate(); err != nil {
helper.BadRequestError(c, err.Error())
return
}
// Create the updatedResult from oldResult
updatedResult := req.updatedResult(oldResult)
// update the Result in the DB
err := oldResult.update(updatedResult)
if !helper.DBError(c, err) {
c.JSON(http.StatusOK, gin.H{"result": updatedResult.Result})
}
}
// getResult godoc
// @Summary Get a Result
// @ID getResult
// @Tags results
// @Produce json
// @Success 200 {object} docs.ResponseResult "Result 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"
// @Router /results/{resultID} [get]
// @Security Bearer
func getResult(c *gin.Context) {
ok, result := CheckPermissions(c, database.Read, "path", -1)
if !ok {
return
}
c.JSON(http.StatusOK, gin.H{"result": result.Result})
}
// deleteResult godoc
// @Summary Delete a Result
// @ID deleteResult
// @Tags results
// @Produce json
// @Success 200 {object} docs.ResponseResult "Result that was deleted"
// @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"
// @Router /results/{resultID} [delete]
// @Security Bearer
func deleteResult(c *gin.Context) {
ok, result := CheckPermissions(c, database.Delete, "path", -1)
if !ok {
return
}
err := result.delete()
if !helper.DBError(c, err) {
c.JSON(http.StatusOK, gin.H{"result": result.Result})
}
}
// addResultFile godoc
// @Summary Upload a result file to the DB and associate it with scenario and result
// @ID addResultFile
// @Tags results
// @Accept text/plain
// @Accept text/csv
// @Accept application/gzip
// @Accept application/x-gtar
// @Accept application/x-tar
// @Accept application/x-ustar
// @Accept application/zip
// @Accept application/msexcel
// @Accept application/xml
// @Accept application/x-bag
// @Produce json
// @Success 200 {object} docs.ResponseResult "Result that was updated"
// @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 inputFile formData file true "File to be uploaded"
// @Param resultID path int true "Result ID"
// @Router /results/{resultID}/file [post]
// @Security Bearer
func addResultFile(c *gin.Context) {
ok, _ := 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)
if !ok {
return
}
// TODO download result file
}
// deleteResultFile godoc
// @Summary Delete a result file
// @ID deleteResultFile
// @Tags results
// @Produce json
// @Success 200 {object} docs.ResponseResult "Result for which file was deleted"
// @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 delete"
// @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)
if !ok {
return
}
}

View file

@ -0,0 +1,75 @@
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/scenario"
)
type Result struct {
database.Result
}
func (r *Result) save() error {
db := database.GetDB()
err := db.Create(r).Error
return err
}
func (r *Result) ByID(id uint) error {
db := database.GetDB()
err := db.Find(r, id).Error
if err != nil {
return err
}
return nil
}
func (r *Result) addToScenario() error {
db := database.GetDB()
var sco scenario.Scenario
err := sco.ByID(r.ScenarioID)
if err != nil {
return err
}
// save result to DB
err = r.save()
if err != nil {
return err
}
// associate result with scenario
err = db.Model(&sco).Association("Results").Append(r).Error
return err
}
func (r *Result) update(modifiedResult Result) error {
db := database.GetDB()
err := db.Model(r).Updates(map[string]interface{}{
"Description": modifiedResult.Description,
"ConfigSnapshots": modifiedResult.ConfigSnapshots,
"ResultFileIDs": modifiedResult.ResultFileIDs,
}).Error
return err
}
func (r *Result) delete() error {
db := database.GetDB()
var sco scenario.Scenario
err := sco.ByID(r.ScenarioID)
if err != nil {
return err
}
// remove association between Result and Scenario
err = db.Model(&sco).Association("Results").Delete(r).Error
// TODO delete Result + files (if any)
return err
}

View file

@ -0,0 +1,37 @@
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/scenario"
"github.com/gin-gonic/gin"
)
func CheckPermissions(c *gin.Context, operation database.CRUD, resultIDSource string, resultIDBody int) (bool, Result) {
var result Result
err := database.ValidateRole(c, database.ModelResult, operation)
if err != nil {
helper.UnprocessableEntityError(c, fmt.Sprintf("Access denied (role validation failed): %v", err.Error()))
return false, result
}
resultID, err := helper.GetIDOfElement(c, "resultID", resultIDSource, resultIDBody)
if err != nil {
return false, result
}
err = result.ByID(uint(resultID))
if helper.DBError(c, err) {
return false, result
}
ok, _ := scenario.CheckPermissions(c, operation, "body", int(result.ScenarioID))
if !ok {
return false, result
}
return true, result
}

View file

@ -0,0 +1 @@
package result

View file

@ -0,0 +1,63 @@
package result
import (
"github.com/jinzhu/gorm/dialects/postgres"
"gopkg.in/go-playground/validator.v9"
)
var validate *validator.Validate
type validNewResult struct {
Description string `form:"Description" validate:"omitempty"`
ResultFileIDs []int64 `form:"ResultFileIDs" validate:"omitempty"`
ConfigSnapshots []postgres.Jsonb `form:"ConfigSnapshots" validate:"required"`
ScenarioID uint `form:"ScenarioID" validate:"required"`
}
type validUpdatedResult struct {
Description string `form:"Description" validate:"omitempty" json:"description"`
ResultFileIDs []int64 `form:"ResultFileIDs" validate:"omitempty" json:"resultFileIDs"`
ConfigSnapshots []postgres.Jsonb `form:"ConfigSnapshots" validate:"omitempty" json:"configSnapshots"`
}
type addResultRequest struct {
Result validNewResult `json:"result"`
}
type updateResultRequest struct {
Result validUpdatedResult `json:"result"`
}
func (r *addResultRequest) validate() error {
validate = validator.New()
errs := validate.Struct(r)
return errs
}
func (r *validUpdatedResult) validate() error {
validate = validator.New()
errs := validate.Struct(r)
return errs
}
func (r *addResultRequest) createResult() Result {
var s Result
s.Description = r.Result.Description
s.ConfigSnapshots = r.Result.ConfigSnapshots
s.ResultFileIDs = r.Result.ResultFileIDs
s.ScenarioID = r.Result.ScenarioID
return s
}
func (r *updateResultRequest) updatedResult(oldResult Result) Result {
// Use the old Result as a basis for the updated Result `s`
s := oldResult
s.Result.Description = r.Result.Description
s.ConfigSnapshots = r.Result.ConfigSnapshots
s.ResultFileIDs = r.Result.ResultFileIDs
return s
}