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_USER: deploy
DEPLOY_HOST: acs-os-fein-website DEPLOY_HOST: acs-os-fein-website
DEPLOY_PATH: /var/www/villas/api/web/ DEPLOY_PATH: /var/www/villas/api/web/
TEST_FOLDER: routes/scenario
stages: stages:
- prepare - prepare
@ -64,7 +65,7 @@ test:api:bundle:
- doc/api/index.html - doc/api/index.html
- doc/api/swagger.json - doc/api/swagger.json
test:backend:database: test:database:
stage: test stage: test
tags: tags:
- docker - docker
@ -79,7 +80,7 @@ test:backend:database:
dependencies: dependencies:
- build:backend - build:backend
test:backend:endpoints: test:scenario:
stage: test stage: test
tags: tags:
- docker - docker
@ -89,13 +90,40 @@ test:backend:endpoints:
- go mod tidy - go mod tidy
- go get -u github.com/swaggo/swag/cmd/swag - go get -u github.com/swaggo/swag/cmd/swag
- ~/go/bin/swag init -p pascalcase -g "start.go" -o "./doc/api/" - ~/go/bin/swag init -p pascalcase -g "start.go" -o "./doc/api/"
- cd routes/simulation - cd ${TEST_FOLDER}
- go test -v -args -dbhost=/var/run/postgresql
- cd ../simulationmodel
- go test -v -args -dbhost=/var/run/postgresql - go test -v -args -dbhost=/var/run/postgresql
dependencies: dependencies:
- build:backend - 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 # 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" "fmt"
"github.com/jinzhu/gorm" "github.com/jinzhu/gorm"
_ "github.com/jinzhu/gorm/dialects/postgres" _ "github.com/jinzhu/gorm/dialects/postgres"
"golang.org/x/crypto/bcrypt"
"log" "log"
) )
@ -13,6 +12,7 @@ var DB_HOST string
var DB_NAME string var DB_NAME string
var DB_DUMMY string var DB_DUMMY string
var DB_SSLMODE string var DB_SSLMODE string
var WITH_AMQP bool
var DBpool *gorm.DB 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_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_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.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() flag.Parse()
fmt.Println("DB_HOST has value ", DB_HOST) fmt.Println("DB_HOST has value ", DB_HOST)
fmt.Println("DB_NAME has value ", DB_NAME) fmt.Println("DB_NAME has value ", DB_NAME)
fmt.Println("DB_DUMMY has value ", DB_DUMMY) fmt.Println("DB_DUMMY has value ", DB_DUMMY)
fmt.Println("DB_SSLMODE has value ", DB_SSLMODE) fmt.Println("DB_SSLMODE has value ", DB_SSLMODE)
fmt.Println("WITH_AMQP has value ", WITH_AMQP)
} }
// Initialize connection to the database // Initialize connection to the database
@ -57,9 +59,9 @@ func DropTables(db *gorm.DB) {
db.DropTableIfExists(&Signal{}) db.DropTableIfExists(&Signal{})
db.DropTableIfExists(&SimulationModel{}) db.DropTableIfExists(&SimulationModel{})
db.DropTableIfExists(&File{}) db.DropTableIfExists(&File{})
db.DropTableIfExists(&Simulation{}) db.DropTableIfExists(&Scenario{})
db.DropTableIfExists(&User{}) db.DropTableIfExists(&User{})
db.DropTableIfExists(&Visualization{}) db.DropTableIfExists(&Dashboard{})
db.DropTableIfExists(&Widget{}) db.DropTableIfExists(&Widget{})
} }
@ -69,9 +71,9 @@ func MigrateModels(db *gorm.DB) {
db.AutoMigrate(&Signal{}) db.AutoMigrate(&Signal{})
db.AutoMigrate(&SimulationModel{}) db.AutoMigrate(&SimulationModel{})
db.AutoMigrate(&File{}) db.AutoMigrate(&File{})
db.AutoMigrate(&Simulation{}) db.AutoMigrate(&Scenario{})
db.AutoMigrate(&User{}) db.AutoMigrate(&User{})
db.AutoMigrate(&Visualization{}) db.AutoMigrate(&Dashboard{})
db.AutoMigrate(&Widget{}) db.AutoMigrate(&Widget{})
} }
@ -94,109 +96,84 @@ func DummyPopulateDB(test_db *gorm.DB) {
MigrateModels(test_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"} // Users
simr_B := Simulator{UUID: "2", Host: "Host_B"} checkErr(test_db.Create(&User0).Error) // Admin
checkErr(test_db.Create(&simr_A).Error) checkErr(test_db.Create(&UserA).Error) // Normal User
checkErr(test_db.Create(&simr_B).Error) checkErr(test_db.Create(&UserB).Error) // Normal User
outSig_A := Signal{Name: "outSignal_A", Direction: "out", Index: 0, Unit: "V"} // Simulators
outSig_B := Signal{Name: "outSignal_B", Direction: "out", Index: 1, Unit: "V"} checkErr(test_db.Create(&SimulatorA).Error)
inSig_A := Signal{Name: "inSignal_A", Direction: "in", Index: 0, Unit: "A"} checkErr(test_db.Create(&SimulatorB).Error)
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)
mo_A := SimulationModel{Name: "SimulationModel_A"} // Scenarios
mo_B := SimulationModel{Name: "SimulationModel_B"} checkErr(test_db.Create(&ScenarioA).Error)
checkErr(test_db.Create(&mo_A).Error) checkErr(test_db.Create(&ScenarioB).Error)
checkErr(test_db.Create(&mo_B).Error)
file_A := File{Name: "File_A"} // Signals
file_B := File{Name: "File_B"} checkErr(test_db.Create(&OutSignalA).Error)
checkErr(test_db.Create(&file_A).Error) checkErr(test_db.Create(&OutSignalB).Error)
checkErr(test_db.Create(&file_B).Error) checkErr(test_db.Create(&InSignalA).Error)
checkErr(test_db.Create(&InSignalB).Error)
simn_A := Simulation{Name: "Simulation_A"} // Simulation Models
simn_B := Simulation{Name: "Simulation_B"} checkErr(test_db.Create(&SimulationModelA).Error)
checkErr(test_db.Create(&simn_A).Error) checkErr(test_db.Create(&SimulationModelB).Error)
checkErr(test_db.Create(&simn_B).Error)
// Hash passwords with bcrypt algorithm // Dashboards
var bcryptCost = 10 checkErr(test_db.Create(&DashboardA).Error)
checkErr(test_db.Create(&DashboardB).Error)
pw_0, err := // Files
bcrypt.GenerateFromPassword([]byte("xyz789"), bcryptCost) checkErr(test_db.Create(&FileA).Error)
checkErr(err) checkErr(test_db.Create(&FileB).Error)
checkErr(test_db.Create(&FileC).Error)
checkErr(test_db.Create(&FileD).Error)
pw_A, err := // Widgets
bcrypt.GenerateFromPassword([]byte("abc123"), bcryptCost) checkErr(test_db.Create(&WidgetA).Error)
checkErr(err) checkErr(test_db.Create(&WidgetB).Error)
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)
// Associations between models // Associations between models
// For `belongs to` use the model with id=1 // For `belongs to` use the model with id=1
// For `has many` use the models with id=1 and id=2 // For `has many` use the models with id=1 and id=2
// User HM Simulations, Simulation HM Users (Many-to-Many) // User HM Scenarios, Scenario HM Users (Many-to-Many)
checkErr(test_db.Model(&simn_A).Association("Users").Append(&usr_A).Error) checkErr(test_db.Model(&ScenarioA).Association("Users").Append(&UserA).Error)
checkErr(test_db.Model(&simn_A).Association("Users").Append(&usr_B).Error) checkErr(test_db.Model(&ScenarioA).Association("Users").Append(&UserB).Error)
checkErr(test_db.Model(&simn_B).Association("Users").Append(&usr_A).Error) checkErr(test_db.Model(&ScenarioB).Association("Users").Append(&UserA).Error)
checkErr(test_db.Model(&simn_B).Association("Users").Append(&usr_B).Error) checkErr(test_db.Model(&ScenarioB).Association("Users").Append(&UserB).Error)
// Simulation HM SimulationModels // Scenario HM SimulationModels
checkErr(test_db.Model(&simn_A).Association("SimulationModels").Append(&mo_A).Error) checkErr(test_db.Model(&ScenarioA).Association("SimulationModels").Append(&SimulationModelA).Error)
checkErr(test_db.Model(&simn_A).Association("SimulationModels").Append(&mo_B).Error) checkErr(test_db.Model(&ScenarioA).Association("SimulationModels").Append(&SimulationModelB).Error)
// Simulation HM Visualizations // Scenario HM Dashboards
checkErr(test_db.Model(&simn_A).Association("Visualizations").Append(&vis_A).Error) checkErr(test_db.Model(&ScenarioA).Association("Dashboards").Append(&DashboardA).Error)
checkErr(test_db.Model(&simn_A).Association("Visualizations").Append(&vis_B).Error) checkErr(test_db.Model(&ScenarioA).Association("Dashboards").Append(&DashboardB).Error)
// Visualization HM Widget // Dashboard HM Widget
checkErr(test_db.Model(&vis_A).Association("Widgets").Append(&widg_A).Error) checkErr(test_db.Model(&DashboardA).Association("Widgets").Append(&WidgetA).Error)
checkErr(test_db.Model(&vis_A).Association("Widgets").Append(&widg_B).Error) checkErr(test_db.Model(&DashboardA).Association("Widgets").Append(&WidgetB).Error)
// SimulationModel HM Signals // SimulationModel HM Signals
checkErr(test_db.Model(&mo_A).Association("InputMapping").Append(&inSig_A).Error) checkErr(test_db.Model(&SimulationModelA).Association("InputMapping").Append(&InSignalA).Error)
checkErr(test_db.Model(&mo_A).Association("InputMapping").Append(&inSig_B).Error) checkErr(test_db.Model(&SimulationModelA).Association("InputMapping").Append(&InSignalB).Error)
checkErr(test_db.Model(&mo_A).Association("OutputMapping").Append(&outSig_A).Error) checkErr(test_db.Model(&SimulationModelA).Association("OutputMapping").Append(&OutSignalA).Error)
checkErr(test_db.Model(&mo_A).Association("OutputMapping").Append(&outSig_B).Error) checkErr(test_db.Model(&SimulationModelA).Association("OutputMapping").Append(&OutSignalB).Error)
// SimulationModel HM Files // SimulationModel HM Files
checkErr(test_db.Model(&mo_A).Association("Files").Append(&file_A).Error) checkErr(test_db.Model(&SimulationModelA).Association("Files").Append(&FileC).Error)
checkErr(test_db.Model(&mo_A).Association("Files").Append(&file_B).Error) checkErr(test_db.Model(&SimulationModelA).Association("Files").Append(&FileD).Error)
// Simulator BT SimulationModel // Simulator HM SimulationModels
checkErr(test_db.Model(&mo_A).Association("Simulator").Append(&simr_A).Error) checkErr(test_db.Model(&SimulatorA).Association("SimulationModels").Append(&SimulationModelA).Error)
checkErr(test_db.Model(&mo_B).Association("Simulator").Append(&simr_A).Error) checkErr(test_db.Model(&SimulatorA).Association("SimulationModels").Append(&SimulationModelB).Error)
// Widget HM Files // Widget HM Files
checkErr(test_db.Model(&widg_A).Association("Files").Append(&file_A).Error) checkErr(test_db.Model(&WidgetA).Association("Files").Append(&FileA).Error)
checkErr(test_db.Model(&widg_A).Association("Files").Append(&file_B).Error) checkErr(test_db.Model(&WidgetA).Association("Files").Append(&FileB).Error)
} }
// Erase tables and glose the testdb // Erase tables and glose the testdb

