Merge remote-tracking branch 'origin/master' into user-validators

This commit is contained in:
smavros 2019-07-26 15:29:45 +02:00
commit e6c8fff397
54 changed files with 4039 additions and 3865 deletions

View file

@ -2,6 +2,7 @@ variables:
DEPLOY_USER: deploy
DEPLOY_HOST: acs-os-fein-website
DEPLOY_PATH: /var/www/villas/api/web/
TEST_FOLDER: routes/scenario
stages:
- prepare
@ -64,7 +65,7 @@ test:api:bundle:
- doc/api/index.html
- doc/api/swagger.json
test:backend:database:
test:database:
stage: test
tags:
- docker
@ -79,7 +80,7 @@ test:backend:database:
dependencies:
- build:backend
test:backend:endpoints:
test:scenario:
stage: test
tags:
- docker
@ -89,13 +90,40 @@ test:backend:endpoints:
- go mod tidy
- go get -u github.com/swaggo/swag/cmd/swag
- ~/go/bin/swag init -p pascalcase -g "start.go" -o "./doc/api/"
- cd routes/simulation
- go test -v -args -dbhost=/var/run/postgresql
- cd ../simulationmodel
- cd ${TEST_FOLDER}
- go test -v -args -dbhost=/var/run/postgresql
dependencies:
- build:backend
test:simulationmodel:
extends: test:scenario
variables:
TEST_FOLDER: routes/simulationmodel
test:signal:
extends: test:scenario
variables:
TEST_FOLDER: routes/signal
test:dashboard:
extends: test:scenario
variables:
TEST_FOLDER: routes/dashboard
test:widget:
extends: test:scenario
variables:
TEST_FOLDER: routes/widget
test:simulator:
extends: test:scenario
variables:
TEST_FOLDER: routes/simulator
test:file:
extends: test:scenario
variables:
TEST_FOLDER: routes/file
# Stage: deploy

164
common/amqpclient.go Normal file
View file

@ -0,0 +1,164 @@
package common
import (
"encoding/json"
"fmt"
"github.com/streadway/amqp"
"github.com/tidwall/gjson"
"strings"
"time"
)
const VILLAS_EXCHANGE = "villas"
type AMQPclient struct {
connection *amqp.Connection
channel *amqp.Channel
replies <-chan amqp.Delivery
}
type Action struct {
Act string `json:"action"`
When float32 `json:"when"`
Parameters struct{} `json:"parameters"`
Model struct{} `json:"model"`
Results struct{} `json:"results"`
}
var client AMQPclient
func ConnectAMQP(uri string) error {
var err error
// connect to broker
client.connection, err = amqp.Dial(uri)
if err != nil {
return fmt.Errorf("AMQP: failed to connect to RabbitMQ broker")
}
// create channel
client.channel, err = client.connection.Channel()
if err != nil {
return fmt.Errorf("AMQP: failed to open a channel")
}
// declare exchange
err = client.channel.ExchangeDeclare(VILLAS_EXCHANGE,
"headers",
true,
false,
false,
false,
nil)
if err != nil {
return fmt.Errorf("AMQP: failed to declare the exchange")
}
// add a queue for the simulators
simulatorQueue, err := client.channel.QueueDeclare("simulators",
true,
false,
false,
false,
nil)
if err != nil {
return fmt.Errorf("AMQP: failed to declare the queue")
}
err = client.channel.QueueBind(simulatorQueue.Name, "", VILLAS_EXCHANGE, false, nil)
if err != nil {
return fmt.Errorf("AMQP: failed to bind the queue")
}
// consume deliveries
client.replies, err = client.channel.Consume(simulatorQueue.Name,
"",
false,
false,
false,
false,
nil)
if err != nil {
return fmt.Errorf("AMQP: failed to consume deliveries")
}
// consuming queue
go func() {
for message := range client.replies {
//err = message.Ack(false)
//if err != nil {
// fmt.Println("AMQP: Unable to ack message:", err)
//}
content := string(message.Body)
// any action message sent by the VILLAScontroller should be ignored by the web backend
if strings.Contains(content, "action") {
continue
}
var sToBeUpdated Simulator
db := GetDB()
err = db.Where("UUID = ?", gjson.Get(content, "properties.uuid")).Find(sToBeUpdated).Error
if err != nil {
fmt.Println("AMQP: Unable to find simulator with UUID: ", gjson.Get(content, "properties.uuid"), " DB error message: ", err)
}
err = db.Model(&sToBeUpdated).Updates(map[string]interface{}{
"Host": gjson.Get(content, "host"),
"Modeltype": gjson.Get(content, "model"),
"Uptime": gjson.Get(content, "uptime"),
"State": gjson.Get(content, "state"),
"StateUpdateAt": time.Now().String(),
"RawProperties": gjson.Get(content, "properties"),
}).Error
if err != nil {
fmt.Println("AMQP: Unable to update simulator in DB: ", err)
}
fmt.Println("AMQP: Updated simulator with UUID ", gjson.Get(content, "properties.uuid"))
}
}()
return nil
}
func SendActionAMQP(action Action, uuid string) error {
payload, err := json.Marshal(action)
if err != nil {
return err
}
msg := amqp.Publishing{
DeliveryMode: 2,
Timestamp: time.Now(),
ContentType: "application/json",
ContentEncoding: "utf-8",
Priority: 0,
Body: payload,
}
if uuid != "" {
msg.Headers["uuid"] = uuid
msg.Headers["action"] = "ping"
}
err = client.channel.Publish(VILLAS_EXCHANGE,
"",
false,
false,
msg)
return err
}
func PingAMQP() error {
fmt.Println("AMQP: sending ping command to all simulators")
var a Action
a.Act = "ping"
err := SendActionAMQP(a, "")
return err
}

View file

@ -5,7 +5,6 @@ import (
"fmt"
"github.com/jinzhu/gorm"
_ "github.com/jinzhu/gorm/dialects/postgres"
"golang.org/x/crypto/bcrypt"
"log"
)
@ -13,6 +12,7 @@ var DB_HOST string
var DB_NAME string
var DB_DUMMY string
var DB_SSLMODE string
var WITH_AMQP bool
var DBpool *gorm.DB
@ -22,11 +22,13 @@ func init() {
flag.StringVar(&DB_NAME, "dbname", "villasdb", "Name of the database to use (default is villasdb)")
flag.StringVar(&DB_DUMMY, "dbdummy", "testvillasdb", "Name of the test database to use (default is testvillasdb)")
flag.StringVar(&DB_SSLMODE, "dbsslmode", "disable", "SSL mode of DB (default is disable)") // TODO: change default for production
flag.BoolVar(&WITH_AMQP, "amqp", false, "If AMQP client for simulators shall be enabled, set this option to true (default is false)")
flag.Parse()
fmt.Println("DB_HOST has value ", DB_HOST)
fmt.Println("DB_NAME has value ", DB_NAME)
fmt.Println("DB_DUMMY has value ", DB_DUMMY)
fmt.Println("DB_SSLMODE has value ", DB_SSLMODE)
fmt.Println("WITH_AMQP has value ", WITH_AMQP)
}
// Initialize connection to the database
@ -57,9 +59,9 @@ func DropTables(db *gorm.DB) {
db.DropTableIfExists(&Signal{})
db.DropTableIfExists(&SimulationModel{})
db.DropTableIfExists(&File{})
db.DropTableIfExists(&Simulation{})
db.DropTableIfExists(&Scenario{})
db.DropTableIfExists(&User{})
db.DropTableIfExists(&Visualization{})
db.DropTableIfExists(&Dashboard{})
db.DropTableIfExists(&Widget{})
}
@ -69,9 +71,9 @@ func MigrateModels(db *gorm.DB) {
db.AutoMigrate(&Signal{})
db.AutoMigrate(&SimulationModel{})
db.AutoMigrate(&File{})
db.AutoMigrate(&Simulation{})
db.AutoMigrate(&Scenario{})
db.AutoMigrate(&User{})
db.AutoMigrate(&Visualization{})
db.AutoMigrate(&Dashboard{})
db.AutoMigrate(&Widget{})
}
@ -94,109 +96,84 @@ func DummyPopulateDB(test_db *gorm.DB) {
MigrateModels(test_db)
// Create two entries of each model
// Create entries of each model (data defined in testdata.go)
simr_A := Simulator{UUID: "1", Host: "Host_A"}
simr_B := Simulator{UUID: "2", Host: "Host_B"}
checkErr(test_db.Create(&simr_A).Error)
checkErr(test_db.Create(&simr_B).Error)
// Users
checkErr(test_db.Create(&User0).Error) // Admin
checkErr(test_db.Create(&UserA).Error) // Normal User
checkErr(test_db.Create(&UserB).Error) // Normal User
outSig_A := Signal{Name: "outSignal_A", Direction: "out", Index: 0, Unit: "V"}
outSig_B := Signal{Name: "outSignal_B", Direction: "out", Index: 1, Unit: "V"}
inSig_A := Signal{Name: "inSignal_A", Direction: "in", Index: 0, Unit: "A"}
inSig_B := Signal{Name: "inSignal_B", Direction: "in", Index: 1, Unit: "A"}
checkErr(test_db.Create(&outSig_A).Error)
checkErr(test_db.Create(&outSig_B).Error)
checkErr(test_db.Create(&inSig_A).Error)
checkErr(test_db.Create(&inSig_B).Error)
// Simulators
checkErr(test_db.Create(&SimulatorA).Error)
checkErr(test_db.Create(&SimulatorB).Error)
mo_A := SimulationModel{Name: "SimulationModel_A"}
mo_B := SimulationModel{Name: "SimulationModel_B"}
checkErr(test_db.Create(&mo_A).Error)
checkErr(test_db.Create(&mo_B).Error)
// Scenarios
checkErr(test_db.Create(&ScenarioA).Error)
checkErr(test_db.Create(&ScenarioB).Error)
file_A := File{Name: "File_A"}
file_B := File{Name: "File_B"}
checkErr(test_db.Create(&file_A).Error)
checkErr(test_db.Create(&file_B).Error)
// Signals
checkErr(test_db.Create(&OutSignalA).Error)
checkErr(test_db.Create(&OutSignalB).Error)
checkErr(test_db.Create(&InSignalA).Error)
checkErr(test_db.Create(&InSignalB).Error)
simn_A := Simulation{Name: "Simulation_A"}
simn_B := Simulation{Name: "Simulation_B"}
checkErr(test_db.Create(&simn_A).Error)
checkErr(test_db.Create(&simn_B).Error)
// Simulation Models
checkErr(test_db.Create(&SimulationModelA).Error)
checkErr(test_db.Create(&SimulationModelB).Error)
// Hash passwords with bcrypt algorithm
var bcryptCost = 10
// Dashboards
checkErr(test_db.Create(&DashboardA).Error)
checkErr(test_db.Create(&DashboardB).Error)
pw_0, err :=
bcrypt.GenerateFromPassword([]byte("xyz789"), bcryptCost)
checkErr(err)
// Files
checkErr(test_db.Create(&FileA).Error)
checkErr(test_db.Create(&FileB).Error)
checkErr(test_db.Create(&FileC).Error)
checkErr(test_db.Create(&FileD).Error)
pw_A, err :=
bcrypt.GenerateFromPassword([]byte("abc123"), bcryptCost)
checkErr(err)
pw_B, err :=
bcrypt.GenerateFromPassword([]byte("bcd234"), bcryptCost)
checkErr(err)
usr_0 := User{Username: "User_0", Password: string(pw_0), Role: "Admin"}
usr_A := User{Username: "User_A", Password: string(pw_A), Role: "User"}
usr_B := User{Username: "User_B", Password: string(pw_B), Role: "User"}
checkErr(test_db.Create(&usr_0).Error)
checkErr(test_db.Create(&usr_A).Error)
checkErr(test_db.Create(&usr_B).Error)
vis_A := Visualization{Name: "Visualization_A"}
vis_B := Visualization{Name: "Visualization_B"}
checkErr(test_db.Create(&vis_A).Error)
checkErr(test_db.Create(&vis_B).Error)
widg_A := Widget{Name: "Widget_A"}
widg_B := Widget{Name: "Widget_B"}
checkErr(test_db.Create(&widg_A).Error)
checkErr(test_db.Create(&widg_B).Error)
// Widgets
checkErr(test_db.Create(&WidgetA).Error)
checkErr(test_db.Create(&WidgetB).Error)
// Associations between models
// For `belongs to` use the model with id=1
// For `has many` use the models with id=1 and id=2
// User HM Simulations, Simulation HM Users (Many-to-Many)
checkErr(test_db.Model(&simn_A).Association("Users").Append(&usr_A).Error)
checkErr(test_db.Model(&simn_A).Association("Users").Append(&usr_B).Error)
checkErr(test_db.Model(&simn_B).Association("Users").Append(&usr_A).Error)
checkErr(test_db.Model(&simn_B).Association("Users").Append(&usr_B).Error)
// User HM Scenarios, Scenario HM Users (Many-to-Many)
checkErr(test_db.Model(&ScenarioA).Association("Users").Append(&UserA).Error)
checkErr(test_db.Model(&ScenarioA).Association("Users").Append(&UserB).Error)
checkErr(test_db.Model(&ScenarioB).Association("Users").Append(&UserA).Error)
checkErr(test_db.Model(&ScenarioB).Association("Users").Append(&UserB).Error)
// Simulation HM SimulationModels
checkErr(test_db.Model(&simn_A).Association("SimulationModels").Append(&mo_A).Error)
checkErr(test_db.Model(&simn_A).Association("SimulationModels").Append(&mo_B).Error)
// Scenario HM SimulationModels
checkErr(test_db.Model(&ScenarioA).Association("SimulationModels").Append(&SimulationModelA).Error)
checkErr(test_db.Model(&ScenarioA).Association("SimulationModels").Append(&SimulationModelB).Error)
// Simulation HM Visualizations
checkErr(test_db.Model(&simn_A).Association("Visualizations").Append(&vis_A).Error)
checkErr(test_db.Model(&simn_A).Association("Visualizations").Append(&vis_B).Error)
// Scenario HM Dashboards
checkErr(test_db.Model(&ScenarioA).Association("Dashboards").Append(&DashboardA).Error)
checkErr(test_db.Model(&ScenarioA).Association("Dashboards").Append(&DashboardB).Error)
// Visualization HM Widget
checkErr(test_db.Model(&vis_A).Association("Widgets").Append(&widg_A).Error)
checkErr(test_db.Model(&vis_A).Association("Widgets").Append(&widg_B).Error)
// Dashboard HM Widget
checkErr(test_db.Model(&DashboardA).Association("Widgets").Append(&WidgetA).Error)
checkErr(test_db.Model(&DashboardA).Association("Widgets").Append(&WidgetB).Error)
// SimulationModel HM Signals
checkErr(test_db.Model(&mo_A).Association("InputMapping").Append(&inSig_A).Error)
checkErr(test_db.Model(&mo_A).Association("InputMapping").Append(&inSig_B).Error)
checkErr(test_db.Model(&mo_A).Association("OutputMapping").Append(&outSig_A).Error)
checkErr(test_db.Model(&mo_A).Association("OutputMapping").Append(&outSig_B).Error)
checkErr(test_db.Model(&SimulationModelA).Association("InputMapping").Append(&InSignalA).Error)
checkErr(test_db.Model(&SimulationModelA).Association("InputMapping").Append(&InSignalB).Error)
checkErr(test_db.Model(&SimulationModelA).Association("OutputMapping").Append(&OutSignalA).Error)
checkErr(test_db.Model(&SimulationModelA).Association("OutputMapping").Append(&OutSignalB).Error)
// SimulationModel HM Files
checkErr(test_db.Model(&mo_A).Association("Files").Append(&file_A).Error)
checkErr(test_db.Model(&mo_A).Association("Files").Append(&file_B).Error)
checkErr(test_db.Model(&SimulationModelA).Association("Files").Append(&FileC).Error)
checkErr(test_db.Model(&SimulationModelA).Association("Files").Append(&FileD).Error)
// Simulator BT SimulationModel
checkErr(test_db.Model(&mo_A).Association("Simulator").Append(&simr_A).Error)
checkErr(test_db.Model(&mo_B).Association("Simulator").Append(&simr_A).Error)
// Simulator HM SimulationModels
checkErr(test_db.Model(&SimulatorA).Association("SimulationModels").Append(&SimulationModelA).Error)
checkErr(test_db.Model(&SimulatorA).Association("SimulationModels").Append(&SimulationModelB).Error)
// Widget HM Files
checkErr(test_db.Model(&widg_A).Association("Files").Append(&file_A).Error)
checkErr(test_db.Model(&widg_A).Association("Files").Append(&file_B).Error)
checkErr(test_db.Model(&WidgetA).Association("Files").Append(&FileA).Error)
checkErr(test_db.Model(&WidgetA).Association("Files").Append(&FileB).Error)
}
// Erase tables and glose the testdb

View file

