mirror of
https://git.rwth-aachen.de/acs/public/villas/web-backend-go/
synced 2025-03-30 00:00:12 +01:00
Merge branch 'master' into large-files
# Conflicts: # routes/file/file_methods.go
This commit is contained in:
commit
b9483e48d0
26 changed files with 2912 additions and 94 deletions
|
@ -22,11 +22,10 @@ stages:
|
|||
- test
|
||||
- deploy
|
||||
|
||||
|
||||
# Stage: build
|
||||
##############################################################################
|
||||
|
||||
build:backend:
|
||||
build:
|
||||
stage: build
|
||||
image: ${GO_IMAGE}
|
||||
script:
|
||||
|
@ -42,7 +41,7 @@ build:backend:
|
|||
# Stage: test
|
||||
##############################################################################
|
||||
|
||||
test:gotest:
|
||||
test:
|
||||
stage: test
|
||||
image: ${GO_IMAGE}
|
||||
variables:
|
||||
|
@ -62,75 +61,13 @@ test:gotest:
|
|||
-coverprofile ./testcover.txt
|
||||
- go tool cover -func=testcover.txt
|
||||
dependencies:
|
||||
- build:backend
|
||||
|
||||
test:database:
|
||||
stage: test
|
||||
image: ${GO_IMAGE}
|
||||
variables:
|
||||
TEST_FOLDER: database
|
||||
MODE: test
|
||||
DB_NAME: ${POSTGRES_DB}
|
||||
DB_HOST: ${POSTGRES_HOST}
|
||||
DB_USER: ${POSTGRES_USER}
|
||||
DB_PASS: ${POSTGRES_PASSWORD}
|
||||
script:
|
||||
- go mod tidy
|
||||
- cd ${TEST_FOLDER}
|
||||
- go test -v
|
||||
dependencies:
|
||||
- build:backend
|
||||
|
||||
test:scenario:
|
||||
extends: test:database
|
||||
variables:
|
||||
TEST_FOLDER: routes/scenario
|
||||
|
||||
test:component-configuration:
|
||||
extends: test:database
|
||||
variables:
|
||||
TEST_FOLDER: routes/component-configuration
|
||||
|
||||
test:signal:
|
||||
extends: test:database
|
||||
variables:
|
||||
TEST_FOLDER: routes/signal
|
||||
|
||||
test:dashboard:
|
||||
extends: test:database
|
||||
variables:
|
||||
TEST_FOLDER: routes/dashboard
|
||||
|
||||
test:widget:
|
||||
extends: test:database
|
||||
variables:
|
||||
TEST_FOLDER: routes/widget
|
||||
|
||||
test:infrastructure-component:
|
||||
extends: test:database
|
||||
variables:
|
||||
TEST_FOLDER: routes/infrastructure-component
|
||||
|
||||
test:file:
|
||||
extends: test:database
|
||||
variables:
|
||||
TEST_FOLDER: routes/file
|
||||
|
||||
test:user:
|
||||
extends: test:database
|
||||
variables:
|
||||
TEST_FOLDER: routes/user
|
||||
|
||||
test:healthz:
|
||||
extends: test:database
|
||||
variables:
|
||||
TEST_FOLDER: routes/healthz
|
||||
- build
|
||||
|
||||
|
||||
# Stage: deploy
|
||||
##############################################################################
|
||||
|
||||
deploy:image:
|
||||
deploy:
|
||||
stage: deploy
|
||||
image:
|
||||
name: gcr.io/kaniko-project/executor:debug
|
||||
|
@ -146,4 +83,4 @@ deploy:image:
|
|||
--cache=true
|
||||
--cache-ttl=12h
|
||||
dependencies:
|
||||
- test:gotest
|
||||
- test
|
||||
|
|
|
@ -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{})
|
||||
}
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -33,8 +33,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"`
|
||||
}
|
||||
|
||||
|
@ -72,6 +72,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
|
||||
|
@ -215,3 +217,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[]"`
|
||||
}
|
||||
|
|
|
@ -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,
|
||||
},
|
||||
}
|
||||
|
||||
|
|
615
doc/api/docs.go
615
doc/api/docs.go
|
@ -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
|
||||
// 2021-01-12 17:04:27.751319166 +0100 CET m=+0.123714746
|
||||
|
||||
package docs
|
||||
|
||||
|
@ -806,6 +806,15 @@ var doc = `{
|
|||
],
|
||||
"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",
|
||||
"image/png",
|
||||
"image/jpeg",
|
||||
"image/gif",
|
||||
|
@ -1439,6 +1448,445 @@ 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 incl. all result files",
|
||||
"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}": {
|
||||
"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 +3337,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 +3370,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 +3400,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 +3419,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 +3442,9 @@ var doc = `{
|
|||
"type": {
|
||||
"description": "Type of file (MIME type)",
|
||||
"type": "string"
|
||||
},
|
||||
"updatedAt": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
|
@ -2985,6 +3459,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 +3497,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 +3514,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 +3564,9 @@ var doc = `{
|
|||
"startParameters": {
|
||||
"description": "Start parameters of scenario as JSON",
|
||||
"type": "string"
|
||||
},
|
||||
"updatedAt": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
|
@ -3061,6 +3577,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 +3602,9 @@ var doc = `{
|
|||
"unit": {
|
||||
"description": "Unit of Signal",
|
||||
"type": "string"
|
||||
},
|
||||
"updatedAt": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
|
@ -3093,6 +3615,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 +3629,9 @@ var doc = `{
|
|||
"description": "Role of user",
|
||||
"type": "string"
|
||||
},
|
||||
"updatedAt": {
|
||||
"type": "string"
|
||||
},
|
||||
"username": {
|
||||
"description": "Username of user",
|
||||
"type": "string"
|
||||
|
@ -3113,6 +3641,9 @@ var doc = `{
|
|||
"database.Widget": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"createdAt": {
|
||||
"type": "string"
|
||||
},
|
||||
"customProperties": {
|
||||
"description": "Custom properties of widget as JSON string",
|
||||
"type": "string"
|
||||
|
@ -3152,6 +3683,9 @@ var doc = `{
|
|||
"description": "Type of widget",
|
||||
"type": "string"
|
||||
},
|
||||
"updatedAt": {
|
||||
"type": "string"
|
||||
},
|
||||
"width": {
|
||||
"description": "Width of widget",
|
||||
"type": "integer"
|
||||
|
@ -3279,6 +3813,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 +4016,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": {
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -789,6 +789,15 @@
|
|||
],
|
||||
"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",
|
||||
"image/png",
|
||||
"image/jpeg",
|
||||
"image/gif",
|
||||
|
@ -1422,6 +1431,445 @@
|
|||
}
|
||||
}
|
||||
},
|
||||
"/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 incl. all result files",
|
||||
"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}": {
|
||||
"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 +3320,9 @@
|
|||
"database.ComponentConfiguration": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"createdAt": {
|
||||
"type": "string"
|
||||
},
|
||||
"fileIDs": {
|
||||
"description": "Array of file IDs used by the component configuration",
|
||||
"type": "string"
|
||||
|
@ -2902,12 +3353,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 +3383,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 +3402,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 +3425,9 @@
|
|||
"type": {
|
||||
"description": "Type of file (MIME type)",
|
||||
"type": "string"
|
||||
},
|
||||
"updatedAt": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
|
@ -2968,6 +3442,9 @@
|
|||
"description": "Category of IC (simulator, gateway, database, etc.)",
|
||||
"type": "string"
|
||||
},
|
||||
"createdAt": {
|
||||
"type": "string"
|
||||
},
|
||||
"description": {
|
||||
"description": "Description of the IC",
|
||||
"type": "string"
|
||||
|
@ -3003,6 +3480,9 @@
|
|||
"description": "Type of IC (RTDS, VILLASnode, RTDS, etc.)",
|
||||
"type": "string"
|
||||
},
|
||||
"updatedAt": {
|
||||
"type": "string"
|
||||
},
|
||||
"uptime": {
|
||||
"description": "Uptime of the IC",
|
||||
"type": "number"
|
||||
|
@ -3017,9 +3497,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 +3547,9 @@
|
|||
"startParameters": {
|
||||
"description": "Start parameters of scenario as JSON",
|
||||
"type": "string"
|
||||
},
|
||||
"updatedAt": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
|
@ -3044,6 +3560,9 @@
|
|||
"description": "ID of Component Configuration",
|
||||
"type": "integer"
|
||||
},
|
||||
"createdAt": {
|
||||
"type": "string"
|
||||
},
|
||||
"direction": {
|
||||
"description": "Direction of the signal (in or out)",
|
||||
"type": "string"
|
||||
|
@ -3066,6 +3585,9 @@
|
|||
"unit": {
|
||||
"description": "Unit of Signal",
|
||||
"type": "string"
|
||||
},
|
||||
"updatedAt": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
|
@ -3076,6 +3598,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 +3612,9 @@
|
|||
"description": "Role of user",
|
||||
"type": "string"
|
||||
},
|
||||
"updatedAt": {
|
||||
"type": "string"
|
||||
},
|
||||
"username": {
|
||||
"description": "Username of user",
|
||||
"type": "string"
|
||||
|
@ -3096,6 +3624,9 @@
|
|||
"database.Widget": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"createdAt": {
|
||||
"type": "string"
|
||||
},
|
||||
"customProperties": {
|
||||
"description": "Custom properties of widget as JSON string",
|
||||
"type": "string"
|
||||
|
@ -3135,6 +3666,9 @@
|
|||
"description": "Type of widget",
|
||||
"type": "string"
|
||||
},
|
||||
"updatedAt": {
|
||||
"type": "string"
|
||||
},
|
||||
"width": {
|
||||
"description": "Width of widget",
|
||||
"type": "integer"
|
||||
|
@ -3262,6 +3796,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 +3999,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": {
|
||||
|
|
|
@ -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:
|
||||
|
@ -1244,6 +1355,15 @@ paths:
|
|||
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
|
||||
- image/png
|
||||
- image/jpeg
|
||||
- image/gif
|
||||
|
@ -1616,6 +1736,292 @@ 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 incl. all result files
|
||||
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
|
||||
/scenarios:
|
||||
get:
|
||||
operationId: getScenarios
|
||||
|
|
1
go.sum
1
go.sum
|
@ -258,6 +258,7 @@ golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7w
|
|||
golang.org/x/sys v0.0.0-20190610200419-93c9922d18ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20191010194322-b09406accb47 h1:/XfQ9z7ib8eEJX2hdgFTZJ/ntt0swNk5oYBziWeTCvY=
|
||||
golang.org/x/sys v0.0.0-20191010194322-b09406accb47/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f h1:+Nyd8tzPX9R7BWHguqsrbFdRx3WQ/1ib8I44HXV5yTA=
|
||||
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
|
|
|
@ -251,7 +251,7 @@ var FileD = database.File{
|
|||
var customPropertiesBox = json.RawMessage(`{"border_color" : "#4287f5", "border_color_opacity": 1, "background_color" : "#961520", "background_color_opacity" : 1}`)
|
||||
var customPropertiesSlider = json.RawMessage(`{"default_value" : 0, "orientation" : 0, "rangeUseMinMax": false, "rangeMin" : 0, "rangeMax": 200, "rangeUseMinMax" : true, "showUnit": true, "continous_update": false, "value": "", "resizeLeftRightLock": false, "resizeTopBottomLock": true, "step": 0.1 }`)
|
||||
var customPropertiesLabel = json.RawMessage(`{"textSize" : "20", "fontColor" : "#4287f5", "fontColor_opacity": 1}`)
|
||||
var customPropertiesButton = json.RawMessage(`{"pressed": false, "toggle" : false, "on_value" : 1, "off_value" : 0, "background_color": "#961520", "font_color": "#4287f5"}`)
|
||||
var customPropertiesButton = json.RawMessage(`{"pressed": false, "toggle" : false, "on_value" : 1, "off_value" : 0, "background_color": "#961520", "font_color": "#4287f5", "border_color": "#4287f5", "background_color_opacity": 1}`)
|
||||
var customPropertiesLamp = json.RawMessage(`{"signal" : 0, "on_color" : "#4287f5", "off_color": "#961520", "threshold" : 0.5, "on_color_opacity": 1, "off_color_opacity": 1}`)
|
||||
|
||||
var WidgetA = database.Widget{
|
||||
|
|
|
@ -131,7 +131,6 @@ func (m *ComponentConfiguration) delete() error {
|
|||
}
|
||||
|
||||
// remove association between ComponentConfiguration and Scenario
|
||||
// ComponentConfiguration itself is not deleted from DB, it remains as "dangling"
|
||||
err = db.Model(&so).Association("ComponentConfigurations").Delete(m).Error
|
||||
if err != nil {
|
||||
return err
|
||||
|
@ -143,6 +142,32 @@ func (m *ComponentConfiguration) delete() error {
|
|||
return err
|
||||
}
|
||||
|
||||
// Get Signals of InputMapping and delete them
|
||||
var InputMappingSignals []database.Signal
|
||||
err = db.Model(m).Related(&InputMappingSignals, "InputMapping").Error
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
for sig, _ := range InputMappingSignals {
|
||||
err = db.Delete(&sig).Error
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
// Get Signals of OutputMapping and delete them
|
||||
var OutputMappingSignals []database.Signal
|
||||
err = db.Model(m).Related(&OutputMappingSignals, "OutputMapping").Error
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
for sig, _ := range OutputMappingSignals {
|
||||
err = db.Delete(&sig).Error
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
// delete component configuration
|
||||
err = db.Delete(m).Error
|
||||
if err != nil {
|
||||
|
|
|
@ -30,9 +30,9 @@ type Dashboard struct {
|
|||
database.Dashboard
|
||||
}
|
||||
|
||||
func (v *Dashboard) save() error {
|
||||
func (d *Dashboard) save() error {
|
||||
db := database.GetDB()
|
||||
err := db.Create(v).Error
|
||||
err := db.Create(d).Error
|
||||
return err
|
||||
}
|
||||
|
||||
|
@ -88,8 +88,22 @@ 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
|
||||
|
||||
// get all widgets of the dashboard
|
||||
var widgets []database.Widget
|
||||
err = db.Order("ID asc").Model(d).Related(&widgets, "Widgets").Error
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Delete widgets
|
||||
for widget, _ := range widgets {
|
||||
err = db.Delete(&widget).Error
|
||||
}
|
||||
|
||||
// Delete dashboard
|
||||
err = db.Delete(d).Error
|
||||
|
||||
return err
|
||||
}
|
||||
|
|
|
@ -104,7 +104,7 @@ func addFile(c *gin.Context) {
|
|||
}
|
||||
|
||||
var newFile File
|
||||
err = newFile.register(file_header, so.ID)
|
||||
err = newFile.Register(file_header, so.ID)
|
||||
if !helper.DBError(c, err) {
|
||||
c.JSON(http.StatusOK, gin.H{"file": newFile.File})
|
||||
}
|
||||
|
@ -116,6 +116,15 @@ func addFile(c *gin.Context) {
|
|||
// @ID getFile
|
||||
// @Tags files
|
||||
// @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
|
||||
// @Produce png
|
||||
// @Produce jpeg
|
||||
// @Produce gif
|
||||
|
@ -132,7 +141,7 @@ func addFile(c *gin.Context) {
|
|||
func getFile(c *gin.Context) {
|
||||
|
||||
// check access
|
||||
ok, f := checkPermissions(c, database.Read)
|
||||
ok, f := CheckPermissions(c, database.Read)
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
|
@ -165,7 +174,7 @@ func getFile(c *gin.Context) {
|
|||
func updateFile(c *gin.Context) {
|
||||
|
||||
// check access
|
||||
ok, f := checkPermissions(c, database.Update)
|
||||
ok, f := CheckPermissions(c, database.Update)
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
|
@ -199,12 +208,12 @@ func updateFile(c *gin.Context) {
|
|||
func deleteFile(c *gin.Context) {
|
||||
|
||||
// check access
|
||||
ok, f := checkPermissions(c, database.Delete)
|
||||
ok, f := CheckPermissions(c, database.Delete)
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
|
||||
err := f.delete()
|
||||
err := f.Delete()
|
||||
if !helper.DBError(c, err) {
|
||||
c.JSON(http.StatusOK, gin.H{"file": f.File})
|
||||
}
|
||||
|
|
|
@ -46,7 +46,7 @@ type File struct {
|
|||
database.File
|
||||
}
|
||||
|
||||
func (f *File) byID(id uint) error {
|
||||
func (f *File) ByID(id uint) error {
|
||||
db := database.GetDB()
|
||||
err := db.Find(f, id).Error
|
||||
if err != nil {
|
||||
|
@ -82,7 +82,8 @@ func (f *File) download(c *gin.Context) error {
|
|||
return nil
|
||||
}
|
||||
|
||||
func (f *File) register(fileHeader *multipart.FileHeader, scenarioID uint) error {
|
||||
func (f *File) Register(fileHeader *multipart.FileHeader, scenarioID uint) error {
|
||||
|
||||
// Obtain properties of file
|
||||
f.Type = fileHeader.Header.Get("Content-Type")
|
||||
f.Name = filepath.Base(fileHeader.Filename)
|
||||
|
@ -200,7 +201,7 @@ func (f *File) update(fileHeader *multipart.FileHeader) error {
|
|||
return nil
|
||||
}
|
||||
|
||||
func (f *File) delete() error {
|
||||
func (f *File) Delete() error {
|
||||
|
||||
db := database.GetDB()
|
||||
|
||||
|
|
|
@ -29,7 +29,7 @@ import (
|
|||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
func checkPermissions(c *gin.Context, operation database.CRUD) (bool, File) {
|
||||
func CheckPermissions(c *gin.Context, operation database.CRUD) (bool, File) {
|
||||
|
||||
var f File
|
||||
|
||||
|
@ -44,7 +44,7 @@ func checkPermissions(c *gin.Context, operation database.CRUD) (bool, File) {
|
|||
return false, f
|
||||
}
|
||||
|
||||
err = f.byID(uint(fileID))
|
||||
err = f.ByID(uint(fileID))
|
||||
if helper.DBError(c, err) {
|
||||
return false, f
|
||||
}
|
||||
|
|
|
@ -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))
|
||||
|
||||
|
|
320
routes/result/result_endpoints.go
Normal file
320
routes/result/result_endpoints.go
Normal file
|
@ -0,0 +1,320 @@
|
|||
/** Result package, endpoints.
|
||||
*
|
||||
* @author Sonja Happ <sonja.happ@eonerc.rwth-aachen.de>
|
||||
* @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 <http://www.gnu.org/licenses/>.
|
||||
*********************************************************************************/
|
||||
|
||||
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"
|
||||
)
|
||||
|
||||
func RegisterResultEndpoints(r *gin.RouterGroup) {
|
||||
r.GET("", getResults)
|
||||
r.POST("", addResult)
|
||||
r.PUT("/:resultID", updateResult)
|
||||
r.GET("/:resultID", getResult)
|
||||
r.DELETE("/:resultID", deleteResult)
|
||||
r.POST("/:resultID/file", addResultFile)
|
||||
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, sco := scenario.CheckPermissions(c, database.Read, "query", -1)
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
|
||||
db := database.GetDB()
|
||||
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{"results": results})
|
||||
}
|
||||
}
|
||||
|
||||
// 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 incl. all result files
|
||||
// @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
|
||||
}
|
||||
|
||||
// Check if user is allowed to modify scenario associated with result
|
||||
ok, _ = scenario.CheckPermissions(c, database.Update, "body", int(result.ScenarioID))
|
||||
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, result := checkPermissions(c, database.Update, "path", -1)
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
|
||||
// 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
|
||||
}
|
||||
|
||||
// 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
|
||||
// @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) {
|
||||
|
||||
// check access
|
||||
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})
|
||||
}
|
||||
|
||||
}
|
148
routes/result/result_methods.go
Normal file
148
routes/result/result_methods.go
Normal file
|
@ -0,0 +1,148 @@
|
|||
/** Result package, methods.
|
||||
*
|
||||
* @author Sonja Happ <sonja.happ@eonerc.rwth-aachen.de>
|
||||
* @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 <http://www.gnu.org/licenses/>.
|
||||
*********************************************************************************/
|
||||
|
||||
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 {
|
||||
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
|
||||
|
||||
// Delete result files
|
||||
for _, fileid := range r.ResultFileIDs {
|
||||
var f file.File
|
||||
err := f.ByID(uint(fileid))
|
||||
if err != nil {
|
||||
log.Println("Unable to delete file with ID ", fileid, 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
|
||||
}
|
59
routes/result/result_middleware.go
Normal file
59
routes/result/result_middleware.go
Normal file
|
@ -0,0 +1,59 @@
|
|||
/** Result package, middleware.
|
||||
*
|
||||
* @author Sonja Happ <sonja.happ@eonerc.rwth-aachen.de>
|
||||
* @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 <http://www.gnu.org/licenses/>.
|
||||
*********************************************************************************/
|
||||
|
||||
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
|
||||
}
|
506
routes/result/result_test.go
Normal file
506
routes/result/result_test.go
Normal file
|
@ -0,0 +1,506 @@
|
|||
/** Result package, testing.
|
||||
*
|
||||
* @author Sonja Happ <sonja.happ@eonerc.rwth-aachen.de>
|
||||
* @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 <http://www.gnu.org/licenses/>.
|
||||
*********************************************************************************/
|
||||
|
||||
package result
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"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"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"mime/multipart"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"os"
|
||||
"testing"
|
||||
)
|
||||
|
||||
var router *gin.Engine
|
||||
var base_api_results = "/api/results"
|
||||
var base_api_auth = "/api/authenticate"
|
||||
|
||||
type ScenarioRequest struct {
|
||||
Name string `json:"name,omitempty"`
|
||||
Running bool `json:"running,omitempty"`
|
||||
StartParameters postgres.Jsonb `json:"startParameters,omitempty"`
|
||||
}
|
||||
|
||||
type ResultRequest struct {
|
||||
Description string `json:"description,omitempty"`
|
||||
ScenarioID uint `json:"scenarioID,omitempty"`
|
||||
ConfigSnapshots postgres.Jsonb `json:"configSnapshots,omitempty"`
|
||||
}
|
||||
|
||||
type ResponseResult struct {
|
||||
Result database.Result `json:"result"`
|
||||
}
|
||||
|
||||
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) {
|
||||
|
||||
database.DropTables()
|
||||
database.MigrateModels()
|
||||
assert.NoError(t, helper.DBAddAdminAndUserAndGuest())
|
||||
|
||||
// prepare the content of the DB for testing
|
||||
// by adding a scenario
|
||||
scenarioID := addScenario()
|
||||
|
||||
// authenticate as normal user
|
||||
token, err := helper.AuthenticateForTest(router,
|
||||
base_api_auth, "POST", helper.UserACredentials)
|
||||
assert.NoError(t, err)
|
||||
|
||||
// test POST newResult
|
||||
configSnapshot1 := json.RawMessage(`{"configs": [ {"Name" : "conf1", "scenarioID" : 1}, {"Name" : "conf2", "scenarioID" : 1}]}`)
|
||||
confSnapshots := postgres.Jsonb{configSnapshot1}
|
||||
|
||||
newResult := ResultRequest{
|
||||
Description: "This is a test result.",
|
||||
ScenarioID: scenarioID,
|
||||
ConfigSnapshots: confSnapshots,
|
||||
}
|
||||
|
||||
code, resp, err := helper.TestEndpoint(router, token,
|
||||
base_api_results, "POST", helper.KeyModels{"result": newResult})
|
||||
assert.NoError(t, err)
|
||||
assert.Equalf(t, 200, code, "Response body: \n%v\n", resp)
|
||||
|
||||
// Count the number of all the results returned for scenario
|
||||
NumberOfConfigs, err := helper.LengthOfResponse(router, token,
|
||||
fmt.Sprintf("%v?scenarioID=%v", base_api_results, scenarioID), "GET", nil)
|
||||
assert.NoError(t, err)
|
||||
|
||||
assert.Equal(t, 1, NumberOfConfigs)
|
||||
|
||||
// authenticate as normal userB who has no access to scenario
|
||||
token, err = helper.AuthenticateForTest(router,
|
||||
base_api_auth, "POST", helper.UserBCredentials)
|
||||
assert.NoError(t, err)
|
||||
|
||||
// try to get results without access
|
||||
// should result in unprocessable entity
|
||||
code, resp, err = helper.TestEndpoint(router, token,
|
||||
fmt.Sprintf("%v?scenarioID=%v", base_api_results, scenarioID), "GET", nil)
|
||||
assert.NoError(t, err)
|
||||
assert.Equalf(t, 422, code, "Response body: \n%v\n", resp)
|
||||
|
||||
}
|
||||
|
||||
func TestAddGetUpdateDeleteResult(t *testing.T) {
|
||||
|
||||
database.DropTables()
|
||||
database.MigrateModels()
|
||||
assert.NoError(t, helper.DBAddAdminAndUserAndGuest())
|
||||
|
||||
// prepare the content of the DB for testing
|
||||
// by adding a scenario
|
||||
scenarioID := addScenario()
|
||||
configSnapshot1 := json.RawMessage(`{"configs": [ {"Name" : "conf1", "scenarioID" : 1}, {"Name" : "conf2", "scenarioID" : 1}]}`)
|
||||
confSnapshots := postgres.Jsonb{configSnapshot1}
|
||||
|
||||
newResult := ResultRequest{
|
||||
Description: "This is a test result.",
|
||||
ScenarioID: scenarioID,
|
||||
ConfigSnapshots: confSnapshots,
|
||||
}
|
||||
|
||||
// authenticate as normal userB who has no access to new scenario
|
||||
token, err := helper.AuthenticateForTest(router,
|
||||
base_api_auth, "POST", helper.UserBCredentials)
|
||||
assert.NoError(t, err)
|
||||
|
||||
// try to POST with no access
|
||||
// should result in unprocessable entity
|
||||
code, resp, err := helper.TestEndpoint(router, token,
|
||||
base_api_results, "POST", helper.KeyModels{"result": newResult})
|
||||
assert.NoError(t, err)
|
||||
assert.Equalf(t, 422, code, "Response body: \n%v\n", resp)
|
||||
|
||||
// authenticate as normal user
|
||||
token, err = helper.AuthenticateForTest(router,
|
||||
base_api_auth, "POST", helper.UserACredentials)
|
||||
assert.NoError(t, err)
|
||||
|
||||
// try to POST non JSON body
|
||||
code, resp, err = helper.TestEndpoint(router, token,
|
||||
base_api_results, "POST", "this is not JSON")
|
||||
assert.NoError(t, err)
|
||||
assert.Equalf(t, 400, code, "Response body: \n%v\n", resp)
|
||||
|
||||
// test POST newResult
|
||||
code, resp, err = helper.TestEndpoint(router, token,
|
||||
base_api_results, "POST", helper.KeyModels{"result": newResult})
|
||||
assert.NoError(t, err)
|
||||
assert.Equalf(t, 200, code, "Response body: \n%v\n", resp)
|
||||
|
||||
// Compare POST's response with the newResult
|
||||
err = helper.CompareResponse(resp, helper.KeyModels{"result": newResult})
|
||||
assert.NoError(t, err)
|
||||
|
||||
// Read newResults's ID from the response
|
||||
newResultID, err := helper.GetResponseID(resp)
|
||||
assert.NoError(t, err)
|
||||
|
||||
// Get the newResult
|
||||
code, resp, err = helper.TestEndpoint(router, token,
|
||||
fmt.Sprintf("%v/%v", base_api_results, newResultID), "GET", nil)
|
||||
assert.NoError(t, err)
|
||||
assert.Equalf(t, 200, code, "Response body: \n%v\n", resp)
|
||||
|
||||
// Compare GET's response with the newResult
|
||||
err = helper.CompareResponse(resp, helper.KeyModels{"result": newResult})
|
||||
assert.NoError(t, err)
|
||||
|
||||
// try to POST a malformed result
|
||||
// Required fields are missing
|
||||
malformedNewResult := ResultRequest{
|
||||
Description: "ThisIsAMalformedRequest",
|
||||
}
|
||||
// this should NOT work and return a unprocessable entity 442 status code
|
||||
code, resp, err = helper.TestEndpoint(router, token,
|
||||
base_api_results, "POST", helper.KeyModels{"result": malformedNewResult})
|
||||
assert.NoError(t, err)
|
||||
assert.Equalf(t, 422, code, "Response body: \n%v\n", resp)
|
||||
|
||||
// authenticate as normal userB who has no access to new scenario
|
||||
token, err = helper.AuthenticateForTest(router,
|
||||
base_api_auth, "POST", helper.UserBCredentials)
|
||||
assert.NoError(t, err)
|
||||
|
||||
// Try to GET the newResult with no access
|
||||
// Should result in unprocessable entity
|
||||
code, resp, err = helper.TestEndpoint(router, token,
|
||||
fmt.Sprintf("%v/%v", base_api_results, newResultID), "GET", nil)
|
||||
assert.NoError(t, err)
|
||||
assert.Equalf(t, 422, code, "Response body: \n%v\n", resp)
|
||||
|
||||
// Test UPDATE/ PUT
|
||||
|
||||
updatedResult := ResultRequest{
|
||||
Description: "This is an updated description",
|
||||
ConfigSnapshots: confSnapshots,
|
||||
}
|
||||
|
||||
// try to PUT with no access
|
||||
// should result in unprocessable entity
|
||||
code, resp, err = helper.TestEndpoint(router, token,
|
||||
fmt.Sprintf("%v/%v", base_api_results, newResultID), "PUT", helper.KeyModels{"result": updatedResult})
|
||||
assert.NoError(t, err)
|
||||
assert.Equalf(t, 422, code, "Response body: \n%v\n", resp)
|
||||
|
||||
// authenticate as guest user who has access to result
|
||||
token, err = helper.AuthenticateForTest(router,
|
||||
base_api_auth, "POST", helper.GuestCredentials)
|
||||
assert.NoError(t, err)
|
||||
|
||||
// try to PUT as guest
|
||||
// should NOT work and result in unprocessable entity
|
||||
code, resp, err = helper.TestEndpoint(router, token,
|
||||
fmt.Sprintf("%v/%v", base_api_results, newResultID), "PUT", helper.KeyModels{"result": updatedResult})
|
||||
assert.NoError(t, err)
|
||||
assert.Equalf(t, 422, code, "Response body: \n%v\n", resp)
|
||||
|
||||
// authenticate as normal user
|
||||
token, err = helper.AuthenticateForTest(router,
|
||||
base_api_auth, "POST", helper.UserACredentials)
|
||||
assert.NoError(t, err)
|
||||
|
||||
// try to PUT a non JSON body
|
||||
// should result in a bad request
|
||||
code, resp, err = helper.TestEndpoint(router, token,
|
||||
fmt.Sprintf("%v/%v", base_api_results, newResultID), "PUT", "This is not JSON")
|
||||
assert.NoError(t, err)
|
||||
assert.Equalf(t, 400, code, "Response body: \n%v\n", resp)
|
||||
|
||||
// test PUT
|
||||
code, resp, err = helper.TestEndpoint(router, token,
|
||||
fmt.Sprintf("%v/%v", base_api_results, newResultID), "PUT", helper.KeyModels{"result": updatedResult})
|
||||
assert.NoError(t, err)
|
||||
assert.Equalf(t, 200, code, "Response body: \n%v\n", resp)
|
||||
|
||||
// Compare PUT's response with the updatedResult
|
||||
err = helper.CompareResponse(resp, helper.KeyModels{"result": updatedResult})
|
||||
assert.NoError(t, err)
|
||||
|
||||
// try to update a result that does not exist (should return not found 404 status code)
|
||||
code, resp, err = helper.TestEndpoint(router, token,
|
||||
fmt.Sprintf("%v/%v", base_api_results, newResultID+1), "PUT", helper.KeyModels{"result": updatedResult})
|
||||
assert.NoError(t, err)
|
||||
assert.Equalf(t, 404, code, "Response body: \n%v\n", resp)
|
||||
|
||||
// Test DELETE
|
||||
newResult.Description = updatedResult.Description
|
||||
|
||||
// authenticate as normal userB who has no access to new scenario
|
||||
token, err = helper.AuthenticateForTest(router,
|
||||
base_api_auth, "POST", helper.UserBCredentials)
|
||||
assert.NoError(t, err)
|
||||
|
||||
// try to DELETE with no access
|
||||
// should result in unprocessable entity
|
||||
code, resp, err = helper.TestEndpoint(router, token,
|
||||
fmt.Sprintf("%v/%v", base_api_results, newResultID), "DELETE", nil)
|
||||
assert.NoError(t, err)
|
||||
assert.Equalf(t, 422, code, "Response body: \n%v\n", resp)
|
||||
|
||||
// authenticate as normal user
|
||||
token, err = helper.AuthenticateForTest(router,
|
||||
base_api_auth, "POST", helper.UserACredentials)
|
||||
assert.NoError(t, err)
|
||||
|
||||
// Count the number of all the results returned for scenario
|
||||
initialNumber, err := helper.LengthOfResponse(router, token,
|
||||
fmt.Sprintf("%v?scenarioID=%v", base_api_results, scenarioID), "GET", nil)
|
||||
assert.NoError(t, err)
|
||||
|
||||
// Delete the added newResult
|
||||
code, resp, err = helper.TestEndpoint(router, token,
|
||||
fmt.Sprintf("%v/%v", base_api_results, newResultID), "DELETE", nil)
|
||||
assert.NoError(t, err)
|
||||
assert.Equalf(t, 200, code, "Response body: \n%v\n", resp)
|
||||
|
||||
// Compare DELETE's response with the newResult
|
||||
err = helper.CompareResponse(resp, helper.KeyModels{"result": newResult})
|
||||
assert.NoError(t, err)
|
||||
|
||||
// Again count the number of all the results returned
|
||||
finalNumber, err := helper.LengthOfResponse(router, token,
|
||||
fmt.Sprintf("%v?scenarioID=%v", base_api_results, scenarioID), "GET", nil)
|
||||
assert.NoError(t, err)
|
||||
|
||||
assert.Equal(t, initialNumber-1, finalNumber)
|
||||
|
||||
}
|
||||
|
||||
func TestAddDeleteResultFile(t *testing.T) {
|
||||
database.DropTables()
|
||||
database.MigrateModels()
|
||||
assert.NoError(t, helper.DBAddAdminAndUserAndGuest())
|
||||
|
||||
// prepare the content of the DB for testing
|
||||
// by adding a scenario
|
||||
scenarioID := addScenario()
|
||||
configSnapshot1 := json.RawMessage(`{"configs": [ {"Name" : "conf1", "scenarioID" : 1}, {"Name" : "conf2", "scenarioID" : 1}]}`)
|
||||
confSnapshots := postgres.Jsonb{configSnapshot1}
|
||||
|
||||
newResult := ResultRequest{
|
||||
Description: "This is a test result.",
|
||||
ScenarioID: scenarioID,
|
||||
ConfigSnapshots: confSnapshots,
|
||||
}
|
||||
|
||||
// authenticate as normal user
|
||||
token, err := helper.AuthenticateForTest(router,
|
||||
base_api_auth, "POST", helper.UserACredentials)
|
||||
assert.NoError(t, err)
|
||||
|
||||
// test POST newResult
|
||||
code, resp, err := helper.TestEndpoint(router, token,
|
||||
base_api_results, "POST", helper.KeyModels{"result": newResult})
|
||||
assert.NoError(t, err)
|
||||
assert.Equalf(t, 200, code, "Response body: \n%v\n", resp)
|
||||
|
||||
// Compare POST's response with the newResult
|
||||
err = helper.CompareResponse(resp, helper.KeyModels{"result": newResult})
|
||||
assert.NoError(t, err)
|
||||
|
||||
// Read newResults's ID from the response
|
||||
newResultID, err := helper.GetResponseID(resp)
|
||||
assert.NoError(t, err)
|
||||
|
||||
// test POST result file
|
||||
|
||||
// create a testfile.txt in local folder
|
||||
c1 := []byte("a,few,values\n1,2,3\n")
|
||||
err = ioutil.WriteFile("testfile.csv", c1, 0644)
|
||||
assert.NoError(t, err)
|
||||
|
||||
bodyBuf := &bytes.Buffer{}
|
||||
bodyWriter := multipart.NewWriter(bodyBuf)
|
||||
fileWriter, err := bodyWriter.CreateFormFile("file", "testuploadfile.csv")
|
||||
assert.NoError(t, err, "writing to buffer")
|
||||
|
||||
// open file handle
|
||||
fh, err := os.Open("testfile.csv")
|
||||
assert.NoError(t, err, "opening file")
|
||||
defer fh.Close()
|
||||
|
||||
// io copy
|
||||
_, err = io.Copy(fileWriter, fh)
|
||||
assert.NoError(t, err, "IO copy")
|
||||
|
||||
contentType := bodyWriter.FormDataContentType()
|
||||
bodyWriter.Close()
|
||||
|
||||
// Create the request
|
||||
w := httptest.NewRecorder()
|
||||
req, err := http.NewRequest("POST", fmt.Sprintf("%v/%v/file", base_api_results, newResultID), bodyBuf)
|
||||
assert.NoError(t, err, "create request")
|
||||
|
||||
req.Header.Set("Content-Type", contentType)
|
||||
req.Header.Add("Authorization", "Bearer "+token)
|
||||
router.ServeHTTP(w, req)
|
||||
|
||||
assert.Equalf(t, 200, w.Code, "Response body: \n%v\n", w.Body)
|
||||
err = helper.CompareResponse(w.Body, helper.KeyModels{"result": newResult})
|
||||
|
||||
// extract file ID from response body
|
||||
var respResult ResponseResult
|
||||
err = json.Unmarshal(w.Body.Bytes(), &respResult)
|
||||
assert.NoError(t, err, "unmarshal response body")
|
||||
|
||||
assert.Equal(t, 1, len(respResult.Result.ResultFileIDs))
|
||||
fileID := respResult.Result.ResultFileIDs[0]
|
||||
|
||||
// DELETE the file
|
||||
|
||||
code, resp, err = helper.TestEndpoint(router, token,
|
||||
fmt.Sprintf("%v/%v/file/%v", base_api_results, newResultID, fileID), "DELETE", nil)
|
||||
assert.NoError(t, err)
|
||||
assert.Equalf(t, 200, code, "Response body: \n%v\n", resp)
|
||||
|
||||
var respResult2 ResponseResult
|
||||
err = json.Unmarshal(resp.Bytes(), &respResult2)
|
||||
assert.NoError(t, err, "unmarshal response body")
|
||||
assert.Equal(t, 0, len(respResult2.Result.ResultFileIDs))
|
||||
|
||||
// ADD the file again
|
||||
|
||||
bodyBuf2 := &bytes.Buffer{}
|
||||
bodyWriter2 := multipart.NewWriter(bodyBuf2)
|
||||
fileWriter2, err := bodyWriter2.CreateFormFile("file", "testuploadfile.csv")
|
||||
assert.NoError(t, err, "writing to buffer")
|
||||
|
||||
// open file handle
|
||||
fh2, err := os.Open("testfile.csv")
|
||||
assert.NoError(t, err, "opening file")
|
||||
defer fh2.Close()
|
||||
|
||||
// io copy
|
||||
_, err = io.Copy(fileWriter2, fh2)
|
||||
assert.NoError(t, err, "IO copy")
|
||||
|
||||
contentType2 := bodyWriter2.FormDataContentType()
|
||||
bodyWriter2.Close()
|
||||
|
||||
// Create the request
|
||||
w2 := httptest.NewRecorder()
|
||||
req2, err := http.NewRequest("POST", fmt.Sprintf("%v/%v/file", base_api_results, newResultID), bodyBuf2)
|
||||
assert.NoError(t, err, "create request")
|
||||
|
||||
req2.Header.Set("Content-Type", contentType2)
|
||||
req2.Header.Add("Authorization", "Bearer "+token)
|
||||
router.ServeHTTP(w2, req2)
|
||||
|
||||
assert.Equalf(t, 200, w2.Code, "Response body: \n%v\n", w2.Body)
|
||||
err = helper.CompareResponse(w2.Body, helper.KeyModels{"result": newResult})
|
||||
|
||||
// extract file ID from response body
|
||||
var respResult3 ResponseResult
|
||||
err = json.Unmarshal(w2.Body.Bytes(), &respResult3)
|
||||
assert.NoError(t, err, "unmarshal response body")
|
||||
|
||||
assert.Equal(t, 1, len(respResult3.Result.ResultFileIDs))
|
||||
|
||||
// DELETE result inlc. file
|
||||
code, resp, err = helper.TestEndpoint(router, token,
|
||||
fmt.Sprintf("%v/%v", base_api_results, newResultID), "DELETE", nil)
|
||||
assert.NoError(t, err)
|
||||
assert.Equalf(t, 200, code, "Response body: \n%v\n", resp)
|
||||
|
||||
// Compare DELETE's response with the newResult
|
||||
err = helper.CompareResponse(resp, helper.KeyModels{"result": newResult})
|
||||
assert.NoError(t, err)
|
||||
|
||||
// Again count the number of all the results returned
|
||||
finalNumber, err := helper.LengthOfResponse(router, token,
|
||||
fmt.Sprintf("%v?scenarioID=%v", base_api_results, scenarioID), "GET", nil)
|
||||
assert.NoError(t, err)
|
||||
|
||||
assert.Equal(t, 0, finalNumber)
|
||||
|
||||
}
|
97
routes/result/result_validators.go
Normal file
97
routes/result/result_validators.go
Normal file
|
@ -0,0 +1,97 @@
|
|||
/** Result package, validators.
|
||||
*
|
||||
* @author Sonja Happ <sonja.happ@eonerc.rwth-aachen.de>
|
||||
* @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 <http://www.gnu.org/licenses/>.
|
||||
*********************************************************************************/
|
||||
|
||||
package result
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"github.com/jinzhu/gorm/dialects/postgres"
|
||||
"github.com/nsf/jsondiff"
|
||||
"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.ResultFileIDs = r.Result.ResultFileIDs
|
||||
|
||||
// only update snapshots if not empty
|
||||
var emptyJson postgres.Jsonb
|
||||
// Serialize empty json and params
|
||||
emptyJson_ser, _ := json.Marshal(emptyJson)
|
||||
configSnapshots_ser, _ := json.Marshal(r.Result.ConfigSnapshots)
|
||||
opts := jsondiff.DefaultConsoleOptions()
|
||||
diff, _ := jsondiff.Compare(emptyJson_ser, configSnapshots_ser, &opts)
|
||||
if diff.String() != "FullMatch" {
|
||||
s.ConfigSnapshots = r.Result.ConfigSnapshots
|
||||
}
|
||||
|
||||
return s
|
||||
}
|
|
@ -59,7 +59,6 @@ func getScenarios(c *gin.Context) {
|
|||
|
||||
// ATTENTION: do not use c.GetInt (common.UserIDCtx) since user_id is of type uint and not int
|
||||
userID, _ := c.Get(database.UserIDCtx)
|
||||
userRole, _ := c.Get(database.UserRoleCtx)
|
||||
|
||||
var u user.User
|
||||
err := u.ByID(userID.(uint))
|
||||
|
@ -70,7 +69,7 @@ func getScenarios(c *gin.Context) {
|
|||
// get all scenarios for the user who issues the request
|
||||
db := database.GetDB()
|
||||
var scenarios []database.Scenario
|
||||
if userRole == "Admin" { // Admin can see all scenarios
|
||||
if u.Role == "Admin" { // Admin can see all scenarios
|
||||
err = db.Order("ID asc").Find(&scenarios).Error
|
||||
if helper.DBError(c, err) {
|
||||
return
|
||||
|
|
|
@ -500,7 +500,12 @@ func TestAddUserToScenario(t *testing.T) {
|
|||
assert.Equalf(t, 200, code, "Response body: \n%v\n", resp)
|
||||
|
||||
// Compare resp to userB
|
||||
err = helper.CompareResponse(resp, helper.KeyModels{"user": helper.UserB})
|
||||
userB := UserRequest{
|
||||
Username: helper.UserB.Username,
|
||||
Mail: helper.UserB.Mail,
|
||||
Role: helper.UserB.Role,
|
||||
}
|
||||
err = helper.CompareResponse(resp, helper.KeyModels{"user": userB})
|
||||
assert.NoError(t, err)
|
||||
|
||||
// Count AGAIN the number of all the users returned for newScenario
|
||||
|
@ -663,8 +668,13 @@ func TestRemoveUserFromScenario(t *testing.T) {
|
|||
assert.NoError(t, err)
|
||||
assert.Equalf(t, 200, code, "Response body: \n%v\n", resp)
|
||||
|
||||
// Compare DELETE's response with UserB
|
||||
err = helper.CompareResponse(resp, helper.KeyModels{"user": helper.UserC})
|
||||
// Compare DELETE's response with UserC's data
|
||||
userC := UserRequest{
|
||||
Username: helper.UserC.Username,
|
||||
Mail: helper.UserC.Mail,
|
||||
Role: helper.UserC.Role,
|
||||
}
|
||||
err = helper.CompareResponse(resp, helper.KeyModels{"user": userC})
|
||||
assert.NoError(t, err)
|
||||
|
||||
// Count AGAIN the number of all the users returned for newScenario
|
||||
|
|
|
@ -107,7 +107,6 @@ func (s *Signal) delete() error {
|
|||
}
|
||||
|
||||
// remove association between Signal and ComponentConfiguration
|
||||
// Signal itself is not deleted from DB, it remains as "dangling"
|
||||
if s.Direction == "in" {
|
||||
err = db.Model(&m).Association("InputMapping").Delete(s).Error
|
||||
if err != nil {
|
||||
|
@ -117,6 +116,9 @@ func (s *Signal) delete() error {
|
|||
// Reduce length of mapping by 1
|
||||
var newInputLength = m.InputLength - 1
|
||||
err = db.Model(m).Update("InputLength", newInputLength).Error
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
} else {
|
||||
err = db.Model(&m).Association("OutputMapping").Delete(s).Error
|
||||
|
@ -127,7 +129,13 @@ func (s *Signal) delete() error {
|
|||
// Reduce length of mapping by 1
|
||||
var newOutputLength = m.OutputLength - 1
|
||||
err = db.Model(m).Update("OutputLength", newOutputLength).Error
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
// Delete signal
|
||||
err = db.Delete(s).Error
|
||||
|
||||
return err
|
||||
}
|
||||
|
|
|
@ -96,10 +96,13 @@ func (w *Widget) delete() error {
|
|||
}
|
||||
|
||||
// remove association between Dashboard and Widget
|
||||
// Widget itself is not deleted from DB, it remains as "dangling"
|
||||
err = db.Model(&dab).Association("Widgets").Delete(w).Error
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// TODO: What about files that belong to a widget? Keep them or remove them here?
|
||||
// Delete Widget
|
||||
err = db.Delete(w).Error
|
||||
|
||||
return err
|
||||
}
|
||||
|
|
Loading…
Add table
Reference in a new issue