View file

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

View file

@ -1,13 +1,13 @@
package common package common
import ( import (
"time" "github.com/jinzhu/gorm"
"github.com/jinzhu/gorm/dialects/postgres"
) )
// User data model // User data model
type User struct { type User struct {
// ID of user gorm.Model
ID uint `gorm:"primary_key;auto_increment"`
// Username of user // Username of user
Username string `gorm:"unique;not null"` Username string `gorm:"unique;not null"`
// Password of user // Password of user
@ -16,44 +16,40 @@ type User struct {
Mail string `gorm:"default:''"` Mail string `gorm:"default:''"`
// Role of user // Role of user
Role string `gorm:"default:'user'"` Role string `gorm:"default:'user'"`
// Simulations to which user has access // Scenarios to which user has access
Simulations []*Simulation `gorm:"many2many:user_simulations"` Scenarios []*Scenario `gorm:"many2many:user_scenarios"`
} }
// Simulation data model // Scenario data model
type Simulation struct { type Scenario struct {
// ID of simulation gorm.Model
ID uint `gorm:"primary_key;auto_increment"` // Name of scenario
// Name of simulation
Name string `gorm:"not null"` Name string `gorm:"not null"`
// Running state of simulation // Running state of scenario
Running bool `gorm:"default:false"` Running bool `gorm:"default:false"`
// Start parameters of simulation as JSON string // Start parameters of scenario as JSON
StartParameters string StartParameters postgres.Jsonb
// Users that have access to the simulation // Users that have access to the scenario
Users []*User `gorm:"not null;many2many:user_simulations"` Users []*User `gorm:"not null;many2many:user_scenarios"`
// SimulationModels that belong to the simulation // SimulationModels that belong to the scenario
SimulationModels []SimulationModel `gorm:"foreignkey:SimulationID"` SimulationModels []SimulationModel `gorm:"foreignkey:ScenarioID"`
// Visualizations that belong to the simulation // Dashboards that belong to the Scenario
Visualizations []Visualization `gorm:"foreignkey:SimulationID"` Dashboards []Dashboard `gorm:"foreignkey:ScenarioID"`
} }
// SimulationModel data model // SimulationModel data model
type SimulationModel struct { type SimulationModel struct {
// ID of simulation model gorm.Model
ID uint `gorm:"primary_key;auto_increment"`
// Name of simulation model // Name of simulation model
Name string `gorm:"not null"` Name string `gorm:"not null"`
// Number of output signals // Number of output signals
OutputLength int `gorm:"default:1"` OutputLength int `gorm:"default:1"`
// Number of input signals // Number of input signals
InputLength int `gorm:"default:1"` InputLength int `gorm:"default:1"`
// Start parameters of simulation model as JSON string // Start parameters of simulation model as JSON
StartParameters string StartParameters postgres.Jsonb
// ID of simulation to which simulation model belongs // ID of Scenario to which simulation model belongs
SimulationID uint ScenarioID uint
// Simulator associated with simulation model
Simulator Simulator
// ID of simulator associated with simulation model // ID of simulator associated with simulation model
SimulatorID uint SimulatorID uint
// Mapping of output signals of the simulation model, order of signals is important // Mapping of output signals of the simulation model, order of signals is important
@ -66,8 +62,7 @@ type SimulationModel struct {
// Signal data model // Signal data model
type Signal struct { type Signal struct {
// ID of simulation model gorm.Model
ID uint `gorm:"primary_key;auto_increment"`
// Name of Signal // Name of Signal
Name string Name string
// Unit of Signal // Unit of Signal
@ -82,44 +77,43 @@ type Signal struct {
// Simulator data model // Simulator data model
type Simulator struct { type Simulator struct {
// ID of the simulator gorm.Model
ID uint `gorm:"primary_key;auto_increment"`
// UUID of the simulator // UUID of the simulator
UUID string `gorm:"unique;not null"` UUID string `gorm:"not null",json:"uuid"`
// Host if the simulator // Host if the simulator
Host string `gorm:"default:''"` Host string `gorm:"default:''",json:"host"`
// Model type supported by the simulator // Model type supported by the simulator
Modeltype string `gorm:"default:''"` Modeltype string `gorm:"default:''",json:"modelType"`
// Uptime of the simulator // Uptime of the simulator
Uptime int `gorm:"default:0"` Uptime int `gorm:"default:0",json:"uptime"`
// State of the simulator // State of the simulator
State string `gorm:"default:''"` State string `gorm:"default:''",json:"state"`
// Time of last state update // Time of last state update
StateUpdateAt time.Time StateUpdateAt string `gorm:"default:''",json:"stateUpdateAt"`
// Properties of simulator as JSON string // Properties of simulator as JSON string
Properties string Properties postgres.Jsonb `json:"properties"`
// Raw properties of simulator as JSON string // 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 // Dashboard data model
type Visualization struct { type Dashboard struct {
// ID of visualization gorm.Model
ID uint `gorm:"primary_key;auto_increment"` // Name of dashboard
// Name of visualization
Name string `gorm:"not null"` Name string `gorm:"not null"`
// Grid of visualization // Grid of dashboard
Grid int `gorm:"default:15"` Grid int `gorm:"default:15"`
// ID of simulation to which visualization belongs // ID of scenario to which dashboard belongs
SimulationID uint `gorm:"not null"` ScenarioID uint
// Widgets that belong to visualization // Widgets that belong to dashboard
Widgets []Widget `gorm:"foreignkey:VisualizationID"` Widgets []Widget `gorm:"foreignkey:DashboardID"`
} }
// Widget data model // Widget data model
type Widget struct { type Widget struct {
// ID of widget gorm.Model
ID uint `gorm:"primary_key;auto_increment"`
// Name of widget // Name of widget
Name string `gorm:"not null"` Name string `gorm:"not null"`
// Type of widget // Type of widget
@ -141,33 +135,38 @@ type Widget struct {
// Locked state of widget // Locked state of widget
IsLocked bool `gorm:"default:false"` IsLocked bool `gorm:"default:false"`
// Custom properties of widget as JSON string // Custom properties of widget as JSON string
CustomProperties string CustomProperties postgres.Jsonb
// ID of visualization to which widget belongs // ID of dashboard to which widget belongs
VisualizationID uint `gorm:"not null"` DashboardID uint
// Files that belong to widget (for example images) // Files that belong to widget (for example images)
Files []File `gorm:"foreignkey:WidgetID"` Files []File `gorm:"foreignkey:WidgetID"`
} }
// File data model // File data model
type File struct { type File struct {
// ID of file gorm.Model
ID uint `gorm:"primary_key;auto_increment"`
// Name of file // Name of file
Name string `gorm:"not null"` Name string `gorm:"not null"`
// Path at which file is saved at server side
Path string `gorm:"not null"`
// Type of file (MIME type) // Type of file (MIME type)
Type string `gorm:"not null"` Type string
// Size of file (in byte) // Size of file (in byte)
Size uint `gorm:"not null"` Size uint
// Height of image (only needed in case of image) // Height of image (only needed in case of image)
ImageHeight uint ImageHeight uint
// Width of image (only needed in case of image) // Width of image (only needed in case of image)
ImageWidth uint ImageWidth uint
// Last modification time of file // Last modification time of file
Date time.Time Date string
// ID of model to which file belongs // ID of model to which file belongs
SimulationModelID uint `gorm:""` SimulationModelID uint
// ID of widget to which file belongs // ID of widget to which file belongs
WidgetID uint `gorm:""` WidgetID uint
// 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 package common
import ( import "github.com/jinzhu/gorm/dialects/postgres"
"time"
)
type UserResponse struct { type UserResponse struct {
Username string `json:"Username"` Username string `json:"username"`
Role string `json:"Role"` Role string `json:"role"`
Mail string `json:"Mail"` Mail string `json:"mail"`
ID uint `json:"id"`
} }
type SimulationResponse struct { type ScenarioResponse struct {
Name string `json:"Name"` Name string `json:"name"`
ID uint `json:"SimulationID"` ID uint `json:"id"`
Running bool `json:"Running"` Running bool `json:"running"`
StartParams string `json:"Starting Parameters"` StartParameters postgres.Jsonb `json:"startParameters"`
} }
type SimulationModelResponse struct { type SimulationModelResponse struct {
ID uint `json:"ID"` ID uint `json:"id"`
Name string `json:"Name"` Name string `json:"name"`
OutputLength int `json:"OutputLength"` OutputLength int `json:"outputLength"`
InputLength int `json:"InputLength"` InputLength int `json:"inputLength"`
SimulationID uint `json:"SimulationID"` ScenarioID uint `json:"scenarioID"`
SimulatorID uint `json:"SimulatorID"` SimulatorID uint `json:"simulatorID"`
StartParams string `json:"StartParams"` StartParameters postgres.Jsonb `json:"startParameters"`
} }
type SimulatorResponse struct { type SimulatorResponse struct {
UUID string `json:"UUID"` ID uint `json:"id"`
Host string `json:"Host"` UUID string `json:"uuid"`
ModelType string `json:"ModelType"` Host string `json:"host"`
Uptime int `json:"Uptime"` Modeltype string `json:"modelType"`
State string `json:"State"` Uptime int `json:"uptime"`
StateUpdateAt time.Time `json:"StateUpdateAt"` State string `json:"state"`
Properties string `json:"Properties"` StateUpdateAt string `json:"stateUpdateAt"`
RawProperties string `json:"RawProperties"` Properties postgres.Jsonb `json:"properties"`
RawProperties postgres.Jsonb `json:"rawProperties"`
} }
type VisualizationResponse struct { type DashboardResponse struct {
Name string `json:"Name"` ID uint `json:"id"`
Grid int `json:"Grid"` Name string `json:"name"`
SimulationID uint `json:"SimulationID"` Grid int `json:"grid"`
ScenarioID uint `json:"scenarioID"`
} }
type WidgetResponse struct { type WidgetResponse struct {
Name string `json:"Name"` ID uint `json:"id"`
Type string `json:"Type"` Name string `json:"name"`
Width uint `json:"Width"` Type string `json:"type"`
Height uint `json:"Height"` Width uint `json:"width"`
MinWidth uint `json:"MinWidth"` Height uint `json:"height"`
MinHeight uint `json:"MinHeight"` MinWidth uint `json:"minWidth"`
X int `json:"X"` MinHeight uint `json:"minHeight"`
Y int `json:"Y"` X int `json:"x"`
Z int `json:"Z"` Y int `json:"y"`
VisualizationID uint `json:"VisualizationID"` Z int `json:"z"`
IsLocked bool `json:"IsLocked"` DashboardID uint `json:"dashboardID"`
CustomProperties string `json:"CustomProperties"` IsLocked bool `json:"isLocked"`
CustomProperties postgres.Jsonb `json:"customProperties"`
} }
type FileResponse struct { type FileResponse struct {
Name string `json:"Name"` Name string `json:"name"`
ID uint `json:"FileID"` ID uint `json:"id"`
Path string `json:"Path"` Type string `json:"type"`
Type string `json:"Type"` Size uint `json:"size"`
Size uint `json:"Size"` ImageWidth uint `json:"imageHeight"`
H uint `json:"ImageHeight"` ImageHeight uint `json:"imageWidth"`
W uint `json:"ImageWidth"` Date string `json:"date"`
Date time.Time `json:"Date"` WidgetID uint `json:"widgetID"`
SimulationModelID uint `json:"simulationModelID"`
} }
type SignalResponse struct { type SignalResponse struct {
Name string `json:"Name"` Name string `json:"name"`
Unit string `json:"Unit"` Unit string `json:"unit"`
Index uint `json:"Index"` Index uint `json:"index"`
Direction string `json:"Direction"` Direction string `json:"direction"`
SimulationModelID uint `json:"SimulationModelID"` SimulationModelID uint `json:"simulationModelID"`
} }
// Response messages // Response messages
@ -92,12 +95,12 @@ type ResponseMsgUser struct {
User UserResponse `json:"user"` User UserResponse `json:"user"`
} }
type ResponseMsgSimulations struct { type ResponseMsgScenarios struct {
Simulations []SimulationResponse `json:"simulations"` Scenarios []ScenarioResponse `json:"scenarios"`
} }
type ResponseMsgSimulation struct { type ResponseMsgScenario struct {
Simulation SimulationResponse `json:"simulation"` Scenario ScenarioResponse `json:"scenario"`
} }
type ResponseMsgSimulationModels struct { type ResponseMsgSimulationModels struct {
@ -111,3 +114,39 @@ type ResponseMsgSimulationModel struct {
type ResponseMsgSignals struct { type ResponseMsgSignals struct {
Signals []SignalResponse `json:"signals"` 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 type ModelName string
const ModelUser = ModelName("user") const ModelUser = ModelName("user")
const ModelSimulation = ModelName("simulation") const ModelScenario = ModelName("scenario")
const ModelSimulator = ModelName("simulator") 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 ModelSimulationModel = ModelName("simulationmodel")
const ModelSignal = ModelName("signal")
const ModelFile = ModelName("file")
type CRUD string type CRUD string
@ -47,18 +51,36 @@ var _r__ = Permission{Create: false, Read: true, Update: false, Delete: false}
var Roles = RoleActions{ var Roles = RoleActions{
"Admin": { "Admin": {
ModelUser: crud, ModelUser: crud,
ModelSimulation: crud, ModelScenario: crud,
ModelSimulationModel: crud, ModelSimulationModel: crud,
ModelSimulator: crud, ModelSimulator: crud,
ModelSimulatorAction: crud,
ModelWidget: crud,
ModelDashboard: crud,
ModelSignal: crud,
ModelFile: crud,
}, },
"User": { "User": {
ModelUser: __u_, ModelUser: __u_,
ModelSimulation: crud, ModelScenario: crud,
ModelSimulationModel: crud, ModelSimulationModel: crud,
ModelSimulator: _r__, ModelSimulator: _r__,
ModelSimulatorAction: _ru_,
ModelWidget: crud,
ModelDashboard: crud,
ModelSignal: crud,
ModelFile: crud,
}, },
"Guest": { "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, Username: self.Username,
Role: self.Role, Role: self.Role,
Mail: self.Mail, Mail: self.Mail,
ID: self.ID,
} }
// Associated models MUST NOT called with assoc=true otherwise we // 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 // TODO: maybe all those should be made in one transaction
//simulations, _, _ := simulation.FindUserSimulations(&self.User) //scenarios, _, _ := scenario.FindUserScenarios(&self.User)
//simulationsSerializer := //scenariosSerializer :=
// SimulationsSerializer{self.Ctx, simulations} // ScenariosSerializer{self.Ctx, scenarios}
// Add the associated models to the response // Add the associated models to the response
//response.Simulations = simulationsSerializer.Response() //response.Scenarios = scenariosSerializer.Response()
} }
return response return response
} }
// Simulation/s Serializers // Scenario/s Serializers
type SimulationsSerializer struct { type ScenariosSerializer struct {
Ctx *gin.Context Ctx *gin.Context
Simulations []Simulation Scenarios []Scenario
} }
func (self *SimulationsSerializer) Response() []SimulationResponse { func (self *ScenariosSerializer) Response() []ScenarioResponse {
response := []SimulationResponse{} response := []ScenarioResponse{}
for _, simulation := range self.Simulations { for _, so := range self.Scenarios {
serializer := SimulationSerializer{self.Ctx, simulation} serializer := ScenarioSerializer{self.Ctx, so}
response = append(response, serializer.Response()) response = append(response, serializer.Response())
} }
return response return response
} }
type SimulationSerializer struct { type ScenarioSerializer struct {
Ctx *gin.Context Ctx *gin.Context
Simulation Scenario
} }
func (self *SimulationSerializer) Response() SimulationResponse { func (self *ScenarioSerializer) Response() ScenarioResponse {
response := SimulationResponse{ response := ScenarioResponse{
Name: self.Name, Name: self.Name,
ID: self.ID, ID: self.ID,
Running: self.Running, Running: self.Running,
StartParams: self.StartParameters, StartParameters: self.StartParameters,
} }
return response return response
} }
@ -104,13 +105,13 @@ type SimulationModelSerializer struct {
func (self *SimulationModelSerializer) Response() SimulationModelResponse { func (self *SimulationModelSerializer) Response() SimulationModelResponse {
response := SimulationModelResponse{ response := SimulationModelResponse{
ID: self.ID, ID: self.ID,
Name: self.Name, Name: self.Name,
OutputLength: self.OutputLength, OutputLength: self.OutputLength,
InputLength: self.InputLength, InputLength: self.InputLength,
SimulationID: self.SimulationID, ScenarioID: self.ScenarioID,
SimulatorID: self.SimulatorID, SimulatorID: self.SimulatorID,
StartParams: self.StartParameters, StartParameters: self.StartParameters,
} }
return response return response
} }
@ -139,43 +140,47 @@ type SimulatorSerializer struct {
func (self *SimulatorSerializer) Response() SimulatorResponse { func (self *SimulatorSerializer) Response() SimulatorResponse {
response := SimulatorResponse{ response := SimulatorResponse{
ID: self.ID,
UUID: self.UUID, UUID: self.UUID,
Host: self.Host, Host: self.Host,
ModelType: self.Modeltype, Modeltype: self.Modeltype,
Uptime: self.Uptime, Uptime: self.Uptime,
State: self.State, State: self.State,
StateUpdateAt: self.StateUpdateAt, StateUpdateAt: self.StateUpdateAt,
Properties: self.Properties,
RawProperties: self.RawProperties,
} }
return response return response
} }
// Visualization/s Serializers // Dashboard/s Serializers
type VisualizationsSerializer struct { type DashboardsSerializer struct {
Ctx *gin.Context Ctx *gin.Context
Visualizations []Visualization Dashboards []Dashboard
} }
func (self *VisualizationsSerializer) Response() []VisualizationResponse { func (self *DashboardsSerializer) Response() []DashboardResponse {
response := []VisualizationResponse{} response := []DashboardResponse{}
for _, visualization := range self.Visualizations { for _, dashboard := range self.Dashboards {
serializer := VisualizationSerializer{self.Ctx, visualization} serializer := DashboardSerializer{self.Ctx, dashboard}
response = append(response, serializer.Response()) response = append(response, serializer.Response())
} }
return response return response
} }
type VisualizationSerializer struct { type DashboardSerializer struct {
Ctx *gin.Context Ctx *gin.Context
Visualization Dashboard
} }
func (self *VisualizationSerializer) Response() VisualizationResponse { func (self *DashboardSerializer) Response() DashboardResponse {
response := VisualizationResponse{ response := DashboardResponse{
Name: self.Name, Name: self.Name,
Grid: self.Grid, Grid: self.Grid,
SimulationID: self.SimulationID, ScenarioID: self.ScenarioID,
ID: self.ID,
} }
return response return response
} }
@ -204,18 +209,19 @@ type WidgetSerializer struct {
func (self *WidgetSerializer) Response() WidgetResponse { func (self *WidgetSerializer) Response() WidgetResponse {
response := WidgetResponse{ response := WidgetResponse{
Name: self.Name, ID: self.ID,
Type: self.Type, Name: self.Name,
Width: self.Width, Type: self.Type,
Height: self.Height, Width: self.Width,
MinWidth: self.MinWidth, Height: self.Height,
MinHeight: self.MinHeight, MinWidth: self.MinWidth,
X: self.X, MinHeight: self.MinHeight,
Y: self.Y, X: self.X,
Z: self.Z, Y: self.Y,
VisualizationID: self.VisualizationID, Z: self.Z,
IsLocked: self.IsLocked, DashboardID: self.DashboardID,
//CustomProperties IsLocked: self.IsLocked,
CustomProperties: self.CustomProperties,
} }
return response return response
} }
@ -245,12 +251,14 @@ func (self *FileSerializerNoAssoc) Response() FileResponse {
response := FileResponse{ response := FileResponse{
Name: self.Name, Name: self.Name,
ID: self.ID, ID: self.ID,
Path: self.Path, //Path: self.Path,
Type: self.Type, Type: self.Type,
Size: self.Size, Size: self.Size,
H: self.ImageHeight, ImageHeight: self.ImageHeight,
W: self.ImageWidth, ImageWidth: self.ImageWidth,
// Date Date: self.Date,
WidgetID: self.WidgetID,
SimulationModelID: self.SimulationModelID,
} }
return response 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/gin-gonic/gin"
"github.com/jinzhu/gorm" "github.com/jinzhu/gorm"
"github.com/nsf/jsondiff"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
) )
@ -35,53 +36,7 @@ func ProvideErrorResponse(c *gin.Context, err error) bool {
return false // No error return false // No error
} }
func GetVisualizationID(c *gin.Context) (int, error) { func TestEndpoint(t *testing.T, router *gin.Engine, token string, url string, method string, body []byte, expected_code int, expected_response []byte) {
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) {
w := httptest.NewRecorder() w := httptest.NewRecorder()
if body != nil { 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) assert.Equal(t, expected_code, w.Code)
fmt.Println(w.Body.String()) //fmt.Println("Actual:", w.Body.String())
assert.Equal(t, expected_response, 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 { 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) success := body_data["success"].(bool)
if !success { if !success {
fmt.Println("Authentication not successful: ", body_data["message"])
panic(-1) 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/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc
github.com/dgrijalva/jwt-go v3.2.0+incompatible github.com/dgrijalva/jwt-go v3.2.0+incompatible
github.com/gin-gonic/gin v1.4.0 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/locales v0.12.1 // indirect
github.com/go-playground/universal-translator v0.16.0 // 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/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/stretchr/testify v1.3.0
github.com/swaggo/gin-swagger v1.1.0 github.com/swaggo/gin-swagger v1.2.0
github.com/swaggo/swag v1.5.0 github.com/swaggo/swag v1.6.0
golang.org/x/crypto v0.0.0-20190325154230-a5d413f7728c 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 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 h1:glPeL3BQJsbF6aIIYfZizMwc5LTYz250bDMjttbBGAU=
cloud.google.com/go v0.37.4/go.mod h1:NHPJ89PdicEuT9hdPXMROBD91xc5uRDxsMtSB16k7hw= cloud.google.com/go v0.37.4/go.mod h1:NHPJ89PdicEuT9hdPXMROBD91xc5uRDxsMtSB16k7hw=
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= 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.0/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbtSwDGJws/X0=
github.com/PuerkitoBio/purell v1.1.1 h1:WEQqlqaGbrPkxLJWfBwQmfEAE1Z7ONdDLqrN38tNFfI= github.com/PuerkitoBio/purell v1.1.1 h1:WEQqlqaGbrPkxLJWfBwQmfEAE1Z7ONdDLqrN38tNFfI=
github.com/PuerkitoBio/purell v1.1.1/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbtSwDGJws/X0= 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.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 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 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-20190515213511-eb9f6a1743f3 h1:tkum0XDgfR0jcVVXuTsYv/erY2NnEDqwRojbxR1rBYA=
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/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 h1:7qlOGliEKZXTDg6OTjfoBKDXWrumCAMpl/TFQ4/5kLM=
github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ= 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= 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 h1:Yzb9+7DPaBjB8zlTR87/ElzFsnQfuHnVUVqpZZIcV5Y=
github.com/erikstmartin/go-testdb v0.0.0-20160219214506-8d10e4a1bae5/go.mod h1:a2zkGnVExMxdzMo3M0Hi/3sEU+cWnZpSni0O6/Yb/P0= 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/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/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-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.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.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 h1:3tMoCCfM7ppqsR0ptz/wi1impNpT7/9wQtMZ8lr1mCQ=
github.com/gin-gonic/gin v1.4.0/go.mod h1:OW2EZn3DO8Ln9oIKOvM++LBO+5UPHJJDH72/q/3rZdM= 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-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-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.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.19.2 h1:A9+F4Dc/MCNB5jibxf6rRvOvR/iFgQdyNx9eIhnGqq0=
github.com/go-openapi/jsonpointer v0.18.0/go.mod h1:cOnomiV+CVVwFLk0A/MExoFMjwdsUdVpsRhURCKh+3M= 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.17.0/go.mod h1:g4xxGn04lDIRh0GJb5QlpE3HfopLOL6uZrK/VgnsK9I=
github.com/go-openapi/jsonreference v0.18.0 h1:oP2OUNdG1l2r5kYhrfVMXO54gWmzcfAwP/GFuHpNTkE= github.com/go-openapi/jsonreference v0.19.0 h1:BqWKpV1dFd+AuiKlgtddwVIFQsuMpxfBDBHGfM2yNpk=
github.com/go-openapi/jsonreference v0.18.0/go.mod h1:g4xxGn04lDIRh0GJb5QlpE3HfopLOL6uZrK/VgnsK9I= github.com/go-openapi/jsonreference v0.19.0/go.mod h1:g4xxGn04lDIRh0GJb5QlpE3HfopLOL6uZrK/VgnsK9I=
github.com/go-openapi/spec v0.18.0 h1:aIjeyG5mo5/FrvDkpKKEGZPmF9MPHahS72mzfVqeQXQ= github.com/go-openapi/jsonreference v0.19.2 h1:o20suLFB4Ri0tuzpWtyHlh7E7HnkqTNLq6aR6WVNS1w=
github.com/go-openapi/spec v0.18.0/go.mod h1:XkF/MOi14NmjsfZ8VtAKf8pIlbZzyoTvZsdfssdxcBI= 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.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.19.2/go.mod h1:POnQmlKehdgb5mhVOsnJFsivZCEZ/vjK9gh66Z9tfKk=
github.com/go-openapi/swag v0.18.0/go.mod h1:AByQ+nYG6gQg71GINrmuDXCPWdL640yX49/kXLo40Tg= 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 h1:2FITxuFt/xuCNP1Acdhv62OzaCiviiE4kotfhkmOqEc=
github.com/go-playground/locales v0.12.1/go.mod h1:IUMDtCfWo/w/mtMfIE/IG2K+Ey3ygWanZIBtBW0W2TM= 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= 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/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/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/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.10 h1:HvrsqdhCW78xpJF67g1hMxS6eCToo9PZH4LDB8WKPac=
github.com/jinzhu/gorm v1.9.8/go.mod h1:bdqTT3q6dhSph2K3pWxrHP6nqxuAp2yQ3KFtc3U3F84= github.com/jinzhu/gorm v1.9.10/go.mod h1:Kh6hTsSGffh4ui079FHrR5Gg+5D0hgihqDcsDN2BBJY=
github.com/jinzhu/inflection v0.0.0-20180308033659-04140366298a h1:eeaG9XMUvRBYXJi4pg1ZKM7nxc5AfXfojeLLW7O5J3k= github.com/jinzhu/inflection v1.0.0 h1:K317FqzuhWc8YvSVlFMCCUb36O/S9MCKRDI7QkRKD/E=
github.com/jinzhu/inflection v0.0.0-20180308033659-04140366298a/go.mod h1:h+uFLlag+Qp1Va5pdKtLDYj+kHp5pxUVkryuEj+Srlc= github.com/jinzhu/inflection v1.0.0/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.1 h1:HjfetcXq097iXP0uoPCdnM4Efp5/9MsM0/M+XOTeR3M=
github.com/jinzhu/now v1.0.0/go.mod h1:oHTiXerJ20+SfYcrdlBO7rzZRJWGwSTQ0iUY2jI6Gfc= 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 h1:MrUvLMLTMxbqFJ9kzlvat/rYZqZnW3u4wkLzWTaFwKs=
github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= 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= 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 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI=
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= 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.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 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE=
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= 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 h1:Sm1gr51B1kKyfD2BlRcLSiEkffoG96g6TPv6eRoEiB8=
github.com/leodido/go-urn v1.1.0/go.mod h1:+cyI34gQWZcE1eQU7NVgKkkzdXDQHr1dBMtdAPozLkw= 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.1 h1:sJZmqHoEaY7f+NPP8pgLB/WxulyR3fewgCM2qaSlBb4=
github.com/lib/pq v1.1.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= 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-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-20190614124828-94de47d64c63/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc=
github.com/mailru/easyjson v0.0.0-20190312143242-1de009706dbe/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.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.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 h1:jbhqpg7tQe4SupckyijYiy0mJJ/pRyHvXf7JdWK860o=
github.com/mattn/go-sqlite3 v1.10.0/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc= 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= 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 h1:9f412s+6RmYXLWZSEzVVgPGK7C2PphHj5RJrvfx9AWI=
github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= 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/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.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
github.com/onsi/ginkgo v1.7.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= 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-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk=
github.com/prometheus/procfs v0.0.0-20190117184657-bf6a532e95b1/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/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/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.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.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.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
github.com/stretchr/testify v1.3.0 h1:TivCn/peBQ7UY8ooIcPgZFpTNSz0Q2U6UrFlUfqbe0Q= github.com/stretchr/testify v1.3.0 h1:TivCn/peBQ7UY8ooIcPgZFpTNSz0Q2U6UrFlUfqbe0Q=
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= 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.2.0 h1:YskZXEiv51fjOMTsXrOetAjrMDfFaXD79PEoQBOe2W0=
github.com/swaggo/gin-swagger v1.1.0/go.mod h1:FQlm07YuT1glfN3hQiO11UQ2m39vOCZ/aa3WWr5E+XU= github.com/swaggo/gin-swagger v1.2.0/go.mod h1:qlH2+W7zXGZkczuL+r2nEBR2JTT+/lX05Nn6vPhc7OI=
github.com/swaggo/swag v1.4.0/go.mod h1:hog2WgeMOrQ/LvQ+o1YGTeT+vWVrbi0SiIslBtxKTyM= github.com/swaggo/swag v1.5.1/go.mod h1:1Bl9F/ZBpVWh22nY0zmYyASPO1lI/zIwRDrpZU+tv8Y=
github.com/swaggo/swag v1.5.0 h1:haK8VG3hj+v/c8hQ4f3U+oYpkdI/26m9LAUTXHOv+2U= github.com/swaggo/swag v1.6.0 h1:d6Z3kNGQmM2Z6DZtOyK138eFTkpi2JwSwNVOV4wnlLk=
github.com/swaggo/swag v1.5.0/go.mod h1:+xZrnu5Ut3GcUkKAJm9spnOooIS1WB1cUOkLNPrvrE0= github.com/swaggo/swag v1.6.0/go.mod h1:YyZstMc22WYm6GEDx/CYWxq+faBbjQ5EqwQcrjREDBo=
github.com/ugorji/go v1.1.2/go.mod h1:hnLbHMwcvSihnDhEfx2/BzKp2xb0Y+ErdfYcrs9tkJQ= github.com/tidwall/gjson v1.3.0 h1:kfpsw1W3trbg4Xm6doUtqSl9+LhLB6qJ9PkltVAQZYs=
github.com/ugorji/go v1.1.4 h1:j4s+tAvLfL3bZyefP2SEWmhBzmuIlH/eqNuPdFPgngw= 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 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 v1.1.5-pre h1:jyJKFOSEbdOc2HODrf2qcCkYOdq7zzXqA9bhW5oV4fM=
github.com/ugorji/go/codec v0.0.0-20190204201341-e444a5086c43 h1:BasDe+IErOQKrMVXab7UayvSlIpiyGwRvuX3EKYY7UA= github.com/ugorji/go v1.1.5-pre/go.mod h1:FwP/aQVg39TXzItUBMwnWp9T9gPQnXw4Poh4/oBQZ/0=
github.com/ugorji/go/codec v0.0.0-20190204201341-e444a5086c43/go.mod h1:iT03XoTwV7xq/+UGwKO3UbC1nNNlopQiY61beSdrtOA= github.com/ugorji/go/codec v0.0.0-20181022190402-e5e69e061d4f/go.mod h1:VFNgLljTbGfSG7qAOspJ7OScBnGdDN/yBr0sguwnwf0=
github.com/ugorji/go/codec v0.0.0-20190320090025-2dc34c0b8780 h1:vG/gY/PxA3v3l04qxe3tDjXyu3bozii8ulSlIPOYKhI= github.com/ugorji/go/codec v1.1.5-pre h1:5YV9PsFAN+ndcCtTM7s60no7nY7eTG3LPtxhSwuxzCs=
github.com/ugorji/go/codec v0.0.0-20190320090025-2dc34c0b8780/go.mod h1:iT03XoTwV7xq/+UGwKO3UbC1nNNlopQiY61beSdrtOA= 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= github.com/urfave/cli v1.20.0/go.mod h1:70zkFmudgCuE/ngEzBv17Jvp/497gISqfk5gWijbERA=
go.opencensus.io v0.20.1/go.mod h1:6WKK9ahsWS3RSO+PY9ZHZUfv2irvY6gN279GOPZjmmk= 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-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-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-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/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-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= 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-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-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-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-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-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-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-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-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c h1:uOCk1iQW6Vc18bnC13MfzScl+wdKBmM9Y9kU7Z83/lw=
golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/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-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= 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-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-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-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-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-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-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-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-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-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-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-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-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190322080309-f49334f85ddc/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.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.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/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-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-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-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-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-20190606050223-4d9ae51c2468/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
golang.org/x/tools v0.0.0-20190322203728-c1a832b0ad89/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= 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/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.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
google.golang.org/appengine v1.4.0 h1:/wp5JvzpHIxhs/dumFmF7BXTf3Z+dd4uXta4kVyO508= 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" "github.com/gin-gonic/gin"
"git.rwth-aachen.de/acs/public/villas/villasweb-backend-go/common" "git.rwth-aachen.de/acs/public/villas/villasweb-backend-go/common"
"git.rwth-aachen.de/acs/public/villas/villasweb-backend-go/routes/simulationmodel"
"git.rwth-aachen.de/acs/public/villas/villasweb-backend-go/routes/widget"
) )
func RegisterFileEndpoints(r *gin.RouterGroup) { func RegisterFileEndpoints(r *gin.RouterGroup) {
@ -28,8 +30,8 @@ func RegisterFileEndpoints(r *gin.RouterGroup) {
// @Failure 403 "Access forbidden." // @Failure 403 "Access forbidden."
// @Failure 404 "Not found" // @Failure 404 "Not found"
// @Failure 500 "Internal server error" // @Failure 500 "Internal server error"
// @Param originType query string true "Set to model for files of model, set to widget for files of widget" // @Param objectType query string true "Set to model for files of model, set to widget for files of widget"
// @Param originID query int true "ID of either model or widget of which files are requested" // @Param objectID query int true "ID of either model or widget of which files are requested"
// @Router /files [get] // @Router /files [get]
func getFiles(c *gin.Context) { func getFiles(c *gin.Context) {
@ -51,15 +53,33 @@ func getFiles(c *gin.Context) {
return return
} }
//Check access
var ok bool
var m simulationmodel.SimulationModel
var w widget.Widget
if objectType == "model" {
ok, m = simulationmodel.CheckPermissions(c, common.Read, "body", objectID)
if !ok {
return
}
} else {
ok, w = widget.CheckPermissions(c, common.Read, objectID)
if !ok {
return
}
}
// get meta data of files
db := common.GetDB() db := common.GetDB()
var files []common.File var files []common.File
if objectType == "model" { if objectType == "model" {
err = db.Where("ModelID", objectID).Find(&files).Error err = db.Order("ID asc").Model(&m).Related(&files, "Files").Error
if common.ProvideErrorResponse(c, err) { if common.ProvideErrorResponse(c, err) {
return return
} }
} else { } else {
err = db.Where("WidgetID", objectID).Find(&files).Error err = db.Order("ID asc").Model(&w).Related(&files, "Files").Error
if common.ProvideErrorResponse(c, err) { if common.ProvideErrorResponse(c, err) {
return return
} }
@ -93,15 +113,6 @@ func getFiles(c *gin.Context) {
// @Param objectID query int true "ID of either model or widget of which files are requested" // @Param objectID query int true "ID of either model or widget of which files are requested"
// @Router /files [post] // @Router /files [post]
func addFile(c *gin.Context) { func addFile(c *gin.Context) {
// Extract file from PUT request form
file_header, err := c.FormFile("file")
if err != nil {
errormsg := fmt.Sprintf("Bad request. Get form error: %s", err.Error())
c.JSON(http.StatusBadRequest, gin.H{
"error": errormsg,
})
return
}
objectType := c.Request.URL.Query().Get("objectType") objectType := c.Request.URL.Query().Get("objectType")
if objectType != "model" && objectType != "widget" { if objectType != "model" && objectType != "widget" {
@ -121,6 +132,30 @@ func addFile(c *gin.Context) {
return return
} }
// Check access
var ok bool
if objectType == "model" {
ok, _ = simulationmodel.CheckPermissions(c, common.Create, "body", objectID)
if !ok {
return
}
} else {
ok, _ = widget.CheckPermissions(c, common.Create, objectID)
if !ok {
return
}
}
// Extract file from POST request form
file_header, err := c.FormFile("file")
if err != nil {
errormsg := fmt.Sprintf("Bad request. Get form error: %s", err.Error())
c.JSON(http.StatusBadRequest, gin.H{
"error": errormsg,
})
return
}
var newFile File var newFile File
err = newFile.register(file_header, objectType, uint(objectID)) err = newFile.register(file_header, objectType, uint(objectID))
if common.ProvideErrorResponse(c, err) == false { if common.ProvideErrorResponse(c, err) == false {
@ -149,22 +184,16 @@ func addFile(c *gin.Context) {
// @Router /files/{fileID} [get] // @Router /files/{fileID} [get]
func getFile(c *gin.Context) { func getFile(c *gin.Context) {
fileID, err := common.GetFileID(c) // check access
if err != nil { ok, f := checkPermissions(c, common.Read)
if !ok {
return return
} }
var f File err := f.download(c)
err = f.byID(uint(fileID))
if common.ProvideErrorResponse(c, err) { if common.ProvideErrorResponse(c, err) {
return return
} }
f.download(c)
c.JSON(http.StatusOK, gin.H{
"message": "OK.",
})
} }
// updateFile godoc // updateFile godoc
@ -188,6 +217,12 @@ func getFile(c *gin.Context) {
// @Router /files/{fileID} [put] // @Router /files/{fileID} [put]
func updateFile(c *gin.Context) { func updateFile(c *gin.Context) {
// check access
ok, f := checkPermissions(c, common.Update)
if !ok {
return
}
// Extract file from PUT request form // Extract file from PUT request form
err := c.Request.ParseForm() err := c.Request.ParseForm()
if err != nil { if err != nil {
@ -207,17 +242,6 @@ func updateFile(c *gin.Context) {
return return
} }
fileID, err := common.GetFileID(c)
if err != nil {
return
}
var f File
err = f.byID(uint(fileID))
if common.ProvideErrorResponse(c, err) {
return
}
err = f.update(file_header) err = f.update(file_header)
if common.ProvideErrorResponse(c, err) == false { if common.ProvideErrorResponse(c, err) == false {
c.JSON(http.StatusOK, gin.H{ c.JSON(http.StatusOK, gin.H{
@ -239,7 +263,17 @@ func updateFile(c *gin.Context) {
// @Param fileID path int true "ID of the file to update" // @Param fileID path int true "ID of the file to update"
// @Router /files/{fileID} [delete] // @Router /files/{fileID} [delete]
func deleteFile(c *gin.Context) { func deleteFile(c *gin.Context) {
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 ( import (
"fmt" "fmt"
"io" "github.com/gin-gonic/gin"
"io/ioutil"
"mime/multipart" "mime/multipart"
"os" "os"
"path/filepath" "path/filepath"
"strconv" "time"
"github.com/gin-gonic/gin"
"git.rwth-aachen.de/acs/public/villas/villasweb-backend-go/common" "git.rwth-aachen.de/acs/public/villas/villasweb-backend-go/common"
"git.rwth-aachen.de/acs/public/villas/villasweb-backend-go/routes/simulationmodel" "git.rwth-aachen.de/acs/public/villas/villasweb-backend-go/routes/simulationmodel"
@ -38,29 +37,40 @@ func (f *File) byID(id uint) error {
} }
func (f *File) save() error { func (f *File) save() error {
// get last modify time of target file
fileinfo, err := os.Stat(f.Path)
if err != nil {
return fmt.Errorf("error stat on file: %s", err.Error())
}
f.Date = fileinfo.ModTime()
f.ImageWidth = 0
f.ImageHeight = 0
db := common.GetDB() db := common.GetDB()
err = db.Create(f).Error err := db.Create(f).Error
return err return err
} }
func (f *File) download(c *gin.Context) error {
err := ioutil.WriteFile(f.Name, f.FileData, 0644)
if err != nil {
return fmt.Errorf("file could not be temporarily created on server disk: %s", err.Error())
}
defer os.Remove(f.Name)
//Seems this headers needed for some browsers (for example without this headers Chrome will download files as txt)
c.Header("Content-Description", "File Transfer")
c.Header("Content-Transfer-Encoding", "binary")
c.Header("Content-Disposition", "attachment; filename="+f.Name)
//c.Header("Content-Type", contentType)
c.File(f.Name)
return nil
}
func (f *File) register(fileHeader *multipart.FileHeader, objectType string, objectID uint) error { func (f *File) register(fileHeader *multipart.FileHeader, objectType string, objectID uint) error {
// Obtain properties of file // Obtain properties of file
f.Type = fileHeader.Header.Get("Content-Type") f.Type = fileHeader.Header.Get("Content-Type")
f.Name = filepath.Base(fileHeader.Filename) f.Name = filepath.Base(fileHeader.Filename)
f.Path = filepath.Join(getFolderName(objectType, objectID), f.Name) //f.Path = filepath.Join(getFolderName(objectType, objectID), f.Name)
f.Size = uint(fileHeader.Size) f.Size = uint(fileHeader.Size)
f.Date = time.Now().String()
f.ImageWidth = 0 // TODO: do we need this?
f.ImageHeight = 0 // TODO: do we need this?
var m simulationmodel.SimulationModel var m simulationmodel.SimulationModel
var w widget.Widget var w widget.Widget
@ -68,12 +78,16 @@ func (f *File) register(fileHeader *multipart.FileHeader, objectType string, obj
if objectType == "model" { if objectType == "model" {
// check if model exists // check if model exists
err = m.ByID(objectID) err = m.ByID(objectID)
f.WidgetID = 0
f.SimulationModelID = objectID
if err != nil { if err != nil {
return err return err
} }
} else { } else {
// check if widget exists // check if widget exists
f.WidgetID = objectID
f.SimulationModelID = 0
err = w.ByID(uint(objectID)) err = w.ByID(uint(objectID))
if err != nil { if err != nil {
return err return err
@ -81,12 +95,15 @@ func (f *File) register(fileHeader *multipart.FileHeader, objectType string, obj
} }
// Save file to local disc (NOT DB!) // set file data
err = f.modifyFileOnDisc(fileHeader, true) fileContent, err := fileHeader.Open()
if err != nil { if err != nil {
return fmt.Errorf("File could not be saved/ modified on disk: ", err.Error()) return err
} }
f.FileData, err = ioutil.ReadAll(fileContent)
defer fileContent.Close()
// Add File object with parameters to DB // Add File object with parameters to DB
err = f.save() err = f.save()
if err != nil { if err != nil {
@ -95,12 +112,14 @@ func (f *File) register(fileHeader *multipart.FileHeader, objectType string, obj
// Create association to model or widget // Create association to model or widget
if objectType == "model" { if objectType == "model" {
err = f.addToModel(m) db := common.GetDB()
err := db.Model(&m).Association("Files").Append(f).Error
if err != nil { if err != nil {
return err return err
} }
} else { } else {
err = f.addToWidget(w) db := common.GetDB()
err := db.Model(&w).Association("Files").Append(f).Error
if err != nil { if err != nil {
return err return err
} }
@ -110,99 +129,54 @@ func (f *File) register(fileHeader *multipart.FileHeader, objectType string, obj
} }
func (f *File) update(fileHeader *multipart.FileHeader) error { func (f *File) update(fileHeader *multipart.FileHeader) error {
err := f.modifyFileOnDisc(fileHeader, false)
// set file data
fileContent, err := fileHeader.Open()
if err != nil { if err != nil {
return err return err
} }
fileData, err := ioutil.ReadAll(fileContent)
fmt.Println("File content: ", string(fileData))
defer fileContent.Close()
db := common.GetDB() db := common.GetDB()
err = db.Model(f).Update("Size", fileHeader.Size).Error err = db.Model(f).Updates(map[string]interface{}{"Size": fileHeader.Size,
"FileData": fileData,
"Date": time.Now().String()}).Error
return err return err
} }
func (f *File) modifyFileOnDisc(fileHeader *multipart.FileHeader, createFile bool) error { func (f *File) delete() error {
//filesavepath := filepath.Join(foldername, filename) db := common.GetDB()
var err error
if createFile { if f.WidgetID > 0 {
// Ensure folder with name foldername exists // remove association between file and widget
err = os.MkdirAll(f.Path, os.ModePerm) var w widget.Widget
} else { err := w.ByID(f.WidgetID)
// test if file exists if err != nil {
_, err = os.Stat(f.Path) return err
} }
if err != nil { err = db.Model(&w).Association("Files").Delete(f).Error
return err if err != nil {
} return err
}
var open_options int } else {
if createFile { // remove association between file and simulation model
// create file it not exists, file MUST not exist var m simulationmodel.SimulationModel
open_options = os.O_RDWR | os.O_CREATE | os.O_EXCL err := m.ByID(f.SimulationModelID)
} else { if err != nil {
open_options = os.O_RDWR return err
} }
err = db.Model(&m).Association("Files").Delete(f).Error
fileTarget, err := os.OpenFile(f.Path, open_options, 0666)
if err != nil {
return err
}
defer fileTarget.Close()
// Save file to target path
uploadedFile, err := fileHeader.Open()
if err != nil {
return err
}
defer uploadedFile.Close()
var uploadContent = make([]byte, f.Size)
for {
n, err := uploadedFile.Read(uploadContent)
if err != nil && err != io.EOF {
return err
}
if n == 0 {
break
}
_, err = fileTarget.Write(uploadContent[:n])
if err != nil { if err != nil {
return err return err
} }
} }
// delete file from DB
err := db.Delete(f).Error
return err 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 ( import (
"fmt" "fmt"
@ -7,46 +7,46 @@ import (
"git.rwth-aachen.de/acs/public/villas/villasweb-backend-go/routes/user" "git.rwth-aachen.de/acs/public/villas/villasweb-backend-go/routes/user"
) )
type Simulation struct { type Scenario struct {
common.Simulation common.Scenario
} }
func (s *Simulation) ByID(id uint) error { func (s *Scenario) ByID(id uint) error {
db := common.GetDB() db := common.GetDB()
err := db.Find(s, id).Error err := db.Find(s, id).Error
if err != nil { 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 return nil
} }
func (s *Simulation) getUsers() ([]common.User, int, error) { func (s *Scenario) getUsers() ([]common.User, int, error) {
db := common.GetDB() db := common.GetDB()
var users []common.User var users []common.User
err := db.Order("ID asc").Model(s).Related(&users, "Users").Error err := db.Order("ID asc").Model(s).Related(&users, "Users").Error
return users, len(users), err return users, len(users), err
} }
func (s *Simulation) save() error { func (s *Scenario) save() error {
db := common.GetDB() db := common.GetDB()
err := db.Create(s).Error err := db.Create(s).Error
return err return err
} }
func (s *Simulation) update(modifiedSimulation Simulation) error { func (s *Scenario) update(modifiedScenario common.ScenarioResponse) error {
db := common.GetDB() db := common.GetDB()
err := db.Model(s).Update(modifiedSimulation).Error err := db.Model(s).Update(modifiedScenario).Error
return err return err
} }
func (s *Simulation) addUser(u *common.User) error { func (s *Scenario) addUser(u *common.User) error {
db := common.GetDB() db := common.GetDB()
err := db.Model(s).Association("Users").Append(u).Error err := db.Model(s).Association("Users").Append(u).Error
return err return err
} }
func (s *Simulation) deleteUser(username string) error { func (s *Scenario) deleteUser(username string) error {
db := common.GetDB() db := common.GetDB()
var deletedUser user.User var deletedUser user.User
@ -58,27 +58,27 @@ func (s *Simulation) deleteUser(username string) error {
no_users := db.Model(s).Association("Users").Count() no_users := db.Model(s).Association("Users").Count()
if no_users > 1 { if no_users > 1 {
// remove user from simulation // remove user from scenario
err = db.Model(s).Association("Users").Delete(&deletedUser.User).Error err = db.Model(s).Association("Users").Delete(&deletedUser.User).Error
if err != nil { if err != nil {
return err return err
} }
// remove simulation from user // remove scenario from user
err = db.Model(&deletedUser.User).Association("Simulations").Delete(s).Error err = db.Model(&deletedUser.User).Association("Scenarios").Delete(s).Error
if err != nil { if err != nil {
return err return err
} }
} else { } 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 return nil
} }
func (s *Simulation) delete() error { func (s *Scenario) delete() error {
db := common.GetDB() 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() users, no_users, err := s.getUsers()
if err != nil { if err != nil {
@ -87,23 +87,23 @@ func (s *Simulation) delete() error {
if no_users > 0 { if no_users > 0 {
for _, u := range users { for _, u := range users {
// remove user from simulation // remove user from scenario
err = db.Model(s).Association("Users").Delete(&u).Error err = db.Model(s).Association("Users").Delete(&u).Error
if err != nil { if err != nil {
return err return err
} }
// remove simulation from user // remove scenario from user
err = db.Model(&u).Association("Simulations").Delete(s).Error err = db.Model(&u).Association("Scenarios").Delete(s).Error
if err != nil { if err != nil {
return err return err
} }
} }
} }
// Simulation is not deleted from DB, only associations with users are removed // Scenario is not deleted from DB, only associations with users are removed
// Simulation remains "dangling" in DB // Scenario remains "dangling" in DB
// Delete simulation // Delete scenario
//err = db.Delete(s).Error //err = db.Delete(s).Error
//if err != nil { //if err != nil {
// return err // return err
@ -112,7 +112,7 @@ func (s *Simulation) delete() error {
return nil return nil
} }
func (s *Simulation) checkAccess(userID uint, userRole string) bool { func (s *Scenario) checkAccess(userID uint, userRole string) bool {
if userRole == "Admin" { if userRole == "Admin" {
return true return true

View file

@ -1,4 +1,4 @@
package simulation package scenario
import ( import (
"fmt" "fmt"
@ -10,62 +10,62 @@ import (
"git.rwth-aachen.de/acs/public/villas/villasweb-backend-go/common" "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 { if err != nil {
c.JSON(http.StatusUnprocessableEntity, "Access denied (role validation failed).") c.JSON(http.StatusUnprocessableEntity, "Access denied (role validation failed).")
return false, sim return false, so
} }
if operation == common.Create || (operation == common.Read && simIDSource == "none") { if operation == common.Create || (operation == common.Read && simIDSource == "none") {
return true, sim return true, so
} }
var simID int var simID int
if simIDSource == "path" { if simIDSource == "path" {
simID, err = strconv.Atoi(c.Param("simulationID")) simID, err = strconv.Atoi(c.Param("scenarioID"))
if err != nil { 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{ c.JSON(http.StatusBadRequest, gin.H{
"error": errormsg, "error": errormsg,
}) })
return false, sim return false, so
} }
} else if simIDSource == "query" { } 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 { 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{ c.JSON(http.StatusBadRequest, gin.H{
"error": errormsg, "error": errormsg,
}) })
return false, sim return false, so
} }
} else if simIDSource == "body" { } else if simIDSource == "body" {
simID = simIDBody simID = simIDBody
} else { } 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{ c.JSON(http.StatusBadRequest, gin.H{
"error": errormsg, "error": errormsg,
}) })
return false, sim return false, so
} }
userID, _ := c.Get(common.UserIDCtx) userID, _ := c.Get(common.UserIDCtx)
userRole, _ := c.Get(common.UserRoleCtx) userRole, _ := c.Get(common.UserRoleCtx)
err = sim.ByID(uint(simID)) err = so.ByID(uint(simID))
if common.ProvideErrorResponse(c, err) { if common.ProvideErrorResponse(c, err) {
return false, sim return false, so
} }
if sim.checkAccess(userID.(uint), userRole.(string)) == false { if so.checkAccess(userID.(uint), userRole.(string)) == false {
c.JSON(http.StatusUnprocessableEntity, "Access denied (for simulation ID).") c.JSON(http.StatusUnprocessableEntity, "Access denied (for scenario ID).")
return false, sim 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" "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 // Test /models endpoints
func TestSimulationModelEndpoints(t *testing.T) { 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() db := common.DummyInitDB()
defer db.Close() defer db.Close()
common.DummyPopulateDB(db) common.DummyPopulateDB(db)
@ -225,113 +35,30 @@ func TestSimulationModelEndpoints(t *testing.T) {
RegisterSimulationModelEndpoints(api.Group("/models")) RegisterSimulationModelEndpoints(api.Group("/models"))
credjson, err := json.Marshal(cred) credjson, _ := json.Marshal(common.CredUser)
if err != nil { msgOKjson, _ := json.Marshal(common.MsgOK)
panic(err) msgModelsjson, _ := json.Marshal(msgModels)
} msgModeljson, _ := json.Marshal(msgModel)
msgModelupdatedjson, _ := json.Marshal(msgModelupdated)
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)
}
token = common.AuthenticateForTest(t, router, "/api/authenticate", "POST", credjson, 200) token = common.AuthenticateForTest(t, router, "/api/authenticate", "POST", credjson, 200)
// test GET models // 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 // 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 // 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 // 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", "PUT", msgModelupdatedjson, 200, msgOKjson)
common.TestEndpoint(t, router, token, "/api/models/3", "GET", nil, 200, string(msgModelupdatedjson)) common.TestEndpoint(t, router, token, "/api/models/3", "GET", nil, 200, msgModelupdatedjson)
// test DELETE models/:ModelID // test DELETE models/:ModelID
common.TestEndpoint(t, router, token, "/api/models/3", "DELETE", nil, 200, string(msgOKjson)) common.TestEndpoint(t, router, token, "/api/models/3", "DELETE", nil, 200, msgOKjson)
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 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))
// TODO add testing for other return codes // 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 { type AuthResponse struct {
Success bool `json:"success"` Success bool `json:"success"`
Message string `json:"message"` Message string `json:"message"`
Token string `json:"token"` Token string `json:"token"`
User common.UserResponse `json:"user"`
} }
func VisitorAuthenticate(r *gin.RouterGroup) { func VisitorAuthenticate(r *gin.RouterGroup) {
@ -32,10 +33,10 @@ func VisitorAuthenticate(r *gin.RouterGroup) {
func RegisterUserEndpoints(r *gin.RouterGroup) { func RegisterUserEndpoints(r *gin.RouterGroup) {
r.POST("", addUser) r.POST("", addUser)
r.PUT("/:UserID", updateUser) r.PUT("/:userID", updateUser)
r.GET("", getUsers) r.GET("", getUsers)
r.GET("/:UserID", getUser) r.GET("/:userID", getUser)
r.DELETE("/:UserID", deleteUser) r.DELETE("/:userID", deleteUser)
} }
// authenticate godoc // authenticate godoc
@ -123,10 +124,13 @@ func authenticate(c *gin.Context) {
return return
} }
serializer := common.UserSerializer{c, user.User}
c.JSON(http.StatusOK, gin.H{ c.JSON(http.StatusOK, gin.H{
"success": true, "success": true,
"message": "Authenticated", "message": "Authenticated",
"token": tokenString, "token": tokenString,
"user": serializer.Response(false),
}) })
} }
@ -242,7 +246,7 @@ func addUser(c *gin.Context) {
// @Tags users // @Tags users
// @Accept json // @Accept json
// @Produce 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." // @Success 200 "OK."
// @Failure 401 "Unauthorized Access" // @Failure 401 "Unauthorized Access"
// @Failure 403 "Access forbidden." // @Failure 403 "Access forbidden."
@ -260,7 +264,7 @@ func updateUser(c *gin.Context) {
// Find the user // Find the user
var user User var user User
toBeUpdatedID, _ := common.UintParamFromCtx(c, "UserID") toBeUpdatedID, _ := common.UintParamFromCtx(c, "userID")
err = user.ByID(toBeUpdatedID) err = user.ByID(toBeUpdatedID)
if err != nil { if err != nil {
c.JSON(http.StatusNotFound, fmt.Sprintf("%v", err)) c.JSON(http.StatusNotFound, fmt.Sprintf("%v", err))
@ -324,6 +328,15 @@ func updateUser(c *gin.Context) {
return 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 // Finaly update the user
err = user.update(updatedUser) err = user.update(updatedUser)
if err != nil { if err != nil {
@ -359,7 +372,7 @@ func getUser(c *gin.Context) {
} }
var user User var user User
id, _ := common.UintParamFromCtx(c, "UserID") id, _ := common.UintParamFromCtx(c, "userID")
err = user.ByID(id) err = user.ByID(id)
if err != nil { if err != nil {
@ -394,7 +407,7 @@ func deleteUser(c *gin.Context) {
} }
var user User var user User
id, _ := common.UintParamFromCtx(c, "UserID") id, _ := common.UintParamFromCtx(c, "userID")
// Check that the user exist // Check that the user exist
err = user.ByID(uint(id)) 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" "github.com/gin-gonic/gin"
"git.rwth-aachen.de/acs/public/villas/villasweb-backend-go/common" "git.rwth-aachen.de/acs/public/villas/villasweb-backend-go/common"
"git.rwth-aachen.de/acs/public/villas/villasweb-backend-go/routes/visualization" "git.rwth-aachen.de/acs/public/villas/villasweb-backend-go/routes/dashboard"
) )
func RegisterWidgetEndpoints(r *gin.RouterGroup) { func RegisterWidgetEndpoints(r *gin.RouterGroup) {
r.GET("", getWidgets) r.GET("", getWidgets)
r.POST("", addWidget) r.POST("", addWidget)
//r.POST("/:widgetID", cloneWidget)
r.PUT("/:widgetID", updateWidget) r.PUT("/:widgetID", updateWidget)
r.GET("/:widgetID", getWidget) r.GET("/:widgetID", getWidget)
r.DELETE("/:widgetID", deleteWidget) r.DELETE("/:widgetID", deleteWidget)
} }
// getWidgets godoc // getWidgets godoc
// @Summary Get all widgets of visualization // @Summary Get all widgets of dashboard
// @ID getWidgets // @ID getWidgets
// @Produce json // @Produce json
// @Tags widgets // @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 401 "Unauthorized Access"
// @Failure 403 "Access forbidden." // @Failure 403 "Access forbidden."
// @Failure 404 "Not found" // @Failure 404 "Not found"
// @Failure 500 "Internal server error" // @Failure 500 "Internal server error"
// @Param visualizationID query int true "Visualization ID" // @Param dashboardID query int true "Dashboard ID"
// @Router /widgets [get] // @Router /widgets [get]
func getWidgets(c *gin.Context) { func getWidgets(c *gin.Context) {
visID, err := common.GetVisualizationID(c) ok, dab := dashboard.CheckPermissions(c, common.Read, "query", -1)
if err != nil { if !ok {
return
}
var vis visualization.Visualization
err = vis.ByID(uint(visID))
if common.ProvideErrorResponse(c, err) {
return return
} }
db := common.GetDB() db := common.GetDB()
var widgets []common.Widget 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) { if common.ProvideErrorResponse(c, err) {
return return
} }
@ -57,12 +50,12 @@ func getWidgets(c *gin.Context) {
} }
// addWidget godoc // addWidget godoc
// @Summary Add a widget to a visualization // @Summary Add a widget to a dashboard
// @ID addWidget // @ID addWidget
// @Accept json // @Accept json
// @Produce json // @Produce json
// @Tags widgets // @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." // @Success 200 "OK."
// @Failure 401 "Unauthorized Access" // @Failure 401 "Unauthorized Access"
// @Failure 403 "Access forbidden." // @Failure 403 "Access forbidden."
@ -71,8 +64,8 @@ func getWidgets(c *gin.Context) {
// @Router /widgets [post] // @Router /widgets [post]
func addWidget(c *gin.Context) { func addWidget(c *gin.Context) {
var newWidget Widget var newWidgetData common.ResponseMsgWidget
err := c.BindJSON(&newWidget) err := c.BindJSON(&newWidgetData)
if err != nil { if err != nil {
errormsg := "Bad request. Error binding form data to JSON: " + err.Error() errormsg := "Bad request. Error binding form data to JSON: " + err.Error()
c.JSON(http.StatusBadRequest, gin.H{ c.JSON(http.StatusBadRequest, gin.H{
@ -81,7 +74,26 @@ func addWidget(c *gin.Context) {
return 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 { if common.ProvideErrorResponse(c, err) == false {
c.JSON(http.StatusOK, gin.H{ 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 // updateWidget godoc
// @Summary Update a widget // @Summary Update a widget
// @ID updateWidget // @ID updateWidget
// @Tags widgets // @Tags widgets
// @Accept json // @Accept json
// @Produce 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." // @Success 200 "OK."
// @Failure 401 "Unauthorized Access" // @Failure 401 "Unauthorized Access"
// @Failure 403 "Access forbidden." // @Failure 403 "Access forbidden."
@ -112,13 +118,13 @@ func cloneWidget(c *gin.Context) {
// @Router /widgets/{widgetID} [put] // @Router /widgets/{widgetID} [put]
func updateWidget(c *gin.Context) { func updateWidget(c *gin.Context) {
widgetID, err := common.GetWidgetID(c) ok, w := CheckPermissions(c, common.Update, -1)
if err != nil { if !ok {
return return
} }
var modifiedWidget Widget var modifiedWidget common.ResponseMsgWidget
err = c.BindJSON(&modifiedWidget) err := c.BindJSON(&modifiedWidget)
if err != nil { if err != nil {
errormsg := "Bad request. Error binding form data to JSON: " + err.Error() errormsg := "Bad request. Error binding form data to JSON: " + err.Error()
c.JSON(http.StatusBadRequest, gin.H{ c.JSON(http.StatusBadRequest, gin.H{
@ -127,16 +133,10 @@ func updateWidget(c *gin.Context) {
return return
} }
var w Widget err = w.update(modifiedWidget.Widget)
err = w.ByID(uint(widgetID))
if common.ProvideErrorResponse(c, err) {
return
}
err = w.update(modifiedWidget)
if common.ProvideErrorResponse(c, err) == false { if common.ProvideErrorResponse(c, err) == false {
c.JSON(http.StatusOK, gin.H{ c.JSON(http.StatusOK, gin.H{
"message": "OK", "message": "OK.",
}) })
} }
} }
@ -155,14 +155,8 @@ func updateWidget(c *gin.Context) {
// @Router /widgets/{widgetID} [get] // @Router /widgets/{widgetID} [get]
func getWidget(c *gin.Context) { func getWidget(c *gin.Context) {
widgetID, err := common.GetWidgetID(c) ok, w := CheckPermissions(c, common.Read, -1)
if err != nil { if !ok {
return
}
var w Widget
err = w.ByID(uint(widgetID))
if common.ProvideErrorResponse(c, err) {
return return
} }
@ -186,21 +180,17 @@ func getWidget(c *gin.Context) {
// @Router /widgets/{widgetID} [delete] // @Router /widgets/{widgetID} [delete]
func deleteWidget(c *gin.Context) { func deleteWidget(c *gin.Context) {
// widgetID, err := GetWidgetID(c) ok, w := CheckPermissions(c, common.Delete, -1)
// if err != nil { if !ok {
// return return
// } }
//
// widget, err := queries.FindWidgetOfVisualization(&visualization, widgetID)
// if common.ProvideErrorResponse(c, err) {
// return
// }
// TODO delete files of widget in DB and on disk err := w.delete()
if common.ProvideErrorResponse(c, err) {
// TODO Delete widget itself + association with visualization return
}
c.JSON(http.StatusOK, gin.H{ 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 package main
import ( import (
"git.rwth-aachen.de/acs/public/villas/villasweb-backend-go/routes/file" "fmt"
"time"
"github.com/gin-gonic/gin" "github.com/gin-gonic/gin"
"github.com/swaggo/gin-swagger" "github.com/swaggo/gin-swagger"
"github.com/swaggo/gin-swagger/swaggerFiles" "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/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/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/simulationmodel"
"git.rwth-aachen.de/acs/public/villas/villasweb-backend-go/routes/simulator" "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/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" "git.rwth-aachen.de/acs/public/villas/villasweb-backend-go/routes/widget"
) )
@ -50,9 +54,10 @@ func main() {
api.Use(user.Authentication(true)) api.Use(user.Authentication(true))
simulation.RegisterSimulationEndpoints(api.Group("/simulations")) scenario.RegisterScenarioEndpoints(api.Group("/scenarios"))
simulationmodel.RegisterSimulationModelEndpoints(api.Group("/models")) 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")) widget.RegisterWidgetEndpoints(api.Group("/widgets"))
file.RegisterFileEndpoints(api.Group("/files")) file.RegisterFileEndpoints(api.Group("/files"))
user.RegisterUserEndpoints(api.Group("/users")) user.RegisterUserEndpoints(api.Group("/users"))
@ -60,6 +65,29 @@ func main() {
r.GET("swagger/*any", ginSwagger.WrapHandler(swaggerFiles.Handler)) 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 // server at port 4000 to match frontend's redirect path
r.Run(":4000") r.Run(":4000")
} }