@ -31,18 +31,18 @@ func TestDummyDBAssociations(t *testing.T) {
var simr Simulator
var mo SimulationModel
var file File
var simn Simulation
var so Scenario
var usr User
var usrs []User
var vis Visualization
var dab Dashboard
var widg Widget
var sigs []Signal
var mos []SimulationModel
var files []File
var files_sm []File
var simns []Simulation
var viss []Visualization
var sos []Scenario
var dabs []Dashboard
var widgs []Widget
// User
@ -52,35 +52,46 @@ func TestDummyDBAssociations(t *testing.T) {
// User Associations
a.NoError(db.Model(&usr).Related(&simns, "Simulations").Error)
if len(simns) != 2 {
a.NoError(db.Model(&usr).Related(&sos, "Scenarios").Error)
if len(sos) != 2 {
a.Fail("User Associations",
"Expected to have %v Simulations. Has %v.", 2, len(simns))
"Expected to have %v Scenarios. Has %v.", 2, len(sos))
}
// Simulation
// Scenario
a.NoError(db.Find(&simn, 1).Error, fM("Simulation"))
a.EqualValues("Simulation_A", simn.Name)
a.NoError(db.Find(&so, 1).Error, fM("Scenario"))
a.EqualValues("Scenario_A", so.Name)
// Simulation Associations
// Scenario Associations
a.NoError(db.Model(&simn).Association("Users").Find(&usrs).Error)
a.NoError(db.Model(&so).Association("Users").Find(&usrs).Error)
if len(usrs) != 2 {
a.Fail("Simulations Associations",
a.Fail("Scenario Associations",
"Expected to have %v Users. Has %v.", 2, len(usrs))
}
a.NoError(db.Model(&simn).Related(&mos, "SimulationModels").Error)
a.NoError(db.Model(&so).Related(&mos, "SimulationModels").Error)
if len(mos) != 2 {
a.Fail("Simulation Associations",
a.Fail("Scenario Associations",
"Expected to have %v simulation models. Has %v.", 2, len(mos))
}
a.NoError(db.Model(&simn).Related(&viss, "Visualizations").Error)
if len(viss) != 2 {
a.Fail("Simulation Associations",
"Expected to have %v Visualizations. Has %v.", 2, len(viss))
a.NoError(db.Model(&so).Related(&dabs, "Dashboards").Error)
if len(dabs) != 2 {
a.Fail("Scenario Associations",
"Expected to have %v Dashboards. Has %v.", 2, len(dabs))
}
// Simulator
a.NoError(db.Find(&simr, 1).Error, fM("Simulator"))
a.EqualValues("Host_A", simr.Host)
// Simulator Associations
a.NoError(db.Model(&simr).Association("SimulationModels").Find(&mos).Error)
if len(mos) != 2 {
a.Fail("Simulator Associations",
"Expected to have %v SimulationModels. Has %v.", 2, len(mos))
}
// SimulationModel
@ -90,9 +101,6 @@ func TestDummyDBAssociations(t *testing.T) {
// SimulationModel Associations
a.NoError(db.Model(&mo).Association("Simulator").Find(&simr).Error)
a.EqualValues("Host_A", simr.Host, "Expected Host_A")
a.NoError(db.Model(&mo).Where("Direction = ?", "out").Related(&sigs, "OutputMapping").Error)
if len(sigs) != 2 {
a.Fail("SimulationModel Associations",
@ -105,14 +113,16 @@ func TestDummyDBAssociations(t *testing.T) {
"Expected to have %v Files. Has %v.", 2, len(files_sm))
}
// Visualization
fmt.Println("SimulatorID: ", mo.SimulatorID)
a.NoError(db.Find(&vis, 1).Error, fM("Visualization"))
a.EqualValues("Visualization_A", vis.Name)
// Dashboard
// Visualization Associations
a.NoError(db.Find(&dab, 1).Error, fM("Dashboard"))
a.EqualValues("Dashboard_A", dab.Name)
a.NoError(db.Model(&vis).Related(&widgs, "Widgets").Error)
// Dashboard Associations
a.NoError(db.Model(&dab).Related(&widgs, "Widgets").Error)
if len(widgs) != 2 {
a.Fail("Widget Associations",
"Expected to have %v Widget. Has %v.", 2, len(widgs))

View file

@ -1,13 +1,13 @@
package common
import (
"time"
"github.com/jinzhu/gorm"
"github.com/jinzhu/gorm/dialects/postgres"
)
// User data model
type User struct {
// ID of user
ID uint `gorm:"primary_key;auto_increment"`
gorm.Model
// Username of user
Username string `gorm:"unique;not null"`
// Password of user
@ -16,44 +16,40 @@ type User struct {
Mail string `gorm:"default:''"`
// Role of user
Role string `gorm:"default:'user'"`
// Simulations to which user has access
Simulations []*Simulation `gorm:"many2many:user_simulations"`
// Scenarios to which user has access
Scenarios []*Scenario `gorm:"many2many:user_scenarios"`
}
// Simulation data model
type Simulation struct {
// ID of simulation
ID uint `gorm:"primary_key;auto_increment"`
// Name of simulation
// Scenario data model
type Scenario struct {
gorm.Model
// Name of scenario
Name string `gorm:"not null"`
// Running state of simulation
// Running state of scenario
Running bool `gorm:"default:false"`
// Start parameters of simulation as JSON string
StartParameters string
// Users that have access to the simulation
Users []*User `gorm:"not null;many2many:user_simulations"`
// SimulationModels that belong to the simulation
SimulationModels []SimulationModel `gorm:"foreignkey:SimulationID"`
// Visualizations that belong to the simulation
Visualizations []Visualization `gorm:"foreignkey:SimulationID"`
// Start parameters of scenario as JSON
StartParameters postgres.Jsonb
// Users that have access to the scenario
Users []*User `gorm:"not null;many2many:user_scenarios"`
// SimulationModels that belong to the scenario
SimulationModels []SimulationModel `gorm:"foreignkey:ScenarioID"`
// Dashboards that belong to the Scenario
Dashboards []Dashboard `gorm:"foreignkey:ScenarioID"`
}
// SimulationModel data model
type SimulationModel struct {
// ID of simulation model
ID uint `gorm:"primary_key;auto_increment"`
gorm.Model
// Name of simulation model
Name string `gorm:"not null"`
// Number of output signals
OutputLength int `gorm:"default:1"`
// Number of input signals
InputLength int `gorm:"default:1"`
// Start parameters of simulation model as JSON string
StartParameters string
// ID of simulation to which simulation model belongs
SimulationID uint
// Simulator associated with simulation model
Simulator Simulator
// Start parameters of simulation model as JSON
StartParameters postgres.Jsonb
// ID of Scenario to which simulation model belongs
ScenarioID uint
// ID of simulator associated with simulation model
SimulatorID uint
// Mapping of output signals of the simulation model, order of signals is important
@ -66,8 +62,7 @@ type SimulationModel struct {
// Signal data model
type Signal struct {
// ID of simulation model
ID uint `gorm:"primary_key;auto_increment"`
gorm.Model
// Name of Signal
Name string
// Unit of Signal
@ -82,44 +77,43 @@ type Signal struct {
// Simulator data model
type Simulator struct {
// ID of the simulator
ID uint `gorm:"primary_key;auto_increment"`
gorm.Model
// UUID of the simulator
UUID string `gorm:"unique;not null"`
UUID string `gorm:"not null",json:"uuid"`
// Host if the simulator
Host string `gorm:"default:''"`
Host string `gorm:"default:''",json:"host"`
// Model type supported by the simulator
Modeltype string `gorm:"default:''"`
Modeltype string `gorm:"default:''",json:"modelType"`
// Uptime of the simulator
Uptime int `gorm:"default:0"`
Uptime int `gorm:"default:0",json:"uptime"`
// State of the simulator
State string `gorm:"default:''"`
State string `gorm:"default:''",json:"state"`
// Time of last state update
StateUpdateAt time.Time
StateUpdateAt string `gorm:"default:''",json:"stateUpdateAt"`
// Properties of simulator as JSON string
Properties string
Properties postgres.Jsonb `json:"properties"`
// Raw properties of simulator as JSON string
RawProperties string
RawProperties postgres.Jsonb `json:"rawProperties"`
// SimulationModels in which the simulator is used
SimulationModels []SimulationModel `gorm:"foreignkey:SimulatorID"`
}
// Visualization data model
type Visualization struct {
// ID of visualization
ID uint `gorm:"primary_key;auto_increment"`
// Name of visualization
// Dashboard data model
type Dashboard struct {
gorm.Model
// Name of dashboard
Name string `gorm:"not null"`
// Grid of visualization
// Grid of dashboard
Grid int `gorm:"default:15"`
// ID of simulation to which visualization belongs
SimulationID uint `gorm:"not null"`
// Widgets that belong to visualization
Widgets []Widget `gorm:"foreignkey:VisualizationID"`
// ID of scenario to which dashboard belongs
ScenarioID uint
// Widgets that belong to dashboard
Widgets []Widget `gorm:"foreignkey:DashboardID"`
}
// Widget data model
type Widget struct {
// ID of widget
ID uint `gorm:"primary_key;auto_increment"`
gorm.Model
// Name of widget
Name string `gorm:"not null"`
// Type of widget
@ -141,33 +135,38 @@ type Widget struct {
// Locked state of widget
IsLocked bool `gorm:"default:false"`
// Custom properties of widget as JSON string
CustomProperties string
// ID of visualization to which widget belongs
VisualizationID uint `gorm:"not null"`
CustomProperties postgres.Jsonb
// ID of dashboard to which widget belongs
DashboardID uint
// Files that belong to widget (for example images)
Files []File `gorm:"foreignkey:WidgetID"`
}
// File data model
type File struct {
// ID of file
ID uint `gorm:"primary_key;auto_increment"`
gorm.Model
// Name of file
Name string `gorm:"not null"`
// Path at which file is saved at server side
Path string `gorm:"not null"`
// Type of file (MIME type)
Type string `gorm:"not null"`
Type string
// Size of file (in byte)
Size uint `gorm:"not null"`
Size uint
// Height of image (only needed in case of image)
ImageHeight uint
// Width of image (only needed in case of image)
ImageWidth uint
// Last modification time of file
Date time.Time
Date string
// ID of model to which file belongs
SimulationModelID uint `gorm:""`
SimulationModelID uint
// ID of widget to which file belongs
WidgetID uint `gorm:""`
WidgetID uint
// File itself
FileData []byte `gorm:"column:FileData"`
}
// Credentials type (not for DB)
type credentials struct {
Username string `json:"username"`
Password string `json:"password"`
}

View file

@ -1,81 +1,84 @@
package common
import (
"time"
)
import "github.com/jinzhu/gorm/dialects/postgres"
type UserResponse struct {
Username string `json:"Username"`
Role string `json:"Role"`
Mail string `json:"Mail"`
Username string `json:"username"`
Role string `json:"role"`
Mail string `json:"mail"`
ID uint `json:"id"`
}
type SimulationResponse struct {
Name string `json:"Name"`
ID uint `json:"SimulationID"`
Running bool `json:"Running"`
StartParams string `json:"Starting Parameters"`
type ScenarioResponse struct {
Name string `json:"name"`
ID uint `json:"id"`
Running bool `json:"running"`
StartParameters postgres.Jsonb `json:"startParameters"`
}
type SimulationModelResponse struct {
ID uint `json:"ID"`
Name string `json:"Name"`
OutputLength int `json:"OutputLength"`
InputLength int `json:"InputLength"`
SimulationID uint `json:"SimulationID"`
SimulatorID uint `json:"SimulatorID"`
StartParams string `json:"StartParams"`
ID uint `json:"id"`
Name string `json:"name"`
OutputLength int `json:"outputLength"`
InputLength int `json:"inputLength"`
ScenarioID uint `json:"scenarioID"`
SimulatorID uint `json:"simulatorID"`
StartParameters postgres.Jsonb `json:"startParameters"`
}
type SimulatorResponse struct {
UUID string `json:"UUID"`
Host string `json:"Host"`
ModelType string `json:"ModelType"`
Uptime int `json:"Uptime"`
State string `json:"State"`
StateUpdateAt time.Time `json:"StateUpdateAt"`
Properties string `json:"Properties"`
RawProperties string `json:"RawProperties"`
ID uint `json:"id"`
UUID string `json:"uuid"`
Host string `json:"host"`
Modeltype string `json:"modelType"`
Uptime int `json:"uptime"`
State string `json:"state"`
StateUpdateAt string `json:"stateUpdateAt"`
Properties postgres.Jsonb `json:"properties"`
RawProperties postgres.Jsonb `json:"rawProperties"`
}
type VisualizationResponse struct {
Name string `json:"Name"`
Grid int `json:"Grid"`
SimulationID uint `json:"SimulationID"`
type DashboardResponse struct {
ID uint `json:"id"`
Name string `json:"name"`
Grid int `json:"grid"`
ScenarioID uint `json:"scenarioID"`
}
type WidgetResponse struct {
Name string `json:"Name"`
Type string `json:"Type"`
Width uint `json:"Width"`
Height uint `json:"Height"`
MinWidth uint `json:"MinWidth"`
MinHeight uint `json:"MinHeight"`
X int `json:"X"`
Y int `json:"Y"`
Z int `json:"Z"`
VisualizationID uint `json:"VisualizationID"`
IsLocked bool `json:"IsLocked"`
CustomProperties string `json:"CustomProperties"`
ID uint `json:"id"`
Name string `json:"name"`
Type string `json:"type"`
Width uint `json:"width"`
Height uint `json:"height"`
MinWidth uint `json:"minWidth"`
MinHeight uint `json:"minHeight"`
X int `json:"x"`
Y int `json:"y"`
Z int `json:"z"`
DashboardID uint `json:"dashboardID"`
IsLocked bool `json:"isLocked"`
CustomProperties postgres.Jsonb `json:"customProperties"`
}
type FileResponse struct {
Name string `json:"Name"`
ID uint `json:"FileID"`
Path string `json:"Path"`
Type string `json:"Type"`
Size uint `json:"Size"`
H uint `json:"ImageHeight"`
W uint `json:"ImageWidth"`
Date time.Time `json:"Date"`
Name string `json:"name"`
ID uint `json:"id"`
Type string `json:"type"`
Size uint `json:"size"`
ImageWidth uint `json:"imageHeight"`
ImageHeight uint `json:"imageWidth"`
Date string `json:"date"`
WidgetID uint `json:"widgetID"`
SimulationModelID uint `json:"simulationModelID"`
}
type SignalResponse struct {
Name string `json:"Name"`
Unit string `json:"Unit"`
Index uint `json:"Index"`
Direction string `json:"Direction"`
SimulationModelID uint `json:"SimulationModelID"`
Name string `json:"name"`
Unit string `json:"unit"`
Index uint `json:"index"`
Direction string `json:"direction"`
SimulationModelID uint `json:"simulationModelID"`
}
// Response messages
@ -92,12 +95,12 @@ type ResponseMsgUser struct {
User UserResponse `json:"user"`
}
type ResponseMsgSimulations struct {
Simulations []SimulationResponse `json:"simulations"`
type ResponseMsgScenarios struct {
Scenarios []ScenarioResponse `json:"scenarios"`
}
type ResponseMsgSimulation struct {
Simulation SimulationResponse `json:"simulation"`
type ResponseMsgScenario struct {
Scenario ScenarioResponse `json:"scenario"`
}
type ResponseMsgSimulationModels struct {
@ -111,3 +114,39 @@ type ResponseMsgSimulationModel struct {
type ResponseMsgSignals struct {
Signals []SignalResponse `json:"signals"`
}
type ResponseMsgSignal struct {
Signal SignalResponse `json:"signal"`
}
type ResponseMsgDashboards struct {
Dashboards []DashboardResponse `json:"dashboards"`
}
type ResponseMsgDashboard struct {
Dashboard DashboardResponse `json:"dashboard"`
}
type ResponseMsgWidgets struct {
Widgets []WidgetResponse `json:"widgets"`
}
type ResponseMsgWidget struct {
Widget WidgetResponse `json:"widget"`
}
type ResponseMsgSimulators struct {
Simulators []SimulatorResponse `json:"simulators"`
}
type ResponseMsgSimulator struct {
Simulator SimulatorResponse `json:"simulator"`
}
type ResponseMsgFiles struct {
Files []FileResponse `json:"files"`
}
type ResponseMsgFile struct {
File FileResponse `json:"file"`
}

View file

@ -15,10 +15,14 @@ import (
type ModelName string
const ModelUser = ModelName("user")
const ModelSimulation = ModelName("simulation")
const ModelScenario = ModelName("scenario")
const ModelSimulator = ModelName("simulator")
const ModelVisualization = ModelName("visualization")
const ModelSimulatorAction = ModelName("simulatoraction")
const ModelDashboard = ModelName("dashboard")
const ModelWidget = ModelName("widget")
const ModelSimulationModel = ModelName("simulationmodel")
const ModelSignal = ModelName("signal")
const ModelFile = ModelName("file")
type CRUD string
@ -47,18 +51,36 @@ var _r__ = Permission{Create: false, Read: true, Update: false, Delete: false}
var Roles = RoleActions{
"Admin": {
ModelUser: crud,
ModelSimulation: crud,
ModelScenario: crud,
ModelSimulationModel: crud,
ModelSimulator: crud,
ModelSimulatorAction: crud,
ModelWidget: crud,
ModelDashboard: crud,
ModelSignal: crud,
ModelFile: crud,
},
"User": {
ModelUser: __u_,
ModelSimulation: crud,
ModelScenario: crud,
ModelSimulationModel: crud,
ModelSimulator: _r__,
ModelSimulatorAction: _ru_,
ModelWidget: crud,
ModelDashboard: crud,
ModelSignal: crud,
ModelFile: crud,
},
"Guest": {
ModelVisualization: _r__,
ModelScenario: _r__,
ModelSimulationModel: _r__,
ModelDashboard: _r__,
ModelWidget: _r__,
ModelSimulator: _r__,
ModelSimulatorAction: _r__,
ModelUser: _ru_,
ModelSignal: _r__,
ModelFile: _r__,
},
}

View file

@ -31,6 +31,7 @@ func (self *UserSerializer) Response(assoc bool) UserResponse {
Username: self.Username,
Role: self.Role,
Mail: self.Mail,
ID: self.ID,
}
// Associated models MUST NOT called with assoc=true otherwise we
@ -39,44 +40,44 @@ func (self *UserSerializer) Response(assoc bool) UserResponse {
// TODO: maybe all those should be made in one transaction
//simulations, _, _ := simulation.FindUserSimulations(&self.User)
//simulationsSerializer :=
// SimulationsSerializer{self.Ctx, simulations}
//scenarios, _, _ := scenario.FindUserScenarios(&self.User)
//scenariosSerializer :=
// ScenariosSerializer{self.Ctx, scenarios}
// Add the associated models to the response
//response.Simulations = simulationsSerializer.Response()
//response.Scenarios = scenariosSerializer.Response()
}
return response
}
// Simulation/s Serializers
// Scenario/s Serializers
type SimulationsSerializer struct {
Ctx *gin.Context
Simulations []Simulation
type ScenariosSerializer struct {
Ctx *gin.Context
Scenarios []Scenario
}
func (self *SimulationsSerializer) Response() []SimulationResponse {
response := []SimulationResponse{}
for _, simulation := range self.Simulations {
serializer := SimulationSerializer{self.Ctx, simulation}
func (self *ScenariosSerializer) Response() []ScenarioResponse {
response := []ScenarioResponse{}
for _, so := range self.Scenarios {
serializer := ScenarioSerializer{self.Ctx, so}
response = append(response, serializer.Response())
}
return response
}
type SimulationSerializer struct {
type ScenarioSerializer struct {
Ctx *gin.Context
Simulation
Scenario
}
func (self *SimulationSerializer) Response() SimulationResponse {
response := SimulationResponse{
Name: self.Name,
ID: self.ID,
Running: self.Running,
StartParams: self.StartParameters,
func (self *ScenarioSerializer) Response() ScenarioResponse {
response := ScenarioResponse{
Name: self.Name,
ID: self.ID,
Running: self.Running,
StartParameters: self.StartParameters,
}
return response
}
@ -104,13 +105,13 @@ type SimulationModelSerializer struct {
func (self *SimulationModelSerializer) Response() SimulationModelResponse {
response := SimulationModelResponse{
ID: self.ID,
Name: self.Name,
OutputLength: self.OutputLength,
InputLength: self.InputLength,
SimulationID: self.SimulationID,
SimulatorID: self.SimulatorID,
StartParams: self.StartParameters,
ID: self.ID,
Name: self.Name,
OutputLength: self.OutputLength,
InputLength: self.InputLength,
ScenarioID: self.ScenarioID,
SimulatorID: self.SimulatorID,
StartParameters: self.StartParameters,
}
return response
}
@ -139,43 +140,47 @@ type SimulatorSerializer struct {
func (self *SimulatorSerializer) Response() SimulatorResponse {
response := SimulatorResponse{
ID: self.ID,
UUID: self.UUID,
Host: self.Host,
ModelType: self.Modeltype,
Modeltype: self.Modeltype,
Uptime: self.Uptime,
State: self.State,
StateUpdateAt: self.StateUpdateAt,
Properties: self.Properties,
RawProperties: self.RawProperties,
}
return response
}
// Visualization/s Serializers
// Dashboard/s Serializers
type VisualizationsSerializer struct {
Ctx *gin.Context
Visualizations []Visualization
type DashboardsSerializer struct {
Ctx *gin.Context
Dashboards []Dashboard
}
func (self *VisualizationsSerializer) Response() []VisualizationResponse {
response := []VisualizationResponse{}
for _, visualization := range self.Visualizations {
serializer := VisualizationSerializer{self.Ctx, visualization}
func (self *DashboardsSerializer) Response() []DashboardResponse {
response := []DashboardResponse{}
for _, dashboard := range self.Dashboards {
serializer := DashboardSerializer{self.Ctx, dashboard}
response = append(response, serializer.Response())
}
return response
}
type VisualizationSerializer struct {
type DashboardSerializer struct {
Ctx *gin.Context
Visualization
Dashboard
}
func (self *VisualizationSerializer) Response() VisualizationResponse {
func (self *DashboardSerializer) Response() DashboardResponse {
response := VisualizationResponse{
Name: self.Name,
Grid: self.Grid,
SimulationID: self.SimulationID,
response := DashboardResponse{
Name: self.Name,
Grid: self.Grid,
ScenarioID: self.ScenarioID,
ID: self.ID,
}
return response
}
@ -204,18 +209,19 @@ type WidgetSerializer struct {
func (self *WidgetSerializer) Response() WidgetResponse {
response := WidgetResponse{
Name: self.Name,
Type: self.Type,
Width: self.Width,
Height: self.Height,
MinWidth: self.MinWidth,
MinHeight: self.MinHeight,
X: self.X,
Y: self.Y,
Z: self.Z,
VisualizationID: self.VisualizationID,
IsLocked: self.IsLocked,
//CustomProperties
ID: self.ID,
Name: self.Name,
Type: self.Type,
Width: self.Width,
Height: self.Height,
MinWidth: self.MinWidth,
MinHeight: self.MinHeight,
X: self.X,
Y: self.Y,
Z: self.Z,
DashboardID: self.DashboardID,
IsLocked: self.IsLocked,
CustomProperties: self.CustomProperties,
}
return response
}
@ -245,12 +251,14 @@ func (self *FileSerializerNoAssoc) Response() FileResponse {
response := FileResponse{
Name: self.Name,
ID: self.ID,
Path: self.Path,
Type: self.Type,
Size: self.Size,
H: self.ImageHeight,
W: self.ImageWidth,
// Date
//Path: self.Path,
Type: self.Type,
Size: self.Size,
ImageHeight: self.ImageHeight,
ImageWidth: self.ImageWidth,
Date: self.Date,
WidgetID: self.WidgetID,
SimulationModelID: self.SimulationModelID,
}
return response
}

526
common/testdata.go Normal file
View file

@ -0,0 +1,526 @@
package common
import (
"encoding/json"
"github.com/jinzhu/gorm/dialects/postgres"
"golang.org/x/crypto/bcrypt"
"time"
)
// Generic
var MsgOK = ResponseMsg{
Message: "OK.",
}
// Users
// Hash passwords with bcrypt algorithm
var bcryptCost = 10
var pw0, _ = bcrypt.GenerateFromPassword([]byte("xyz789"), bcryptCost)
var pwA, _ = bcrypt.GenerateFromPassword([]byte("abc123"), bcryptCost)
var pwB, _ = bcrypt.GenerateFromPassword([]byte("bcd234"), bcryptCost)
var User0 = User{Username: "User_0", Password: string(pw0), Role: "Admin", Mail: "User_0@example.com"}
var User0_response = UserResponse{Username: User0.Username, Role: User0.Role, ID: 1, Mail: User0.Mail}
var UserA = User{Username: "User_A", Password: string(pwA), Role: "User", Mail: "User_A@example.com"}
var UserA_response = UserResponse{Username: UserA.Username, Role: UserA.Role, ID: 2, Mail: UserA.Mail}
var UserB = User{Username: "User_B", Password: string(pwB), Role: "User", Mail: "User_B@example.com"}
var UserB_response = UserResponse{Username: UserB.Username, Role: UserB.Role, ID: 3, Mail: UserB.Mail}
// Credentials
var CredAdmin = credentials{
Username: User0.Username,
Password: "xyz789",
}
var CredUser = credentials{
Username: UserA.Username,
Password: "abc123",
}
// Simulators
var propertiesA = json.RawMessage(`{"name" : "TestNameA", "category" : "CategoryA", "location" : "anywhere on earth", "type": "dummy"}`)
var propertiesB = json.RawMessage(`{"name" : "TestNameB", "category" : "CategoryB", "location" : "where ever you want", "type": "generic"}`)
var propertiesC = json.RawMessage(`{"name" : "TestNameC", "category" : "CategoryC", "location" : "my desk", "type": "blubb"}`)
var propertiesCupdated = json.RawMessage(`{"name" : "TestNameCUpdate", "category" : "CategoryC", "location" : "my desk", "type": "blubb"}`)
var SimulatorA = Simulator{
UUID: "4854af30-325f-44a5-ad59-b67b2597de68",
Host: "Host_A",
Modeltype: "ModelTypeA",
Uptime: 0,
State: "running",
StateUpdateAt: time.Now().String(),
Properties: postgres.Jsonb{propertiesA},
RawProperties: postgres.Jsonb{propertiesA},
}
var SimulatorA_response = SimulatorResponse{
ID: 1,
UUID: SimulatorA.UUID,
Host: SimulatorA.Host,
Modeltype: SimulatorA.Modeltype,
Uptime: SimulatorA.Uptime,
State: SimulatorA.State,
StateUpdateAt: SimulatorA.StateUpdateAt,
Properties: SimulatorA.Properties,
RawProperties: SimulatorA.RawProperties,
}
var SimulatorB = Simulator{
UUID: "7be0322d-354e-431e-84bd-ae4c9633138b",
Host: "Host_B",
Modeltype: "ModelTypeB",
Uptime: 0,
State: "idle",
StateUpdateAt: time.Now().String(),
Properties: postgres.Jsonb{propertiesB},
RawProperties: postgres.Jsonb{propertiesB},
}
var SimulatorB_response = SimulatorResponse{
ID: 2,
UUID: SimulatorB.UUID,
Host: SimulatorB.Host,
Modeltype: SimulatorB.Modeltype,
Uptime: SimulatorB.Uptime,
State: SimulatorB.State,
StateUpdateAt: SimulatorB.StateUpdateAt,
Properties: SimulatorB.Properties,
RawProperties: SimulatorB.RawProperties,
}
var SimulatorC = Simulator{
UUID: "6d9776bf-b693-45e8-97b6-4c13d151043f",
Host: "Host_C",
Modeltype: "ModelTypeC",
Uptime: 0,
State: "idle",
StateUpdateAt: time.Now().String(),
Properties: postgres.Jsonb{propertiesC},
RawProperties: postgres.Jsonb{propertiesC},
}
var SimulatorC_response = SimulatorResponse{
ID: 3,
UUID: SimulatorC.UUID,
Host: SimulatorC.Host,
Modeltype: SimulatorC.Modeltype,
Uptime: SimulatorC.Uptime,
State: SimulatorC.State,
StateUpdateAt: SimulatorC.StateUpdateAt,
Properties: SimulatorC.Properties,
RawProperties: SimulatorC.RawProperties,
}
var SimulatorCUpdated = Simulator{
UUID: SimulatorC.UUID,
Host: "Host_Cupdated",
Modeltype: "ModelTypeCUpdated",
Uptime: SimulatorC.Uptime,
State: "running",
StateUpdateAt: time.Now().String(),
Properties: postgres.Jsonb{propertiesCupdated},
RawProperties: postgres.Jsonb{propertiesCupdated},
}
var SimulatorCUpdated_response = SimulatorResponse{
ID: 3,
UUID: SimulatorCUpdated.UUID,
Host: SimulatorCUpdated.Host,
Modeltype: SimulatorCUpdated.Modeltype,
Uptime: SimulatorCUpdated.Uptime,
State: SimulatorCUpdated.State,
StateUpdateAt: SimulatorCUpdated.StateUpdateAt,
Properties: SimulatorCUpdated.Properties,
RawProperties: SimulatorCUpdated.RawProperties,
}
// Scenarios
var startParametersA = json.RawMessage(`{"parameter1" : "testValue1A", "parameter2" : "testValue2A", "parameter3" : 42}`)
var startParametersB = json.RawMessage(`{"parameter1" : "testValue1B", "parameter2" : "testValue2B", "parameter3" : 43}`)
var startParametersC = json.RawMessage(`{"parameter1" : "testValue1C", "parameter2" : "testValue2C", "parameter3" : 44}`)
var ScenarioA = Scenario{Name: "Scenario_A", Running: true, StartParameters: postgres.Jsonb{startParametersA}}
var ScenarioA_response = ScenarioResponse{ID: 1, Name: ScenarioA.Name, Running: ScenarioA.Running, StartParameters: ScenarioA.StartParameters}
var ScenarioB = Scenario{Name: "Scenario_B", Running: false, StartParameters: postgres.Jsonb{startParametersB}}
var ScenarioB_response = ScenarioResponse{ID: 2, Name: ScenarioB.Name, Running: ScenarioB.Running, StartParameters: ScenarioB.StartParameters}
var ScenarioC = Scenario{Name: "Scenario_C", Running: false, StartParameters: postgres.Jsonb{startParametersC}}
var ScenarioC_response = ScenarioResponse{ID: 3, Name: ScenarioC.Name, Running: ScenarioC.Running, StartParameters: ScenarioC.StartParameters}
var ScenarioCUpdated = Scenario{Name: "Scenario_Cupdated", Running: true, StartParameters: postgres.Jsonb{startParametersC}}
var ScenarioCUpdated_response = ScenarioResponse{ID: 3, Name: ScenarioCUpdated.Name, Running: ScenarioCUpdated.Running, StartParameters: ScenarioCUpdated.StartParameters}
// Simulation Models
var SimulationModelA = SimulationModel{
Name: "SimulationModel_A",
OutputLength: 1,
InputLength: 1,
ScenarioID: 1,
SimulatorID: 1,
StartParameters: postgres.Jsonb{startParametersA},
}
var SimulationModelA_response = SimulationModelResponse{
ID: 1,
Name: SimulationModelA.Name,
InputLength: SimulationModelA.InputLength,
OutputLength: SimulationModelA.OutputLength,
ScenarioID: SimulationModelA.ScenarioID,
SimulatorID: SimulationModelA.SimulatorID,
StartParameters: SimulationModelA.StartParameters,
}
var SimulationModelB = SimulationModel{
Name: "SimulationModel_B",
OutputLength: 1,
InputLength: 1,
ScenarioID: 1,
SimulatorID: 1,
StartParameters: postgres.Jsonb{startParametersB},
}
var SimulationModelB_response = SimulationModelResponse{
ID: 2,
Name: SimulationModelB.Name,
InputLength: SimulationModelB.InputLength,
OutputLength: SimulationModelB.OutputLength,
ScenarioID: SimulationModelB.ScenarioID,
SimulatorID: SimulationModelB.SimulatorID,
StartParameters: SimulationModelB.StartParameters,
}
var SimulationModelC = SimulationModel{
Name: "SimulationModel_C",
OutputLength: 1,
InputLength: 1,
ScenarioID: 1,
SimulatorID: 1,
StartParameters: postgres.Jsonb{startParametersC},
}
var SimulationModelC_response = SimulationModelResponse{
ID: 3,
Name: SimulationModelC.Name,
InputLength: SimulationModelC.InputLength,
OutputLength: SimulationModelC.OutputLength,
ScenarioID: SimulationModelC.ScenarioID,
SimulatorID: SimulationModelC.SimulatorID,
StartParameters: SimulationModelC.StartParameters,
}
var SimulationModelCUpdated = SimulationModel{
Name: "SimulationModel_CUpdated",
OutputLength: SimulationModelC.OutputLength,
InputLength: SimulationModelC.InputLength,
ScenarioID: SimulationModelC.ScenarioID,
SimulatorID: 2,
StartParameters: SimulationModelC.StartParameters,
InputMapping: SimulationModelC.InputMapping,
OutputMapping: SimulationModelC.OutputMapping,
}
var SimulationModelCUpdated_response = SimulationModelResponse{
ID: 3,
Name: SimulationModelCUpdated.Name,
InputLength: SimulationModelCUpdated.InputLength,
OutputLength: SimulationModelCUpdated.OutputLength,
ScenarioID: SimulationModelCUpdated.ScenarioID,
SimulatorID: SimulationModelCUpdated.SimulatorID,
StartParameters: SimulationModelCUpdated.StartParameters,
}
// Signals
var OutSignalA = Signal{
Name: "outSignal_A",
Direction: "out",
Index: 0,
Unit: "V",
SimulationModelID: 1,
}
var OutSignalA_response = SignalResponse{
Name: OutSignalA.Name,
Direction: OutSignalA.Direction,
Index: OutSignalA.Index,
Unit: OutSignalA.Unit,
SimulationModelID: OutSignalA.SimulationModelID,
}
var OutSignalB = Signal{
Name: "outSignal_B",
Direction: "out",
Index: 1,
Unit: "V",
SimulationModelID: 1,
}
var OutSignalB_response = SignalResponse{
Name: OutSignalB.Name,
Direction: OutSignalB.Direction,
Index: OutSignalB.Index,
Unit: OutSignalB.Unit,
SimulationModelID: OutSignalB.SimulationModelID,
}
var InSignalA = Signal{
Name: "inSignal_A",
Direction: "in",
Index: 0,
Unit: "A",
SimulationModelID: 1,
}
var InSignalA_response = SignalResponse{
Name: InSignalA.Name,
Direction: InSignalA.Direction,
Index: InSignalA.Index,
Unit: InSignalA.Unit,
SimulationModelID: InSignalA.SimulationModelID,
}
var InSignalB = Signal{
Name: "inSignal_B",
Direction: "in",
Index: 1,
Unit: "A",
SimulationModelID: 1,
}
var InSignalB_response = SignalResponse{
Name: InSignalB.Name,
Direction: InSignalB.Direction,
Index: InSignalB.Index,
Unit: InSignalB.Unit,
SimulationModelID: InSignalB.SimulationModelID,
}
var InSignalC = Signal{
Name: "inSignal_C",
Direction: "in",
Index: 2,
Unit: "A",
SimulationModelID: 1,
}
var InSignalC_response = SignalResponse{
Name: InSignalC.Name,
Direction: InSignalC.Direction,
Index: InSignalC.Index,
Unit: InSignalC.Unit,
SimulationModelID: InSignalC.SimulationModelID,
}
var InSignalCUpdated = Signal{
Name: "inSignalupdated_C",
Direction: InSignalC.Direction,
Index: InSignalC.Index,
Unit: "Ohm",
SimulationModelID: InSignalC.SimulationModelID,
}
var InSignalCUpdated_response = SignalResponse{
Name: InSignalCUpdated.Name,
Direction: InSignalCUpdated.Direction,
Index: InSignalCUpdated.Index,
Unit: InSignalCUpdated.Unit,
SimulationModelID: InSignalCUpdated.SimulationModelID,
}
// Dashboards
var DashboardA = Dashboard{Name: "Dashboard_A", Grid: 15, ScenarioID: 1}
var DashboardA_response = DashboardResponse{ID: 1, Name: DashboardA.Name, Grid: DashboardA.Grid, ScenarioID: DashboardA.ScenarioID}
var DashboardB = Dashboard{Name: "Dashboard_B", Grid: 10, ScenarioID: 1}
var DashboardB_response = DashboardResponse{ID: 2, Name: DashboardB.Name, Grid: DashboardB.Grid, ScenarioID: DashboardB.ScenarioID}
var DashboardC = Dashboard{Name: "Dashboard_C", Grid: 25, ScenarioID: 1}
var DashboardC_response = DashboardResponse{ID: 3, Name: DashboardC.Name, Grid: DashboardC.Grid, ScenarioID: DashboardC.ScenarioID}
var DashboardCUpdated = Dashboard{Name: "Dashboard_Cupdated", Grid: 24, ScenarioID: DashboardC.ScenarioID}
var DashboardCUpdated_response = DashboardResponse{ID: 3, Name: DashboardCUpdated.Name, Grid: DashboardCUpdated.Grid, ScenarioID: DashboardCUpdated.ScenarioID}
// Files
var FileA = File{
Name: "File_A",
Type: "text/plain",
Size: 42,
ImageHeight: 333,
ImageWidth: 111,
Date: time.Now().String(),
WidgetID: 1,
SimulationModelID: 0,
}
var FileA_response = FileResponse{
ID: 1,
Name: FileA.Name,
Type: FileA.Type,
Size: FileA.Size,
ImageWidth: FileA.ImageWidth,
ImageHeight: FileA.ImageHeight,
Date: FileA.Date,
WidgetID: FileA.WidgetID,
SimulationModelID: FileA.SimulationModelID,
}
var FileB = File{
Name: "File_B",
Type: "text/plain",
Size: 1234,
ImageHeight: 55,
ImageWidth: 22,
Date: time.Now().String(),
WidgetID: 1,
SimulationModelID: 0,
}
var FileB_response = FileResponse{
ID: 2,
Name: FileB.Name,
Type: FileB.Type,
Size: FileB.Size,
ImageWidth: FileB.ImageWidth,
ImageHeight: FileB.ImageHeight,
Date: FileB.Date,
WidgetID: FileB.WidgetID,
SimulationModelID: FileB.SimulationModelID,
}
var FileC = File{
Name: "File_C",
Type: "text/plain",
Size: 32,
ImageHeight: 10,
ImageWidth: 10,
Date: time.Now().String(),
WidgetID: 0,
SimulationModelID: 1,
}
var FileD = File{
Name: "File_D",
Type: "text/plain",
Size: 5000,
ImageHeight: 400,
ImageWidth: 800,
Date: time.Now().String(),
WidgetID: 0,
SimulationModelID: 1,
}
// Widgets
var customPropertiesA = json.RawMessage(`{"property1" : "testValue1A", "property2" : "testValue2A", "property3" : 42}`)
var customPropertiesB = json.RawMessage(`{"property1" : "testValue1B", "property2" : "testValue2B", "property3" : 43}`)
var customPropertiesC = json.RawMessage(`{"property1" : "testValue1C", "property2" : "testValue2C", "property3" : 44}`)
var WidgetA = Widget{
Name: "Widget_A",
Type: "graph",
Width: 100,
Height: 50,
MinHeight: 40,
MinWidth: 80,
X: 10,
Y: 10,
Z: 10,
IsLocked: false,
CustomProperties: postgres.Jsonb{customPropertiesA},
DashboardID: 1,
}
var WidgetA_response = WidgetResponse{
ID: 1,
Name: WidgetA.Name,
Type: WidgetA.Type,
Width: WidgetA.Width,
Height: WidgetA.Height,
MinWidth: WidgetA.MinWidth,
MinHeight: WidgetA.MinHeight,
X: WidgetA.X,
Y: WidgetA.Y,
Z: WidgetA.Z,
IsLocked: WidgetA.IsLocked,
CustomProperties: WidgetA.CustomProperties,
DashboardID: WidgetA.DashboardID,
}
var WidgetB = Widget{
Name: "Widget_B",
Type: "slider",
Width: 200,
Height: 20,
MinHeight: 10,
MinWidth: 50,
X: 100,
Y: -40,
Z: 0,
IsLocked: false,
CustomProperties: postgres.Jsonb{customPropertiesB},
DashboardID: 1,
}
var WidgetB_response = WidgetResponse{
ID: 2,
Name: WidgetB.Name,
Type: WidgetB.Type,
Width: WidgetB.Width,
Height: WidgetB.Height,
MinWidth: WidgetB.MinWidth,
MinHeight: WidgetB.MinHeight,
X: WidgetB.X,
Y: WidgetB.Y,
Z: WidgetB.Z,
IsLocked: WidgetB.IsLocked,
CustomProperties: WidgetB.CustomProperties,
DashboardID: WidgetB.DashboardID,
}
var WidgetC = Widget{
Name: "Widget_C",
Type: "bargraph",
Height: 30,
Width: 100,
MinHeight: 20,
MinWidth: 50,
X: 11,
Y: 12,
Z: 13,
IsLocked: false,
CustomProperties: postgres.Jsonb{customPropertiesC},
DashboardID: 1,
}
var WidgetC_response = WidgetResponse{
ID: 3,
Name: WidgetC.Name,
Type: WidgetC.Type,
Width: WidgetC.Width,
Height: WidgetC.Height,
MinWidth: WidgetC.MinWidth,
MinHeight: WidgetC.MinHeight,
X: WidgetC.X,
Y: WidgetC.Y,
Z: WidgetC.Z,
IsLocked: WidgetC.IsLocked,
CustomProperties: WidgetC.CustomProperties,
DashboardID: WidgetC.DashboardID,
}
var WidgetCUpdated_response = WidgetResponse{
ID: 3,
Name: "Widget_CUpdated",
Type: WidgetC.Type,
Height: 35,
Width: 110,
MinHeight: WidgetC.MinHeight,
MinWidth: WidgetC.MinWidth,
X: WidgetC.X,
Y: WidgetC.Y,
Z: WidgetC.Z,
IsLocked: WidgetC.IsLocked,
CustomProperties: WidgetC.CustomProperties,
DashboardID: WidgetC.DashboardID,
}

View file

@ -11,6 +11,7 @@ import (
"github.com/gin-gonic/gin"
"github.com/jinzhu/gorm"
"github.com/nsf/jsondiff"
"github.com/stretchr/testify/assert"
)
@ -35,53 +36,7 @@ func ProvideErrorResponse(c *gin.Context, err error) bool {
return false // No error
}
func GetVisualizationID(c *gin.Context) (int, error) {
simID, err := strconv.Atoi(c.Param("visualizationID"))
if err != nil {
errormsg := fmt.Sprintf("Bad request. No or incorrect format of visualization ID")
c.JSON(http.StatusBadRequest, gin.H{
"error": errormsg,
})
return -1, err
} else {
return simID, err
}
}
func GetWidgetID(c *gin.Context) (int, error) {
widgetID, err := strconv.Atoi(c.Param("widgetID"))
if err != nil {
errormsg := fmt.Sprintf("Bad request. No or incorrect format of widget ID")
c.JSON(http.StatusBadRequest, gin.H{
"error": errormsg,
})
return -1, err
} else {
return widgetID, err
}
}
func GetFileID(c *gin.Context) (int, error) {
fileID, err := strconv.Atoi(c.Param("fileID"))
if err != nil {
errormsg := fmt.Sprintf("Bad request. No or incorrect format of file ID")
c.JSON(http.StatusBadRequest, gin.H{
"error": errormsg,
})
return -1, err
} else {
return fileID, err
}
}
func TestEndpoint(t *testing.T, router *gin.Engine, token string, url string, method string, body []byte, expected_code int, expected_response string) {
func TestEndpoint(t *testing.T, router *gin.Engine, token string, url string, method string, body []byte, expected_code int, expected_response []byte) {
w := httptest.NewRecorder()
if body != nil {
@ -96,8 +51,12 @@ func TestEndpoint(t *testing.T, router *gin.Engine, token string, url string, me
}
assert.Equal(t, expected_code, w.Code)
fmt.Println(w.Body.String())
assert.Equal(t, expected_response, w.Body.String())
//fmt.Println("Actual:", w.Body.String())
//fmt.Println("Expected: ", string(expected_response))
opts := jsondiff.DefaultConsoleOptions()
diff, _ := jsondiff.Compare(w.Body.Bytes(), expected_response, &opts)
assert.Equal(t, "FullMatch", diff.String())
}
func AuthenticateForTest(t *testing.T, router *gin.Engine, url string, method string, body []byte, expected_code int) string {
@ -118,6 +77,7 @@ func AuthenticateForTest(t *testing.T, router *gin.Engine, url string, method st
success := body_data["success"].(bool)
if !success {
fmt.Println("Authentication not successful: ", body_data["message"])
panic(-1)
}

File diff suppressed because it is too large Load diff

19
go.mod
View file

@ -4,15 +4,22 @@ require (
github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc
github.com/dgrijalva/jwt-go v3.2.0+incompatible
github.com/gin-gonic/gin v1.4.0
github.com/go-openapi/spec v0.19.2 // indirect
github.com/go-openapi/swag v0.19.3 // indirect
github.com/go-playground/locales v0.12.1 // indirect
github.com/go-playground/universal-translator v0.16.0 // indirect
github.com/jinzhu/gorm v1.9.8
github.com/jinzhu/gorm v1.9.10
github.com/leodido/go-urn v1.1.0 // indirect
github.com/mailru/easyjson v0.0.0-20190626092158-b2ccc519800e // indirect
github.com/nsf/jsondiff v0.0.0-20190712045011-8443391ee9b6
github.com/streadway/amqp v0.0.0-20190404075320-75d898a42a94
github.com/stretchr/testify v1.3.0
github.com/swaggo/gin-swagger v1.1.0
github.com/swaggo/swag v1.5.0
golang.org/x/crypto v0.0.0-20190325154230-a5d413f7728c
github.com/swaggo/gin-swagger v1.2.0
github.com/swaggo/swag v1.6.0
github.com/tidwall/gjson v1.3.0
golang.org/x/crypto v0.0.0-20190701094942-4def268fd1a4
golang.org/x/net v0.0.0-20190628185345-da137c7871d7 // indirect
golang.org/x/sys v0.0.0-20190626221950-04f50cda93cb // indirect
golang.org/x/tools v0.0.0-20190703212419-2214986f1668 // indirect
gopkg.in/go-playground/validator.v9 v9.29.0
)
replace github.com/ugorji/go v1.1.4 => github.com/ugorji/go/codec v0.0.0-20190204201341-e444a5086c43

136
go.sum
View file

@ -3,6 +3,9 @@ cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMT
cloud.google.com/go v0.37.4 h1:glPeL3BQJsbF6aIIYfZizMwc5LTYz250bDMjttbBGAU=
cloud.google.com/go v0.37.4/go.mod h1:NHPJ89PdicEuT9hdPXMROBD91xc5uRDxsMtSB16k7hw=
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
github.com/KyleBanks/depth v1.2.1 h1:5h8fQADFrWtarTdtDudMmGsC7GPbOAu6RVB3ffsVFHc=
github.com/KyleBanks/depth v1.2.1/go.mod h1:jzSb9d0L43HxTQfT+oSA1EEp2q+ne2uh6XgeJcm8brE=
github.com/PuerkitoBio/purell v1.1.0 h1:rmGxhojJlM0tuKtfdvliR84CFHljx9ag64t2xmVkjK4=
github.com/PuerkitoBio/purell v1.1.0/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbtSwDGJws/X0=
github.com/PuerkitoBio/purell v1.1.1 h1:WEQqlqaGbrPkxLJWfBwQmfEAE1Z7ONdDLqrN38tNFfI=
github.com/PuerkitoBio/purell v1.1.1/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbtSwDGJws/X0=
@ -19,8 +22,8 @@ github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDk
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/denisenkom/go-mssqldb v0.0.0-20190423183735-731ef375ac02 h1:PS3xfVPa8N84AzoWZHFCbA0+ikz4f4skktfjQoNMsgk=
github.com/denisenkom/go-mssqldb v0.0.0-20190423183735-731ef375ac02/go.mod h1:zAg7JM8CkOJ43xKXIj7eRO9kmWm/TW578qo+oDO6tuM=
github.com/denisenkom/go-mssqldb v0.0.0-20190515213511-eb9f6a1743f3 h1:tkum0XDgfR0jcVVXuTsYv/erY2NnEDqwRojbxR1rBYA=
github.com/denisenkom/go-mssqldb v0.0.0-20190515213511-eb9f6a1743f3/go.mod h1:zAg7JM8CkOJ43xKXIj7eRO9kmWm/TW578qo+oDO6tuM=
github.com/dgrijalva/jwt-go v3.2.0+incompatible h1:7qlOGliEKZXTDg6OTjfoBKDXWrumCAMpl/TFQ4/5kLM=
github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ=
github.com/eapache/go-resiliency v1.1.0/go.mod h1:kFI+JgMyC7bLPUVY133qvEBtVayf5mFgVsvEsIPBvNs=
@ -29,26 +32,37 @@ github.com/eapache/queue v1.1.0/go.mod h1:6eCeP0CKFpHLu8blIFXhExK/dRa7WDZfr6jVFP
github.com/erikstmartin/go-testdb v0.0.0-20160219214506-8d10e4a1bae5 h1:Yzb9+7DPaBjB8zlTR87/ElzFsnQfuHnVUVqpZZIcV5Y=
github.com/erikstmartin/go-testdb v0.0.0-20160219214506-8d10e4a1bae5/go.mod h1:a2zkGnVExMxdzMo3M0Hi/3sEU+cWnZpSni0O6/Yb/P0=
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
github.com/ghodss/yaml v1.0.0 h1:wQHKEahhL6wmXdzwWG11gIVCkOv05bNOh+Rxn0yngAk=
github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
github.com/gin-contrib/gzip v0.0.1 h1:ezvKOL6jH+jlzdHNE4h9h8q8uMpDQjyl0NN0Jd7jozc=
github.com/gin-contrib/gzip v0.0.1/go.mod h1:fGBJBCdt6qCZuCAOwWuFhBB4OOq9EFqlo5dEaFhhu5w=
github.com/gin-contrib/sse v0.0.0-20170109093832-22d885f9ecc7/go.mod h1:VJ0WA2NBN22VlZ2dKZQPAPnyWw5XTlK1KymzLKsr59s=
github.com/gin-contrib/sse v0.0.0-20190301062529-5545eab6dad3 h1:t8FVkw33L+wilf2QiWkw0UV77qRpcH/JHPKGpKa2E8g=
github.com/gin-contrib/sse v0.0.0-20190301062529-5545eab6dad3/go.mod h1:VJ0WA2NBN22VlZ2dKZQPAPnyWw5XTlK1KymzLKsr59s=
github.com/gin-contrib/sse v0.1.0 h1:Y/yl/+YNO8GZSjAhjMsSuLt29uWRFHdHYUb5lYOV9qE=
github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI=
github.com/gin-gonic/gin v1.3.0/go.mod h1:7cKuhb5qV2ggCFctp2fJQ+ErvciLZrIeoOSOm6mUr7Y=
github.com/gin-gonic/gin v1.4.0 h1:3tMoCCfM7ppqsR0ptz/wi1impNpT7/9wQtMZ8lr1mCQ=
github.com/gin-gonic/gin v1.4.0/go.mod h1:OW2EZn3DO8Ln9oIKOvM++LBO+5UPHJJDH72/q/3rZdM=
github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE=
github.com/go-openapi/jsonpointer v0.17.0 h1:nH6xp8XdXHx8dqveo0ZuJBluCO2qGrPbDNZ0dwoRHP0=
github.com/go-openapi/jsonpointer v0.17.0/go.mod h1:cOnomiV+CVVwFLk0A/MExoFMjwdsUdVpsRhURCKh+3M=
github.com/go-openapi/jsonpointer v0.18.0 h1:KVRzjXpMzgdM4GEMDmDTnGcY5yBwGWreJwmmk4k35yU=
github.com/go-openapi/jsonpointer v0.18.0/go.mod h1:cOnomiV+CVVwFLk0A/MExoFMjwdsUdVpsRhURCKh+3M=
github.com/go-openapi/jsonpointer v0.19.2 h1:A9+F4Dc/MCNB5jibxf6rRvOvR/iFgQdyNx9eIhnGqq0=
github.com/go-openapi/jsonpointer v0.19.2/go.mod h1:3akKfEdA7DF1sugOqz1dVQHBcuDBPKZGEoHC/NkiQRg=
github.com/go-openapi/jsonreference v0.17.0/go.mod h1:g4xxGn04lDIRh0GJb5QlpE3HfopLOL6uZrK/VgnsK9I=
github.com/go-openapi/jsonreference v0.18.0 h1:oP2OUNdG1l2r5kYhrfVMXO54gWmzcfAwP/GFuHpNTkE=
github.com/go-openapi/jsonreference v0.18.0/go.mod h1:g4xxGn04lDIRh0GJb5QlpE3HfopLOL6uZrK/VgnsK9I=
github.com/go-openapi/spec v0.18.0 h1:aIjeyG5mo5/FrvDkpKKEGZPmF9MPHahS72mzfVqeQXQ=
github.com/go-openapi/spec v0.18.0/go.mod h1:XkF/MOi14NmjsfZ8VtAKf8pIlbZzyoTvZsdfssdxcBI=
github.com/go-openapi/jsonreference v0.19.0 h1:BqWKpV1dFd+AuiKlgtddwVIFQsuMpxfBDBHGfM2yNpk=
github.com/go-openapi/jsonreference v0.19.0/go.mod h1:g4xxGn04lDIRh0GJb5QlpE3HfopLOL6uZrK/VgnsK9I=
github.com/go-openapi/jsonreference v0.19.2 h1:o20suLFB4Ri0tuzpWtyHlh7E7HnkqTNLq6aR6WVNS1w=
github.com/go-openapi/jsonreference v0.19.2/go.mod h1:jMjeRr2HHw6nAVajTXJ4eiUwohSTlpa0o73RUL1owJc=
github.com/go-openapi/spec v0.19.0 h1:A4SZ6IWh3lnjH0rG0Z5lkxazMGBECtrZcbyYQi+64k4=
github.com/go-openapi/spec v0.19.0/go.mod h1:XkF/MOi14NmjsfZ8VtAKf8pIlbZzyoTvZsdfssdxcBI=
github.com/go-openapi/spec v0.19.2 h1:SStNd1jRcYtfKCN7R0laGNs80WYYvn5CbBjM2sOmCrE=
github.com/go-openapi/spec v0.19.2/go.mod h1:sCxk3jxKgioEJikev4fgkNmwS+3kuYdJtcsZsD5zxMY=
github.com/go-openapi/swag v0.17.0 h1:iqrgMg7Q7SvtbWLlltPrkMs0UBJI6oTSs79JFRUi880=
github.com/go-openapi/swag v0.17.0/go.mod h1:AByQ+nYG6gQg71GINrmuDXCPWdL640yX49/kXLo40Tg=
github.com/go-openapi/swag v0.18.0 h1:1DU8Km1MRGv9Pj7BNLmkA+umwTStwDHttXvx3NhJA70=
github.com/go-openapi/swag v0.18.0/go.mod h1:AByQ+nYG6gQg71GINrmuDXCPWdL640yX49/kXLo40Tg=
github.com/go-openapi/swag v0.19.2/go.mod h1:POnQmlKehdgb5mhVOsnJFsivZCEZ/vjK9gh66Z9tfKk=
github.com/go-openapi/swag v0.19.3 h1:FKTmP0GPWwRqRP2WIYltgctgYTN+gr8iZ7zSKdZtbz0=
github.com/go-openapi/swag v0.19.3/go.mod h1:POnQmlKehdgb5mhVOsnJFsivZCEZ/vjK9gh66Z9tfKk=
github.com/go-playground/locales v0.12.1 h1:2FITxuFt/xuCNP1Acdhv62OzaCiviiE4kotfhkmOqEc=
github.com/go-playground/locales v0.12.1/go.mod h1:IUMDtCfWo/w/mtMfIE/IG2K+Ey3ygWanZIBtBW0W2TM=
github.com/go-playground/universal-translator v0.16.0 h1:X++omBR/4cE2MNg91AoC3rmGrCjJ8eAeUP/K/EKx4DM=
@ -75,12 +89,13 @@ github.com/gorilla/context v1.1.1/go.mod h1:kBGZzfjB9CEq2AlWe17Uuf7NDRt0dE0s8S51
github.com/gorilla/mux v1.6.2/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs=
github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=
github.com/jinzhu/gorm v1.9.8 h1:n5uvxqLepIP2R1XF7pudpt9Rv8I3m7G9trGxJVjLZ5k=
github.com/jinzhu/gorm v1.9.8/go.mod h1:bdqTT3q6dhSph2K3pWxrHP6nqxuAp2yQ3KFtc3U3F84=
github.com/jinzhu/inflection v0.0.0-20180308033659-04140366298a h1:eeaG9XMUvRBYXJi4pg1ZKM7nxc5AfXfojeLLW7O5J3k=
github.com/jinzhu/inflection v0.0.0-20180308033659-04140366298a/go.mod h1:h+uFLlag+Qp1Va5pdKtLDYj+kHp5pxUVkryuEj+Srlc=
github.com/jinzhu/now v1.0.0 h1:6WV8LvwPpDhKjo5U9O6b4+xdG/jTXNPwlDme/MTo8Ns=
github.com/jinzhu/now v1.0.0/go.mod h1:oHTiXerJ20+SfYcrdlBO7rzZRJWGwSTQ0iUY2jI6Gfc=
github.com/jinzhu/gorm v1.9.10 h1:HvrsqdhCW78xpJF67g1hMxS6eCToo9PZH4LDB8WKPac=
github.com/jinzhu/gorm v1.9.10/go.mod h1:Kh6hTsSGffh4ui079FHrR5Gg+5D0hgihqDcsDN2BBJY=
github.com/jinzhu/inflection v1.0.0 h1:K317FqzuhWc8YvSVlFMCCUb36O/S9MCKRDI7QkRKD/E=
github.com/jinzhu/inflection v1.0.0/go.mod h1:h+uFLlag+Qp1Va5pdKtLDYj+kHp5pxUVkryuEj+Srlc=
github.com/jinzhu/now v1.0.1 h1:HjfetcXq097iXP0uoPCdnM4Efp5/9MsM0/M+XOTeR3M=
github.com/jinzhu/now v1.0.1/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8=
github.com/json-iterator/go v1.1.5/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU=
github.com/json-iterator/go v1.1.6 h1:MrUvLMLTMxbqFJ9kzlvat/rYZqZnW3u4wkLzWTaFwKs=
github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU=
github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU=
@ -91,18 +106,22 @@ github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFB
github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI=
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
github.com/kr/pty v1.1.5/go.mod h1:9r2w37qlBe7rQ6e1fg1S/9xpWHSnaqNdHD3WcMdbPDA=
github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE=
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
github.com/leodido/go-urn v1.1.0 h1:Sm1gr51B1kKyfD2BlRcLSiEkffoG96g6TPv6eRoEiB8=
github.com/leodido/go-urn v1.1.0/go.mod h1:+cyI34gQWZcE1eQU7NVgKkkzdXDQHr1dBMtdAPozLkw=
github.com/lib/pq v1.1.0 h1:/5u4a+KGJptBRqGzPvYQL9p0d/tPR4S31+Tnzj9lEO4=
github.com/lib/pq v1.1.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
github.com/lib/pq v1.1.1 h1:sJZmqHoEaY7f+NPP8pgLB/WxulyR3fewgCM2qaSlBb4=
github.com/lib/pq v1.1.1/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
github.com/mailru/easyjson v0.0.0-20180823135443-60711f1a8329 h1:2gxZ0XQIU/5z3Z3bUBu+FXuk2pFbkN6tcwi/pjyaDic=
github.com/mailru/easyjson v0.0.0-20180823135443-60711f1a8329/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc=
github.com/mailru/easyjson v0.0.0-20190312143242-1de009706dbe h1:W/GaMY0y69G4cFlmsC6B9sbuo2fP8OFP1ABjt4kPz+w=
github.com/mailru/easyjson v0.0.0-20190312143242-1de009706dbe/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc=
github.com/mailru/easyjson v0.0.0-20190614124828-94de47d64c63/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc=
github.com/mailru/easyjson v0.0.0-20190626092158-b2ccc519800e h1:hB2xlXdHp/pmPZq0y3QnmWAArdw9PqbmotexnWx/FU8=
github.com/mailru/easyjson v0.0.0-20190626092158-b2ccc519800e/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc=
github.com/mattn/go-isatty v0.0.4/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4=
github.com/mattn/go-isatty v0.0.7 h1:UvyT9uN+3r7yLEYSlJsbQGdsaB/a0DlgWP3pql6iwOc=
github.com/mattn/go-isatty v0.0.7/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s=
github.com/mattn/go-isatty v0.0.8 h1:HLtExJ+uU2HOZ+wI0Tt5DtUDrx8yhUqDcp7fYERX4CE=
github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s=
github.com/mattn/go-sqlite3 v1.10.0 h1:jbhqpg7tQe4SupckyijYiy0mJJ/pRyHvXf7JdWK860o=
github.com/mattn/go-sqlite3 v1.10.0/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc=
github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0=
@ -111,6 +130,8 @@ github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJ
github.com/modern-go/reflect2 v1.0.1 h1:9f412s+6RmYXLWZSEzVVgPGK7C2PphHj5RJrvfx9AWI=
github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U=
github.com/nsf/jsondiff v0.0.0-20190712045011-8443391ee9b6 h1:qsqscDgSJy+HqgMTR+3NwjYJBbp1+honwDsszLoS+pA=
github.com/nsf/jsondiff v0.0.0-20190712045011-8443391ee9b6/go.mod h1:uFMI8w+ref4v2r9jz+c9i1IfIttS/OkmLfrk1jne5hs=
github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
github.com/onsi/ginkgo v1.7.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
github.com/onsi/gomega v1.4.3/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY=
@ -129,33 +150,41 @@ github.com/prometheus/common v0.2.0/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y8
github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk=
github.com/prometheus/procfs v0.0.0-20190117184657-bf6a532e95b1/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk=
github.com/rcrowley/go-metrics v0.0.0-20181016184325-3113b8401b8a/go.mod h1:bCqnVzQkZxMG4s8nGwiZ5l3QUCyqpo9Y+/ZMZ9VjZe4=
github.com/satori/go.uuid v1.2.0/go.mod h1:dA0hQrYB0VpLJoorglMZABFdXlWrHn1NEOzdhQKdks0=
github.com/shopspring/decimal v0.0.0-20180709203117-cd690d0c9e24/go.mod h1:M+9NzErvs504Cn4c5DxATwIqPbtswREoFCre64PpcG4=
github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo=
github.com/streadway/amqp v0.0.0-20190404075320-75d898a42a94 h1:0ngsPmuP6XIjiFRNFYlvKwSr5zff2v+uPHaffZ6/M4k=
github.com/streadway/amqp v0.0.0-20190404075320-75d898a42a94/go.mod h1:AZpEONHx3DKn8O/DFsRAY58/XVQiIPMTMB1SddzLXVw=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.2.0/go.mod h1:qt09Ya8vawLte6SNmTgCsAVtYtaKzEcn8ATUoHMkEqE=
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
github.com/stretchr/testify v1.3.0 h1:TivCn/peBQ7UY8ooIcPgZFpTNSz0Q2U6UrFlUfqbe0Q=
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
github.com/swaggo/gin-swagger v1.1.0 h1:ZI6/82S07DkkrMfGKbJhKj1R+QNTICkeAJP06pU36pU=
github.com/swaggo/gin-swagger v1.1.0/go.mod h1:FQlm07YuT1glfN3hQiO11UQ2m39vOCZ/aa3WWr5E+XU=
github.com/swaggo/swag v1.4.0/go.mod h1:hog2WgeMOrQ/LvQ+o1YGTeT+vWVrbi0SiIslBtxKTyM=
github.com/swaggo/swag v1.5.0 h1:haK8VG3hj+v/c8hQ4f3U+oYpkdI/26m9LAUTXHOv+2U=
github.com/swaggo/swag v1.5.0/go.mod h1:+xZrnu5Ut3GcUkKAJm9spnOooIS1WB1cUOkLNPrvrE0=
github.com/ugorji/go v1.1.2/go.mod h1:hnLbHMwcvSihnDhEfx2/BzKp2xb0Y+ErdfYcrs9tkJQ=
github.com/ugorji/go v1.1.4 h1:j4s+tAvLfL3bZyefP2SEWmhBzmuIlH/eqNuPdFPgngw=
github.com/swaggo/gin-swagger v1.2.0 h1:YskZXEiv51fjOMTsXrOetAjrMDfFaXD79PEoQBOe2W0=
github.com/swaggo/gin-swagger v1.2.0/go.mod h1:qlH2+W7zXGZkczuL+r2nEBR2JTT+/lX05Nn6vPhc7OI=
github.com/swaggo/swag v1.5.1/go.mod h1:1Bl9F/ZBpVWh22nY0zmYyASPO1lI/zIwRDrpZU+tv8Y=
github.com/swaggo/swag v1.6.0 h1:d6Z3kNGQmM2Z6DZtOyK138eFTkpi2JwSwNVOV4wnlLk=
github.com/swaggo/swag v1.6.0/go.mod h1:YyZstMc22WYm6GEDx/CYWxq+faBbjQ5EqwQcrjREDBo=
github.com/tidwall/gjson v1.3.0 h1:kfpsw1W3trbg4Xm6doUtqSl9+LhLB6qJ9PkltVAQZYs=
github.com/tidwall/gjson v1.3.0/go.mod h1:P256ACg0Mn+j1RXIDXoss50DeIABTYK1PULOJHhxOls=
github.com/tidwall/match v1.0.1 h1:PnKP62LPNxHKTwvHHZZzdOAOCtsJTjo6dZLCwpKm5xc=
github.com/tidwall/match v1.0.1/go.mod h1:LujAq0jyVjBy028G1WhWfIzbpQfMO8bBZ6Tyb0+pL9E=
github.com/tidwall/pretty v1.0.0 h1:HsD+QiTn7sK6flMKIvNmpqz1qrpP3Ps6jOKIKMooyg4=
github.com/tidwall/pretty v1.0.0/go.mod h1:XNkn88O1ChpSDQmQeStsy+sBenx6DDtFZJxhVysOjyk=
github.com/ugorji/go v1.1.4/go.mod h1:uQMGLiO92mf5W77hV/PUCpI3pbzQx3CRekS0kk+RGrc=
github.com/ugorji/go/codec v0.0.0-20181209151446-772ced7fd4c2/go.mod h1:VFNgLljTbGfSG7qAOspJ7OScBnGdDN/yBr0sguwnwf0=
github.com/ugorji/go/codec v0.0.0-20190204201341-e444a5086c43 h1:BasDe+IErOQKrMVXab7UayvSlIpiyGwRvuX3EKYY7UA=
github.com/ugorji/go/codec v0.0.0-20190204201341-e444a5086c43/go.mod h1:iT03XoTwV7xq/+UGwKO3UbC1nNNlopQiY61beSdrtOA=
github.com/ugorji/go/codec v0.0.0-20190320090025-2dc34c0b8780 h1:vG/gY/PxA3v3l04qxe3tDjXyu3bozii8ulSlIPOYKhI=
github.com/ugorji/go/codec v0.0.0-20190320090025-2dc34c0b8780/go.mod h1:iT03XoTwV7xq/+UGwKO3UbC1nNNlopQiY61beSdrtOA=
github.com/ugorji/go v1.1.5-pre h1:jyJKFOSEbdOc2HODrf2qcCkYOdq7zzXqA9bhW5oV4fM=
github.com/ugorji/go v1.1.5-pre/go.mod h1:FwP/aQVg39TXzItUBMwnWp9T9gPQnXw4Poh4/oBQZ/0=
github.com/ugorji/go/codec v0.0.0-20181022190402-e5e69e061d4f/go.mod h1:VFNgLljTbGfSG7qAOspJ7OScBnGdDN/yBr0sguwnwf0=
github.com/ugorji/go/codec v1.1.5-pre h1:5YV9PsFAN+ndcCtTM7s60no7nY7eTG3LPtxhSwuxzCs=
github.com/ugorji/go/codec v1.1.5-pre/go.mod h1:tULtS6Gy1AE1yCENaw4Vb//HLH5njI2tfCQDUqRd8fI=
github.com/urfave/cli v1.20.0 h1:fDqGv3UG/4jbVl/QkFwEdddtEDjh/5Ov6X+0B/3bPaw=
github.com/urfave/cli v1.20.0/go.mod h1:70zkFmudgCuE/ngEzBv17Jvp/497gISqfk5gWijbERA=
go.opencensus.io v0.20.1/go.mod h1:6WKK9ahsWS3RSO+PY9ZHZUfv2irvY6gN279GOPZjmmk=
golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20190325154230-a5d413f7728c h1:Vj5n4GlwjmQteupaxJ9+0FNOmBrHfq7vN4btdGoDZgI=
golang.org/x/crypto v0.0.0-20190325154230-a5d413f7728c/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20190611184440-5c40567a22f8/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20190701094942-4def268fd1a4 h1:HuIa8hRrWRSrqYzx1qI49NNxhdi2PrY7gxVSq1JjLDc=
golang.org/x/crypto v0.0.0-20190701094942-4def268fd1a4/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=
@ -165,39 +194,56 @@ golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73r
golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20181005035420-146acd28ed58/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20181220203305-927f97764cc3/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190125091013-d26f9f9a57f3/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190322120337-addf6b3196f6/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c h1:uOCk1iQW6Vc18bnC13MfzScl+wdKBmM9Y9kU7Z83/lw=
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190611141213-3f473d35a33a h1:+KkCgOMgnKSgenxTBoiwkMqTiouMIy/3o8RLdmSbGoY=
golang.org/x/net v0.0.0-20190611141213-3f473d35a33a/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20190613194153-d28f0bde5980/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20190628185345-da137c7871d7 h1:rTIdg5QFRR7XCaK4LCjBiPbx8j4DQRpdYMnGn/bJUEU=
golang.org/x/net v0.0.0-20190628185345-da137c7871d7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20181122145206-62eef0e2fa9b/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20181228144115-9a3f9b0469bb/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190322080309-f49334f85ddc h1:4gbWbmmPFp4ySWICouJl6emP0MyS31yy9SrTlAGFT+g=
golang.org/x/sys v0.0.0-20190322080309-f49334f85ddc/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190610200419-93c9922d18ae h1:xiXzMMEQdQcric9hXtr1QU98MHunKK7OTtsoU6bYWs4=
golang.org/x/sys v0.0.0-20190610200419-93c9922d18ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190616124812-15dcb6c0061f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190626221950-04f50cda93cb h1:fgwFCsaw9buMuxNd6+DQfAuSFqbNiQZpcgJQAgJsK6k=
golang.org/x/sys v0.0.0-20190626221950-04f50cda93cb/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 h1:z99zHgr7hKfrUcX/KsoJk5FJfjTceCKIp96+biqP4To=
golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.2 h1:tW2bmiBqwgJj/UpqtC8EpXEZVYOwU0yG4iWbprSVAcs=
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/tools v0.0.0-20180828015842-6cd1fcedba52/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20190110015856-aa033095749b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY=
golang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
golang.org/x/tools v0.0.0-20190322203728-c1a832b0ad89 h1:iWXXYN3edZ3Nd/7I6Rt1sXrWVmhF9bgVtlEJ7BbH124=
golang.org/x/tools v0.0.0-20190322203728-c1a832b0ad89/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
golang.org/x/tools v0.0.0-20190606050223-4d9ae51c2468/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
golang.org/x/tools v0.0.0-20190611222205-d73e1c7e250b h1:/mJ+GKieZA6hFDQGdWZrjj4AXPl5ylY+5HusG80roy0=
golang.org/x/tools v0.0.0-20190611222205-d73e1c7e250b/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
golang.org/x/tools v0.0.0-20190614205625-5aca471b1d59/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
golang.org/x/tools v0.0.0-20190703212419-2214986f1668 h1:3LJOYcj2ObWSZJXX21oGIIPv5SaOoi5JkzQTWnCXRhg=
golang.org/x/tools v0.0.0-20190703212419-2214986f1668/go.mod h1:jcCCGcm9btYwXyDqrUWc6MKQKKGJCWEQ3AfLSRIbEuI=
google.golang.org/api v0.3.1/go.mod h1:6wY9I6uQWHQ8EM57III9mq/AjF+i8G65rmVagqKMtkk=
google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
google.golang.org/appengine v1.4.0 h1:/wp5JvzpHIxhs/dumFmF7BXTf3Z+dd4uXta4kVyO508=

View file

@ -0,0 +1,189 @@
package dashboard
import (
"net/http"
"github.com/gin-gonic/gin"
"git.rwth-aachen.de/acs/public/villas/villasweb-backend-go/common"
"git.rwth-aachen.de/acs/public/villas/villasweb-backend-go/routes/scenario"
)
func RegisterDashboardEndpoints(r *gin.RouterGroup) {
r.GET("", getDashboards)
r.POST("", addDashboard)
r.PUT("/:dashboardID", updateDashboard)
r.GET("/:dashboardID", getDashboard)
r.DELETE("/:dashboardID", deleteDashboard)
}
// getDashboards godoc
// @Summary Get all dashboards of scenario
// @ID getDashboards
// @Produce json
// @Tags dashboards
// @Success 200 {array} common.DashboardResponse "Array of dashboards to which belong to scenario"
// @Failure 401 "Unauthorized Access"
// @Failure 403 "Access forbidden."
// @Failure 404 "Not found"
// @Failure 500 "Internal server error"
// @Param scenarioID query int true "Scenario ID"
// @Router /dashboards [get]
func getDashboards(c *gin.Context) {
ok, sim := scenario.CheckPermissions(c, common.Read, "query", -1)
if !ok {
return
}
db := common.GetDB()
var dab []common.Dashboard
err := db.Order("ID asc").Model(sim).Related(&dab, "Dashboards").Error
if common.ProvideErrorResponse(c, err) {
return
}
serializer := common.DashboardsSerializer{c, dab}
c.JSON(http.StatusOK, gin.H{
"dashboards": serializer.Response(),
})
}
// addDashboard godoc
// @Summary Add a dashboard to a scenario
// @ID addDashboard
// @Accept json
// @Produce json
// @Tags dashboards
// @Param inputDab body common.ResponseMsgDashboard true "Dashboard to be added incl. ID of Scenario"
// @Success 200 "OK."
// @Failure 401 "Unauthorized Access"
// @Failure 403 "Access forbidden."
// @Failure 404 "Not found"
// @Failure 500 "Internal server error"
// @Router /dashboards [post]
func addDashboard(c *gin.Context) {
var newDabData common.ResponseMsgDashboard
err := c.BindJSON(&newDabData)
if err != nil {
errormsg := "Bad request. Error binding form data to JSON: " + err.Error()
c.JSON(http.StatusBadRequest, gin.H{
"error": errormsg,
})
return
}
var newDab Dashboard
newDab.ID = newDabData.Dashboard.ID
newDab.Grid = newDabData.Dashboard.Grid
newDab.ScenarioID = newDabData.Dashboard.ScenarioID
newDab.Name = newDabData.Dashboard.Name
ok, _ := scenario.CheckPermissions(c, common.Create, "body", int(newDab.ScenarioID))
if !ok {
return
}
// add dashboard to DB and add association to scenario
err = newDab.addToScenario()
if common.ProvideErrorResponse(c, err) == false {
c.JSON(http.StatusOK, gin.H{
"message": "OK.",
})
}
}
// updateDashboard godoc
// @Summary Update a dashboard
// @ID updateDashboard
// @Tags dashboards
// @Accept json
// @Produce json
// @Param inputDab body common.ResponseMsgDashboard true "Dashboard to be updated"
// @Success 200 "OK."
// @Failure 401 "Unauthorized Access"
// @Failure 403 "Access forbidden."
// @Failure 404 "Not found"
// @Failure 500 "Internal server error"
// @Param dashboardID path int true "Dashboard ID"
// @Router /dashboards/{dashboardID} [put]
func updateDashboard(c *gin.Context) {
ok, d := CheckPermissions(c, common.Update, "path", -1)
if !ok {
return
}
var modifiedDab common.ResponseMsgDashboard
err := c.BindJSON(&modifiedDab)
if err != nil {
errormsg := "Bad request. Error binding form data to JSON: " + err.Error()
c.JSON(http.StatusBadRequest, gin.H{
"error": errormsg,
})
return
}
err = d.update(modifiedDab.Dashboard)
if common.ProvideErrorResponse(c, err) == false {
c.JSON(http.StatusOK, gin.H{
"message": "OK.",
})
}
}
// getDashboard godoc
// @Summary Get a dashboard
// @ID getDashboard
// @Tags dashboards
// @Produce json
// @Success 200 {object} common.DashboardResponse "Requested dashboard."
// @Failure 401 "Unauthorized Access"
// @Failure 403 "Access forbidden."
// @Failure 404 "Not found"
// @Failure 500 "Internal server error"
// @Param dashboardID path int true "Dashboard ID"
// @Router /dashboards/{dashboardID} [get]
func getDashboard(c *gin.Context) {
ok, dab := CheckPermissions(c, common.Read, "path", -1)
if !ok {
return
}
serializer := common.DashboardSerializer{c, dab.Dashboard}
c.JSON(http.StatusOK, gin.H{
"dashboard": serializer.Response(),
})
}
// deleteDashboard godoc
// @Summary Delete a dashboard
// @ID deleteDashboard
// @Tags dashboards
// @Produce json
// @Success 200 "OK."
// @Failure 401 "Unauthorized Access"
// @Failure 403 "Access forbidden."
// @Failure 404 "Not found"
// @Failure 500 "Internal server error"
// @Param dashboardID path int true "Dashboard ID"
// @Router /dashboards/{dashboardID} [delete]
func deleteDashboard(c *gin.Context) {
ok, dab := CheckPermissions(c, common.Delete, "path", -1)
if !ok {
return
}
err := dab.delete()
if common.ProvideErrorResponse(c, err) {
return
}
c.JSON(http.StatusOK, gin.H{
"message": "OK.",
})
}

View file

@ -0,0 +1,75 @@
package dashboard
import (
"fmt"
"git.rwth-aachen.de/acs/public/villas/villasweb-backend-go/common"
"git.rwth-aachen.de/acs/public/villas/villasweb-backend-go/routes/scenario"
)
type Dashboard struct {
common.Dashboard
}
func (v *Dashboard) save() error {
db := common.GetDB()
err := db.Create(v).Error
return err
}
func (d *Dashboard) ByID(id uint) error {
db := common.GetDB()
err := db.Find(d, id).Error
if err != nil {
return fmt.Errorf("Dashboard with id=%v does not exist", id)
}
return nil
}
func (d *Dashboard) addToScenario() error {
db := common.GetDB()
var sim scenario.Scenario
err := sim.ByID(d.ScenarioID)
if err != nil {
return err
}
// save dashboard to DB
err = d.save()
if err != nil {
return err
}
// associate dashboard with scenario
err = db.Model(&sim).Association("Dashboards").Append(d).Error
return err
}
func (d *Dashboard) update(modifiedDab common.DashboardResponse) error {
db := common.GetDB()
err := db.Model(d).Updates(map[string]interface{}{
"Name": modifiedDab.Name,
"Grid": modifiedDab.Grid,
}).Error
return err
}
func (d *Dashboard) delete() error {
db := common.GetDB()
var sim scenario.Scenario
err := sim.ByID(d.ScenarioID)
if err != nil {
return err
}
// 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
return err
}

View file

@ -0,0 +1,58 @@
package dashboard
import (
"fmt"
"git.rwth-aachen.de/acs/public/villas/villasweb-backend-go/routes/scenario"
"net/http"
"strconv"
"github.com/gin-gonic/gin"
"git.rwth-aachen.de/acs/public/villas/villasweb-backend-go/common"
)
func CheckPermissions(c *gin.Context, operation common.CRUD, dabIDSource string, dabIDBody int) (bool, Dashboard) {
var dab Dashboard
err := common.ValidateRole(c, common.ModelDashboard, operation)
if err != nil {
c.JSON(http.StatusUnprocessableEntity, "Access denied (role validation failed).")
return false, dab
}
var dabID int
if dabIDSource == "path" {
dabID, err = strconv.Atoi(c.Param("dashboardID"))
if err != nil {
errormsg := fmt.Sprintf("Bad request. No or incorrect format of dashboardID path parameter")
c.JSON(http.StatusBadRequest, gin.H{
"error": errormsg,
})
return false, dab
}
} else if dabIDSource == "query" {
dabID, err = strconv.Atoi(c.Request.URL.Query().Get("dashboardID"))
if err != nil {
errormsg := fmt.Sprintf("Bad request. No or incorrect format of dashboardID query parameter")
c.JSON(http.StatusBadRequest, gin.H{
"error": errormsg,
})
return false, dab
}
} else if dabIDSource == "body" {
dabID = dabIDBody
}
err = dab.ByID(uint(dabID))
if common.ProvideErrorResponse(c, err) {
return false, dab
}
ok, _ := scenario.CheckPermissions(c, operation, "body", int(dab.ScenarioID))
if !ok {
return false, dab
}
return true, dab
}

View file

@ -0,0 +1,65 @@
package dashboard
import (
"encoding/json"
"testing"
"github.com/gin-gonic/gin"
"git.rwth-aachen.de/acs/public/villas/villasweb-backend-go/common"
"git.rwth-aachen.de/acs/public/villas/villasweb-backend-go/routes/user"
)
// Test /dashboards endpoints
func TestEndpoints(t *testing.T) {
var token string
var myDashboards = []common.DashboardResponse{common.DashboardA_response, common.DashboardB_response}
var msgDashboards = common.ResponseMsgDashboards{Dashboards: myDashboards}
var msgDab = common.ResponseMsgDashboard{Dashboard: common.DashboardC_response}
var msgDabupdated = common.ResponseMsgDashboard{Dashboard: common.DashboardCUpdated_response}
db := common.DummyInitDB()
defer db.Close()
common.DummyPopulateDB(db)
router := gin.Default()
api := router.Group("/api")
// All endpoints require authentication except when someone wants to
// login (POST /authenticate)
user.VisitorAuthenticate(api.Group("/authenticate"))
api.Use(user.Authentication(true))
RegisterDashboardEndpoints(api.Group("/dashboards"))
credjson, _ := json.Marshal(common.CredUser)
msgOKjson, _ := json.Marshal(common.MsgOK)
msgDashboardsjson, _ := json.Marshal(msgDashboards)
msgDabjson, _ := json.Marshal(msgDab)
msgDabupdatedjson, _ := json.Marshal(msgDabupdated)
token = common.AuthenticateForTest(t, router, "/api/authenticate", "POST", credjson, 200)
// test GET dashboards
common.TestEndpoint(t, router, token, "/api/dashboards?scenarioID=1", "GET", nil, 200, msgDashboardsjson)
// test POST dashboards
common.TestEndpoint(t, router, token, "/api/dashboards", "POST", msgDabjson, 200, msgOKjson)
// test GET dashboards/:dashboardID to check if previous POST worked correctly
common.TestEndpoint(t, router, token, "/api/dashboards/3", "GET", nil, 200, msgDabjson)
// test PUT dashboards/:dashboardID
common.TestEndpoint(t, router, token, "/api/dashboards/3", "PUT", msgDabupdatedjson, 200, msgOKjson)
common.TestEndpoint(t, router, token, "/api/dashboards/3", "GET", nil, 200, msgDabupdatedjson)
// test DELETE dashboards/:dashboardID
common.TestEndpoint(t, router, token, "/api/dashboards/3", "DELETE", nil, 200, msgOKjson)
common.TestEndpoint(t, router, token, "/api/dashboards?scenarioID=1", "GET", nil, 200, msgDashboardsjson)
// TODO add testing for other return codes
}

View file

@ -8,6 +8,8 @@ import (
"github.com/gin-gonic/gin"
"git.rwth-aachen.de/acs/public/villas/villasweb-backend-go/common"
"git.rwth-aachen.de/acs/public/villas/villasweb-backend-go/routes/simulationmodel"
"git.rwth-aachen.de/acs/public/villas/villasweb-backend-go/routes/widget"
)
func RegisterFileEndpoints(r *gin.RouterGroup) {
@ -28,8 +30,8 @@ func RegisterFileEndpoints(r *gin.RouterGroup) {
// @Failure 403 "Access forbidden."
// @Failure 404 "Not found"
// @Failure 500 "Internal server error"
// @Param originType query string true "Set to model for files of model, set to widget for files of widget"
// @Param originID query int true "ID of either model or widget of which files are requested"
// @Param objectType query string true "Set to model for files of model, set to widget for files of widget"
// @Param objectID query int true "ID of either model or widget of which files are requested"
// @Router /files [get]
func getFiles(c *gin.Context) {
@ -51,15 +53,33 @@ func getFiles(c *gin.Context) {
return
}
//Check access
var ok bool
var m simulationmodel.SimulationModel
var w widget.Widget
if objectType == "model" {
ok, m = simulationmodel.CheckPermissions(c, common.Read, "body", objectID)
if !ok {
return
}
} else {
ok, w = widget.CheckPermissions(c, common.Read, objectID)
if !ok {
return
}
}
// get meta data of files
db := common.GetDB()
var files []common.File
if objectType == "model" {
err = db.Where("ModelID", objectID).Find(&files).Error
err = db.Order("ID asc").Model(&m).Related(&files, "Files").Error
if common.ProvideErrorResponse(c, err) {
return
}
} else {
err = db.Where("WidgetID", objectID).Find(&files).Error
err = db.Order("ID asc").Model(&w).Related(&files, "Files").Error
if common.ProvideErrorResponse(c, err) {
return
}
@ -93,15 +113,6 @@ func getFiles(c *gin.Context) {
// @Param objectID query int true "ID of either model or widget of which files are requested"
// @Router /files [post]
func addFile(c *gin.Context) {
// Extract file from PUT request form
file_header, err := c.FormFile("file")
if err != nil {
errormsg := fmt.Sprintf("Bad request. Get form error: %s", err.Error())
c.JSON(http.StatusBadRequest, gin.H{
"error": errormsg,
})
return
}
objectType := c.Request.URL.Query().Get("objectType")
if objectType != "model" && objectType != "widget" {
@ -121,6 +132,30 @@ func addFile(c *gin.Context) {
return
}
// Check access
var ok bool
if objectType == "model" {
ok, _ = simulationmodel.CheckPermissions(c, common.Create, "body", objectID)
if !ok {
return
}
} else {
ok, _ = widget.CheckPermissions(c, common.Create, objectID)
if !ok {
return
}
}
// Extract file from POST request form
file_header, err := c.FormFile("file")
if err != nil {
errormsg := fmt.Sprintf("Bad request. Get form error: %s", err.Error())
c.JSON(http.StatusBadRequest, gin.H{
"error": errormsg,
})
return
}
var newFile File
err = newFile.register(file_header, objectType, uint(objectID))
if common.ProvideErrorResponse(c, err) == false {
@ -149,22 +184,16 @@ func addFile(c *gin.Context) {
// @Router /files/{fileID} [get]
func getFile(c *gin.Context) {
fileID, err := common.GetFileID(c)
if err != nil {
// check access
ok, f := checkPermissions(c, common.Read)
if !ok {
return
}
var f File
err = f.byID(uint(fileID))
err := f.download(c)
if common.ProvideErrorResponse(c, err) {
return
}
f.download(c)
c.JSON(http.StatusOK, gin.H{
"message": "OK.",
})
}
// updateFile godoc
@ -188,6 +217,12 @@ func getFile(c *gin.Context) {
// @Router /files/{fileID} [put]
func updateFile(c *gin.Context) {
// check access
ok, f := checkPermissions(c, common.Update)
if !ok {
return
}
// Extract file from PUT request form
err := c.Request.ParseForm()
if err != nil {
@ -207,17 +242,6 @@ func updateFile(c *gin.Context) {
return
}
fileID, err := common.GetFileID(c)
if err != nil {
return
}
var f File
err = f.byID(uint(fileID))
if common.ProvideErrorResponse(c, err) {
return
}
err = f.update(file_header)
if common.ProvideErrorResponse(c, err) == false {
c.JSON(http.StatusOK, gin.H{
@ -239,7 +263,17 @@ func updateFile(c *gin.Context) {
// @Param fileID path int true "ID of the file to update"
// @Router /files/{fileID} [delete]
func deleteFile(c *gin.Context) {
c.JSON(http.StatusOK, gin.H{
"message": "Not implemented.",
})
// check access
ok, f := checkPermissions(c, common.Delete)
if !ok {
return
}
err := f.delete()
if common.ProvideErrorResponse(c, err) == false {
c.JSON(http.StatusOK, gin.H{
"message": "OK.",
})
}
}

View file

@ -2,13 +2,12 @@ package file
import (
"fmt"
"io"
"github.com/gin-gonic/gin"
"io/ioutil"
"mime/multipart"
"os"
"path/filepath"
"strconv"
"github.com/gin-gonic/gin"
"time"
"git.rwth-aachen.de/acs/public/villas/villasweb-backend-go/common"
"git.rwth-aachen.de/acs/public/villas/villasweb-backend-go/routes/simulationmodel"
@ -38,29 +37,40 @@ func (f *File) byID(id uint) error {
}
func (f *File) save() error {
// get last modify time of target file
fileinfo, err := os.Stat(f.Path)
if err != nil {
return fmt.Errorf("error stat on file: %s", err.Error())
}
f.Date = fileinfo.ModTime()
f.ImageWidth = 0
f.ImageHeight = 0
db := common.GetDB()
err = db.Create(f).Error
err := db.Create(f).Error
return err
}
func (f *File) download(c *gin.Context) error {
err := ioutil.WriteFile(f.Name, f.FileData, 0644)
if err != nil {
return fmt.Errorf("file could not be temporarily created on server disk: %s", err.Error())
}
defer os.Remove(f.Name)
//Seems this headers needed for some browsers (for example without this headers Chrome will download files as txt)
c.Header("Content-Description", "File Transfer")
c.Header("Content-Transfer-Encoding", "binary")
c.Header("Content-Disposition", "attachment; filename="+f.Name)
//c.Header("Content-Type", contentType)
c.File(f.Name)
return nil
}
func (f *File) register(fileHeader *multipart.FileHeader, objectType string, objectID uint) error {
// Obtain properties of file
f.Type = fileHeader.Header.Get("Content-Type")
f.Name = filepath.Base(fileHeader.Filename)
f.Path = filepath.Join(getFolderName(objectType, objectID), f.Name)
//f.Path = filepath.Join(getFolderName(objectType, objectID), f.Name)
f.Size = uint(fileHeader.Size)
f.Date = time.Now().String()
f.ImageWidth = 0 // TODO: do we need this?
f.ImageHeight = 0 // TODO: do we need this?
var m simulationmodel.SimulationModel
var w widget.Widget
@ -68,12 +78,16 @@ func (f *File) register(fileHeader *multipart.FileHeader, objectType string, obj
if objectType == "model" {
// check if model exists
err = m.ByID(objectID)
f.WidgetID = 0
f.SimulationModelID = objectID
if err != nil {
return err
}
} else {
// check if widget exists
f.WidgetID = objectID
f.SimulationModelID = 0
err = w.ByID(uint(objectID))
if err != nil {
return err
@ -81,12 +95,15 @@ func (f *File) register(fileHeader *multipart.FileHeader, objectType string, obj
}
// Save file to local disc (NOT DB!)
err = f.modifyFileOnDisc(fileHeader, true)
// set file data
fileContent, err := fileHeader.Open()
if err != nil {
return fmt.Errorf("File could not be saved/ modified on disk: ", err.Error())
return err
}
f.FileData, err = ioutil.ReadAll(fileContent)
defer fileContent.Close()
// Add File object with parameters to DB
err = f.save()
if err != nil {
@ -95,12 +112,14 @@ func (f *File) register(fileHeader *multipart.FileHeader, objectType string, obj
// Create association to model or widget
if objectType == "model" {
err = f.addToModel(m)
db := common.GetDB()
err := db.Model(&m).Association("Files").Append(f).Error
if err != nil {
return err
}
} else {
err = f.addToWidget(w)
db := common.GetDB()
err := db.Model(&w).Association("Files").Append(f).Error
if err != nil {
return err
}
@ -110,99 +129,54 @@ func (f *File) register(fileHeader *multipart.FileHeader, objectType string, obj
}
func (f *File) update(fileHeader *multipart.FileHeader) error {
err := f.modifyFileOnDisc(fileHeader, false)
// set file data
fileContent, err := fileHeader.Open()
if err != nil {
return err
}
fileData, err := ioutil.ReadAll(fileContent)
fmt.Println("File content: ", string(fileData))
defer fileContent.Close()
db := common.GetDB()
err = db.Model(f).Update("Size", fileHeader.Size).Error
err = db.Model(f).Updates(map[string]interface{}{"Size": fileHeader.Size,
"FileData": fileData,
"Date": time.Now().String()}).Error
return err
}
func (f *File) modifyFileOnDisc(fileHeader *multipart.FileHeader, createFile bool) error {
func (f *File) delete() error {
//filesavepath := filepath.Join(foldername, filename)
var err error
db := common.GetDB()
if createFile {
// Ensure folder with name foldername exists
err = os.MkdirAll(f.Path, os.ModePerm)
} else {
// test if file exists
_, err = os.Stat(f.Path)
}
if err != nil {
return err
}
var open_options int
if createFile {
// create file it not exists, file MUST not exist
open_options = os.O_RDWR | os.O_CREATE | os.O_EXCL
} else {
open_options = os.O_RDWR
}
fileTarget, err := os.OpenFile(f.Path, open_options, 0666)
if err != nil {
return err
}
defer fileTarget.Close()
// Save file to target path
uploadedFile, err := fileHeader.Open()
if err != nil {
return err
}
defer uploadedFile.Close()
var uploadContent = make([]byte, f.Size)
for {
n, err := uploadedFile.Read(uploadContent)
if err != nil && err != io.EOF {
return err
}
if n == 0 {
break
}
_, err = fileTarget.Write(uploadContent[:n])
if f.WidgetID > 0 {
// remove association between file and widget
var w widget.Widget
err := w.ByID(f.WidgetID)
if err != nil {
return err
}
err = db.Model(&w).Association("Files").Delete(f).Error
if err != nil {
return err
}
} else {
// remove association between file and simulation model
var m simulationmodel.SimulationModel
err := m.ByID(f.SimulationModelID)
if err != nil {
return err
}
err = db.Model(&m).Association("Files").Delete(f).Error
if err != nil {
return err
}
}
// delete file from DB
err := db.Delete(f).Error
return err
}
func (f *File) download(c *gin.Context) {
//Seems this headers needed for some browsers (for example without this headers Chrome will download files as txt)
c.Header("Content-Description", "File Transfer")
c.Header("Content-Transfer-Encoding", "binary")
c.Header("Content-Disposition", "attachment; filename="+f.Name)
//c.Header("Content-Type", contentType)
c.File(f.Path)
}
func (f *File) addToModel(model simulationmodel.SimulationModel) error {
db := common.GetDB()
err := db.Model(&model).Association("Files").Append(f).Error
return err
}
func (f *File) addToWidget(widget widget.Widget) error {
db := common.GetDB()
err := db.Model(&widget).Association("Files").Append(f).Error
return err
}
func getFolderName(objectType string, objectID uint) string {
base_foldername := "files/"
foldername := base_foldername + objectType + "_" + strconv.Itoa(int(objectID)) + "/"
return foldername
}

View file

@ -0,0 +1,50 @@
package file
import (
"fmt"
"git.rwth-aachen.de/acs/public/villas/villasweb-backend-go/common"
"git.rwth-aachen.de/acs/public/villas/villasweb-backend-go/routes/simulationmodel"
"git.rwth-aachen.de/acs/public/villas/villasweb-backend-go/routes/widget"
"github.com/gin-gonic/gin"
"net/http"
"strconv"
)
func checkPermissions(c *gin.Context, operation common.CRUD) (bool, File) {
var f File
err := common.ValidateRole(c, common.ModelFile, operation)
if err != nil {
c.JSON(http.StatusUnprocessableEntity, "Access denied (role validation failed).")
return false, f
}
fileID, err := strconv.Atoi(c.Param("fileID"))
if err != nil {
errormsg := fmt.Sprintf("Bad request. No or incorrect format of fileID path parameter")
c.JSON(http.StatusBadRequest, gin.H{
"error": errormsg,
})
return false, f
}
err = f.byID(uint(fileID))
if common.ProvideErrorResponse(c, err) {
return false, f
}
if f.SimulationModelID > 0 {
ok, _ := simulationmodel.CheckPermissions(c, operation, "body", int(f.SimulationModelID))
if !ok {
return false, f
}
} else {
ok, _ := widget.CheckPermissions(c, operation, int(f.WidgetID))
if !ok {
return false, f
}
}
return true, f
}

201
routes/file/file_test.go Normal file
View file

@ -0,0 +1,201 @@
package file
import (
"bytes"
"encoding/json"
"fmt"
"git.rwth-aachen.de/acs/public/villas/villasweb-backend-go/common"
"git.rwth-aachen.de/acs/public/villas/villasweb-backend-go/routes/user"
"github.com/gin-gonic/gin"
"github.com/stretchr/testify/assert"
"io"
"io/ioutil"
"mime/multipart"
"net/http"
"net/http/httptest"
"os"
"testing"
)
// Test /files endpoints
func TestSignalEndpoints(t *testing.T) {
var token string
var filecontent = "This is my testfile"
var filecontent_update = "This is my updated testfile with a dot at the end."
var filename = "testfile.txt"
var filename_update = "testfileupdate.txt"
var myFiles = []common.FileResponse{common.FileA_response, common.FileB_response}
var msgFiles = common.ResponseMsgFiles{Files: myFiles}
db := common.DummyInitDB()
defer db.Close()
common.DummyPopulateDB(db)
// create a testfile in local folder
c1 := []byte(filecontent)
c2 := []byte(filecontent_update)
err := ioutil.WriteFile(filename, c1, 0644)
if err != nil {
panic(err)
}
err = ioutil.WriteFile(filename_update, c2, 0644)
if err != nil {
panic(err)
}
router := gin.Default()
api := router.Group("/api")
// All endpoints require authentication except when someone wants to
// login (POST /authenticate)
user.VisitorAuthenticate(api.Group("/authenticate"))
api.Use(user.Authentication(true))
RegisterFileEndpoints(api.Group("/files"))
credjson, err := json.Marshal(common.CredUser)
if err != nil {
panic(err)
}
msgOKjson, err := json.Marshal(common.MsgOK)
if err != nil {
panic(err)
}
msgFilesjson, err := json.Marshal(msgFiles)
if err != nil {
panic(err)
}
token = common.AuthenticateForTest(t, router, "/api/authenticate", "POST", credjson, 200)
// test GET files
common.TestEndpoint(t, router, token, "/api/files?objectID=1&objectType=widget", "GET", nil, 200, msgFilesjson)
// test POST files
bodyBuf := &bytes.Buffer{}
bodyWriter := multipart.NewWriter(bodyBuf)
fileWriter, err := bodyWriter.CreateFormFile("file", "testuploadfile.txt")
if err != nil {
fmt.Println("error writing to buffer")
panic(err)
}
// open file handle
fh, err := os.Open(filename)
if err != nil {
fmt.Println("error opening file")
panic(err)
}
defer fh.Close()
// io copy
_, err = io.Copy(fileWriter, fh)
if err != nil {
fmt.Println("error on IO copy")
panic(err)
}
contentType := bodyWriter.FormDataContentType()
bodyWriter.Close()
w := httptest.NewRecorder()
req, err := http.NewRequest("POST", "/api/files?objectID=1&objectType=widget", bodyBuf)
req.Header.Add("Authorization", "Bearer "+token)
req.Header.Set("Content-Type", contentType)
if err != nil {
fmt.Println("error creating post request")
panic(err)
}
router.ServeHTTP(w, req)
assert.Equal(t, 200, w.Code)
fmt.Println(w.Body.String())
assert.Equal(t, string(msgOKjson), w.Body.String())
// test GET files/:fileID
w2 := httptest.NewRecorder()
req2, _ := http.NewRequest("GET", "/api/files/5", nil)
req2.Header.Add("Authorization", "Bearer "+token)
router.ServeHTTP(w2, req2)
assert.Equal(t, 200, w2.Code)
fmt.Println(w2.Body.String())
assert.Equal(t, filecontent, w2.Body.String())
//common.TestEndpoint(t, router, token, "/api/files?objectID=1&objectType=widget", "GET", nil, 200, string(msgFilesjson))
// test PUT files/:fileID
bodyBuf_update := &bytes.Buffer{}
bodyWriter_update := multipart.NewWriter(bodyBuf_update)
fileWriter_update, err := bodyWriter_update.CreateFormFile("file", "testuploadfile.txt")
if err != nil {
fmt.Println("error writing to buffer")
panic(err)
}
// open file handle
fh_update, err := os.Open(filename_update)
if err != nil {
fmt.Println("error opening file")
panic(err)
}
defer fh_update.Close()
// io copy
_, err = io.Copy(fileWriter_update, fh_update)
if err != nil {
fmt.Println("error on IO copy")
panic(err)
}
contentType_update := bodyWriter_update.FormDataContentType()
bodyWriter_update.Close()
w_update := httptest.NewRecorder()
req_update, err := http.NewRequest("PUT", "/api/files/5", bodyBuf_update)
req_update.Header.Add("Authorization", "Bearer "+token)
req_update.Header.Set("Content-Type", contentType_update)
if err != nil {
fmt.Println("error creating post request")
panic(err)
}
router.ServeHTTP(w_update, req_update)
assert.Equal(t, 200, w_update.Code)
fmt.Println(w_update.Body.String())
assert.Equal(t, string(msgOKjson), w_update.Body.String())
// Test GET on updated file content
w3 := httptest.NewRecorder()
req3, _ := http.NewRequest("GET", "/api/files/5", nil)
req3.Header.Add("Authorization", "Bearer "+token)
router.ServeHTTP(w3, req3)
assert.Equal(t, 200, w3.Code)
fmt.Println(w3.Body.String())
assert.Equal(t, filecontent_update, w3.Body.String())
// test DELETE files/:fileID
common.TestEndpoint(t, router, token, "/api/files/5", "DELETE", nil, 200, msgOKjson)
common.TestEndpoint(t, router, token, "/api/files?objectID=1&objectType=widget", "GET", nil, 200, msgFilesjson)
// TODO add testing for other return codes
// clean up temporary file
err = os.Remove(filename)
if err != nil {
panic(err)
}
err = os.Remove(filename_update)
if err != nil {
panic(err)
}
}

View file

@ -0,0 +1,322 @@
package scenario
import (
"net/http"
"github.com/gin-gonic/gin"
"git.rwth-aachen.de/acs/public/villas/villasweb-backend-go/common"
"git.rwth-aachen.de/acs/public/villas/villasweb-backend-go/routes/user"
)
func RegisterScenarioEndpoints(r *gin.RouterGroup) {
r.GET("", getScenarios)
r.POST("", addScenario)
r.PUT("/:scenarioID", updateScenario)
r.GET("/:scenarioID", getScenario)
r.DELETE("/:scenarioID", deleteScenario)
r.GET("/:scenarioID/users", getUsersOfScenario)
r.PUT("/:scenarioID/user", addUserToScenario)
r.DELETE("/:scenarioID/user", deleteUserFromScenario)
}
// getScenarios godoc
// @Summary Get all scenarios
// @ID getScenarios
// @Produce json
// @Tags scenarios
// @Success 200 {array} common.ScenarioResponse "Array of scenarios to which user has access"
// @Failure 401 "Unauthorized Access"
// @Failure 403 "Access forbidden."
// @Failure 404 "Not found"
// @Failure 500 "Internal server error"
// @Router /scenarios [get]
func getScenarios(c *gin.Context) {
ok, _ := CheckPermissions(c, common.Read, "none", -1)
if !ok {
return
}
// ATTENTION: do not use c.GetInt (common.UserIDCtx) since user_id is of type uint and not int
userID, _ := c.Get(common.UserIDCtx)
userRole, _ := c.Get(common.UserRoleCtx)
var u user.User
err := u.ByID(userID.(uint))
if common.ProvideErrorResponse(c, err) {
return
}
// get all scenarios for the user who issues the request
db := common.GetDB()
var scenarios []common.Scenario
if userRole == "Admin" { // Admin can see all scenarios
err = db.Order("ID asc").Find(&scenarios).Error
if common.ProvideErrorResponse(c, err) {
return
}
} else { // User or Guest roles see only their scenarios
err = db.Order("ID asc").Model(&u).Related(&scenarios, "Scenarios").Error
if common.ProvideErrorResponse(c, err) {
return
}
}
serializer := common.ScenariosSerializer{c, scenarios}
c.JSON(http.StatusOK, gin.H{
"scenarios": serializer.Response(),
})
}
// addScenario godoc
// @Summary Add a scenario
// @ID addScenario
// @Accept json
// @Produce json
// @Tags scenarios
// @Param inputScenario body common.ResponseMsgScenario true "Scenario to be added"
// @Success 200 "OK."
// @Failure 401 "Unauthorized Access"
// @Failure 403 "Access forbidden."
// @Failure 404 "Not found"
// @Failure 500 "Internal server error"
// @Router /scenarios [post]
func addScenario(c *gin.Context) {
ok, _ := CheckPermissions(c, common.Create, "none", -1)
if !ok {
return
}
userID, _ := c.Get(common.UserIDCtx)
var u user.User
err := u.ByID(userID.(uint))
if common.ProvideErrorResponse(c, err) {
return
}
var newScenarioData common.ResponseMsgScenario
err = c.BindJSON(&newScenarioData)
if err != nil {
errormsg := "Bad request. Error binding form data to JSON: " + err.Error()
c.JSON(http.StatusBadRequest, gin.H{
"error": errormsg,
})
return
}
var newScenario Scenario
newScenario.ID = newScenarioData.Scenario.ID
newScenario.StartParameters = newScenarioData.Scenario.StartParameters
newScenario.Running = newScenarioData.Scenario.Running
newScenario.Name = newScenarioData.Scenario.Name
// save new scenario to DB
err = newScenario.save()
if common.ProvideErrorResponse(c, err) {
return
}
// add user to new scenario
err = newScenario.addUser(&(u.User))
if common.ProvideErrorResponse(c, err) == false {
c.JSON(http.StatusOK, gin.H{
"message": "OK.",
})
}
}
// updateScenario godoc
// @Summary Update a scenario
// @ID updateScenario
// @Tags scenarios
// @Accept json
// @Produce json
// @Param inputScenario body common.ResponseMsgScenario true "Scenario to be updated"
// @Success 200 "OK."
// @Failure 401 "Unauthorized Access"
// @Failure 403 "Access forbidden."
// @Failure 404 "Not found"
// @Failure 500 "Internal server error"
// @Param scenarioID path int true "Scenario ID"
// @Router /scenarios/{scenarioID} [put]
func updateScenario(c *gin.Context) {
ok, so := CheckPermissions(c, common.Update, "path", -1)
if !ok {
return
}
var modifiedScenarioData common.ResponseMsgScenario
err := c.BindJSON(&modifiedScenarioData)
if err != nil {
errormsg := "Bad request. Error binding form data to JSON: " + err.Error()
c.JSON(http.StatusBadRequest, gin.H{
"error": errormsg,
})
return
}
err = so.update(modifiedScenarioData.Scenario)
if common.ProvideErrorResponse(c, err) == false {
c.JSON(http.StatusOK, gin.H{
"message": "OK.",
})
}
}
// getScenario godoc
// @Summary Get scenario
// @ID getScenario
// @Produce json
// @Tags scenarios
// @Success 200 {object} common.ScenarioResponse "Scenario requested by user"
// @Failure 401 "Unauthorized Access"
// @Failure 403 "Access forbidden."
// @Failure 404 "Not found"
// @Failure 500 "Internal server error"
// @Param scenarioID path int true "Scenario ID"
// @Router /scenarios/{scenarioID} [get]
func getScenario(c *gin.Context) {
ok, so := CheckPermissions(c, common.Read, "path", -1)
if !ok {
return
}
serializer := common.ScenarioSerializer{c, so.Scenario}
c.JSON(http.StatusOK, gin.H{
"scenario": serializer.Response(),
})
}
// deleteScenario godoc
// @Summary Delete a scenario
// @ID deleteScenario
// @Tags scenarios
// @Produce json
// @Success 200 "OK."
// @Failure 401 "Unauthorized Access"
// @Failure 403 "Access forbidden."
// @Failure 404 "Not found"
// @Failure 500 "Internal server error"
// @Param scenarioID path int true "Scenario ID"
// @Router /scenarios/{scenarioID} [delete]
func deleteScenario(c *gin.Context) {
ok, so := CheckPermissions(c, common.Delete, "path", -1)
if !ok {
return
}
err := so.delete()
if common.ProvideErrorResponse(c, err) == false {
c.JSON(http.StatusOK, gin.H{
"message": "OK.",
})
}
}
// getUsersOfScenario godoc
// @Summary Get users of a scenario
// @ID getUsersOfScenario
// @Produce json
// @Tags scenarios
// @Success 200 {array} common.UserResponse "Array of users that have access to the scenario"
// @Failure 401 "Unauthorized Access"
// @Failure 403 "Access forbidden."
// @Failure 404 "Not found"
// @Failure 500 "Internal server error"
// @Param scenarioID path int true "Scenario ID"
// @Router /scenarios/{scenarioID}/users/ [get]
func getUsersOfScenario(c *gin.Context) {
ok, so := CheckPermissions(c, common.Read, "path", -1)
if !ok {
return
}
// Find all users of scenario
allUsers, _, err := so.getUsers()
if common.ProvideErrorResponse(c, err) {
return
}
serializer := common.UsersSerializer{c, allUsers}
c.JSON(http.StatusOK, gin.H{
"users": serializer.Response(false),
})
}
// addUserToScenario godoc
// @Summary Add a user to a a scenario
// @ID addUserToScenario
// @Tags scenarios
// @Produce json
// @Success 200 "OK."
// @Failure 401 "Unauthorized Access"
// @Failure 403 "Access forbidden."
// @Failure 404 "Not found"
// @Failure 500 "Internal server error"
// @Param scenarioID path int true "Scenario ID"
// @Param username query string true "User name"
// @Router /scenarios/{scenarioID}/user [put]
func addUserToScenario(c *gin.Context) {
ok, so := CheckPermissions(c, common.Update, "path", -1)
if !ok {
return
}
username := c.Request.URL.Query().Get("username")
var u user.User
err := u.ByUsername(username)
if common.ProvideErrorResponse(c, err) {
return
}
err = so.addUser(&(u.User))
if common.ProvideErrorResponse(c, err) {
return
}
c.JSON(http.StatusOK, gin.H{
"message": "OK.",
})
}
// deleteUserFromScenario godoc
// @Summary Delete a user from a scenario
// @ID deleteUserFromScenario
// @Tags scenarios
// @Produce json
// @Success 200 "OK."
// @Failure 401 "Unauthorized Access"
// @Failure 403 "Access forbidden."
// @Failure 404 "Not found"
// @Failure 500 "Internal server error"
// @Param scenarioID path int true "Scenario ID"
// @Param username query string true "User name"
// @Router /scenarios/{scenarioID}/user [delete]
func deleteUserFromScenario(c *gin.Context) {
ok, so := CheckPermissions(c, common.Update, "path", -1)
if !ok {
return
}
username := c.Request.URL.Query().Get("username")
err := so.deleteUser(username)
if common.ProvideErrorResponse(c, err) {
return
}
c.JSON(http.StatusOK, gin.H{
"message": "OK.",
})
}

View file

@ -1,4 +1,4 @@
package simulation
package scenario
import (
"fmt"
@ -7,46 +7,46 @@ import (
"git.rwth-aachen.de/acs/public/villas/villasweb-backend-go/routes/user"
)
type Simulation struct {
common.Simulation
type Scenario struct {
common.Scenario
}
func (s *Simulation) ByID(id uint) error {
func (s *Scenario) ByID(id uint) error {
db := common.GetDB()
err := db.Find(s, id).Error
if err != nil {
return fmt.Errorf("simulation with id=%v does not exist", id)
return fmt.Errorf("scenario with id=%v does not exist", id)
}
return nil
}
func (s *Simulation) getUsers() ([]common.User, int, error) {
func (s *Scenario) getUsers() ([]common.User, int, error) {
db := common.GetDB()
var users []common.User
err := db.Order("ID asc").Model(s).Related(&users, "Users").Error
return users, len(users), err
}
func (s *Simulation) save() error {
func (s *Scenario) save() error {
db := common.GetDB()
err := db.Create(s).Error
return err
}
func (s *Simulation) update(modifiedSimulation Simulation) error {
func (s *Scenario) update(modifiedScenario common.ScenarioResponse) error {
db := common.GetDB()
err := db.Model(s).Update(modifiedSimulation).Error
err := db.Model(s).Update(modifiedScenario).Error
return err
}
func (s *Simulation) addUser(u *common.User) error {
func (s *Scenario) addUser(u *common.User) error {
db := common.GetDB()
err := db.Model(s).Association("Users").Append(u).Error
return err
}
func (s *Simulation) deleteUser(username string) error {
func (s *Scenario) deleteUser(username string) error {
db := common.GetDB()
var deletedUser user.User
@ -58,27 +58,27 @@ func (s *Simulation) deleteUser(username string) error {
no_users := db.Model(s).Association("Users").Count()
if no_users > 1 {
// remove user from simulation
// remove user from scenario
err = db.Model(s).Association("Users").Delete(&deletedUser.User).Error
if err != nil {
return err
}
// remove simulation from user
err = db.Model(&deletedUser.User).Association("Simulations").Delete(s).Error
// remove scenario from user
err = db.Model(&deletedUser.User).Association("Scenarios").Delete(s).Error
if err != nil {
return err
}
} else {
return fmt.Errorf("cannot delete last user from simulation without deleting simulation itself, doing nothing")
return fmt.Errorf("cannot delete last user from scenario without deleting scenario itself, doing nothing")
}
return nil
}
func (s *Simulation) delete() error {
func (s *Scenario) delete() error {
db := common.GetDB()
// delete simulation from all users and vice versa
// delete scenario from all users and vice versa
users, no_users, err := s.getUsers()
if err != nil {
@ -87,23 +87,23 @@ func (s *Simulation) delete() error {
if no_users > 0 {
for _, u := range users {
// remove user from simulation
// remove user from scenario
err = db.Model(s).Association("Users").Delete(&u).Error
if err != nil {
return err
}
// remove simulation from user
err = db.Model(&u).Association("Simulations").Delete(s).Error
// remove scenario from user
err = db.Model(&u).Association("Scenarios").Delete(s).Error
if err != nil {
return err
}
}
}
// Simulation is not deleted from DB, only associations with users are removed
// Simulation remains "dangling" in DB
// Scenario is not deleted from DB, only associations with users are removed
// Scenario remains "dangling" in DB
// Delete simulation
// Delete scenario
//err = db.Delete(s).Error
//if err != nil {
// return err
@ -112,7 +112,7 @@ func (s *Simulation) delete() error {
return nil
}
func (s *Simulation) checkAccess(userID uint, userRole string) bool {
func (s *Scenario) checkAccess(userID uint, userRole string) bool {
if userRole == "Admin" {
return true

View file

@ -1,4 +1,4 @@
package simulation
package scenario
import (
"fmt"
@ -10,62 +10,62 @@ import (
"git.rwth-aachen.de/acs/public/villas/villasweb-backend-go/common"
)
func CheckPermissions(c *gin.Context, modelname common.ModelName, operation common.CRUD, simIDSource string, simIDBody int) (bool, Simulation) {
func CheckPermissions(c *gin.Context, operation common.CRUD, simIDSource string, simIDBody int) (bool, Scenario) {
var sim Simulation
var so Scenario
err := common.ValidateRole(c, modelname, operation)
err := common.ValidateRole(c, common.ModelScenario, operation)
if err != nil {
c.JSON(http.StatusUnprocessableEntity, "Access denied (role validation failed).")
return false, sim
return false, so
}
if operation == common.Create || (operation == common.Read && simIDSource == "none") {
return true, sim
return true, so
}
var simID int
if simIDSource == "path" {
simID, err = strconv.Atoi(c.Param("simulationID"))
simID, err = strconv.Atoi(c.Param("scenarioID"))
if err != nil {
errormsg := fmt.Sprintf("Bad request. No or incorrect format of simulationID path parameter")
errormsg := fmt.Sprintf("Bad request. No or incorrect format of scenarioID path parameter")
c.JSON(http.StatusBadRequest, gin.H{
"error": errormsg,
})
return false, sim
return false, so
}
} else if simIDSource == "query" {
simID, err = strconv.Atoi(c.Request.URL.Query().Get("simulationID"))
simID, err = strconv.Atoi(c.Request.URL.Query().Get("scenarioID"))
if err != nil {
errormsg := fmt.Sprintf("Bad request. No or incorrect format of simulationID query parameter")
errormsg := fmt.Sprintf("Bad request. No or incorrect format of scenarioID query parameter")
c.JSON(http.StatusBadRequest, gin.H{
"error": errormsg,
})
return false, sim
return false, so
}
} else if simIDSource == "body" {
simID = simIDBody
} else {
errormsg := fmt.Sprintf("Bad request. The following source of your simulation ID is not valid: %s", simIDSource)
errormsg := fmt.Sprintf("Bad request. The following source of your scenario ID is not valid: %s", simIDSource)
c.JSON(http.StatusBadRequest, gin.H{
"error": errormsg,
})
return false, sim
return false, so
}
userID, _ := c.Get(common.UserIDCtx)
userRole, _ := c.Get(common.UserRoleCtx)
err = sim.ByID(uint(simID))
err = so.ByID(uint(simID))
if common.ProvideErrorResponse(c, err) {
return false, sim
return false, so
}
if sim.checkAccess(userID.(uint), userRole.(string)) == false {
c.JSON(http.StatusUnprocessableEntity, "Access denied (for simulation ID).")
return false, sim
if so.checkAccess(userID.(uint), userRole.(string)) == false {
c.JSON(http.StatusUnprocessableEntity, "Access denied (for scenario ID).")
return false, so
}
return true, sim
return true, so
}

View file

@ -0,0 +1,98 @@
package scenario
import (
"encoding/json"
"fmt"
"github.com/gin-gonic/gin"
"github.com/stretchr/testify/assert"
"net/http"
"net/http/httptest"
"testing"
"git.rwth-aachen.de/acs/public/villas/villasweb-backend-go/common"
"git.rwth-aachen.de/acs/public/villas/villasweb-backend-go/routes/user"
)
// Test /scenarios endpoints
func TestScenarioEndpoints(t *testing.T) {
var token string
var myUsers = []common.UserResponse{common.UserA_response, common.UserB_response}
var myUserA = []common.UserResponse{common.UserA_response}
var msgUsers = common.ResponseMsgUsers{Users: myUsers}
var msgUserA = common.ResponseMsgUsers{Users: myUserA}
var myScenarios = []common.ScenarioResponse{common.ScenarioA_response, common.ScenarioB_response}
var msgScenarios = common.ResponseMsgScenarios{Scenarios: myScenarios}
var msgScenario = common.ResponseMsgScenario{Scenario: common.ScenarioC_response}
var msgScenarioUpdated = common.ResponseMsgScenario{Scenario: common.ScenarioCUpdated_response}
db := common.DummyInitDB()
defer db.Close()
common.DummyPopulateDB(db)
router := gin.Default()
api := router.Group("/api")
// All endpoints require authentication except when someone wants to
// login (POST /authenticate)
user.VisitorAuthenticate(api.Group("/authenticate"))
api.Use(user.Authentication(true))
RegisterScenarioEndpoints(api.Group("/scenarios"))
credjson, _ := json.Marshal(common.CredUser)
msgOKjson, _ := json.Marshal(common.MsgOK)
msgScenariosjson, _ := json.Marshal(msgScenarios)
msgScenariojson, _ := json.Marshal(msgScenario)
msgScenarioUpdatedjson, _ := json.Marshal(msgScenarioUpdated)
msgUsersjson, _ := json.Marshal(msgUsers)
msgUserAjson, _ := json.Marshal(msgUserA)
token = common.AuthenticateForTest(t, router, "/api/authenticate", "POST", credjson, 200)
// test GET scenarios/
common.TestEndpoint(t, router, token, "/api/scenarios", "GET", nil, 200, msgScenariosjson)
// test POST scenarios/
common.TestEndpoint(t, router, token, "/api/scenarios", "POST", msgScenariojson, 200, msgOKjson)
// test GET scenarios/:ScenarioID
common.TestEndpoint(t, router, token, "/api/scenarios/3", "GET", nil, 200, msgScenariojson)
// test PUT scenarios/:ScenarioID
common.TestEndpoint(t, router, token, "/api/scenarios/3", "PUT", msgScenarioUpdatedjson, 200, msgOKjson)
common.TestEndpoint(t, router, token, "/api/scenarios/3", "GET", nil, 200, msgScenarioUpdatedjson)
// test DELETE scenarios/:ScenarioID
common.TestEndpoint(t, router, token, "/api/scenarios/3", "DELETE", nil, 200, msgOKjson)
common.TestEndpoint(t, router, token, "/api/scenarios", "GET", nil, 200, msgScenariosjson)
// test GET scenarios/:ScenarioID/users
common.TestEndpoint(t, router, token, "/api/scenarios/1/users", "GET", nil, 200, msgUsersjson)
// test DELETE scenarios/:ScenarioID/user
common.TestEndpoint(t, router, token, "/api/scenarios/1/user?username=User_B", "DELETE", nil, 200, msgOKjson)
common.TestEndpoint(t, router, token, "/api/scenarios/1/users", "GET", nil, 200, msgUserAjson)
// test PUT scenarios/:ScenarioID/user
common.TestEndpoint(t, router, token, "/api/scenarios/1/user?username=User_B", "PUT", nil, 200, msgOKjson)
common.TestEndpoint(t, router, token, "/api/scenarios/1/users", "GET", nil, 200, msgUsersjson)
// test DELETE scenarios/:ScenarioID/user for logged in user User_A
common.TestEndpoint(t, router, token, "/api/scenarios/1/user?username=User_A", "DELETE", nil, 200, msgOKjson)
// test if deletion of user from scenario has worked
w2 := httptest.NewRecorder()
req2, _ := http.NewRequest("GET", "/api/scenarios/1/users", nil)
req2.Header.Add("Authorization", "Bearer "+token)
router.ServeHTTP(w2, req2)
assert.Equal(t, 422, w2.Code)
fmt.Println(w2.Body.String())
assert.Equal(t, "\"Access denied (for scenario ID).\"", w2.Body.String())
// TODO add tests for other return codes
}

View file

@ -0,0 +1,199 @@
package signal
import (
"net/http"
"github.com/gin-gonic/gin"
"git.rwth-aachen.de/acs/public/villas/villasweb-backend-go/common"
"git.rwth-aachen.de/acs/public/villas/villasweb-backend-go/routes/simulationmodel"
)
func RegisterSignalEndpoints(r *gin.RouterGroup) {
r.GET("", getSignals)
r.POST("", addSignal)
r.PUT("/:signalID", updateSignal)
r.GET("/:signalID", getSignal)
r.DELETE("/:signalID", deleteSignal)
}
// getSignals godoc
// @Summary Get all signals of one direction
// @ID getSignals
// @Produce json
// @Tags signals
// @Param direction query string true "Direction of signal (in or out)"
// @Param modelID query string true "Model ID of signals to be obtained"
// @Success 200 {array} common.Signal "Requested signals."
// @Failure 401 "Unauthorized Access"
// @Failure 403 "Access forbidden."
// @Failure 404 "Not found"
// @Failure 500 "Internal server error"
// @Router /signals [get]
func getSignals(c *gin.Context) {
ok, m := simulationmodel.CheckPermissions(c, common.Read, "query", -1)
if !ok {
return
}
var mapping string
direction := c.Request.URL.Query().Get("direction")
if direction == "in" {
mapping = "InputMapping"
} else if direction == "out" {
mapping = "OutputMapping"
} else {
errormsg := "Bad request. Direction has to be in or out"
c.JSON(http.StatusBadRequest, gin.H{
"error": errormsg,
})
return
}
db := common.GetDB()
var sigs []common.Signal
err := db.Order("ID asc").Model(m).Where("Direction = ?", direction).Related(&sigs, mapping).Error
if common.ProvideErrorResponse(c, err) {
return
}
serializer := common.SignalsSerializer{c, sigs}
c.JSON(http.StatusOK, gin.H{
"signals": serializer.Response(),
})
}
// AddSignal godoc
// @Summary Add a signal to a signal mapping of a model
// @ID AddSignal
// @Accept json
// @Produce json
// @Tags signals
// @Param inputSignal body common.ResponseMsgSignal true "A signal to be added to the model incl. direction and model ID to which signal shall be added"
// @Success 200 "OK."
// @Failure 401 "Unauthorized Access"
// @Failure 403 "Access forbidden."
// @Failure 404 "Not found"
// @Failure 500 "Internal server error"
// @Router /signals [post]
func addSignal(c *gin.Context) {
var newSignalData common.ResponseMsgSignal
err := c.BindJSON(&newSignalData)
if err != nil {
errormsg := "Bad request. Error binding form data to JSON: " + err.Error()
c.JSON(http.StatusBadRequest, gin.H{
"error": errormsg,
})
return
}
var newSignal Signal
newSignal.Index = newSignalData.Signal.Index
newSignal.SimulationModelID = newSignalData.Signal.SimulationModelID
newSignal.Direction = newSignalData.Signal.Direction
newSignal.Unit = newSignalData.Signal.Unit
newSignal.Name = newSignalData.Signal.Name
ok, _ := simulationmodel.CheckPermissions(c, common.Update, "body", int(newSignal.SimulationModelID))
if !ok {
return
}
// Add signal to model
err = newSignal.addToSimulationModel()
if common.ProvideErrorResponse(c, err) == false {
c.JSON(http.StatusOK, gin.H{
"message": "OK.",
})
}
}
// updateSignal godoc
// @Summary Update a signal
// @ID updateSignal
// @Tags signals
// @Produce json
// @Success 200 "OK."
// @Failure 401 "Unauthorized Access"
// @Failure 403 "Access forbidden."
// @Failure 404 "Not found"
// @Failure 500 "Internal server error"
// @Param signalID path int true "ID of signal to be updated"
// @Router /signals/{signalID} [put]
func updateSignal(c *gin.Context) {
ok, sig := checkPermissions(c, common.Delete)
if !ok {
return
}
var modifiedSignal common.ResponseMsgSignal
err := c.BindJSON(&modifiedSignal)
if err != nil {
errormsg := "Bad request. Error binding form data to JSON: " + err.Error()
c.JSON(http.StatusBadRequest, gin.H{
"error": errormsg,
})
return
}
err = sig.update(modifiedSignal.Signal)
if common.ProvideErrorResponse(c, err) == false {
c.JSON(http.StatusOK, gin.H{
"message": "OK.",
})
}
}
// getSignal godoc
// @Summary Get a signal
// @ID getSignal
// @Tags signals
// @Produce json
// @Success 200 "OK."
// @Failure 401 "Unauthorized Access"
// @Failure 403 "Access forbidden."
// @Failure 404 "Not found"
// @Failure 500 "Internal server error"
// @Param signalID path int true "ID of signal to be obtained"
// @Router /signals/{signalID} [get]
func getSignal(c *gin.Context) {
ok, sig := checkPermissions(c, common.Delete)
if !ok {
return
}
serializer := common.SignalSerializer{c, sig.Signal}
c.JSON(http.StatusOK, gin.H{
"signal": serializer.Response(),
})
}
// deleteSignal godoc
// @Summary Delete a signal
// @ID deleteSignal
// @Tags signals
// @Produce json
// @Success 200 "OK."
// @Failure 401 "Unauthorized Access"
// @Failure 403 "Access forbidden."
// @Failure 404 "Not found"
// @Failure 500 "Internal server error"
// @Param signalID path int true "ID of signal to be deleted"
// @Router /signals/{signalID} [delete]
func deleteSignal(c *gin.Context) {
ok, sig := checkPermissions(c, common.Delete)
if !ok {
return
}
err := sig.delete()
if common.ProvideErrorResponse(c, err) == false {
c.JSON(http.StatusOK, gin.H{
"message": "OK.",
})
}
}

View file

@ -0,0 +1,113 @@
package signal
import (
"fmt"
"git.rwth-aachen.de/acs/public/villas/villasweb-backend-go/common"
"git.rwth-aachen.de/acs/public/villas/villasweb-backend-go/routes/simulationmodel"
)
type Signal struct {
common.Signal
}
func (s *Signal) save() error {
db := common.GetDB()
err := db.Create(s).Error
return err
}
func (s *Signal) byID(id uint) error {
db := common.GetDB()
err := db.Find(s, id).Error
if err != nil {
return fmt.Errorf("Signal with id=%v does not exist", id)
}
return nil
}
func (s *Signal) addToSimulationModel() error {
db := common.GetDB()
var m simulationmodel.SimulationModel
err := m.ByID(s.SimulationModelID)
if err != nil {
return err
}
// save signal to DB
err = s.save()
if err != nil {
return err
}
// associate signal with simulation model in correct direction
if s.Direction == "in" {
err = db.Model(&m).Association("InputMapping").Append(s).Error
if err != nil {
return err
}
// adapt length of mapping
var newInputLength = db.Model(m).Where("Direction = ?", "in").Association("InputMapping").Count()
err = db.Model(m).Update("InputLength", newInputLength).Error
} else {
err = db.Model(&m).Association("OutputMapping").Append(s).Error
if err != nil {
return err
}
// adapt length of mapping
var newOutputLength = db.Model(m).Where("Direction = ?", "out").Association("OutputMapping").Count()
err = db.Model(m).Update("OutputLength", newOutputLength).Error
}
return err
}
func (s *Signal) update(modifiedSignal common.SignalResponse) error {
db := common.GetDB()
err := db.Model(s).Updates(map[string]interface{}{
"Name": modifiedSignal.Name,
"Unit": modifiedSignal.Unit,
"Index": modifiedSignal.Index,
}).Error
return err
}
func (s *Signal) delete() error {
db := common.GetDB()
var m simulationmodel.SimulationModel
err := m.ByID(s.SimulationModelID)
if err != nil {
return err
}
// remove association between Signal and SimulationModel
// 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 {
return err
}
// Reduce length of mapping by 1
var newInputLength = m.InputLength - 1
err = db.Model(m).Update("InputLength", newInputLength).Error
} else {
err = db.Model(&m).Association("OutputMapping").Delete(s).Error
if err != nil {
return err
}
// Reduce length of mapping by 1
var newOutputLength = m.OutputLength - 1
err = db.Model(m).Update("OutputLength", newOutputLength).Error
}
return err
}

View file

@ -0,0 +1,44 @@
package signal
import (
"fmt"
"net/http"
"strconv"
"github.com/gin-gonic/gin"
"git.rwth-aachen.de/acs/public/villas/villasweb-backend-go/common"
"git.rwth-aachen.de/acs/public/villas/villasweb-backend-go/routes/simulationmodel"
)
func checkPermissions(c *gin.Context, operation common.CRUD) (bool, Signal) {
var sig Signal
err := common.ValidateRole(c, common.ModelSignal, operation)
if err != nil {
c.JSON(http.StatusUnprocessableEntity, "Access denied (role validation failed).")
return false, sig
}
signalID, err := strconv.Atoi(c.Param("signalID"))
if err != nil {
errormsg := fmt.Sprintf("Bad request. No or incorrect format of signalID path parameter")
c.JSON(http.StatusBadRequest, gin.H{
"error": errormsg,
})
return false, sig
}
err = sig.byID(uint(signalID))
if common.ProvideErrorResponse(c, err) {
return false, sig
}
ok, _ := simulationmodel.CheckPermissions(c, operation, "body", int(sig.SimulationModelID))
if !ok {
return false, sig
}
return true, sig
}

View file

@ -0,0 +1,73 @@
package signal
import (
"encoding/json"
"testing"
"github.com/gin-gonic/gin"
"git.rwth-aachen.de/acs/public/villas/villasweb-backend-go/common"
"git.rwth-aachen.de/acs/public/villas/villasweb-backend-go/routes/user"
)
// Test /models endpoints
func TestSignalEndpoints(t *testing.T) {
var token string
var myInSignals = []common.SignalResponse{common.InSignalA_response, common.InSignalB_response}
var myOutSignals = []common.SignalResponse{common.OutSignalA_response, common.OutSignalB_response}
var msgInSignals = common.ResponseMsgSignals{Signals: myInSignals}
var msgInSignalC = common.ResponseMsgSignal{Signal: common.InSignalC_response}
var msgInSignalCupdated = common.ResponseMsgSignal{Signal: common.InSignalCUpdated_response}
var msgOutSignals = common.ResponseMsgSignals{Signals: myOutSignals}
db := common.DummyInitDB()
defer db.Close()
common.DummyPopulateDB(db)
router := gin.Default()
api := router.Group("/api")
// All endpoints require authentication except when someone wants to
// login (POST /authenticate)
user.VisitorAuthenticate(api.Group("/authenticate"))
api.Use(user.Authentication(true))
RegisterSignalEndpoints(api.Group("/signals"))
credjson, _ := json.Marshal(common.CredUser)
msgOKjson, _ := json.Marshal(common.MsgOK)
msgInSignalsjson, _ := json.Marshal(msgInSignals)
msgOutSignalsjson, _ := json.Marshal(msgOutSignals)
inSignalCjson, _ := json.Marshal(msgInSignalC)
inSignalCupdatedjson, _ := json.Marshal(msgInSignalCupdated)
token = common.AuthenticateForTest(t, router, "/api/authenticate", "POST", credjson, 200)
// test GET signals
common.TestEndpoint(t, router, token, "/api/signals?modelID=1&direction=in", "GET", nil, 200, msgInSignalsjson)
common.TestEndpoint(t, router, token, "/api/signals?modelID=1&direction=out", "GET", nil, 200, msgOutSignalsjson)
// test POST signals
common.TestEndpoint(t, router, token, "/api/signals", "POST", inSignalCjson, 200, msgOKjson)
// test GET signals/:signalID
common.TestEndpoint(t, router, token, "/api/signals/5", "GET", nil, 200, inSignalCjson)
// test PUT signals/:signalID
common.TestEndpoint(t, router, token, "/api/signals/5", "PUT", inSignalCupdatedjson, 200, msgOKjson)
common.TestEndpoint(t, router, token, "/api/signals/5", "GET", nil, 200, inSignalCupdatedjson)
// test DELETE signals/:signalID
common.TestEndpoint(t, router, token, "/api/signals/5", "DELETE", nil, 200, msgOKjson)
common.TestEndpoint(t, router, token, "/api/signals?modelID=1&direction=in", "GET", nil, 200, msgInSignalsjson)
common.TestEndpoint(t, router, token, "/api/signals?modelID=1&direction=out", "GET", nil, 200, msgOutSignalsjson)
// TODO test GET models/:ModelID to check if POST and DELETE adapt InputLength correctly??
//common.TestEndpoint(t, router, token, "/api/models/1", "GET", nil, 200, string(msgModelAUpdated2json))
// TODO add testing for other return codes
}

View file

@ -1,316 +0,0 @@
package simulation
import (
"net/http"
"github.com/gin-gonic/gin"
"git.rwth-aachen.de/acs/public/villas/villasweb-backend-go/common"
"git.rwth-aachen.de/acs/public/villas/villasweb-backend-go/routes/user"
)
func RegisterSimulationEndpoints(r *gin.RouterGroup) {
r.GET("", getSimulations)
r.POST("", addSimulation)
r.PUT("/:simulationID", updateSimulation)
r.GET("/:simulationID", getSimulation)
r.DELETE("/:simulationID", deleteSimulation)
r.GET("/:simulationID/users", getUsersOfSimulation)
r.PUT("/:simulationID/user", addUserToSimulation)
r.DELETE("/:simulationID/user", deleteUserFromSimulation)
}
// getSimulations godoc
// @Summary Get all simulations
// @ID getSimulations
// @Produce json
// @Tags simulations
// @Success 200 {array} common.SimulationResponse "Array of simulations to which user has access"
// @Failure 401 "Unauthorized Access"
// @Failure 403 "Access forbidden."
// @Failure 404 "Not found"
// @Failure 500 "Internal server error"
// @Router /simulations [get]
func getSimulations(c *gin.Context) {
ok, _ := CheckPermissions(c, common.ModelSimulation, common.Read, "none", -1)
if !ok {
return
}
// ATTENTION: do not use c.GetInt (common.UserIDCtx) since user_id is of type uint and not int
userID, _ := c.Get(common.UserIDCtx)
userRole, _ := c.Get(common.UserRoleCtx)
var u user.User
err := u.ByID(userID.(uint))
if common.ProvideErrorResponse(c, err) {
return
}
// get all simulations for the user who issues the request
db := common.GetDB()
var simulations []common.Simulation
if userRole == "Admin" { // Admin can see all simulations
err = db.Order("ID asc").Find(&simulations).Error
if common.ProvideErrorResponse(c, err) {
return
}
} else { // User or Guest roles see only their simulations
err = db.Order("ID asc").Model(&u).Related(&simulations, "Simulations").Error
if common.ProvideErrorResponse(c, err) {
return
}
}
serializer := common.SimulationsSerializer{c, simulations}
c.JSON(http.StatusOK, gin.H{
"simulations": serializer.Response(),
})
}
// addSimulation godoc
// @Summary Add a simulation
// @ID addSimulation
// @Accept json
// @Produce json
// @Tags simulations
// @Param inputSimulation body common.SimulationResponse true "Simulation to be added"
// @Success 200 "OK."
// @Failure 401 "Unauthorized Access"
// @Failure 403 "Access forbidden."
// @Failure 404 "Not found"
// @Failure 500 "Internal server error"
// @Router /simulations [post]
func addSimulation(c *gin.Context) {
ok, _ := CheckPermissions(c, common.ModelSimulation, common.Create, "none", -1)
if !ok {
return
}
userID, _ := c.Get(common.UserIDCtx)
var u user.User
err := u.ByID(userID.(uint))
if common.ProvideErrorResponse(c, err) {
return
}
var sim Simulation
err = c.BindJSON(&sim)
if err != nil {
errormsg := "Bad request. Error binding form data to JSON: " + err.Error()
c.JSON(http.StatusBadRequest, gin.H{
"error": errormsg,
})
return
}
// save new simulation to DB
err = sim.save()
if common.ProvideErrorResponse(c, err) {
return
}
// add user to new simulation
err = sim.addUser(&(u.User))
if common.ProvideErrorResponse(c, err) == false {
c.JSON(http.StatusOK, gin.H{
"message": "OK.",
})
}
}
// updateSimulation godoc
// @Summary Update a simulation
// @ID updateSimulation
// @Tags simulations
// @Accept json
// @Produce json
// @Param inputSimulation body common.SimulationResponse true "Simulation to be updated"
// @Success 200 "OK."
// @Failure 401 "Unauthorized Access"
// @Failure 403 "Access forbidden."
// @Failure 404 "Not found"
// @Failure 500 "Internal server error"
// @Param simulationID path int true "Simulation ID"
// @Router /simulations/{simulationID} [put]
func updateSimulation(c *gin.Context) {
ok, sim := CheckPermissions(c, common.ModelSimulation, common.Update, "path", -1)
if !ok {
return
}
var modifiedSim Simulation
err := c.BindJSON(&modifiedSim)
if err != nil {
errormsg := "Bad request. Error binding form data to JSON: " + err.Error()
c.JSON(http.StatusBadRequest, gin.H{
"error": errormsg,
})
return
}
err = sim.update(modifiedSim)
if common.ProvideErrorResponse(c, err) == false {
c.JSON(http.StatusOK, gin.H{
"message": "OK.",
})
}
}
// getSimulation godoc
// @Summary Get simulation
// @ID getSimulation
// @Produce json
// @Tags simulations
// @Success 200 {object} common.SimulationResponse "Simulation requested by user"
// @Failure 401 "Unauthorized Access"
// @Failure 403 "Access forbidden."
// @Failure 404 "Not found"
// @Failure 500 "Internal server error"
// @Param simulationID path int true "Simulation ID"
// @Router /simulations/{simulationID} [get]
func getSimulation(c *gin.Context) {
ok, sim := CheckPermissions(c, common.ModelSimulation, common.Read, "path", -1)
if !ok {
return
}
serializer := common.SimulationSerializer{c, sim.Simulation}
c.JSON(http.StatusOK, gin.H{
"simulation": serializer.Response(),
})
}
// deleteSimulation godoc
// @Summary Delete a simulation
// @ID deleteSimulation
// @Tags simulations
// @Produce json
// @Success 200 "OK."
// @Failure 401 "Unauthorized Access"
// @Failure 403 "Access forbidden."
// @Failure 404 "Not found"
// @Failure 500 "Internal server error"
// @Param simulationID path int true "Simulation ID"
// @Router /simulations/{simulationID} [delete]
func deleteSimulation(c *gin.Context) {
ok, sim := CheckPermissions(c, common.ModelSimulation, common.Delete, "path", -1)
if !ok {
return
}
err := sim.delete()
if common.ProvideErrorResponse(c, err) == false {
c.JSON(http.StatusOK, gin.H{
"message": "OK.",
})
}
}
// getUsersOfSimulation godoc
// @Summary Get users of simulation
// @ID getUsersOfSimulation
// @Produce json
// @Tags simulations
// @Success 200 {array} common.UserResponse "Array of users that have access to the simulation"
// @Failure 401 "Unauthorized Access"
// @Failure 403 "Access forbidden."
// @Failure 404 "Not found"
// @Failure 500 "Internal server error"
// @Param simulationID path int true "Simulation ID"
// @Router /simulations/{simulationID}/users/ [get]
func getUsersOfSimulation(c *gin.Context) {
ok, sim := CheckPermissions(c, common.ModelSimulation, common.Read, "path", -1)
if !ok {
return
}
// Find all users of simulation
allUsers, _, err := sim.getUsers()
if common.ProvideErrorResponse(c, err) {
return
}
serializer := common.UsersSerializer{c, allUsers}
c.JSON(http.StatusOK, gin.H{
"users": serializer.Response(false),
})
}
// addUserToSimulation godoc
// @Summary Add a user to a a simulation
// @ID addUserToSimulation
// @Tags simulations
// @Produce json
// @Success 200 "OK."
// @Failure 401 "Unauthorized Access"
// @Failure 403 "Access forbidden."
// @Failure 404 "Not found"
// @Failure 500 "Internal server error"
// @Param simulationID path int true "Simulation ID"
// @Param username query string true "User name"
// @Router /simulations/{simulationID}/user [put]
func addUserToSimulation(c *gin.Context) {
ok, sim := CheckPermissions(c, common.ModelSimulation, common.Update, "path", -1)
if !ok {
return
}
username := c.Request.URL.Query().Get("username")
var u user.User
err := u.ByUsername(username)
if common.ProvideErrorResponse(c, err) {
return
}
err = sim.addUser(&(u.User))
if common.ProvideErrorResponse(c, err) {
return
}
c.JSON(http.StatusOK, gin.H{
"message": "OK.",
})
}
// deleteUserFromSimulation godoc
// @Summary Delete a user from a simulation
// @ID deleteUserFromSimulation
// @Tags simulations
// @Produce json
// @Success 200 "OK."
// @Failure 401 "Unauthorized Access"
// @Failure 403 "Access forbidden."
// @Failure 404 "Not found"
// @Failure 500 "Internal server error"
// @Param simulationID path int true "Simulation ID"
// @Param username query string true "User name"
// @Router /simulations/{simulationID}/user [delete]
func deleteUserFromSimulation(c *gin.Context) {
ok, sim := CheckPermissions(c, common.ModelSimulation, common.Update, "path", -1)
if !ok {
return
}
username := c.Request.URL.Query().Get("username")
err := sim.deleteUser(username)
if common.ProvideErrorResponse(c, err) {
return
}
c.JSON(http.StatusOK, gin.H{
"message": "OK.",
})
}

View file

@ -1,178 +0,0 @@
package simulation
import (
"encoding/json"
"testing"
"git.rwth-aachen.de/acs/public/villas/villasweb-backend-go/routes/user"
"github.com/gin-gonic/gin"
"git.rwth-aachen.de/acs/public/villas/villasweb-backend-go/common"
)
var token string
type credentials struct {
Username string `json:"username"`
Password string `json:"password"`
}
var cred = credentials{
Username: "User_A",
Password: "abc123",
}
var msgOK = common.ResponseMsg{
Message: "OK.",
}
var user_A = common.UserResponse{
Username: "User_A",
Role: "User",
Mail: "",
}
var user_B = common.UserResponse{
Username: "User_B",
Role: "User",
Mail: "",
}
var myUsers = []common.UserResponse{
user_A,
user_B,
}
var myUserA = []common.UserResponse{
user_A,
}
var msgUsers = common.ResponseMsgUsers{
Users: myUsers,
}
var msgUserA = common.ResponseMsgUsers{
Users: myUserA,
}
var simulationA = common.SimulationResponse{
Name: "Simulation_A",
ID: 1,
Running: false,
}
var simulationB = common.SimulationResponse{
Name: "Simulation_B",
ID: 2,
Running: false,
}
var simulationC = common.Simulation{
Name: "Simulation_C",
Running: false,
StartParameters: "test",
}
var simulationC_response = common.SimulationResponse{
ID: 3,
Name: simulationC.Name,
Running: simulationC.Running,
StartParams: simulationC.StartParameters,
}
var mySimulations = []common.SimulationResponse{
simulationA,
simulationB,
}
var msgSimulations = common.ResponseMsgSimulations{
Simulations: mySimulations,
}
var msgSimulation = common.ResponseMsgSimulation{
Simulation: simulationC_response,
}
// Test /simulation endpoints
func TestSimulationEndpoints(t *testing.T) {
db := common.DummyInitDB()
defer db.Close()
common.DummyPopulateDB(db)
router := gin.Default()
api := router.Group("/api")
// All endpoints require authentication except when someone wants to
// login (POST /authenticate)
user.VisitorAuthenticate(api.Group("/authenticate"))
api.Use(user.Authentication(true))
RegisterSimulationEndpoints(api.Group("/simulations"))
credjson, err := json.Marshal(cred)
msgOKjson, err := json.Marshal(msgOK)
if err != nil {
panic(err)
}
msgUsersjson, err := json.Marshal(msgUsers)
if err != nil {
panic(err)
}
msgUserAjson, err := json.Marshal(msgUserA)
if err != nil {
panic(err)
}
msgSimulationsjson, err := json.Marshal(msgSimulations)
if err != nil {
panic(err)
}
msgSimulationjson, err := json.Marshal(msgSimulation)
if err != nil {
panic(err)
}
simulationCjson, err := json.Marshal(simulationC)
if err != nil {
panic(err)
}
token = common.AuthenticateForTest(t, router, "/api/authenticate", "POST", credjson, 200)
// test GET simulations/
common.TestEndpoint(t, router, token, "/api/simulations", "GET", nil, 200, string(msgSimulationsjson))
// test POST simulations/
common.TestEndpoint(t, router, token, "/api/simulations", "POST", simulationCjson, 200, string(msgOKjson))
// test GET simulations/:SimulationID
common.TestEndpoint(t, router, token, "/api/simulations/3", "GET", nil, 200, string(msgSimulationjson))
// test DELETE simulations/:SimulationID
common.TestEndpoint(t, router, token, "/api/simulations/3", "DELETE", nil, 200, string(msgOKjson))
common.TestEndpoint(t, router, token, "/api/simulations", "GET", nil, 200, string(msgSimulationsjson))
// test GET simulations/:SimulationID/users
common.TestEndpoint(t, router, token, "/api/simulations/1/users", "GET", nil, 200, string(msgUsersjson))
// test DELETE simulations/:SimulationID/user
common.TestEndpoint(t, router, token, "/api/simulations/1/user?username=User_B", "DELETE", nil, 200, string(msgOKjson))
common.TestEndpoint(t, router, token, "/api/simulations/1/users", "GET", nil, 200, string(msgUserAjson))
// test PUT simulations/:SimulationID/user
common.TestEndpoint(t, router, token, "/api/simulations/1/user?username=User_B", "PUT", nil, 200, string(msgOKjson))
common.TestEndpoint(t, router, token, "/api/simulations/1/users", "GET", nil, 200, string(msgUsersjson))
// test DELETE simulations/:SimulationID/user for logged in user User_A
common.TestEndpoint(t, router, token, "/api/simulations/1/user?username=User_A", "DELETE", nil, 200, string(msgOKjson))
common.TestEndpoint(t, router, token, "/api/simulations/1/users", "GET", nil, 422, "\"Access denied (for simulation ID).\"")
// TODO add tests for other return codes
}

View file

@ -1,342 +0,0 @@
package simulationmodel
import (
"net/http"
"github.com/gin-gonic/gin"
"git.rwth-aachen.de/acs/public/villas/villasweb-backend-go/common"
"git.rwth-aachen.de/acs/public/villas/villasweb-backend-go/routes/simulation"
)
func RegisterSimulationModelEndpoints(r *gin.RouterGroup) {
r.GET("", getSimulationModels)
r.POST("", addSimulationModel)
//r.POST("/:modelID", cloneSimulationModel)
r.PUT("/:modelID", updateSimulationModel)
r.GET("/:modelID", getSimulationModel)
r.DELETE("/:modelID", deleteSimulationModel)
r.GET("/:modelID/signals", getSignals)
r.PUT("/:modelID/signals", addSignal)
r.DELETE("/:modelID/signals", deleteSignals)
}
// getSimulationModels godoc
// @Summary Get all simulation models of simulation
// @ID getSimulationModels
// @Produce json
// @Tags models
// @Success 200 {array} common.SimulationModelResponse "Array of models to which belong to simulation"
// @Failure 401 "Unauthorized Access"
// @Failure 403 "Access forbidden."
// @Failure 404 "Not found"
// @Failure 500 "Internal server error"
// @Param simulationID query int true "Simulation ID"
// @Router /models [get]
func getSimulationModels(c *gin.Context) {
ok, sim := simulation.CheckPermissions(c, common.ModelSimulationModel, common.Read, "query", -1)
if !ok {
return
}
db := common.GetDB()
var models []common.SimulationModel
err := db.Order("ID asc").Model(sim).Related(&models, "Models").Error
if common.ProvideErrorResponse(c, err) {
return
}
serializer := common.SimulationModelsSerializer{c, models}
c.JSON(http.StatusOK, gin.H{
"models": serializer.Response(),
})
}
// addSimulationModel godoc
// @Summary Add a simulation model to a simulation
// @ID addSimulationModel
// @Accept json
// @Produce json
// @Tags models
// @Param inputSimulationModel body common.SimulationModelResponse true "Simulation model to be added incl. IDs of simulation and simulator"
// @Success 200 "OK."
// @Failure 401 "Unauthorized Access"
// @Failure 403 "Access forbidden."
// @Failure 404 "Not found"
// @Failure 500 "Internal server error"
// @Router /models [post]
func addSimulationModel(c *gin.Context) {
var newModel SimulationModel
err := c.BindJSON(&newModel)
if err != nil {
errormsg := "Bad request. Error binding form data to JSON: " + err.Error()
c.JSON(http.StatusBadRequest, gin.H{
"error": errormsg,
})
return
}
ok, _ := simulation.CheckPermissions(c, common.ModelSimulationModel, common.Create, "body", int(newModel.SimulationID))
if !ok {
return
}
err = newModel.addToSimulation()
if common.ProvideErrorResponse(c, err) == false {
c.JSON(http.StatusOK, gin.H{
"message": "OK.",
})
}
}
func cloneSimulationModel(c *gin.Context) {
// modelID, err := routes.GetModelID(c)
// if err != nil {
// return
// }
//
// targetSimID, err := strconv.Atoi(c.PostForm("TargetSim"))
// if err != nil {
// errormsg := fmt.Sprintf("Bad request. No or incorrect format of target sim ID")
// c.JSON(http.StatusBadRequest, gin.H{
// "error": errormsg,
// })
// return
// }
// TODO TO BE IMPLEMENTED
// Check if target sim exists
// Check if model exists
// Get all Signals of Model
// Get Simulator of Model
// Get Files of model
// Add new model object to DB and associate with target sim
// Add new signal objects to DB and associate with new model object (careful with directions)
// Associate Simulator with new Model object
c.JSON(http.StatusOK, gin.H{
"message": "Not implemented.",
})
}
// updateSimulationModel godoc
// @Summary Update a simulation model
// @ID updateSimulationModel
// @Tags models
// @Accept json
// @Produce json
// @Param inputSimulationModel body common.SimulationModelResponse true "Simulation model to be updated"
// @Success 200 "OK."
// @Failure 401 "Unauthorized Access"
// @Failure 403 "Access forbidden."
// @Failure 404 "Not found"
// @Failure 500 "Internal server error"
// @Param modelID path int true "Model ID"
// @Router /models/{modelID} [put]
func updateSimulationModel(c *gin.Context) {
ok, m := checkPermissions(c, common.Update)
if !ok {
return
}
var modifiedModel SimulationModel
err := c.BindJSON(&modifiedModel)
if err != nil {
errormsg := "Bad request. Error binding form data to JSON: " + err.Error()
c.JSON(http.StatusBadRequest, gin.H{
"error": errormsg,
})
return
}
err = m.update(modifiedModel)
if common.ProvideErrorResponse(c, err) == false {
c.JSON(http.StatusOK, gin.H{
"message": "OK.",
})
}
}
// getSimulationModel godoc
// @Summary Get a simulation model
// @ID getSimulationModel
// @Tags models
// @Produce json
// @Success 200 {object} common.SimulationModelResponse "Requested simulation model."
// @Failure 401 "Unauthorized Access"
// @Failure 403 "Access forbidden."
// @Failure 404 "Not found"
// @Failure 500 "Internal server error"
// @Param modelID path int true "Model ID"
// @Router /models/{modelID} [get]
func getSimulationModel(c *gin.Context) {
ok, m := checkPermissions(c, common.Read)
if !ok {
return
}
serializer := common.SimulationModelSerializer{c, m.SimulationModel}
c.JSON(http.StatusOK, gin.H{
"model": serializer.Response(),
})
}
// deleteSimulationModel godoc
// @Summary Delete a simulation model
// @ID deleteSimulationModel
// @Tags models
// @Produce json
// @Success 200 "OK."
// @Failure 401 "Unauthorized Access"
// @Failure 403 "Access forbidden."
// @Failure 404 "Not found"
// @Failure 500 "Internal server error"
// @Param modelID path int true "Model ID"
// @Router /models/{modelID} [delete]
func deleteSimulationModel(c *gin.Context) {
ok, m := checkPermissions(c, common.Delete)
if !ok {
return
}
err := m.delete()
if common.ProvideErrorResponse(c, err) {
return
}
c.JSON(http.StatusOK, gin.H{
"message": "OK.",
})
}
// getSignals godoc
// @Summary Get all signals of one direction
// @ID getSignals
// @Produce json
// @Tags models
// @Param direction query string true "Direction of signal (in or out)"
// @Success 200 {array} common.Signal "Requested signals."
// @Failure 401 "Unauthorized Access"
// @Failure 403 "Access forbidden."
// @Failure 404 "Not found"
// @Failure 500 "Internal server error"
// @Router /models/{modelID}/signals [get]
func getSignals(c *gin.Context) {
ok, m := checkPermissions(c, common.Read)
if !ok {
return
}
var mapping string
direction := c.Request.URL.Query().Get("direction")
if direction == "in" {
mapping = "InputMapping"
} else if direction == "out" {
mapping = "OutputMapping"
} else {
errormsg := "Bad request. Direction has to be in or out"
c.JSON(http.StatusBadRequest, gin.H{
"error": errormsg,
})
return
}
db := common.GetDB()
var sigs []common.Signal
err := db.Order("ID asc").Model(m).Where("Direction = ?", direction).Related(&sigs, mapping).Error
if common.ProvideErrorResponse(c, err) {
return
}
serializer := common.SignalsSerializer{c, sigs}
c.JSON(http.StatusOK, gin.H{
"signals": serializer.Response(),
})
}
// AddSignal godoc
// @Summary Add a signal to a signal mapping of a model
// @ID AddSignal
// @Accept json
// @Produce json
// @Tags models
// @Param inputSignal body common.Signal true "A signal to be added to the model incl. direction"
// @Success 200 "OK."
// @Failure 401 "Unauthorized Access"
// @Failure 403 "Access forbidden."
// @Failure 404 "Not found"
// @Failure 500 "Internal server error"
// @Router /models/{modelID}/signals [put]
func addSignal(c *gin.Context) {
ok, m := checkPermissions(c, common.Update)
if !ok {
return
}
var sig common.Signal
err := c.BindJSON(&sig)
if err != nil {
errormsg := "Bad request. Error binding form data to JSON: " + err.Error()
c.JSON(http.StatusBadRequest, gin.H{
"error": errormsg,
})
return
}
// Add signal to model
err = m.addSignal(sig)
if common.ProvideErrorResponse(c, err) == false {
c.JSON(http.StatusOK, gin.H{
"message": "OK.",
})
}
}
// deleteSignals godoc
// @Summary Delete all signals of a direction
// @ID deleteSignals
// @Tags models
// @Produce json
// @Success 200 "OK."
// @Failure 401 "Unauthorized Access"
// @Failure 403 "Access forbidden."
// @Failure 404 "Not found"
// @Failure 500 "Internal server error"
// @Param modelID path int true "Model ID"
// @Param direction query string true "Direction of signals to delete (in or out)"
// @Router /models/{modelID}/signals [delete]
func deleteSignals(c *gin.Context) {
ok, m := checkPermissions(c, common.Update)
if !ok {
return
}
direction := c.Request.URL.Query().Get("direction")
if !(direction == "out") && !(direction == "in") {
errormsg := "Bad request. Direction has to be in or out"
c.JSON(http.StatusBadRequest, gin.H{
"error": errormsg,
})
return
}
err := m.deleteSignals(direction)
if common.ProvideErrorResponse(c, err) == false {
c.JSON(http.StatusOK, gin.H{
"message": "OK.",
})
}
}

View file

@ -1,168 +0,0 @@
package simulationmodel
import (
"fmt"
"git.rwth-aachen.de/acs/public/villas/villasweb-backend-go/common"
"git.rwth-aachen.de/acs/public/villas/villasweb-backend-go/routes/simulation"
"git.rwth-aachen.de/acs/public/villas/villasweb-backend-go/routes/simulator"
)
type SimulationModel struct {
common.SimulationModel
}
func (m *SimulationModel) save() error {
db := common.GetDB()
err := db.Create(m).Error
return err
}
func (m *SimulationModel) ByID(id uint) error {
db := common.GetDB()
err := db.Find(m, id).Error
if err != nil {
return fmt.Errorf("Simulation Model with id=%v does not exist", id)
}
return nil
}
func (m *SimulationModel) addToSimulation() error {
db := common.GetDB()
var sim simulation.Simulation
err := sim.ByID(m.SimulationID)
if err != nil {
return err
}
// save simulation model to DB
err = m.save()
if err != nil {
return err
}
// associate simulator with simulation model
var simltr simulator.Simulator
err = simltr.ByID(m.SimulatorID)
err = db.Model(m).Association("Simulator").Append(&simltr).Error
// associate simulation model with simulation
err = db.Model(&sim).Association("SimulationModels").Append(m).Error
return err
}
func (m *SimulationModel) update(modifiedSimulationModel SimulationModel) error {
db := common.GetDB()
if m.SimulatorID != modifiedSimulationModel.SimulatorID {
// update simulator
var s simulator.Simulator
err := s.ByID(modifiedSimulationModel.SimulatorID)
if err != nil {
return err
}
err = db.Model(m).Association("Simulator").Replace(s).Error
}
err := db.Model(m).Updates(map[string]interface{}{"Name": modifiedSimulationModel.Name,
"OutputLength": modifiedSimulationModel.OutputLength,
"InputLength": modifiedSimulationModel.InputLength,
"StartParameters": modifiedSimulationModel.StartParameters,
"SimulatorID": modifiedSimulationModel.SimulatorID,
}).Error
if err != nil {
return err
}
return err
}
func (m *SimulationModel) delete() error {
db := common.GetDB()
var sim simulation.Simulation
err := sim.ByID(m.SimulationID)
if err != nil {
return err
}
// remove association between SimulationModel and Simulation
// SimulationModel itself is not deleted from DB, it remains as "dangling"
err = db.Model(&sim).Association("SimulationModels").Delete(m).Error
return err
}
func (m *SimulationModel) addSignal(signal common.Signal) error {
db := common.GetDB()
var err error
if signal.Direction == "in" {
err = db.Model(m).Association("InputMapping").Append(signal).Error
if err != nil {
return err
}
// adapt length of mapping
m.InputLength = db.Model(m).Where("Direction = ?", "in").Association("InputMapping").Count()
err = m.update(*m)
} else {
err = db.Model(m).Association("OutputMapping").Append(signal).Error
if err != nil {
return err
}
// adapt length of mapping
m.OutputLength = db.Model(m).Where("Direction = ?", "out").Association("OutputMapping").Count()
err = m.update(*m)
}
return err
}
func (m *SimulationModel) deleteSignals(direction string) error {
db := common.GetDB()
var err error
var columnName string
if direction == "in" {
columnName = "InputMapping"
} else {
columnName = "OutputMapping"
}
var signals []common.Signal
err = db.Order("ID asc").Model(m).Where("Direction = ?", direction).Related(&signals, columnName).Error
if err != nil {
return err
}
// remove association to each signal and delete each signal from db
for _, sig := range signals {
err = db.Model(m).Association(columnName).Delete(sig).Error
if err != nil {
return err
}
err = db.Delete(sig).Error
if err != nil {
return err
}
}
// set length of mapping to 0
if columnName == "InputMapping" {
m.InputLength = 0
} else {
m.OutputLength = 0
}
err = m.update(*m)
return err
}

View file

@ -1,39 +0,0 @@
package simulationmodel
import (
"fmt"
"net/http"
"strconv"
"github.com/gin-gonic/gin"
"git.rwth-aachen.de/acs/public/villas/villasweb-backend-go/common"
"git.rwth-aachen.de/acs/public/villas/villasweb-backend-go/routes/simulation"
)
func checkPermissions(c *gin.Context, operation common.CRUD) (bool, SimulationModel) {
var m SimulationModel
modelID, err := strconv.Atoi(c.Param("modelID"))
if err != nil {
errormsg := fmt.Sprintf("Bad request. No or incorrect format of model ID in path")
c.JSON(http.StatusBadRequest, gin.H{
"error": errormsg,
})
return false, m
}
err = m.ByID(uint(modelID))
if common.ProvideErrorResponse(c, err) {
return false, m
}
ok, _ := simulation.CheckPermissions(c, common.ModelSimulationModel, operation, "body", int(m.SimulationID))
if !ok {
return false, m
}
return true, m
}

View file

@ -0,0 +1,190 @@
package simulationmodel
import (
"net/http"
"github.com/gin-gonic/gin"
"git.rwth-aachen.de/acs/public/villas/villasweb-backend-go/common"
"git.rwth-aachen.de/acs/public/villas/villasweb-backend-go/routes/scenario"
)
func RegisterSimulationModelEndpoints(r *gin.RouterGroup) {
r.GET("", getSimulationModels)
r.POST("", addSimulationModel)
r.PUT("/:modelID", updateSimulationModel)
r.GET("/:modelID", getSimulationModel)
r.DELETE("/:modelID", deleteSimulationModel)
}
// getSimulationModels godoc
// @Summary Get all simulation models of scenario
// @ID getSimulationModels
// @Produce json
// @Tags models
// @Success 200 {array} common.SimulationModelResponse "Array of models to which belong to scenario"
// @Failure 401 "Unauthorized Access"
// @Failure 403 "Access forbidden."
// @Failure 404 "Not found"
// @Failure 500 "Internal server error"
// @Param scenarioID query int true "Scenario ID"
// @Router /models [get]
func getSimulationModels(c *gin.Context) {
ok, so := scenario.CheckPermissions(c, common.Read, "query", -1)
if !ok {
return
}
db := common.GetDB()
var models []common.SimulationModel
err := db.Order("ID asc").Model(so).Related(&models, "Models").Error
if common.ProvideErrorResponse(c, err) {
return
}
serializer := common.SimulationModelsSerializer{c, models}
c.JSON(http.StatusOK, gin.H{
"models": serializer.Response(),
})
}
// addSimulationModel godoc
// @Summary Add a simulation model to a scenario
// @ID addSimulationModel
// @Accept json
// @Produce json
// @Tags models
// @Param inputSimulationModel body common.ResponseMsgSimulationModel true "Simulation model to be added incl. IDs of scenario and simulator"
// @Success 200 "OK."
// @Failure 401 "Unauthorized Access"
// @Failure 403 "Access forbidden."
// @Failure 404 "Not found"
// @Failure 500 "Internal server error"
// @Router /models [post]
func addSimulationModel(c *gin.Context) {
var newModelData common.ResponseMsgSimulationModel
err := c.BindJSON(&newModelData)
if err != nil {
errormsg := "Bad request. Error binding form data to JSON: " + err.Error()
c.JSON(http.StatusBadRequest, gin.H{
"error": errormsg,
})
return
}
var newModel SimulationModel
newModel.ID = newModelData.SimulationModel.ID
newModel.Name = newModelData.SimulationModel.Name
newModel.SimulatorID = newModelData.SimulationModel.SimulatorID
newModel.ScenarioID = newModelData.SimulationModel.ScenarioID
newModel.StartParameters = newModelData.SimulationModel.StartParameters
newModel.OutputLength = 0
newModel.InputLength = 0
ok, _ := scenario.CheckPermissions(c, common.Create, "body", int(newModel.ScenarioID))
if !ok {
return
}
err = newModel.addToScenario()
if common.ProvideErrorResponse(c, err) == false {
c.JSON(http.StatusOK, gin.H{
"message": "OK.",
})
}
}
// updateSimulationModel godoc
// @Summary Update a simulation model
// @ID updateSimulationModel
// @Tags models
// @Accept json
// @Produce json
// @Param inputSimulationModel body common.ResponseMsgSimulationModel true "Simulation model to be updated"
// @Success 200 "OK."
// @Failure 401 "Unauthorized Access"
// @Failure 403 "Access forbidden."
// @Failure 404 "Not found"
// @Failure 500 "Internal server error"
// @Param modelID path int true "Model ID"
// @Router /models/{modelID} [put]
func updateSimulationModel(c *gin.Context) {
ok, m := CheckPermissions(c, common.Update, "path", -1)
if !ok {
return
}
var modifiedModel common.ResponseMsgSimulationModel
err := c.BindJSON(&modifiedModel)
if err != nil {
errormsg := "Bad request. Error binding form data to JSON: " + err.Error()
c.JSON(http.StatusBadRequest, gin.H{
"error": errormsg,
})
return
}
err = m.Update(modifiedModel.SimulationModel)
if common.ProvideErrorResponse(c, err) == false {
c.JSON(http.StatusOK, gin.H{
"message": "OK.",
})
}
}
// getSimulationModel godoc
// @Summary Get a simulation model
// @ID getSimulationModel
// @Tags models
// @Produce json
// @Success 200 {object} common.SimulationModelResponse "Requested simulation model."
// @Failure 401 "Unauthorized Access"
// @Failure 403 "Access forbidden."
// @Failure 404 "Not found"
// @Failure 500 "Internal server error"
// @Param modelID path int true "Model ID"
// @Router /models/{modelID} [get]
func getSimulationModel(c *gin.Context) {
ok, m := CheckPermissions(c, common.Read, "path", -1)
if !ok {
return
}
serializer := common.SimulationModelSerializer{c, m.SimulationModel}
c.JSON(http.StatusOK, gin.H{
"model": serializer.Response(),
})
}
// deleteSimulationModel godoc
// @Summary Delete a simulation model
// @ID deleteSimulationModel
// @Tags models
// @Produce json
// @Success 200 "OK."
// @Failure 401 "Unauthorized Access"
// @Failure 403 "Access forbidden."
// @Failure 404 "Not found"
// @Failure 500 "Internal server error"
// @Param modelID path int true "Model ID"
// @Router /models/{modelID} [delete]
func deleteSimulationModel(c *gin.Context) {
ok, m := CheckPermissions(c, common.Delete, "path", -1)
if !ok {
return
}
err := m.delete()
if common.ProvideErrorResponse(c, err) {
return
}
c.JSON(http.StatusOK, gin.H{
"message": "OK.",
})
}

View file

@ -0,0 +1,110 @@
package simulationmodel
import (
"fmt"
"git.rwth-aachen.de/acs/public/villas/villasweb-backend-go/common"
"git.rwth-aachen.de/acs/public/villas/villasweb-backend-go/routes/scenario"
"git.rwth-aachen.de/acs/public/villas/villasweb-backend-go/routes/simulator"
)
type SimulationModel struct {
common.SimulationModel
}
func (m *SimulationModel) save() error {
db := common.GetDB()
err := db.Create(m).Error
return err
}
func (m *SimulationModel) ByID(id uint) error {
db := common.GetDB()
err := db.Find(m, id).Error
if err != nil {
return fmt.Errorf("Simulation Model with id=%v does not exist", id)
}
return nil
}
func (m *SimulationModel) addToScenario() error {
db := common.GetDB()
var so scenario.Scenario
err := so.ByID(m.ScenarioID)
if err != nil {
return err
}
// save simulation model to DB
err = m.save()
if err != nil {
return err
}
// associate simulator with simulation model
var simltr simulator.Simulator
err = simltr.ByID(m.SimulatorID)
err = db.Model(&simltr).Association("SimulationModels").Append(m).Error
if err != nil {
return err
}
// associate simulation model with scenario
err = db.Model(&so).Association("SimulationModels").Append(m).Error
return err
}
func (m *SimulationModel) Update(modifiedSimulationModel common.SimulationModelResponse) error {
db := common.GetDB()
if m.SimulatorID != modifiedSimulationModel.SimulatorID {
// update simulator
var s simulator.Simulator
var s_old simulator.Simulator
err := s.ByID(modifiedSimulationModel.SimulatorID)
if err != nil {
return err
}
err = s_old.ByID(m.SimulatorID)
if err != nil {
return err
}
// remove simulation model from old simulator
err = db.Model(&s_old).Association("SimulationModels").Delete(m).Error
if err != nil {
return err
}
// add simulation model to new simulator
err = db.Model(&s).Association("SimulationModels").Append(m).Error
if err != nil {
return err
}
}
err := db.Model(m).Updates(map[string]interface{}{
"Name": modifiedSimulationModel.Name,
"OutputLength": modifiedSimulationModel.OutputLength,
"InputLength": modifiedSimulationModel.InputLength,
"StartParameters": modifiedSimulationModel.StartParameters,
"SimulatorID": modifiedSimulationModel.SimulatorID,
}).Error
return err
}
func (m *SimulationModel) delete() error {
db := common.GetDB()
var so scenario.Scenario
err := so.ByID(m.ScenarioID)
if err != nil {
return err
}
// remove association between SimulationModel and Scenario
// SimulationModel itself is not deleted from DB, it remains as "dangling"
err = db.Model(&so).Association("SimulationModels").Delete(m).Error
return err
}

View file

@ -0,0 +1,58 @@
package simulationmodel
import (
"fmt"
"net/http"
"strconv"
"github.com/gin-gonic/gin"
"git.rwth-aachen.de/acs/public/villas/villasweb-backend-go/common"
"git.rwth-aachen.de/acs/public/villas/villasweb-backend-go/routes/scenario"
)
func CheckPermissions(c *gin.Context, operation common.CRUD, modelIDSource string, modelIDBody int) (bool, SimulationModel) {
var m SimulationModel
err := common.ValidateRole(c, common.ModelSimulationModel, operation)
if err != nil {
c.JSON(http.StatusUnprocessableEntity, "Access denied (role validation failed).")
return false, m
}
var modelID int
if modelIDSource == "path" {
modelID, err = strconv.Atoi(c.Param("modelID"))
if err != nil {
errormsg := fmt.Sprintf("Bad request. No or incorrect format of modelID path parameter")
c.JSON(http.StatusBadRequest, gin.H{
"error": errormsg,
})
return false, m
}
} else if modelIDSource == "query" {
modelID, err = strconv.Atoi(c.Request.URL.Query().Get("modelID"))
if err != nil {
errormsg := fmt.Sprintf("Bad request. No or incorrect format of modelID query parameter")
c.JSON(http.StatusBadRequest, gin.H{
"error": errormsg,
})
return false, m
}
} else if modelIDSource == "body" {
modelID = modelIDBody
}
err = m.ByID(uint(modelID))
if common.ProvideErrorResponse(c, err) {
return false, m
}
ok, _ := scenario.CheckPermissions(c, operation, "body", int(m.ScenarioID))
if !ok {
return false, m
}
return true, m
}

View file

@ -10,206 +10,16 @@ import (
"git.rwth-aachen.de/acs/public/villas/villasweb-backend-go/routes/user"
)
var token string
type credentials struct {
Username string `json:"username"`
Password string `json:"password"`
}
var cred = credentials{
Username: "User_A",
Password: "abc123",
}
var msgOK = common.ResponseMsg{
Message: "OK.",
}
var modelA = common.SimulationModelResponse{
ID: 1,
Name: "SimulationModel_A",
OutputLength: 1,
InputLength: 1,
SimulationID: 1,
SimulatorID: 1,
StartParams: "",
}
var modelAUpdated = common.SimulationModelResponse{
ID: 1,
Name: "SimulationModel_A",
OutputLength: 1,
InputLength: 3,
SimulationID: 1,
SimulatorID: 1,
StartParams: "",
}
var modelAUpdated2 = common.SimulationModelResponse{
ID: 1,
Name: "SimulationModel_A",
OutputLength: 1,
InputLength: 0,
SimulationID: 1,
SimulatorID: 1,
StartParams: "",
}
var modelB = common.SimulationModelResponse{
ID: 2,
Name: "SimulationModel_B",
OutputLength: 1,
InputLength: 1,
SimulationID: 1,
SimulatorID: 1,
StartParams: "",
}
var modelC = common.SimulationModel{
ID: 3,
Name: "SimulationModel_C",
OutputLength: 1,
InputLength: 1,
SimulationID: 1,
SimulatorID: 1,
StartParameters: "test",
InputMapping: nil,
OutputMapping: nil,
}
var modelCupdated = common.SimulationModel{
ID: modelC.ID,
Name: "SimulationModel_CUpdated",
OutputLength: modelC.OutputLength,
InputLength: modelC.InputLength,
SimulationID: modelC.SimulationID,
SimulatorID: 2,
StartParameters: modelC.StartParameters,
InputMapping: modelC.InputMapping,
OutputMapping: modelC.OutputMapping,
}
var modelC_response = common.SimulationModelResponse{
ID: modelC.ID,
Name: modelC.Name,
InputLength: modelC.InputLength,
OutputLength: modelC.OutputLength,
SimulationID: modelC.SimulationID,
SimulatorID: modelC.SimulatorID,
StartParams: modelC.StartParameters,
}
var modelC_responseUpdated = common.SimulationModelResponse{
ID: modelC.ID,
Name: modelCupdated.Name,
InputLength: modelC.InputLength,
OutputLength: modelC.OutputLength,
SimulationID: modelC.SimulationID,
SimulatorID: modelCupdated.SimulatorID,
StartParams: modelC.StartParameters,
}
var myModels = []common.SimulationModelResponse{
modelA,
modelB,
}
var msgModels = common.ResponseMsgSimulationModels{
SimulationModels: myModels,
}
var msgModel = common.ResponseMsgSimulationModel{
SimulationModel: modelC_response,
}
var msgModelAUpdated = common.ResponseMsgSimulationModel{
SimulationModel: modelAUpdated,
}
var msgModelAUpdated2 = common.ResponseMsgSimulationModel{
SimulationModel: modelAUpdated2,
}
var msgModelupdated = common.ResponseMsgSimulationModel{
SimulationModel: modelC_responseUpdated,
}
var inSignalA = common.SignalResponse{
Name: "inSignal_A",
Direction: "in",
Index: 0,
Unit: "A",
SimulationModelID: 1,
}
var inSignalB = common.SignalResponse{
Name: "inSignal_B",
Direction: "in",
Index: 1,
Unit: "A",
SimulationModelID: 1,
}
var inSignalC = common.SignalResponse{
Name: "inSignal_C",
Direction: "in",
Index: 2,
Unit: "A",
SimulationModelID: 1,
}
var outSignalA = common.SignalResponse{
Name: "outSignal_A",
Direction: "out",
Index: 0,
Unit: "V",
SimulationModelID: 1,
}
var outSignalB = common.SignalResponse{
Name: "outSignal_B",
Direction: "out",
Index: 1,
Unit: "V",
SimulationModelID: 1,
}
var myInSignals = []common.SignalResponse{
inSignalA,
inSignalB,
}
var myInSignalsUpdated = []common.SignalResponse{
inSignalA,
inSignalB,
inSignalC,
}
var myOutSignals = []common.SignalResponse{
outSignalA,
outSignalB,
}
var msgSignalsEmpty = common.ResponseMsgSignals{
Signals: []common.SignalResponse{},
}
var msgInSignals = common.ResponseMsgSignals{
Signals: myInSignals,
}
var msgInSignalsUpdated = common.ResponseMsgSignals{
Signals: myInSignalsUpdated,
}
var msgOutSignals = common.ResponseMsgSignals{
Signals: myOutSignals,
}
// Test /models endpoints
func TestSimulationModelEndpoints(t *testing.T) {
var token string
var myModels = []common.SimulationModelResponse{common.SimulationModelA_response, common.SimulationModelB_response}
var msgModels = common.ResponseMsgSimulationModels{SimulationModels: myModels}
var msgModel = common.ResponseMsgSimulationModel{SimulationModel: common.SimulationModelC_response}
var msgModelupdated = common.ResponseMsgSimulationModel{SimulationModel: common.SimulationModelCUpdated_response}
db := common.DummyInitDB()
defer db.Close()
common.DummyPopulateDB(db)
@ -225,113 +35,30 @@ func TestSimulationModelEndpoints(t *testing.T) {
RegisterSimulationModelEndpoints(api.Group("/models"))
credjson, err := json.Marshal(cred)
if err != nil {
panic(err)
}
msgOKjson, err := json.Marshal(msgOK)
if err != nil {
panic(err)
}
msgModelsjson, err := json.Marshal(msgModels)
if err != nil {
panic(err)
}
msgModeljson, err := json.Marshal(msgModel)
if err != nil {
panic(err)
}
msgModelupdatedjson, err := json.Marshal(msgModelupdated)
if err != nil {
panic(err)
}
modelCjson, err := json.Marshal(modelC)
if err != nil {
panic(err)
}
modelCupdatedjson, err := json.Marshal(modelCupdated)
if err != nil {
panic(err)
}
msgModelAUpdatedjson, err := json.Marshal(msgModelAUpdated)
if err != nil {
panic(err)
}
msgModelAUpdated2json, err := json.Marshal(msgModelAUpdated2)
if err != nil {
panic(err)
}
msgSignalsEmptyjson, err := json.Marshal(msgSignalsEmpty)
if err != nil {
panic(err)
}
msgInSignalsjson, err := json.Marshal(msgInSignals)
if err != nil {
panic(err)
}
msgInSignalsUpdatedjson, err := json.Marshal(msgInSignalsUpdated)
if err != nil {
panic(err)
}
msgOutSignalsjson, err := json.Marshal(msgOutSignals)
if err != nil {
panic(err)
}
inSignalCjson, err := json.Marshal(inSignalC)
if err != nil {
panic(err)
}
credjson, _ := json.Marshal(common.CredUser)
msgOKjson, _ := json.Marshal(common.MsgOK)
msgModelsjson, _ := json.Marshal(msgModels)
msgModeljson, _ := json.Marshal(msgModel)
msgModelupdatedjson, _ := json.Marshal(msgModelupdated)
token = common.AuthenticateForTest(t, router, "/api/authenticate", "POST", credjson, 200)
// test GET models
common.TestEndpoint(t, router, token, "/api/models?simulationID=1", "GET", nil, 200, string(msgModelsjson))
common.TestEndpoint(t, router, token, "/api/models?scenarioID=1", "GET", nil, 200, msgModelsjson)
// test POST models
common.TestEndpoint(t, router, token, "/api/models", "POST", modelCjson, 200, string(msgOKjson))
common.TestEndpoint(t, router, token, "/api/models", "POST", msgModeljson, 200, msgOKjson)
// test GET models/:ModelID to check if previous POST worked correctly
common.TestEndpoint(t, router, token, "/api/models/3", "GET", nil, 200, string(msgModeljson))
common.TestEndpoint(t, router, token, "/api/models/3", "GET", nil, 200, msgModeljson)
// test PUT models/:ModelID
common.TestEndpoint(t, router, token, "/api/models/3", "PUT", modelCupdatedjson, 200, string(msgOKjson))
common.TestEndpoint(t, router, token, "/api/models/3", "GET", nil, 200, string(msgModelupdatedjson))
common.TestEndpoint(t, router, token, "/api/models/3", "PUT", msgModelupdatedjson, 200, msgOKjson)
common.TestEndpoint(t, router, token, "/api/models/3", "GET", nil, 200, msgModelupdatedjson)
// test DELETE models/:ModelID
common.TestEndpoint(t, router, token, "/api/models/3", "DELETE", nil, 200, string(msgOKjson))
common.TestEndpoint(t, router, token, "/api/models?simulationID=1", "GET", nil, 200, string(msgModelsjson))
// test GET models/:ModelID/signals
common.TestEndpoint(t, router, token, "/api/models/1/signals?direction=in", "GET", nil, 200, string(msgInSignalsjson))
common.TestEndpoint(t, router, token, "/api/models/1/signals?direction=out", "GET", nil, 200, string(msgOutSignalsjson))
// test PUT models/:ModelID/signals
common.TestEndpoint(t, router, token, "/api/models/1/signals", "PUT", inSignalCjson, 200, string(msgOKjson))
common.TestEndpoint(t, router, token, "/api/models/1/signals?direction=in", "GET", nil, 200, string(msgInSignalsUpdatedjson))
// test GET models/:ModelID to check if PUT adapted InputLength correctly
common.TestEndpoint(t, router, token, "/api/models/1", "GET", nil, 200, string(msgModelAUpdatedjson))
// test DELETE models/:ModelID/signals
common.TestEndpoint(t, router, token, "/api/models/1/signals?direction=in", "DELETE", nil, 200, string(msgOKjson))
common.TestEndpoint(t, router, token, "/api/models/1/signals?direction=in", "GET", nil, 200, string(msgSignalsEmptyjson))
common.TestEndpoint(t, router, token, "/api/models/1/signals?direction=out", "GET", nil, 200, string(msgOutSignalsjson))
// test GET models/:ModelID to check if DELETE adapted InputLength correctly
common.TestEndpoint(t, router, token, "/api/models/1", "GET", nil, 200, string(msgModelAUpdated2json))
common.TestEndpoint(t, router, token, "/api/models/3", "DELETE", nil, 200, msgOKjson)
common.TestEndpoint(t, router, token, "/api/models?scenarioID=1", "GET", nil, 200, msgModelsjson)
// TODO add testing for other return codes

View file

@ -1,136 +0,0 @@
package simulator
import (
"net/http"
"github.com/gin-gonic/gin"
"git.rwth-aachen.de/acs/public/villas/villasweb-backend-go/common"
)
func RegisterSimulatorEndpoints(r *gin.RouterGroup) {
r.GET("", GetSimulators)
r.POST("", AddSimulator)
r.PUT("/:simulatorID", UpdateSimulator)
r.GET("/:simulatorID", GetSimulator)
r.DELETE("/:simulatorID", DeleteSimulator)
r.POST("/:simulatorID/action", SendActionToSimulator)
}
// GetSimulators godoc
// @Summary Get all simulators
// @ID GetSimulators
// @Tags simulators
// @Produce json
// @Success 200 {array} common.SimulatorResponse "Simulator parameters requested by user"
// @Failure 401 "Unauthorized Access"
// @Failure 403 "Access forbidden."
// @Failure 404 "Not found"
// @Failure 500 "Internal server error"
// @Router /simulators [get]
func GetSimulators(c *gin.Context) {
db := common.GetDB()
var simulators []common.Simulator
err := db.Order("ID asc").Find(&simulators).Error
if common.ProvideErrorResponse(c, err) {
return
}
serializer := common.SimulatorsSerializer{c, simulators}
c.JSON(http.StatusOK, gin.H{
"simulators": serializer.Response(),
})
}
// AddSimulator godoc
// @Summary Add a simulator
// @ID AddSimulator
// @Accept json
// @Produce json
// @Tags simulators
// @Param inputSimulator body common.SimulatorResponse true "Simulator to be added"
// @Success 200 "OK."
// @Failure 401 "Unauthorized Access"
// @Failure 403 "Access forbidden."
// @Failure 404 "Not found"
// @Failure 500 "Internal server error"
// @Router /simulators [post]
func AddSimulator(c *gin.Context) {
c.JSON(http.StatusOK, gin.H{
"message": "NOT implemented",
})
}
// UpdateSimulator godoc
// @Summary Update a simulator
// @ID UpdateSimulator
// @Tags simulators
// @Accept json
// @Produce json
// @Param inputSimulator body common.SimulatorResponse true "Simulator to be updated"
// @Success 200 "OK."
// @Failure 401 "Unauthorized Access"
// @Failure 403 "Access forbidden."
// @Failure 404 "Not found"
// @Failure 500 "Internal server error"
// @Param simulatorID path int true "Simulator ID"
// @Router /simulators/{simulatorID} [put]
func UpdateSimulator(c *gin.Context) {
c.JSON(http.StatusOK, gin.H{
"message": "NOT implemented",
})
}
// GetSimulator godoc
// @Summary Get simulator
// @ID GetSimulator
// @Produce json
// @Tags simulators
// @Success 200 {object} common.SimulatorResponse "Simulator requested by user"
// @Failure 401 "Unauthorized Access"
// @Failure 403 "Access forbidden."
// @Failure 404 "Not found"
// @Failure 500 "Internal server error"
// @Param simulatorID path int true "Simulator ID"
// @Router /simulators/{simulatorID} [get]
func GetSimulator(c *gin.Context) {
c.JSON(http.StatusOK, gin.H{
"message": "NOT implemented",
})
}
// DeleteSimulator godoc
// @Summary Delete a simulator
// @ID DeleteSimulator
// @Tags simulators
// @Produce json
// @Success 200 "OK."
// @Failure 401 "Unauthorized Access"
// @Failure 403 "Access forbidden."
// @Failure 404 "Not found"
// @Failure 500 "Internal server error"
// @Param simulatorID path int true "Simulator ID"
// @Router /simulators/{simulatorID} [delete]
func DeleteSimulator(c *gin.Context) {
c.JSON(http.StatusOK, gin.H{
"message": "NOT implemented",
})
}
// SendActionToSimulator godoc
// @Summary Send an action to simulator
// @ID SendActionToSimulator
// @Tags simulators
// @Produce json
// @Param inputAction query string true "Action for simulator"
// @Success 200 "OK."
// @Failure 401 "Unauthorized Access"
// @Failure 403 "Access forbidden."
// @Failure 404 "Not found"
// @Failure 500 "Internal server error"
// @Param simulatorID path int true "Simulator ID"
// @Router /simulators/{simulatorID}/action [post]
func SendActionToSimulator(c *gin.Context) {
c.JSON(http.StatusOK, gin.H{
"message": "NOT implemented",
})
}

View file

@ -1,26 +0,0 @@
package simulator
import (
"fmt"
"git.rwth-aachen.de/acs/public/villas/villasweb-backend-go/common"
)
type Simulator struct {
common.Simulator
}
func (s *Simulator) save() error {
db := common.GetDB()
err := db.Create(s).Error
return err
}
func (s *Simulator) ByID(id uint) error {
db := common.GetDB()
err := db.Find(s, id).Error
if err != nil {
return fmt.Errorf("Simulator with id=%v does not exist", id)
}
return nil
}

View file

@ -0,0 +1,363 @@
package simulator
import (
"fmt"
"net/http"
"strconv"
"time"
"github.com/gin-gonic/gin"
"git.rwth-aachen.de/acs/public/villas/villasweb-backend-go/common"
)
func RegisterSimulatorEndpoints(r *gin.RouterGroup) {
r.GET("", getSimulators)
r.POST("", addSimulator)
r.PUT("/:simulatorID", updateSimulator)
r.GET("/:simulatorID", getSimulator)
r.DELETE("/:simulatorID", deleteSimulator)
r.GET("/:simulatorID/models", getModelsOfSimulator)
// register action endpoint only if AMQP client is used
if common.WITH_AMQP == true {
r.POST("/:simulatorID/action", sendActionToSimulator)
}
}
// getSimulators godoc
// @Summary Get all simulators
// @ID getSimulators
// @Tags simulators
// @Produce json
// @Success 200 {array} common.ResponseMsgSimulators "Simulator parameters requested by user"
// @Failure 401 "Unauthorized Access"
// @Failure 403 "Access forbidden."
// @Failure 404 "Not found"
// @Failure 500 "Internal server error"
// @Router /simulators [get]
func getSimulators(c *gin.Context) {
err := common.ValidateRole(c, common.ModelSimulator, common.Read)
if err != nil {
c.JSON(http.StatusUnprocessableEntity, "Access denied (role validation failed).")
return
}
db := common.GetDB()
var simulators []common.Simulator
err = db.Order("ID asc").Find(&simulators).Error
if common.ProvideErrorResponse(c, err) {
return
}
serializer := common.SimulatorsSerializer{c, simulators}
c.JSON(http.StatusOK, gin.H{
"simulators": serializer.Response(),
})
}
// addSimulator godoc
// @Summary Add a simulator
// @ID addSimulator
// @Accept json
// @Produce json
// @Tags simulators
// @Param inputSimulator body common.ResponseMsgSimulator true "Simulator to be added"
// @Success 200 "OK."
// @Failure 401 "Unauthorized Access"
// @Failure 403 "Access forbidden."
// @Failure 404 "Not found"
// @Failure 500 "Internal server error"
// @Router /simulators [post]
func addSimulator(c *gin.Context) {
err := common.ValidateRole(c, common.ModelSimulator, common.Create)
if err != nil {
c.JSON(http.StatusUnprocessableEntity, "Access denied (role validation failed).")
return
}
var newSimulatorData common.ResponseMsgSimulator
err = c.BindJSON(&newSimulatorData)
if err != nil {
errormsg := "Bad request. Error binding form data to JSON: " + err.Error()
c.JSON(http.StatusBadRequest, gin.H{
"error": errormsg,
})
return
}
var newSimulator Simulator
newSimulator.ID = newSimulatorData.Simulator.ID
newSimulator.State = newSimulatorData.Simulator.State
newSimulator.StateUpdateAt = newSimulatorData.Simulator.StateUpdateAt
newSimulator.Modeltype = newSimulatorData.Simulator.Modeltype
newSimulator.UUID = newSimulatorData.Simulator.UUID
newSimulator.Uptime = newSimulatorData.Simulator.Uptime
newSimulator.Host = newSimulatorData.Simulator.Host
newSimulator.RawProperties = newSimulatorData.Simulator.RawProperties
newSimulator.Properties = newSimulatorData.Simulator.Properties
err = newSimulator.save()
if common.ProvideErrorResponse(c, err) == false {
c.JSON(http.StatusOK, gin.H{
"message": "OK.",
})
}
}
// updateSimulator godoc
// @Summary Update a simulator
// @ID updateSimulator
// @Tags simulators
// @Accept json
// @Produce json
// @Param inputSimulator body common.ResponseMsgSimulator true "Simulator to be updated"
// @Success 200 "OK."
// @Failure 401 "Unauthorized Access"
// @Failure 403 "Access forbidden."
// @Failure 404 "Not found"
// @Failure 500 "Internal server error"
// @Param simulatorID path int true "Simulator ID"
// @Router /simulators/{simulatorID} [put]
func updateSimulator(c *gin.Context) {
err := common.ValidateRole(c, common.ModelSimulator, common.Update)
if err != nil {
c.JSON(http.StatusUnprocessableEntity, "Access denied (role validation failed).")
return
}
var modifiedSimulator common.ResponseMsgSimulator
err = c.BindJSON(&modifiedSimulator)
if err != nil {
errormsg := "Bad request. Error unmarshalling data to JSON: " + err.Error()
c.JSON(http.StatusBadRequest, gin.H{
"error": errormsg,
})
return
}
simulatorID, err := strconv.Atoi(c.Param("simulatorID"))
if err != nil {
errormsg := fmt.Sprintf("Bad request. No or incorrect format of simulatorID path parameter")
c.JSON(http.StatusBadRequest, gin.H{
"error": errormsg,
})
return
}
var s Simulator
err = s.ByID(uint(simulatorID))
if common.ProvideErrorResponse(c, err) {
return
}
err = s.update(modifiedSimulator.Simulator)
if common.ProvideErrorResponse(c, err) == false {
c.JSON(http.StatusOK, gin.H{
"message": "OK.",
})
}
}
// getSimulator godoc
// @Summary Get simulator
// @ID getSimulator
// @Produce json
// @Tags simulators
// @Success 200 {object} common.ResponseMsgSimulator "Simulator requested by user"
// @Failure 401 "Unauthorized Access"
// @Failure 403 "Access forbidden."
// @Failure 404 "Not found"
// @Failure 500 "Internal server error"
// @Param simulatorID path int true "Simulator ID"
// @Router /simulators/{simulatorID} [get]
func getSimulator(c *gin.Context) {
err := common.ValidateRole(c, common.ModelSimulator, common.Read)
if err != nil {
c.JSON(http.StatusUnprocessableEntity, "Access denied (role validation failed).")
return
}
simulatorID, err := strconv.Atoi(c.Param("simulatorID"))
if err != nil {
errormsg := fmt.Sprintf("Bad request. No or incorrect format of simulatorID path parameter")
c.JSON(http.StatusBadRequest, gin.H{
"error": errormsg,
})
return
}
var s Simulator
err = s.ByID(uint(simulatorID))
if common.ProvideErrorResponse(c, err) {
return
}
serializer := common.SimulatorSerializer{c, s.Simulator}
c.JSON(http.StatusOK, gin.H{
"simulator": serializer.Response(),
})
}
// deleteSimulator godoc
// @Summary Delete a simulator
// @ID deleteSimulator
// @Tags simulators
// @Produce json
// @Success 200 "OK."
// @Failure 401 "Unauthorized Access"
// @Failure 403 "Access forbidden."
// @Failure 404 "Not found"
// @Failure 500 "Internal server error"
// @Param simulatorID path int true "Simulator ID"
// @Router /simulators/{simulatorID} [delete]
func deleteSimulator(c *gin.Context) {
err := common.ValidateRole(c, common.ModelSimulator, common.Delete)
if err != nil {
c.JSON(http.StatusUnprocessableEntity, "Access denied (role validation failed).")
return
}
simulatorID, err := strconv.Atoi(c.Param("simulatorID"))
if err != nil {
errormsg := fmt.Sprintf("Bad request. No or incorrect format of simulatorID path parameter")
c.JSON(http.StatusBadRequest, gin.H{
"error": errormsg,
})
return
}
var s Simulator
err = s.ByID(uint(simulatorID))
if common.ProvideErrorResponse(c, err) {
return
}
err = s.delete()
if common.ProvideErrorResponse(c, err) {
return
}
c.JSON(http.StatusOK, gin.H{
"message": "OK.",
})
}
// getModelsOfSimulator godoc
// @Summary Get all simulation models in which the simulator is used
// @ID getModelsOfSimulator
// @Tags simulators
// @Produce json
// @Success 200 {object} common.ResponseMsgSimulationModels "Simulation models requested by user"
// @Failure 401 "Unauthorized Access"
// @Failure 403 "Access forbidden."
// @Failure 404 "Not found"
// @Failure 500 "Internal server error"
// @Param simulatorID path int true "Simulator ID"
// @Router /simulators/{simulatorID}/models [get]
func getModelsOfSimulator(c *gin.Context) {
err := common.ValidateRole(c, common.ModelSimulator, common.Read)
if err != nil {
c.JSON(http.StatusUnprocessableEntity, "Access denied (role validation failed).")
return
}
simulatorID, err := strconv.Atoi(c.Param("simulatorID"))
if err != nil {
errormsg := fmt.Sprintf("Bad request. No or incorrect format of simulatorID path parameter")
c.JSON(http.StatusBadRequest, gin.H{
"error": errormsg,
})
return
}
var s Simulator
err = s.ByID(uint(simulatorID))
if common.ProvideErrorResponse(c, err) {
return
}
// get all associated simulation models
allModels, _, err := s.getModels()
if common.ProvideErrorResponse(c, err) {
return
}
serializer := common.SimulationModelsSerializer{c, allModels}
c.JSON(http.StatusOK, gin.H{
"models": serializer.Response(),
})
}
// sendActionToSimulator godoc
// @Summary Send an action to simulator (only available if backend server is started with -amqp parameter)
// @ID sendActionToSimulator
// @Tags simulators
// @Produce json
// @Param inputAction query string true "Action for simulator"
// @Success 200 "OK."
// @Failure 401 "Unauthorized Access"
// @Failure 403 "Access forbidden."
// @Failure 404 "Not found"
// @Failure 500 "Internal server error"
// @Param simulatorID path int true "Simulator ID"
// @Router /simulators/{simulatorID}/action [post]
func sendActionToSimulator(c *gin.Context) {
err := common.ValidateRole(c, common.ModelSimulatorAction, common.Update)
if err != nil {
c.JSON(http.StatusUnprocessableEntity, "Access denied (role validation failed).")
return
}
simulatorID, err := strconv.Atoi(c.Param("simulatorID"))
if err != nil {
errormsg := fmt.Sprintf("Bad request. No or incorrect format of simulatorID path parameter")
c.JSON(http.StatusBadRequest, gin.H{
"error": errormsg,
})
return
}
var actions []common.Action
err = c.BindJSON(&actions)
if err != nil {
errormsg := "Bad request. Error binding form data to JSON: " + err.Error()
c.JSON(http.StatusBadRequest, gin.H{
"error": errormsg,
})
return
}
var s Simulator
err = s.ByID(uint(simulatorID))
if common.ProvideErrorResponse(c, err) {
return
}
now := time.Now()
for _, action := range actions {
if action.When == 0 {
action.When = float32(now.Unix())
}
err = common.SendActionAMQP(action, s.UUID)
if err != nil {
errormsg := "Internal Server Error. Unable to send actions to simulator: " + err.Error()
c.JSON(http.StatusInternalServerError, gin.H{
"error": errormsg,
})
return
}
}
c.JSON(http.StatusOK, gin.H{
"message": "OK.",
})
}

View file

@ -0,0 +1,56 @@
package simulator
import (
"fmt"
"git.rwth-aachen.de/acs/public/villas/villasweb-backend-go/common"
)
type Simulator struct {
common.Simulator
}
func (s *Simulator) save() error {
db := common.GetDB()
err := db.Create(s).Error
return err
}
func (s *Simulator) ByID(id uint) error {
db := common.GetDB()
err := db.Find(s, id).Error
if err != nil {
return fmt.Errorf("Simulator with id=%v does not exist", id)
}
return nil
}
func (s *Simulator) update(modifiedSimulator common.SimulatorResponse) error {
db := common.GetDB()
err := db.Model(s).Updates(modifiedSimulator).Error
return err
}
func (s *Simulator) delete() error {
db := common.GetDB()
no_simulationmodels := db.Model(s).Association("SimulationModels").Count()
if no_simulationmodels > 0 {
return fmt.Errorf("Simulator cannot be deleted as it is still used in SimulationModels (active or dangling)")
}
// delete Simulator from DB (does NOT remain as dangling)
err := db.Delete(s).Error
return err
}
func (s *Simulator) getModels() ([]common.SimulationModel, int, error) {
db := common.GetDB()
var models []common.SimulationModel
err := db.Order("ID asc").Model(s).Related(&models, "SimulationModels").Error
return models, len(models), err
}

View file

@ -0,0 +1,71 @@
package simulator
import (
"encoding/json"
"testing"
"github.com/gin-gonic/gin"
"git.rwth-aachen.de/acs/public/villas/villasweb-backend-go/common"
"git.rwth-aachen.de/acs/public/villas/villasweb-backend-go/routes/user"
)
// Test /simulator endpoints
func TestSimulatorEndpoints(t *testing.T) {
var token string
var myModels = []common.SimulationModelResponse{common.SimulationModelA_response, common.SimulationModelB_response}
var msgModels = common.ResponseMsgSimulationModels{SimulationModels: myModels}
var simulatorC_msg = common.ResponseMsgSimulator{Simulator: common.SimulatorC_response}
var simulatorCupdated_msg = common.ResponseMsgSimulator{Simulator: common.SimulatorCUpdated_response}
var mySimulators = []common.SimulatorResponse{common.SimulatorA_response, common.SimulatorB_response}
var msgSimulators = common.ResponseMsgSimulators{Simulators: mySimulators}
db := common.DummyInitDB()
defer db.Close()
common.DummyPopulateDB(db)
router := gin.Default()
api := router.Group("/api")
// All endpoints require authentication except when someone wants to
// login (POST /authenticate)
user.VisitorAuthenticate(api.Group("/authenticate"))
api.Use(user.Authentication(true))
RegisterSimulatorEndpoints(api.Group("/simulators"))
credjson, _ := json.Marshal(common.CredAdmin)
msgOKjson, _ := json.Marshal(common.MsgOK)
msgModelsjson, _ := json.Marshal(msgModels)
msgSimulatorsjson, _ := json.Marshal(msgSimulators)
msgSimulatorjson, _ := json.Marshal(simulatorC_msg)
simulatorCjson, _ := json.Marshal(simulatorC_msg)
simulatorCupdatedjson, _ := json.Marshal(simulatorCupdated_msg)
token = common.AuthenticateForTest(t, router, "/api/authenticate", "POST", credjson, 200)
// test GET simulators/
common.TestEndpoint(t, router, token, "/api/simulators", "GET", nil, 200, msgSimulatorsjson)
// test POST simulators/
common.TestEndpoint(t, router, token, "/api/simulators", "POST", simulatorCjson, 200, msgOKjson)
// test GET simulators/:SimulatorID
common.TestEndpoint(t, router, token, "/api/simulators/3", "GET", nil, 200, msgSimulatorjson)
// test PUT simulators/:SimulatorID
common.TestEndpoint(t, router, token, "/api/simulators/3", "PUT", simulatorCupdatedjson, 200, msgOKjson)
common.TestEndpoint(t, router, token, "/api/simulators/3", "GET", nil, 200, simulatorCupdatedjson)
// test DELETE simulators/:SimulatorID
common.TestEndpoint(t, router, token, "/api/simulators/3", "DELETE", nil, 200, msgOKjson)
common.TestEndpoint(t, router, token, "/api/simulators", "GET", nil, 200, msgSimulatorsjson)
// test GET simulators/:SimulatorID/models
common.TestEndpoint(t, router, token, "/api/simulators/1/models", "GET", nil, 200, msgModelsjson)
// TODO add tests for other return codes
}

View file

@ -21,9 +21,10 @@ type tokenClaims struct {
}
type AuthResponse struct {
Success bool `json:"success"`
Message string `json:"message"`
Token string `json:"token"`
Success bool `json:"success"`
Message string `json:"message"`
Token string `json:"token"`
User common.UserResponse `json:"user"`
}
func VisitorAuthenticate(r *gin.RouterGroup) {
@ -32,10 +33,10 @@ func VisitorAuthenticate(r *gin.RouterGroup) {
func RegisterUserEndpoints(r *gin.RouterGroup) {
r.POST("", addUser)
r.PUT("/:UserID", updateUser)
r.PUT("/:userID", updateUser)
r.GET("", getUsers)
r.GET("/:UserID", getUser)
r.DELETE("/:UserID", deleteUser)
r.GET("/:userID", getUser)
r.DELETE("/:userID", deleteUser)
}
// authenticate godoc
@ -123,10 +124,13 @@ func authenticate(c *gin.Context) {
return
}
serializer := common.UserSerializer{c, user.User}
c.JSON(http.StatusOK, gin.H{
"success": true,
"message": "Authenticated",
"token": tokenString,
"user": serializer.Response(false),
})
}
@ -242,7 +246,7 @@ func addUser(c *gin.Context) {
// @Tags users
// @Accept json
// @Produce json
// @Param inputUser body common.UserResponse true "User to be updated"
// @Param inputUser body common.User true "User to be updated (anything except for ID can be changed, role can only be change by admin)"
// @Success 200 "OK."
// @Failure 401 "Unauthorized Access"
// @Failure 403 "Access forbidden."
@ -260,7 +264,7 @@ func updateUser(c *gin.Context) {
// Find the user
var user User
toBeUpdatedID, _ := common.UintParamFromCtx(c, "UserID")
toBeUpdatedID, _ := common.UintParamFromCtx(c, "userID")
err = user.ByID(toBeUpdatedID)
if err != nil {
c.JSON(http.StatusNotFound, fmt.Sprintf("%v", err))
@ -324,6 +328,15 @@ func updateUser(c *gin.Context) {
return
}
// To change the role of a user admin role is required
if (updatedUser.Role != user.Role) && (userRole != "Admin") {
c.JSON(http.StatusForbidden, gin.H{
"success": false,
"message": "Invalid authorization. User role can only be changed by Admin",
})
return
}
// Finaly update the user
err = user.update(updatedUser)
if err != nil {
@ -359,7 +372,7 @@ func getUser(c *gin.Context) {
}
var user User
id, _ := common.UintParamFromCtx(c, "UserID")
id, _ := common.UintParamFromCtx(c, "userID")
err = user.ByID(id)
if err != nil {
@ -394,7 +407,7 @@ func deleteUser(c *gin.Context) {
}
var user User
id, _ := common.UintParamFromCtx(c, "UserID")
id, _ := common.UintParamFromCtx(c, "userID")
// Check that the user exist
err = user.ByID(uint(id))

View file

@ -1,196 +0,0 @@
package visualization
import (
"fmt"
"net/http"
"strconv"
"github.com/gin-gonic/gin"
"git.rwth-aachen.de/acs/public/villas/villasweb-backend-go/common"
"git.rwth-aachen.de/acs/public/villas/villasweb-backend-go/routes/simulation"
)
func RegisterVisualizationEndpoints(r *gin.RouterGroup) {
r.GET("", getVisualizations)
r.POST("", addVisualization)
//r.POST("/:visualizationID", cloneVisualization)
r.PUT("/:visualizationID", updateVisualization)
r.GET("/:visualizationID", getVisualization)
r.DELETE("/:visualizationID", deleteVisualization)
}
// getVisualizations godoc
// @Summary Get all visualizations of simulation
// @ID getVisualizations
// @Produce json
// @Tags visualizations
// @Success 200 {array} common.VisualizationResponse "Array of visualizations to which belong to simulation"
// @Failure 401 "Unauthorized Access"
// @Failure 403 "Access forbidden."
// @Failure 404 "Not found"
// @Failure 500 "Internal server error"
// @Param simulationID query int true "Simulation ID"
// @Router /visualizations [get]
func getVisualizations(c *gin.Context) {
simID, err := strconv.Atoi(c.Request.URL.Query().Get("simulationID"))
if err != nil {
errormsg := fmt.Sprintf("Bad request. No or incorrect format of simulationID query parameter")
c.JSON(http.StatusBadRequest, gin.H{
"error": errormsg,
})
return
}
var sim simulation.Simulation
err = sim.ByID(uint(simID))
if common.ProvideErrorResponse(c, err) {
return
}
db := common.GetDB()
var visualizations []common.Visualization
err = db.Order("ID asc").Model(sim).Related(&visualizations, "Visualizations").Error
serializer := common.VisualizationsSerializer{c, visualizations}
c.JSON(http.StatusOK, gin.H{
"visualizations": serializer.Response(),
})
}
// addVisualization godoc
// @Summary Add a visualization to a simulation
// @ID addVisualization
// @Accept json
// @Produce json
// @Tags visualizations
// @Param inputVis body common.VisualizationResponse true "Visualization to be added incl. ID of simulation"
// @Success 200 "OK."
// @Failure 401 "Unauthorized Access"
// @Failure 403 "Access forbidden."
// @Failure 404 "Not found"
// @Failure 500 "Internal server error"
// @Router /visualizations [post]
func addVisualization(c *gin.Context) {
var vis Visualization
err := c.BindJSON(&vis)
if err != nil {
errormsg := "Bad request. Error binding form data to JSON: " + err.Error()
c.JSON(http.StatusBadRequest, gin.H{
"error": errormsg,
})
return
}
// add visualization to DB and add association to simulation
err = vis.addToSimulation(vis.SimulationID)
if common.ProvideErrorResponse(c, err) == false {
c.JSON(http.StatusOK, gin.H{
"message": "OK",
})
}
}
func cloneVisualization(c *gin.Context) {
c.JSON(http.StatusOK, gin.H{
"message": "NOT implemented",
})
}
// updateVisualization godoc
// @Summary Update a visualization
// @ID updateVisualization
// @Tags visualizations
// @Accept json
// @Produce json
// @Param inputVis body common.VisualizationResponse true "Visualization to be updated"
// @Success 200 "OK."
// @Failure 401 "Unauthorized Access"
// @Failure 403 "Access forbidden."
// @Failure 404 "Not found"
// @Failure 500 "Internal server error"
// @Param visualizationID path int true "Visualization ID"
// @Router /visualizations/{visualizationID} [put]
func updateVisualization(c *gin.Context) {
visID, err := common.GetVisualizationID(c)
if err != nil {
return
}
var modifiedVis Visualization
err = c.BindJSON(&modifiedVis)
if err != nil {
errormsg := "Bad request. Error binding form data to JSON: " + err.Error()
c.JSON(http.StatusBadRequest, gin.H{
"error": errormsg,
})
return
}
var vis Visualization
err = vis.ByID(uint(visID))
if common.ProvideErrorResponse(c, err) {
return
}
err = vis.update(modifiedVis)
if common.ProvideErrorResponse(c, err) == false {
c.JSON(http.StatusOK, gin.H{
"message": "OK",
})
}
}
// getVisualization godoc
// @Summary Get a visualization
// @ID getVisualization
// @Tags visualizations
// @Produce json
// @Success 200 {object} common.VisualizationResponse "Requested visualization."
// @Failure 401 "Unauthorized Access"
// @Failure 403 "Access forbidden."
// @Failure 404 "Not found"
// @Failure 500 "Internal server error"
// @Param visualizationID path int true "Visualization ID"
// @Router /visualizations/{visualizationID} [get]
func getVisualization(c *gin.Context) {
visID, err := common.GetVisualizationID(c)
if err != nil {
return
}
var vis Visualization
err = vis.ByID(uint(visID))
if common.ProvideErrorResponse(c, err) {
return
}
serializer := common.VisualizationSerializer{c, vis.Visualization}
c.JSON(http.StatusOK, gin.H{
"visualization": serializer.Response(),
})
}
// deleteVisualization godoc
// @Summary Delete a visualization
// @ID deleteVisualization
// @Tags visualizations
// @Produce json
// @Success 200 "OK."
// @Failure 401 "Unauthorized Access"
// @Failure 403 "Access forbidden."
// @Failure 404 "Not found"
// @Failure 500 "Internal server error"
// @Param visualizationID path int true "Visualization ID"
// @Router /visualizations/{visualizationID} [delete]
func deleteVisualization(c *gin.Context) {
c.JSON(http.StatusOK, gin.H{
"message": "NOT implemented",
})
}

View file

@ -1,57 +0,0 @@
package visualization
import (
"fmt"
"git.rwth-aachen.de/acs/public/villas/villasweb-backend-go/common"
"git.rwth-aachen.de/acs/public/villas/villasweb-backend-go/routes/simulation"
)
type Visualization struct {
common.Visualization
}
func (v *Visualization) save() error {
db := common.GetDB()
err := db.Create(v).Error
return err
}
func (v *Visualization) ByID(id uint) error {
db := common.GetDB()
err := db.Find(v, id).Error
if err != nil {
return fmt.Errorf("Visualization with id=%v does not exist", id)
}
return nil
}
func (v *Visualization) addToSimulation(simID uint) error {
db := common.GetDB()
var sim simulation.Simulation
err := sim.ByID(simID)
if err != nil {
return err
}
// save visualization to DB
err = v.save()
if err != nil {
return err
}
// associate visualization with simulation
err = db.Model(&sim).Association("Visualizations").Append(v).Error
return err
}
func (v *Visualization) update(modifiedVis Visualization) error {
db := common.GetDB()
err := db.Model(v).Update(modifiedVis).Error
if err != nil {
return err
}
return err
}

View file

@ -1,57 +0,0 @@
package widget
import (
"fmt"
"git.rwth-aachen.de/acs/public/villas/villasweb-backend-go/common"
"git.rwth-aachen.de/acs/public/villas/villasweb-backend-go/routes/visualization"
)
type Widget struct {
common.Widget
}
func (w *Widget) save() error {
db := common.GetDB()
err := db.Create(w).Error
return err
}
func (w *Widget) ByID(id uint) error {
db := common.GetDB()
err := db.Find(w, id).Error
if err != nil {
return fmt.Errorf("Widget with id=%v does not exist", id)
}
return nil
}
func (w *Widget) addToVisualization(visID uint) error {
db := common.GetDB()
var vis visualization.Visualization
err := vis.ByID(uint(visID))
if err != nil {
return err
}
// save visualization to DB
err = w.save()
if err != nil {
return err
}
// associate visualization with simulation
err = db.Model(&vis).Association("Widgets").Append(w).Error
return err
}
func (w *Widget) update(modifiedWidget Widget) error {
db := common.GetDB()
err := db.Model(w).Update(modifiedWidget).Error
if err != nil {
return err
}
return err
}

View file

@ -6,46 +6,39 @@ import (
"github.com/gin-gonic/gin"
"git.rwth-aachen.de/acs/public/villas/villasweb-backend-go/common"
"git.rwth-aachen.de/acs/public/villas/villasweb-backend-go/routes/visualization"
"git.rwth-aachen.de/acs/public/villas/villasweb-backend-go/routes/dashboard"
)
func RegisterWidgetEndpoints(r *gin.RouterGroup) {
r.GET("", getWidgets)
r.POST("", addWidget)
//r.POST("/:widgetID", cloneWidget)
r.PUT("/:widgetID", updateWidget)
r.GET("/:widgetID", getWidget)
r.DELETE("/:widgetID", deleteWidget)
}
// getWidgets godoc
// @Summary Get all widgets of visualization
// @Summary Get all widgets of dashboard
// @ID getWidgets
// @Produce json
// @Tags widgets
// @Success 200 {array} common.WidgetResponse "Array of widgets to which belong to visualization"
// @Success 200 {array} common.WidgetResponse "Array of widgets to which belong to dashboard"
// @Failure 401 "Unauthorized Access"
// @Failure 403 "Access forbidden."
// @Failure 404 "Not found"
// @Failure 500 "Internal server error"
// @Param visualizationID query int true "Visualization ID"
// @Param dashboardID query int true "Dashboard ID"
// @Router /widgets [get]
func getWidgets(c *gin.Context) {
visID, err := common.GetVisualizationID(c)
if err != nil {
return
}
var vis visualization.Visualization
err = vis.ByID(uint(visID))
if common.ProvideErrorResponse(c, err) {
ok, dab := dashboard.CheckPermissions(c, common.Read, "query", -1)
if !ok {
return
}
db := common.GetDB()
var widgets []common.Widget
err = db.Order("ID asc").Model(vis).Related(&widgets, "Widgets").Error
err := db.Order("ID asc").Model(dab).Related(&widgets, "Widgets").Error
if common.ProvideErrorResponse(c, err) {
return
}
@ -57,12 +50,12 @@ func getWidgets(c *gin.Context) {
}
// addWidget godoc
// @Summary Add a widget to a visualization
// @Summary Add a widget to a dashboard
// @ID addWidget
// @Accept json
// @Produce json
// @Tags widgets
// @Param inputWidget body common.WidgetResponse true "Widget to be added incl. ID of visualization"
// @Param inputWidget body common.ResponseMsgWidget true "Widget to be added incl. ID of dashboard"
// @Success 200 "OK."
// @Failure 401 "Unauthorized Access"
// @Failure 403 "Access forbidden."
@ -71,8 +64,8 @@ func getWidgets(c *gin.Context) {
// @Router /widgets [post]
func addWidget(c *gin.Context) {
var newWidget Widget
err := c.BindJSON(&newWidget)
var newWidgetData common.ResponseMsgWidget
err := c.BindJSON(&newWidgetData)
if err != nil {
errormsg := "Bad request. Error binding form data to JSON: " + err.Error()
c.JSON(http.StatusBadRequest, gin.H{
@ -81,7 +74,26 @@ func addWidget(c *gin.Context) {
return
}
err = newWidget.addToVisualization(newWidget.VisualizationID)
var newWidget Widget
newWidget.Name = newWidgetData.Widget.Name
newWidget.Type = newWidgetData.Widget.Type
newWidget.Height = newWidgetData.Widget.Height
newWidget.Width = newWidgetData.Widget.Width
newWidget.MinHeight = newWidgetData.Widget.MinHeight
newWidget.MinWidth = newWidgetData.Widget.MinWidth
newWidget.X = newWidgetData.Widget.X
newWidget.Y = newWidgetData.Widget.Y
newWidget.Z = newWidgetData.Widget.Z
newWidget.CustomProperties = newWidgetData.Widget.CustomProperties
newWidget.IsLocked = newWidgetData.Widget.IsLocked
newWidget.DashboardID = newWidgetData.Widget.DashboardID
ok, _ := dashboard.CheckPermissions(c, common.Create, "body", int(newWidget.DashboardID))
if !ok {
return
}
err = newWidget.addToDashboard()
if common.ProvideErrorResponse(c, err) == false {
c.JSON(http.StatusOK, gin.H{
@ -90,19 +102,13 @@ func addWidget(c *gin.Context) {
}
}
func cloneWidget(c *gin.Context) {
c.JSON(http.StatusOK, gin.H{
"message": "NOT implemented",
})
}
// updateWidget godoc
// @Summary Update a widget
// @ID updateWidget
// @Tags widgets
// @Accept json
// @Produce json
// @Param inputWidget body common.WidgetResponse true "Widget to be updated"
// @Param inputWidget body common.ResponseMsgWidget true "Widget to be updated"
// @Success 200 "OK."
// @Failure 401 "Unauthorized Access"
// @Failure 403 "Access forbidden."
@ -112,13 +118,13 @@ func cloneWidget(c *gin.Context) {
// @Router /widgets/{widgetID} [put]
func updateWidget(c *gin.Context) {
widgetID, err := common.GetWidgetID(c)
if err != nil {
ok, w := CheckPermissions(c, common.Update, -1)
if !ok {
return
}
var modifiedWidget Widget
err = c.BindJSON(&modifiedWidget)
var modifiedWidget common.ResponseMsgWidget
err := c.BindJSON(&modifiedWidget)
if err != nil {
errormsg := "Bad request. Error binding form data to JSON: " + err.Error()
c.JSON(http.StatusBadRequest, gin.H{
@ -127,16 +133,10 @@ func updateWidget(c *gin.Context) {
return
}
var w Widget
err = w.ByID(uint(widgetID))
if common.ProvideErrorResponse(c, err) {
return
}
err = w.update(modifiedWidget)
err = w.update(modifiedWidget.Widget)
if common.ProvideErrorResponse(c, err) == false {
c.JSON(http.StatusOK, gin.H{
"message": "OK",
"message": "OK.",
})
}
}
@ -155,14 +155,8 @@ func updateWidget(c *gin.Context) {
// @Router /widgets/{widgetID} [get]
func getWidget(c *gin.Context) {
widgetID, err := common.GetWidgetID(c)
if err != nil {
return
}
var w Widget
err = w.ByID(uint(widgetID))
if common.ProvideErrorResponse(c, err) {
ok, w := CheckPermissions(c, common.Read, -1)
if !ok {
return
}
@ -186,21 +180,17 @@ func getWidget(c *gin.Context) {
// @Router /widgets/{widgetID} [delete]
func deleteWidget(c *gin.Context) {
// widgetID, err := GetWidgetID(c)
// if err != nil {
// return
// }
//
// widget, err := queries.FindWidgetOfVisualization(&visualization, widgetID)
// if common.ProvideErrorResponse(c, err) {
// return
// }
ok, w := CheckPermissions(c, common.Delete, -1)
if !ok {
return
}
// TODO delete files of widget in DB and on disk
// TODO Delete widget itself + association with visualization
err := w.delete()
if common.ProvideErrorResponse(c, err) {
return
}
c.JSON(http.StatusOK, gin.H{
"message": "NOT implemented",
"message": "OK.",
})
}

View file

@ -0,0 +1,85 @@
package widget
import (
"fmt"
"git.rwth-aachen.de/acs/public/villas/villasweb-backend-go/common"
"git.rwth-aachen.de/acs/public/villas/villasweb-backend-go/routes/dashboard"
)
type Widget struct {
common.Widget
}
func (w *Widget) save() error {
db := common.GetDB()
err := db.Create(w).Error
return err
}
func (w *Widget) ByID(id uint) error {
db := common.GetDB()
err := db.Find(w, id).Error
if err != nil {
return fmt.Errorf("Widget with id=%v does not exist", id)
}
return nil
}
func (w *Widget) addToDashboard() error {
db := common.GetDB()
var dab dashboard.Dashboard
err := dab.ByID(uint(w.DashboardID))
if err != nil {
return err
}
// save widget to DB
err = w.save()
if err != nil {
return err
}
// associate dashboard with simulation
err = db.Model(&dab).Association("Widgets").Append(w).Error
return err
}
func (w *Widget) update(modifiedWidget common.WidgetResponse) error {
db := common.GetDB()
err := db.Model(w).Updates(map[string]interface{}{
"Name": modifiedWidget.Name,
"Type": modifiedWidget.Type,
"Width": modifiedWidget.Width,
"Height": modifiedWidget.Height,
"MinWidth": modifiedWidget.MinWidth,
"MinHeight": modifiedWidget.MinHeight,
"X": modifiedWidget.X,
"Y": modifiedWidget.Y,
"Z": modifiedWidget.Z,
"IsLocked": modifiedWidget.IsLocked,
"CustomProperties": modifiedWidget.CustomProperties,
}).Error
return err
}
func (w *Widget) delete() error {
db := common.GetDB()
var dab dashboard.Dashboard
err := dab.ByID(w.DashboardID)
if err != nil {
return err
}
// 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
// TODO: What about files that belong to a widget? Keep them or remove them here?
return err
}

View file

@ -0,0 +1,49 @@
package widget
import (
"fmt"
"net/http"
"strconv"
"github.com/gin-gonic/gin"
"git.rwth-aachen.de/acs/public/villas/villasweb-backend-go/common"
"git.rwth-aachen.de/acs/public/villas/villasweb-backend-go/routes/dashboard"
)
func CheckPermissions(c *gin.Context, operation common.CRUD, widgetIDBody int) (bool, Widget) {
var w Widget
err := common.ValidateRole(c, common.ModelWidget, operation)
if err != nil {
c.JSON(http.StatusUnprocessableEntity, "Access denied (role validation failed).")
return false, w
}
var widgetID int
if widgetIDBody < 0 {
widgetID, err = strconv.Atoi(c.Param("widgetID"))
if err != nil {
errormsg := fmt.Sprintf("Bad request. No or incorrect format of widgetID path parameter")
c.JSON(http.StatusBadRequest, gin.H{
"error": errormsg,
})
return false, w
}
} else {
widgetID = widgetIDBody
}
err = w.ByID(uint(widgetID))
if common.ProvideErrorResponse(c, err) {
return false, w
}
ok, _ := dashboard.CheckPermissions(c, operation, "body", int(w.DashboardID))
if !ok {
return false, w
}
return true, w
}

View file

@ -0,0 +1,65 @@
package widget
import (
"encoding/json"
"testing"
"github.com/gin-gonic/gin"
"git.rwth-aachen.de/acs/public/villas/villasweb-backend-go/common"
"git.rwth-aachen.de/acs/public/villas/villasweb-backend-go/routes/user"
)
// Test /widgets endpoints
func TestWidgetEndpoints(t *testing.T) {
var token string
var myWidgets = []common.WidgetResponse{common.WidgetA_response, common.WidgetB_response}
var msgWidgets = common.ResponseMsgWidgets{Widgets: myWidgets}
var msgWdg = common.ResponseMsgWidget{Widget: common.WidgetC_response}
var msgWdgupdated = common.ResponseMsgWidget{Widget: common.WidgetCUpdated_response}
db := common.DummyInitDB()
defer db.Close()
common.DummyPopulateDB(db)
router := gin.Default()
api := router.Group("/api")
// All endpoints require authentication except when someone wants to
// login (POST /authenticate)
user.VisitorAuthenticate(api.Group("/authenticate"))
api.Use(user.Authentication(true))
RegisterWidgetEndpoints(api.Group("/widgets"))
credjson, _ := json.Marshal(common.CredUser)
msgOKjson, _ := json.Marshal(common.MsgOK)
msgWidgetsjson, _ := json.Marshal(msgWidgets)
msgWdgjson, _ := json.Marshal(msgWdg)
msgWdgupdatedjson, _ := json.Marshal(msgWdgupdated)
token = common.AuthenticateForTest(t, router, "/api/authenticate", "POST", credjson, 200)
// test GET widgets
common.TestEndpoint(t, router, token, "/api/widgets?dashboardID=1", "GET", nil, 200, msgWidgetsjson)
// test POST widgets
common.TestEndpoint(t, router, token, "/api/widgets", "POST", msgWdgjson, 200, msgOKjson)
// test GET widgets/:widgetID to check if previous POST worked correctly
common.TestEndpoint(t, router, token, "/api/widgets/3", "GET", nil, 200, msgWdgjson)
// test PUT widgets/:widgetID
common.TestEndpoint(t, router, token, "/api/widgets/3", "PUT", msgWdgupdatedjson, 200, msgOKjson)
common.TestEndpoint(t, router, token, "/api/widgets/3", "GET", nil, 200, msgWdgupdatedjson)
// test DELETE widgets/:widgetID
common.TestEndpoint(t, router, token, "/api/widgets/3", "DELETE", nil, 200, msgOKjson)
common.TestEndpoint(t, router, token, "/api/widgets?dashboardID=1", "GET", nil, 200, msgWidgetsjson)
// TODO add testing for other return codes
}

View file

@ -1,18 +1,22 @@
package main
import (
"git.rwth-aachen.de/acs/public/villas/villasweb-backend-go/routes/file"
"fmt"
"time"
"github.com/gin-gonic/gin"
"github.com/swaggo/gin-swagger"
"github.com/swaggo/gin-swagger/swaggerFiles"
"git.rwth-aachen.de/acs/public/villas/villasweb-backend-go/common"
_ "git.rwth-aachen.de/acs/public/villas/villasweb-backend-go/doc/api" // doc/api folder is used by Swag CLI, you have to import it
"git.rwth-aachen.de/acs/public/villas/villasweb-backend-go/routes/simulation"
"git.rwth-aachen.de/acs/public/villas/villasweb-backend-go/routes/dashboard"
"git.rwth-aachen.de/acs/public/villas/villasweb-backend-go/routes/file"
"git.rwth-aachen.de/acs/public/villas/villasweb-backend-go/routes/scenario"
"git.rwth-aachen.de/acs/public/villas/villasweb-backend-go/routes/signal"
"git.rwth-aachen.de/acs/public/villas/villasweb-backend-go/routes/simulationmodel"
"git.rwth-aachen.de/acs/public/villas/villasweb-backend-go/routes/simulator"
"git.rwth-aachen.de/acs/public/villas/villasweb-backend-go/routes/user"
"git.rwth-aachen.de/acs/public/villas/villasweb-backend-go/routes/visualization"
"git.rwth-aachen.de/acs/public/villas/villasweb-backend-go/routes/widget"
)
@ -50,9 +54,10 @@ func main() {
api.Use(user.Authentication(true))
simulation.RegisterSimulationEndpoints(api.Group("/simulations"))
scenario.RegisterScenarioEndpoints(api.Group("/scenarios"))
simulationmodel.RegisterSimulationModelEndpoints(api.Group("/models"))
visualization.RegisterVisualizationEndpoints(api.Group("/visualizations"))
signal.RegisterSignalEndpoints(api.Group("/signals"))
dashboard.RegisterDashboardEndpoints(api.Group("/dashboards"))
widget.RegisterWidgetEndpoints(api.Group("/widgets"))
file.RegisterFileEndpoints(api.Group("/files"))
user.RegisterUserEndpoints(api.Group("/users"))
@ -60,6 +65,29 @@ func main() {
r.GET("swagger/*any", ginSwagger.WrapHandler(swaggerFiles.Handler))
if common.WITH_AMQP == true {
fmt.Println("Starting AMQP client")
err := common.ConnectAMQP("amqp://localhost")
if err != nil {
panic(err)
}
// Periodically call the Ping function to check which simulators are still there
ticker := time.NewTicker(10 * time.Second)
go func() {
for {
select {
case <-ticker.C:
err = common.PingAMQP()
if err != nil {
fmt.Println("AMQP Error: ", err.Error())
}
}
}
}()
}
// server at port 4000 to match frontend's redirect path
r.Run(":4000")
}