diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 7b7862d..67de5d3 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -51,13 +51,15 @@ build:backend: image: villaswebbackendgo:ubuntu script: - go mod tidy + - go get -u github.com/swaggo/swag/cmd/swag + - ~/go/bin/swag init -p pascalcase -g "start.go" -o "./doc/autoapi/" - go build # Stage: test ############################################################################## -test:backend: +test:backend:database: stage: test tags: - docker @@ -65,11 +67,28 @@ test:backend: script: - /etc/init.d/postgresql start - go mod tidy + - go get -u github.com/swaggo/swag/cmd/swag + - ~/go/bin/swag init -p pascalcase -g "start.go" -o "./doc/autoapi/" - cd common - go test -v -args -dbhost=/var/run/postgresql dependencies: - build:backend +test:backend:endpoints: + stage: test + tags: + - docker + image: villaswebbackendgo:ubuntu + script: + - /etc/init.d/postgresql start + - go mod tidy + - go get -u github.com/swaggo/swag/cmd/swag + - ~/go/bin/swag init -p pascalcase -g "start.go" -o "./doc/autoapi/" + - cd routes/simulation + - go test -v -args -dbhost=/var/run/postgresql + dependencies: + - build:backend + # Stage: deploy ############################################################################## diff --git a/common/database.go b/common/database.go index bfd4528..6770503 100644 --- a/common/database.go +++ b/common/database.go @@ -54,10 +54,9 @@ func VerifyConnection(db *gorm.DB) error { // to the Dummy*() where it is called func DropTables(db *gorm.DB) { db.DropTableIfExists(&Simulator{}) - db.DropTableIfExists(&Signal{}) - db.DropTableIfExists(&SimulationModel{}) + //db.DropTableIfExists(&Signal{}) + db.DropTableIfExists(&Model{}) db.DropTableIfExists(&File{}) - db.DropTableIfExists(&Project{}) db.DropTableIfExists(&Simulation{}) db.DropTableIfExists(&User{}) db.DropTableIfExists(&Visualization{}) @@ -67,10 +66,9 @@ func DropTables(db *gorm.DB) { // AutoMigrate the models func MigrateModels(db *gorm.DB) { db.AutoMigrate(&Simulator{}) - db.AutoMigrate(&Signal{}) - db.AutoMigrate(&SimulationModel{}) + //db.AutoMigrate(&Signal{}) + db.AutoMigrate(&Model{}) db.AutoMigrate(&File{}) - db.AutoMigrate(&Project{}) db.AutoMigrate(&Simulation{}) db.AutoMigrate(&User{}) db.AutoMigrate(&Visualization{}) @@ -103,30 +101,25 @@ func DummyPopulateDB(test_db *gorm.DB) { checkErr(test_db.Create(&simr_A).Error) checkErr(test_db.Create(&simr_B).Error) - outSig_A := Signal{Name: "outSignal_A"} - outSig_B := Signal{Name: "outSignal_B"} - inSig_A := Signal{Name: "inSignal_A"} - inSig_B := Signal{Name: "inSignal_B"} - 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) + //outSig_A := Signal{Name: "outSignal_A", Direction: "out"} + //outSig_B := Signal{Name: "outSignal_B", Direction: "out"} + //inSig_A := Signal{Name: "inSignal_A", Direction: "in"} + //inSig_B := Signal{Name: "inSignal_B", Direction: "in"} + //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) - smo_A := SimulationModel{Name: "SimModel_A"} - smo_B := SimulationModel{Name: "SimModel_B"} - checkErr(test_db.Create(&smo_A).Error) - checkErr(test_db.Create(&smo_B).Error) + mo_A := Model{Name: "Model_A"} + mo_B := Model{Name: "Model_B"} + checkErr(test_db.Create(&mo_A).Error) + checkErr(test_db.Create(&mo_B).Error) file_A := File{Name: "File_A"} file_B := File{Name: "File_B"} checkErr(test_db.Create(&file_A).Error) checkErr(test_db.Create(&file_B).Error) - proj_A := Project{Name: "Project_A"} - proj_B := Project{Name: "Project_B"} - checkErr(test_db.Create(&proj_A).Error) - checkErr(test_db.Create(&proj_B).Error) - simn_A := Simulation{Name: "Simulation_A"} simn_B := Simulation{Name: "Simulation_B"} checkErr(test_db.Create(&simn_A).Error) @@ -162,49 +155,40 @@ func DummyPopulateDB(test_db *gorm.DB) { // For `belongs to` use the model with id=1 // For `has many` use the models with id=1 and id=2 - // Project HM Visualization, Visualization BT Project - checkErr(test_db.Model(&vis_A).Association("Project").Append(&proj_A).Error) - checkErr(test_db.Model(&vis_B).Association("Project").Append(&proj_A).Error) + // User HM Simulations, Simulation HM Users (Many-to-Many) + checkErr(test_db.Model(&simn_A).Association("Users").Append(&usr_A).Error) + checkErr(test_db.Model(&simn_A).Association("Users").Append(&usr_B).Error) + checkErr(test_db.Model(&simn_B).Association("Users").Append(&usr_A).Error) + checkErr(test_db.Model(&simn_B).Association("Users").Append(&usr_B).Error) - // User HM Project, Project BT User - checkErr(test_db.Model(&proj_A).Association("User").Append(&usr_A).Error) - checkErr(test_db.Model(&proj_B).Association("User").Append(&usr_A).Error) + // Simulation HM Model + checkErr(test_db.Model(&simn_A).Association("Models").Append(&mo_A).Error) + checkErr(test_db.Model(&simn_A).Association("Models").Append(&mo_B).Error) - // Simulation HM Project, Project BT Simulation - checkErr(test_db.Model(&proj_A).Association("Simulation").Append(&simn_A).Error) - checkErr(test_db.Model(&proj_B).Association("Simulation").Append(&simn_A).Error) - - // User HM Files - checkErr(test_db.Model(&usr_A).Association("Files").Append(&file_A).Error) - checkErr(test_db.Model(&usr_A).Association("Files").Append(&file_B).Error) - - // Simulation HM SimModel, SimModel BT Simulation - checkErr(test_db.Model(&smo_A).Association("BelongsToSimulation").Append(&simn_A).Error) - checkErr(test_db.Model(&smo_B).Association("BelongsToSimulation").Append(&simn_A).Error) - - // User HM Simulation, Simulation BT User - checkErr(test_db.Model(&simn_A).Association("User").Append(&usr_A).Error) - checkErr(test_db.Model(&simn_B).Association("User").Append(&usr_A).Error) + // Simulation HM Visualizations + checkErr(test_db.Model(&simn_A).Association("Visualizations").Append(&vis_A).Error) + checkErr(test_db.Model(&simn_A).Association("Visualizations").Append(&vis_B).Error) // Visualization HM Widget checkErr(test_db.Model(&vis_A).Association("Widgets").Append(&widg_A).Error) checkErr(test_db.Model(&vis_A).Association("Widgets").Append(&widg_B).Error) - // SimModel HM Signal - checkErr(test_db.Model(&smo_A).Association("InputMapping").Append(&inSig_A).Error) - checkErr(test_db.Model(&smo_A).Association("InputMapping").Append(&inSig_B).Error) - checkErr(test_db.Model(&smo_A).Association("OutputMapping").Append(&outSig_A).Error) - checkErr(test_db.Model(&smo_A).Association("OutputMapping").Append(&outSig_B).Error) + // Model HM Signal + //checkErr(test_db.Model(&mo_A).Association("InputMapping").Append(&inSig_A).Error) + //checkErr(test_db.Model(&mo_A).Association("InputMapping").Append(&inSig_B).Error) + //checkErr(test_db.Model(&mo_A).Association("OutputMapping").Append(&outSig_A).Error) + //checkErr(test_db.Model(&mo_A).Association("OutputMapping").Append(&outSig_B).Error) - //SimulationModel HM Files - checkErr(test_db.Model(&smo_A).Association("Files").Append(&file_A).Error) - checkErr(test_db.Model(&smo_A).Association("Files").Append(&file_B).Error) + // Model HM Files + checkErr(test_db.Model(&mo_A).Association("Files").Append(&file_A).Error) + checkErr(test_db.Model(&mo_A).Association("Files").Append(&file_B).Error) - // Visualization BT User - checkErr(test_db.Model(&vis_A).Association("User").Append(&usr_A).Error) + // Simulator BT Model + checkErr(test_db.Model(&mo_A).Association("Simulator").Append(&simr_A).Error) - // Simulator BT SimModel - checkErr(test_db.Model(&smo_A).Association("BelongsToSimulator").Append(&simr_A).Error) + // Widget HM Files + checkErr(test_db.Model(&widg_A).Association("Files").Append(&file_A).Error) + checkErr(test_db.Model(&widg_A).Association("Files").Append(&file_B).Error) } diff --git a/common/database_test.go b/common/database_test.go index 92d0296..319f4e8 100644 --- a/common/database_test.go +++ b/common/database_test.go @@ -2,8 +2,9 @@ package common import ( "fmt" - "github.com/stretchr/testify/assert" "testing" + + "github.com/stretchr/testify/assert" ) // Verify that you can connect to the database @@ -28,45 +29,33 @@ func TestDummyDBAssociations(t *testing.T) { // Variables for tests var simr Simulator - var smo SimulationModel + var mo Model var file File - var proj Project var simn Simulation var usr User + var usrs []User var vis Visualization + var widg Widget - var sigs []Signal - var smos []SimulationModel + //var sigs []Signal + var mos []Model var files []File var files_sm []File - var projs []Project var simns []Simulation var viss []Visualization var widgs []Widget - // Simulation Model + // User - a.NoError(db.Find(&smo, 1).Error, fM("SimulationModel")) - a.EqualValues("SimModel_A", smo.Name) + a.NoError(db.Find(&usr, 1).Error, fM("User")) + a.EqualValues("User_A", usr.Username) - // Simulation Model Associations + // User Associations - a.NoError(db.Model(&smo).Association("BelongsToSimulation").Find(&simn).Error) - a.EqualValues("Simulation_A", simn.Name, "Expected Simulation_A") - - a.NoError(db.Model(&smo).Association("BelongsToSimulator").Find(&simr).Error) - a.EqualValues("Host_A", simr.Host, "Expected Host_A") - - a.NoError(db.Model(&smo).Related(&sigs, "OutputMapping").Error) - if len(sigs) != 4 { - a.Fail("Simulation Model Associations", - "Expected to have %v Output AND Input Signals. Has %v.", 4, len(sigs)) - } - - a.NoError(db.Model(&smo).Related(&files_sm, "Files").Error) - if len(files_sm) != 2 { - a.Fail("Simulation Model Associations", - "Expected to have %v Files. Has %v.", 2, len(files_sm)) + a.NoError(db.Model(&usr).Related(&simns, "Simulations").Error) + if len(simns) != 2 { + a.Fail("User Associations", + "Expected to have %v Simulations. Has %v.", 2, len(simns)) } // Simulation @@ -76,64 +65,47 @@ func TestDummyDBAssociations(t *testing.T) { // Simulation Associations - a.NoError(db.Model(&simn).Association("User").Find(&usr).Error) - a.EqualValues("User_A", usr.Username) - - a.NoError(db.Model(&simn).Related(&smos, "Models").Error) - if len(smos) != 2 { - a.Fail("Simulation Associations", - "Expected to have %v Simulation Models. Has %v.", 2, len(smos)) + a.NoError(db.Model(&simn).Association("Users").Find(&usrs).Error) + if len(usrs) != 2 { + a.Fail("Simulations Associations", + "Expected to have %v Users. Has %v.", 2, len(usrs)) } - a.NoError(db.Model(&simn).Related(&projs, "Projects").Error) - if len(projs) != 2 { + a.NoError(db.Model(&simn).Related(&mos, "Models").Error) + if len(mos) != 2 { a.Fail("Simulation Associations", - "Expected to have %v Projects. Has %v.", 2, len(projs)) + "Expected to have %v Models. Has %v.", 2, len(mos)) } - // Project - - a.NoError(db.Find(&proj, 1).Error, fM("Project")) - a.EqualValues("Project_A", proj.Name) - - // Project Associations - - a.NoError(db.Model(&proj).Association("Simulation").Find(&simn).Error) - a.EqualValues("Simulation_A", simn.Name) - - a.NoError(db.Model(&proj).Association("User").Find(&usr).Error) - a.EqualValues("User_A", usr.Username) - - a.NoError(db.Model(&proj).Related(&viss, "Visualizations").Error) + a.NoError(db.Model(&simn).Related(&viss, "Visualizations").Error) if len(viss) != 2 { - a.Fail("Project Associations", + a.Fail("Simulation Associations", "Expected to have %v Visualizations. Has %v.", 2, len(viss)) } - // User - a.NoError(db.Find(&usr, 1).Error, fM("User")) - a.EqualValues("User_A", usr.Username) + // Model - // User Associations + a.NoError(db.Find(&mo, 1).Error, fM("Model")) + a.EqualValues("Model_A", mo.Name) - a.NoError(db.Model(&usr).Related(&projs, "Projects").Error) - if len(projs) != 2 { - a.Fail("User Associations", - "Expected to have %v Projects. Has %v.", 2, len(projs)) + // Model Associations + + a.NoError(db.Model(&mo).Association("Simulator").Find(&simr).Error) + a.EqualValues("Host_A", simr.Host, "Expected Host_A") + + //a.NoError(db.Model(&mo).Where("Direction = ?", "out").Related(&sigs, "OutputMapping").Error) + //if len(sigs) != 2 { + // a.Fail("Model Associations", + // "Expected to have %v Output AND Input Signals. Has %v.", 2, len(sigs)) + //} + + a.NoError(db.Model(&mo).Related(&files_sm, "Files").Error) + if len(files_sm) != 2 { + a.Fail("Model Associations", + "Expected to have %v Files. Has %v.", 2, len(files_sm)) } - a.NoError(db.Model(&usr).Related(&simns, "Simulations").Error) - if len(simns) != 2 { - a.Fail("User Associations", - "Expected to have %v Simulations. Has %v.", 2, len(simns)) - } - - a.NoError(db.Model(&usr).Related(&files, "Files").Error) - if len(files) != 2 { - a.Fail("User Associations", - "Expected to have %v Files. Has %v.", 2, len(files)) - } // Visualization @@ -142,26 +114,29 @@ func TestDummyDBAssociations(t *testing.T) { // Visualization Associations - a.NoError(db.Model(&vis).Association("Project").Find(&proj).Error) - a.EqualValues("Project_A", proj.Name) - - a.NoError(db.Model(&vis).Association("User").Find(&usr).Error) - a.EqualValues("User_A", usr.Username) - a.NoError(db.Model(&vis).Related(&widgs, "Widgets").Error) if len(widgs) != 2 { a.Fail("Widget Associations", "Expected to have %v Widget. Has %v.", 2, len(widgs)) } + + // Widget + a.NoError(db.Find(&widg, 1).Error, fM("Widget")) + a.EqualValues("Widget_A", widg.Name) + + + // Widget Association + a.NoError(db.Model(&widg).Related(&files, "Files").Error) + if len(files) != 2 { + a.Fail("Widget Associations", + "Expected to have %v Files. Has %v.", 2, len(files)) + } + // File a.NoError(db.Find(&file, 1).Error, fM("File")) a.EqualValues("File_A", file.Name) - // File Associations - - //a.NoError(db.Model(&file).Association("User").Find(&usr).Error) - //a.EqualValues("User_A", usr.Username) } diff --git a/common/datastructs.go b/common/datastructs.go deleted file mode 100644 index 24e004f..0000000 --- a/common/datastructs.go +++ /dev/null @@ -1,142 +0,0 @@ -package common - -import ( - //"github.com/jinzhu/gorm" - "github.com/jinzhu/gorm/dialects/postgres" - "time" -) - -type Simulator struct { - //gorm.Model - ID uint `gorm:"primary_key;auto_increment"` - UUID string `gorm:"unique;not null"` - Host string `gorm:"default:''"` - Modeltype string `gorm:"default:''"` - Uptime int `gorm:"default:0"` - State string `gorm:"default:''"` - StateUpdateAt time.Time - Properties postgres.Jsonb // TODO: default value? - RawProperties postgres.Jsonb // TODO: default value? -} - -type File struct { - //gorm.Model - ID uint `gorm:"primary_key;auto_increment"` - Name string `gorm:"not null"` - Path string `gorm:"not null"` - Type string `gorm:"not null"` - Size uint `gorm:"not null"` - ImageHeight uint // only required in case file is an image - ImageWidth uint // only required in case file is an image - Date time.Time - - //remove belongs to User relation - //User User `gorm:"not null;association_autoupdate:false"` - UserID uint `gorm:"not null"` - SimulationModelID uint `gorm:""` -} - -type Project struct { - //gorm.Model - ID uint `gorm:"primary_key;auto_increment"` - Name string `gorm:"not null"` - - User User `gorm:"not null;association_autoupdate:false"` - UserID uint `gorm:"not null"` - - Simulation Simulation `gorm:"not null;association_autoupdate:false"` - SimulationID uint `gorm:"not null"` - - Visualizations []Visualization `gorm:"association_autoupdate:false"` -} - -type Simulation struct { - //gorm.Model - ID uint `gorm:"primary_key;auto_increment"` - Name string `gorm:"not null"` - Running bool `gorm:"default:false"` - StartParameters postgres.Jsonb // TODO default value - - User User `gorm:"not null;association_autoupdate:false"` - UserID uint `gorm:"not null"` - - Models []SimulationModel `gorm:"foreignkey:BelongsToSimulationID;association_autoupdate:false"` - Projects []Project `gorm:"association_autoupdate:false"` -} - -type SimulationModel struct { - //gorm.Model - ID uint `gorm:"primary_key;auto_increment"` - Name string `gorm:"not null"` - OutputLength int `gorm:"default:1"` - InputLength int `gorm:"default:1"` - StartParameters postgres.Jsonb // TODO: default value? - - BelongsToSimulation Simulation `gorm:"not null;association_autoupdate:false"` - BelongsToSimulationID uint `gorm:"not null"` - - BelongsToSimulator Simulator `gorm:"not null;association_autoupdate:false"` - BelongsToSimulatorID uint `gorm:"not null"` - // NOTE: order of signals is important - OutputMapping []Signal `gorm:""` - InputMapping []Signal `gorm:""` - - //new in villasweb 2.0 (for CIM file of simulation model and other model file formats) - Files []File `gorm:""` - - -} - -type User struct { - //gorm.Model - ID uint `gorm:"primary_key;auto_increment"` - Username string `gorm:"unique;not null"` - Password string `gorm:"not null"` - Mail string `gorm:"default:''"` - Role string `gorm:"default:'user'"` - - Projects []Project `gorm:"association_autoupdate:false"` - Simulations []Simulation `gorm:"association_autoupdate:false"` - Files []File `gorm:""` -} - -type Visualization struct { - //gorm.Model - ID uint `gorm:"primary_key;auto_increment"` - Name string `gorm:"not null"` - Grid int `gorm:"default:15"` - - Project Project `gorm:"not null;association_autoupdate:false"` - ProjectID uint `gorm:"not null"` - - User User `gorm:"not null;association_autoupdate:false"` - UserID uint `gorm:"not null"` - - Widgets []Widget `gorm:""` -} - -type Signal struct { - //gorm.Model - ID uint `gorm:"primary_key;auto_increment"` - Name string `gorm:"not null"` - Unit string `gorm:"not null"` - SimulationModelID uint - //IsRecorded bool `gorm:"default:false"` -} - -type Widget struct { - //gorm.Model - ID uint `gorm:"primary_key;auto_increment"` - Name string `gorm:"not null"` - Type string `gorm:"not null"` - Width uint `gorm:"not null"` - Height uint `gorm:"not null"` - MinWidth uint `gorm:"not null"` - MinHeight uint `gorm:"not null"` - X int `gorm:"not null"` - Y int `gorm:"not null"` - Z int `gorm:"not null"` - IsLocked bool `gorm:"default:false"` - CustomProperties postgres.Jsonb // TODO: default value? - VisualizationID uint -} diff --git a/common/models.go b/common/models.go new file mode 100644 index 0000000..bb19053 --- /dev/null +++ b/common/models.go @@ -0,0 +1,172 @@ +package common + +import ( + "time" +) + +// User data model +type User struct { + // ID of user + ID uint `gorm:"primary_key;auto_increment"` + // Username of user + Username string `gorm:"unique;not null"` + // Password of user + Password string `gorm:"not null"` + // Mail of user + Mail string `gorm:"default:''"` + // Role of user + Role string `gorm:"default:'user'"` + // Simulations to which user has access + Simulations []Simulation `gorm:"many2many:user_simulations"` +} + + +// Simulation data model +type Simulation struct { + // ID of simulation + ID uint `gorm:"primary_key;auto_increment"` + // Name of simulation + Name string `gorm:"not null"` + // Running state of simulation + Running bool `gorm:"default:false"` + // Start parameters of simulation as JSON string + StartParameters string + // Users that have access to the simulation + Users []User `gorm:"not null;many2many:user_simulations"` + // Models that belong to the simulation + Models []Model `gorm:"foreignkey:SimulationID"` + // Visualizations that belong to the simulation + Visualizations []Visualization `gorm:"foreignkey:SimulationID"` +} + +// Model data model +type Model struct { + // ID of model + ID uint `gorm:"primary_key;auto_increment"` + // Name of model + Name string `gorm:"not null"` + // Number of output signals + OutputLength int `gorm:"default:1"` + // Number of input signals + InputLength int `gorm:"default:1"` + // Start parameters of model as JSON string + StartParameters string + // ID of simulation to which model belongs + SimulationID uint + // Simulator associated with model + Simulator Simulator + // ID of simulator associated with model + SimulatorID uint + // Mapping of output signals of the model, order of signals is important + OutputMapping []Signal + // Mapping of input signals of the model, order of signals is important + InputMapping []Signal + // Files of model (can be CIM and other model file formats) + Files []File `gorm:"foreignkey:ModelID"` +} + +type Signal struct { + // Name of Signal + Name string + // Unit of Signal + Unit string + // Index of the Signal in the mapping + Index uint + // Direction of the signal (in or out) + Direction string +} + + +// Simulator data model +type Simulator struct { + // ID of the simulator + ID uint `gorm:"primary_key;auto_increment"` + // UUID of the simulator + UUID string `gorm:"unique;not null"` + // Host if the simulator + Host string `gorm:"default:''"` + // Model type supported by the simulator + Modeltype string `gorm:"default:''"` + // Uptime of the simulator + Uptime int `gorm:"default:0"` + // State of the simulator + State string `gorm:"default:''"` + // Time of last state update + StateUpdateAt time.Time + // Properties of simulator as JSON string + Properties string + // Raw properties of simulator as JSON string + RawProperties string +} + +// Visualization data model +type Visualization struct { + // ID of visualization + ID uint `gorm:"primary_key;auto_increment"` + // Name of visualization + Name string `gorm:"not null"` + // Grid of visualization + Grid int `gorm:"default:15"` + // ID of simulation to which visualization belongs + SimulationID uint `gorm:"not null"` + // Widgets that belong to visualization + Widgets []Widget `gorm:"foreignkey:VisualizationID"` +} + + +// Widget data model +type Widget struct { + // ID of widget + ID uint `gorm:"primary_key;auto_increment"` + // Name of widget + Name string `gorm:"not null"` + // Type of widget + Type string `gorm:"not null"` + // Width of widget + Width uint `gorm:"not null"` + // Height of widget + Height uint `gorm:"not null"` + // Minimal width of widget + MinWidth uint `gorm:"not null"` + // Minimal height of widget + MinHeight uint `gorm:"not null"` + // X position of widget + X int `gorm:"not null"` + // Y position of widget + Y int `gorm:"not null"` + // Z position of widget + Z int `gorm:"not null"` + // Locked state of widget + IsLocked bool `gorm:"default:false"` + // Custom properties of widget as JSON string + CustomProperties string + // ID of visualization to which widget belongs + VisualizationID uint `gorm:"not null"` + // Files that belong to widget (for example images) + Files []File `gorm:"foreignkey:WidgetID"` +} + + +// File data model +type File struct { + // ID of file + ID uint `gorm:"primary_key;auto_increment"` + // Name of file + Name string `gorm:"not null"` + // Path at which file is saved at server side + Path string `gorm:"not null"` + // Type of file (MIME type) + Type string `gorm:"not null"` + // Size of file (in byte) + Size uint `gorm:"not null"` + // Height of image (only needed in case of image) + ImageHeight uint + // Width of image (only needed in case of image) + ImageWidth uint + // Last modification time of file + Date time.Time + // ID of model to which file belongs + ModelID uint `gorm:""` + // ID of widget to which file belongs + WidgetID uint `gorm:""` +} diff --git a/common/responses.go b/common/responses.go new file mode 100644 index 0000000..5415866 --- /dev/null +++ b/common/responses.go @@ -0,0 +1,95 @@ +package common + +import ( + "time" +) + +type UserResponse struct { + Username string `json:"Username"` + Role string `json:"Role"` + Mail string `json:"Mail"` +} + +type SimulationResponse struct { + Name string `json:"Name"` + ID uint `json:"SimulationID"` + Running bool `json:"Running"` + StartParams string `json:"Starting Parameters"` +} + +type ModelResponse struct { + Name string `json:"Name"` + OutputLength int `json:"OutputLength"` + InputLength int `json:"InputLength"` + SimulationID uint `json:"SimulationID"` + SimulatorID uint `json:"SimulatorID"` + StartParams string `json:"StartParams"` + InputMapping []Signal `json:"InputMapping"` + OutputMapping []Signal `json:"OutputMapping"` +} + +type SimulatorResponse struct { + UUID string `json:"UUID"` + Host string `json:"Host"` + ModelType string `json:"ModelType"` + Uptime int `json:"Uptime"` + State string `json:"State"` + StateUpdateAt time.Time `json:"StateUpdateAt"` + Properties string `json:"Properties"` + RawProperties string `json:"RawProperties"` +} + +type VisualizationResponse struct { + Name string `json:"Name"` + Grid int `json:"Grid"` + SimulationID uint `json:"SimulationID"` +} + +type WidgetResponse struct { + Name string `json:"Name"` + Type string `json:"Type"` + Width uint `json:"Width"` + Height uint `json:"Height"` + MinWidth uint `json:"MinWidth"` + MinHeight uint `json:"MinHeight"` + X int `json:"X"` + Y int `json:"Y"` + Z int `json:"Z"` + VisualizationID uint `json:"VisualizationID"` + IsLocked bool `json:"IsLocked"` + CustomProperties string `json:"CustomProperties"` +} + +type FileResponse struct { + Name string `json:"Name"` + ID uint `json:"FileID"` + Path string `json:"Path"` + Type string `json:"Type"` + Size uint `json:"Size"` + H uint `json:"ImageHeight"` + W uint `json:"ImageWidth"` + Date time.Time `json:"Date"` +} + +// Response messages + +type ResponseMsg struct{ + Message string `json:"message"` +} + +type ResponseMsgUsers struct { + Users []UserResponse `json:"users"` +} + +type ResponseMsgUser struct { + User UserResponse `json:"user"` +} + +type ResponseMsgSimulations struct { + Simulations []SimulationResponse `json:"simulations"` +} + +type ResponseMsgSimulation struct { + Simulation SimulationResponse `json:"simulation"` +} + diff --git a/common/serializers.go b/common/serializers.go new file mode 100644 index 0000000..ba3af89 --- /dev/null +++ b/common/serializers.go @@ -0,0 +1,262 @@ +package common + +import ( + "github.com/gin-gonic/gin" +) + +// User/s Serializers + +type UsersSerializer struct { + Ctx *gin.Context + Users []User +} + +func (self *UsersSerializer) Response(assoc bool) []UserResponse { + response := []UserResponse{} + for _, user := range self.Users { + serializer := UserSerializer{self.Ctx, user} + response = append(response, serializer.Response(assoc)) + } + return response +} + +type UserSerializer struct { + Ctx *gin.Context + User +} + + +func (self *UserSerializer) Response(assoc bool) UserResponse { + + response := UserResponse{ + Username: self.Username, + Role: self.Role, + Mail: self.Mail, + } + + // Associated models MUST NOT called with assoc=true otherwise we + // will have an infinite loop due to the circular dependencies + if assoc { + + // TODO: maybe all those should be made in one transaction + + //simulations, _, _ := simulation.FindUserSimulations(&self.User) + //simulationsSerializer := + // SimulationsSerializer{self.Ctx, simulations} + + // Add the associated models to the response + //response.Simulations = simulationsSerializer.Response() + } + + return response +} + +// Simulation/s Serializers + +type SimulationsSerializer struct { + Ctx *gin.Context + Simulations []Simulation +} + +func (self *SimulationsSerializer) Response() []SimulationResponse { + response := []SimulationResponse{} + for _, simulation := range self.Simulations { + serializer := SimulationSerializer{self.Ctx, simulation} + response = append(response, serializer.Response()) + } + return response +} + +type SimulationSerializer struct { + Ctx *gin.Context + Simulation +} + + +func (self *SimulationSerializer) Response() SimulationResponse { + response := SimulationResponse{ + Name: self.Name, + ID: self.ID, + Running: self.Running, + StartParams: self.StartParameters, + } + return response +} + + +// Model/s Serializers + +type ModelsSerializer struct { + Ctx *gin.Context + Models []Model +} + +func (self *ModelsSerializer) Response() []ModelResponse { + response := []ModelResponse{} + for _, model := range self.Models { + serializer := ModelSerializer{self.Ctx, model} + response = append(response, serializer.Response()) + } + return response +} + +type ModelSerializer struct { + Ctx *gin.Context + Model +} + +func (self *ModelSerializer) Response() ModelResponse { + response := ModelResponse{ + Name: self.Name, + OutputLength: self.OutputLength, + InputLength: self.InputLength, + SimulationID: self.SimulationID, + SimulatorID: self.SimulatorID, + StartParams: self.StartParameters, + //InputMapping + //OutputMapping + } + return response +} + +// Simulator/s Serializers + +type SimulatorsSerializer struct { + Ctx *gin.Context + Simulators []Simulator +} + +func (self *SimulatorsSerializer) Response() []SimulatorResponse { + response := []SimulatorResponse{} + for _, simulator := range self.Simulators { + serializer := SimulatorSerializer{self.Ctx, simulator} + response = append(response, serializer.Response()) + } + return response +} + +type SimulatorSerializer struct { + Ctx *gin.Context + Simulator +} + +func (self *SimulatorSerializer) Response() SimulatorResponse { + + response := SimulatorResponse{ + UUID: self.UUID, + Host: self.Host, + ModelType: self.Modeltype, + Uptime: self.Uptime, + State: self.State, + StateUpdateAt: self.StateUpdateAt, + } + return response +} + +// Visualization/s Serializers + +type VisualizationsSerializer struct { + Ctx *gin.Context + Visualizations []Visualization +} + +func (self *VisualizationsSerializer) Response() []VisualizationResponse { + response := []VisualizationResponse{} + for _, visualization := range self.Visualizations { + serializer := VisualizationSerializer{self.Ctx, visualization} + response = append(response, serializer.Response()) + } + return response +} + +type VisualizationSerializer struct { + Ctx *gin.Context + Visualization +} + + +func (self *VisualizationSerializer) Response() VisualizationResponse { + + response := VisualizationResponse{ + Name: self.Name, + Grid: self.Grid, + SimulationID: self.SimulationID, + } + return response +} + +// Widget/s Serializers + +type WidgetsSerializer struct { + Ctx *gin.Context + Widgets []Widget +} + +func (self *WidgetsSerializer) Response() []WidgetResponse { + response := []WidgetResponse{} + for _, widget := range self.Widgets { + serializer := WidgetSerializer{self.Ctx, widget} + response = append(response, serializer.Response()) + } + return response +} + +type WidgetSerializer struct { + Ctx *gin.Context + Widget +} + +func (self *WidgetSerializer) Response() WidgetResponse { + + response := WidgetResponse{ + Name: self.Name, + Type: self.Type, + Width: self.Width, + Height: self.Height, + MinWidth: self.MinWidth, + MinHeight: self.MinHeight, + X: self.X, + Y: self.Y, + Z: self.Z, + VisualizationID: self.VisualizationID, + IsLocked: self.IsLocked, + //CustomProperties + } + return response +} + +// File/s Serializers + +type FilesSerializerNoAssoc struct { + Ctx *gin.Context + Files []File +} + +func (self *FilesSerializerNoAssoc) Response() []FileResponse { + response := []FileResponse{} + for _, files := range self.Files { + serializer := FileSerializerNoAssoc{self.Ctx, files} + response = append(response, serializer.Response()) + } + return response +} + +type FileSerializerNoAssoc struct { + Ctx *gin.Context + File +} + + +func (self *FileSerializerNoAssoc) Response() FileResponse { + response := FileResponse{ + Name: self.Name, + ID: self.ID, + Path: self.Path, + Type: self.Type, + Size: self.Size, + H: self.ImageHeight, + W: self.ImageWidth, + // Date + } + return response +} diff --git a/common/utilities.go b/common/utilities.go new file mode 100644 index 0000000..18c790d --- /dev/null +++ b/common/utilities.go @@ -0,0 +1,93 @@ +package common + +import ( + "fmt" + "net/http" + "strconv" + + "github.com/gin-gonic/gin" + "github.com/jinzhu/gorm" +) + +func ProvideErrorResponse(c *gin.Context, err error) bool { + if err != nil { + if err == gorm.ErrRecordNotFound { + errormsg := "Record not Found in DB: " + err.Error() + c.JSON(http.StatusNotFound, gin.H{ + "error": errormsg, + }) + } else { + errormsg := "Error on DB Query or transaction: " + err.Error() + c.JSON(http.StatusInternalServerError, gin.H{ + "error": errormsg, + }) + } + return true // Error + } + return false // No error +} + + +func GetSimulationID(c *gin.Context) (int, error) { + + simID, err := strconv.Atoi(c.Param("simulationID")) + + if err != nil { + errormsg := fmt.Sprintf("Bad request. No or incorrect format of simulation ID") + c.JSON(http.StatusBadRequest, gin.H{ + "error": errormsg, + }) + return -1, err + } else { + return simID, err + + } +} + +func GetModelID(c *gin.Context) (int, error) { + + modelID, err := strconv.Atoi(c.Param("modelID")) + + if err != nil { + errormsg := fmt.Sprintf("Bad request. No or incorrect format of model ID") + c.JSON(http.StatusBadRequest, gin.H{ + "error": errormsg, + }) + return -1, err + } else { + return modelID, err + + } +} + +func GetVisualizationID(c *gin.Context) (int, error) { + + simID, err := strconv.Atoi(c.Param("visualizationID")) + + if err != nil { + errormsg := fmt.Sprintf("Bad request. No or incorrect format of visualization ID") + c.JSON(http.StatusBadRequest, gin.H{ + "error": errormsg, + }) + return -1, err + } else { + return simID, err + + } +} + +func GetWidgetID(c *gin.Context) (int, error) { + + widgetID, err := strconv.Atoi(c.Param("widgetID")) + + if err != nil { + errormsg := fmt.Sprintf("Bad request. No or incorrect format of widget ID") + c.JSON(http.StatusBadRequest, gin.H{ + "error": errormsg, + }) + return -1, err + } else { + return widgetID, err + + } +} \ No newline at end of file diff --git a/doc/api/api.yaml b/doc/api/api.yaml index 746b96f..08a5e5e 100644 --- a/doc/api/api.yaml +++ b/doc/api/api.yaml @@ -17,11 +17,11 @@ tags: - name: simulators description: Manage Simulators - name: projects - description: Manage Projects + description: (REMOVED) Manage Projects - name: simulations description: Manage Simulations -- name: simulationmodels - description: Manage SimulationModels +- name: models + description: Manage Models - name: visualizations description: Manage Visualizations - name: uploads @@ -244,8 +244,8 @@ paths: get: tags: - files - summary: Get files of user - operationId: getFilesOfUser + summary: (REMOVED) Get files of user + operationId: (REMOVED) getFilesOfUser responses: 200: description: OK. @@ -266,8 +266,8 @@ paths: post: tags: - files - summary: Add a new file to the database (NEW, was /uploads before)) - operationId: addFile + summary: (REMOVED) Add a new file to the database + operationId: (REMOVED) addFile requestBody: description: "File object to be added" required: true @@ -292,8 +292,8 @@ paths: get: tags: - files - summary: Get properties of file with ID FileID - operationId: getFileProperties + summary: (REMOVED) Get properties of file with ID FileID + operationId: (REMOVED) getFileProperties parameters: - in: path name: FileID @@ -329,8 +329,8 @@ paths: put: tags: - files - summary: Update properties of file with ID FileID (NEW) - operationId: updateFileProperties + summary: (REMOVED) Update properties of file with ID FileID + operationId: (REMOVED) updateFileProperties requestBody: description: "File object to be updated" required: true @@ -361,8 +361,8 @@ paths: delete: tags: - files - summary: Delete file with ID FileID - operationId: deleteFile + summary: (REMOVED) Delete file with ID FileID + operationId: (REMOVED) deleteFile parameters: - in: path name: FileID @@ -385,8 +385,8 @@ paths: get: tags: - projects - summary: Get projects of user - operationId: getProjects + summary: (REMOVED) Get projects of user + operationId: (REMOVED) getProjects responses: 200: description: OK. @@ -407,8 +407,8 @@ paths: post: tags: - projects - summary: Add a new project to the database - operationId: addProject + summary: (REMOVED) Add a new project to the database + operationId: (REMOVED) addProject requestBody: description: "Project object to add to DB" required: true @@ -431,8 +431,8 @@ paths: put: tags: - projects - summary: Update properties of project with ID ProjectID - operationId: updateProjectProperties + summary: (REMOVED) Update properties of project with ID ProjectID + operationId: (REMOVED) updateProjectProperties parameters: - in: path name: ProjectID @@ -463,8 +463,8 @@ paths: get: tags: - projects - summary: Get properties of project with ID ProjectID - operationId: getProjectProperties + summary: (REMOVED) Get properties of project with ID ProjectID + operationId: (REMOVED) getProjectProperties parameters: - in: path name: ProjectID @@ -490,8 +490,8 @@ paths: delete: tags: - projects - summary: Delete project with ID ProjectID - operationId: deleteProject + summary: (REMOVED) Delete project with ID ProjectID + operationId: (REMOVED) deleteProject parameters: - in: path name: ProjectID @@ -513,9 +513,9 @@ paths: /models: get: tags: - - simulationmodels - summary: Get simulation models of user - operationId: getSimulationModels + - models + summary: (REMOVED) Get simulation models of user + operationId: (REMOVED) getModels responses: 200: description: OK. @@ -524,7 +524,7 @@ paths: schema: type: array items: - $ref: '#/components/schemas/SimulationModel' + $ref: '#/components/schemas/Model' 401: description: Unauthorized access. 403: @@ -535,16 +535,16 @@ paths: description: Internal server error. post: tags: - - simulationmodels - summary: Add a new SimulationModel to the database - operationId: addSimulationModel + - models + summary: (REMOVED) Add a new Model to the database + operationId: (REMOVED) addModel requestBody: - description: "SimulationModel object to add to DB" + description: "Model object to add to DB" required: true content: application/json: schema: - $ref: '#/components/schemas/SimulationModel' + $ref: '#/components/schemas/Model' responses: 200: description: OK. @@ -556,26 +556,26 @@ paths: description: Access forbidden. 500: description: Internal server error. Unable to save simulation or simulation model. - /models/{SimulationModelID}: + /models/{ModelID}: put: tags: - - simulationmodels - summary: Update properties of SimulationModel with ID SimulationModelID - operationId: updateSimulationModelProperties + - models + summary: (REMOVED) Update properties of Model with ID ModelID + operationId: (REMOVED) updateModelProperties parameters: - in: path - name: SimulationModelID - description: ID of a SimulationModel + name: ModelID + description: ID of a Model required: true schema: type: integer requestBody: - description: "SimulationModel object with new properties" + description: "Model object with new properties" required: true content: application/json: schema: - $ref: '#/components/schemas/SimulationModel' + $ref: '#/components/schemas/Model' responses: 200: description: OK. @@ -591,13 +591,13 @@ paths: description: Internal server error. Unable to save simulation model. get: tags: - - simulationmodels - summary: Get properties of SimulationModel with ID SimulationModelID - operationId: getSimulationModelProperties + - models + summary: (REMOVED) Get properties of Model with ID ModelID + operationId: (REMOVED) getModelProperties parameters: - in: path - name: SimulationModelID - description: ID of a SimulationModel + name: ModelID + description: ID of a Model required: true schema: type: integer @@ -607,7 +607,7 @@ paths: content: application/json: schema: - $ref: '#/components/schemas/SimulationModel' + $ref: '#/components/schemas/Model' 401: description: Unauthorized access. 403: @@ -618,13 +618,13 @@ paths: description: Internal server error. delete: tags: - - simulationmodels - summary: Delete SimulationModel with ID SimulationModelID - operationId: deleteSimulationModel + - models + summary: (REMOVED) Delete Model with ID ModelID + operationId: (REMOVED) deleteModel parameters: - in: path - name: SimulationModelID - description: ID of a SimulationModel + name: ModelID + description: ID of a Model required: true schema: type: integer @@ -639,16 +639,16 @@ paths: description: Not found. Simulation or simulation model not found. 500: description: Internal server error. Unable to save changed simulation or to remove simulation model. - /models/{SimulationModelID}/file: + simulations/{SimulationID}/models/{ModelID}/file: get: tags: - - simulationmodels - summary: Get file from SimulationModel with ID SimulationModelID (NEW) - operationId: getFileFromSimulationModel + - simulations + summary: (NEW) Get file from Model with ID ModelID + operationId: getFileFromModel parameters: - in: path - name: SimulationModelID - description: ID of a SimulationModel + name: ModelID + description: ID of a Model required: true schema: type: integer @@ -691,13 +691,13 @@ paths: description: Internal server error. put: tags: - - simulationmodels - summary: Update (Overwrite) file of SimulationModel with ID SimulationModelID, File object has to be in database (NEW) - operationId: updateFileForSimulationModel + - simulations + summary: (NEW) Update (Overwrite) file of Model with ID ModelID, File object has to be in database + operationId: updateFileForModel parameters: - in: path - name: SimulationModelID - description: ID of a SimulationModel + name: ModelID + description: ID of a Model required: true schema: type: integer @@ -1035,9 +1035,9 @@ paths: get: tags: - visualizations - summary: Get all available visualizations - description: Return a JSON representation of all visualizations - operationId: getVisualizations + summary: (REMOVED) Get all available visualizations + description: (REMOVED) Return a JSON representation of all visualizations + operationId: (REMOVED) getVisualizations responses: 200: description: OK. @@ -1058,8 +1058,8 @@ paths: post: tags: - visualizations - summary: Add a new visualization to the database - operationId: addVisualization + summary: (REMOVED) Add a new visualization to the database + operationId: (REMOVED) addVisualization requestBody: description: "Visualization object to add to DB" required: true @@ -1084,8 +1084,8 @@ paths: put: tags: - visualizations - summary: Update properties of Visualization with ID VisualizationID - operationId: updateVisualizationrProperties + summary: (REMOVED) Update properties of Visualization with ID VisualizationID + operationId: (REMOVED) updateVisualizationrProperties parameters: - in: path name: VisualizationID @@ -1116,8 +1116,8 @@ paths: get: tags: - visualizations - summary: Get properties of Visualization with ID VisualizationID - operationId: getVisualizationProperties + summary: (REMOVED) Get properties of Visualization with ID VisualizationID + operationId: (REMOVED) getVisualizationProperties parameters: - in: path name: VisualizationID @@ -1143,8 +1143,8 @@ paths: delete: tags: - visualizations - summary: Delete Visualization with ID VisualizationID - operationId: deleteVisualization + summary: (REMOVED) Delete Visualization with ID VisualizationID + operationId: (REMOVED) deleteVisualization parameters: - in: path name: VisualizationID @@ -1167,8 +1167,8 @@ paths: post: tags: - uploads - summary: Upload a new file to the database (REMOVED) - operationId: uploadFile + summary: (REMOVED) Upload a new file to the database + operationId: (REMOVED) uploadFile requestBody: description: "File object to upload TODO CHANGE TO FORM" required: true @@ -1280,7 +1280,7 @@ components: type: integer UserID: type: integer - SimulationModelID: + ModelID: type: integer Date: type: string @@ -1309,7 +1309,7 @@ components: RawProperties: type: object properties: {} - SimulationModel: + Model: required: - Name - OutputLength @@ -1343,14 +1343,14 @@ components: required: - Name - Unit - - SimulationModelID + - ModelID type: object properties: Name: type: string Unit: type: string - SimulationModelID: + ModelID: type: integer Widget: required: diff --git a/doc/autoapi/generateapidoc.sh b/doc/autoapi/generateapidoc.sh new file mode 100755 index 0000000..37d26d3 --- /dev/null +++ b/doc/autoapi/generateapidoc.sh @@ -0,0 +1,9 @@ +#!/usr/bin/env bash + + +cd ../../ +go mod tidy +swag init -p pascalcase -g "start.go" -o "./doc/autoapi/" +cd - + +redoc-cli bundle --cdn --title "VILLASweb Backend API" --output index.html swagger.yaml \ No newline at end of file diff --git a/go.mod b/go.mod index afb3ba9..25c82c2 100644 --- a/go.mod +++ b/go.mod @@ -1,26 +1,14 @@ module git.rwth-aachen.de/acs/public/villas/villasweb-backend-go require ( - github.com/denisenkom/go-mssqldb v0.0.0-20190401154936-ce35bd87d4b3 // indirect + github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc github.com/dgrijalva/jwt-go v3.2.0+incompatible - github.com/erikstmartin/go-testdb v0.0.0-20160219214506-8d10e4a1bae5 // indirect - github.com/gin-contrib/sse v0.0.0-20190301062529-5545eab6dad3 // indirect - github.com/gin-gonic/gin v1.3.0 - github.com/go-sql-driver/mysql v1.4.1 // indirect - github.com/gofrs/uuid v3.2.0+incompatible // indirect - github.com/jinzhu/gorm v1.9.2 - github.com/jinzhu/inflection v0.0.0-20180308033659-04140366298a // indirect - github.com/jinzhu/now v1.0.0 // indirect - github.com/json-iterator/go v1.1.6 // indirect - github.com/kr/pretty v0.1.0 // indirect - github.com/lib/pq v1.0.0 // indirect - github.com/mattn/go-isatty v0.0.7 // indirect - github.com/mattn/go-sqlite3 v1.10.0 // indirect - github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect - github.com/modern-go/reflect2 v1.0.1 // indirect - github.com/stretchr/testify v1.2.2 - github.com/ugorji/go v1.1.4 // indirect + github.com/gin-gonic/gin v1.4.0 + github.com/jinzhu/gorm v1.9.8 + github.com/stretchr/testify v1.3.0 + github.com/swaggo/gin-swagger v1.1.0 + github.com/swaggo/swag v1.5.0 golang.org/x/crypto v0.0.0-20190325154230-a5d413f7728c - gopkg.in/go-playground/assert.v1 v1.2.1 // indirect - gopkg.in/go-playground/validator.v8 v8.18.2 // indirect ) + +replace github.com/ugorji/go v1.1.4 => github.com/ugorji/go/codec v0.0.0-20190204201341-e444a5086c43 diff --git a/go.sum b/go.sum index 552585e..39ebcf0 100644 --- a/go.sum +++ b/go.sum @@ -1,25 +1,26 @@ cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= -cloud.google.com/go v0.31.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= -cloud.google.com/go v0.37.2 h1:4y4L7BdHenTfZL0HervofNTHh9Ad6mNX72cQvl+5eH0= -cloud.google.com/go v0.37.2/go.mod h1:H8IAquKe2L30IxoupDgqTaQvKSwF/c8prYHynGIWQbA= -git.apache.org/thrift.git v0.0.0-20180902110319-2566ecd5d999/go.mod h1:fPE2ZNJGynbRyZ4dJvy6G277gSllfV2HJqblrnkyeyg= -git.apache.org/thrift.git v0.12.0/go.mod h1:fPE2ZNJGynbRyZ4dJvy6G277gSllfV2HJqblrnkyeyg= +cloud.google.com/go v0.37.4 h1:glPeL3BQJsbF6aIIYfZizMwc5LTYz250bDMjttbBGAU= +cloud.google.com/go v0.37.4/go.mod h1:NHPJ89PdicEuT9hdPXMROBD91xc5uRDxsMtSB16k7hw= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= +github.com/PuerkitoBio/purell v1.1.0/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbtSwDGJws/X0= +github.com/PuerkitoBio/purell v1.1.1 h1:WEQqlqaGbrPkxLJWfBwQmfEAE1Z7ONdDLqrN38tNFfI= +github.com/PuerkitoBio/purell v1.1.1/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbtSwDGJws/X0= +github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578 h1:d+Bc7a5rLufV/sSk/8dngufqelfh6jnri85riMAaF/M= +github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578/go.mod h1:uGdkoq3SwY9Y+13GIhn11/XLaGBb4BfwItxLd5jeuXE= github.com/Shopify/sarama v1.19.0/go.mod h1:FVkBWblsNy7DGZRfXLU0O9RCGt5g3g3yEuWXgklEdEo= github.com/Shopify/toxiproxy v2.1.4+incompatible/go.mod h1:OXgGpZ6Cli1/URJOF1DMxUHB2q5Ap20/P/eIdh4G0pI= +github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc h1:cAKDfWh5VpdgMhJosfJnn5/FoN2SRZ4p7fJNX58YPaU= github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= -github.com/anmitsu/go-shlex v0.0.0-20161002113705-648efa622239/go.mod h1:2FmKhYUyUczH0OGQWaF5ceTx0UBShxjsH6f8oGKYe2c= github.com/apache/thrift v0.12.0/go.mod h1:cp2SuWMxlEZw2r+iP2GNCdIi4C1qmUzdZFSVb+bacwQ= github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q= -github.com/bradfitz/go-smtpd v0.0.0-20170404230938-deb6d6237625/go.mod h1:HYsPBTaaSFSlLx/70C2HPIMNZpVV8+vt/A+FMnYP11g= github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= -github.com/coreos/go-systemd v0.0.0-20181012123002-c6f51f82210d/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/denisenkom/go-mssqldb v0.0.0-20190401154936-ce35bd87d4b3 h1:3mNLx0iFqaq/Ssxqkjte26072KMu96uz1VBlbiZhQU4= -github.com/denisenkom/go-mssqldb v0.0.0-20190401154936-ce35bd87d4b3/go.mod h1:EcO5fNtMZHCMjAvj8LE6T+5bphSdR6LQ75n+m1TtsFI= +github.com/denisenkom/go-mssqldb v0.0.0-20190423183735-731ef375ac02 h1:PS3xfVPa8N84AzoWZHFCbA0+ikz4f4skktfjQoNMsgk= +github.com/denisenkom/go-mssqldb v0.0.0-20190423183735-731ef375ac02/go.mod h1:zAg7JM8CkOJ43xKXIj7eRO9kmWm/TW578qo+oDO6tuM= github.com/dgrijalva/jwt-go v3.2.0+incompatible h1:7qlOGliEKZXTDg6OTjfoBKDXWrumCAMpl/TFQ4/5kLM= github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ= github.com/eapache/go-resiliency v1.1.0/go.mod h1:kFI+JgMyC7bLPUVY133qvEBtVayf5mFgVsvEsIPBvNs= @@ -27,49 +28,51 @@ github.com/eapache/go-xerial-snappy v0.0.0-20180814174437-776d5712da21/go.mod h1 github.com/eapache/queue v1.1.0/go.mod h1:6eCeP0CKFpHLu8blIFXhExK/dRa7WDZfr6jVFPTqq+I= 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/flynn/go-shlex v0.0.0-20150515145356-3f9db97f8568/go.mod h1:xEzjJPgXI435gkrCt3MPfRiAkVrwSbHsst4LCFVfpJc= github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= +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-gonic/gin v1.3.0 h1:kCmZyPklC0gVdL728E6Aj20uYBJV93nj/TkwBTKhFbs= github.com/gin-gonic/gin v1.3.0/go.mod h1:7cKuhb5qV2ggCFctp2fJQ+ErvciLZrIeoOSOm6mUr7Y= -github.com/gliderlabs/ssh v0.1.1/go.mod h1:U7qILu1NlMHj9FlMhZLlkCdDnU1DBEAqr0aevW3Awn0= +github.com/gin-gonic/gin v1.4.0 h1:3tMoCCfM7ppqsR0ptz/wi1impNpT7/9wQtMZ8lr1mCQ= +github.com/gin-gonic/gin v1.4.0/go.mod h1:OW2EZn3DO8Ln9oIKOvM++LBO+5UPHJJDH72/q/3rZdM= github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE= +github.com/go-openapi/jsonpointer v0.17.0/go.mod h1:cOnomiV+CVVwFLk0A/MExoFMjwdsUdVpsRhURCKh+3M= +github.com/go-openapi/jsonpointer v0.18.0 h1:KVRzjXpMzgdM4GEMDmDTnGcY5yBwGWreJwmmk4k35yU= +github.com/go-openapi/jsonpointer v0.18.0/go.mod h1:cOnomiV+CVVwFLk0A/MExoFMjwdsUdVpsRhURCKh+3M= +github.com/go-openapi/jsonreference v0.17.0/go.mod h1:g4xxGn04lDIRh0GJb5QlpE3HfopLOL6uZrK/VgnsK9I= +github.com/go-openapi/jsonreference v0.18.0 h1:oP2OUNdG1l2r5kYhrfVMXO54gWmzcfAwP/GFuHpNTkE= +github.com/go-openapi/jsonreference v0.18.0/go.mod h1:g4xxGn04lDIRh0GJb5QlpE3HfopLOL6uZrK/VgnsK9I= +github.com/go-openapi/spec v0.18.0 h1:aIjeyG5mo5/FrvDkpKKEGZPmF9MPHahS72mzfVqeQXQ= +github.com/go-openapi/spec v0.18.0/go.mod h1:XkF/MOi14NmjsfZ8VtAKf8pIlbZzyoTvZsdfssdxcBI= +github.com/go-openapi/swag v0.17.0/go.mod h1:AByQ+nYG6gQg71GINrmuDXCPWdL640yX49/kXLo40Tg= +github.com/go-openapi/swag v0.18.0 h1:1DU8Km1MRGv9Pj7BNLmkA+umwTStwDHttXvx3NhJA70= +github.com/go-openapi/swag v0.18.0/go.mod h1:AByQ+nYG6gQg71GINrmuDXCPWdL640yX49/kXLo40Tg= github.com/go-sql-driver/mysql v1.4.1 h1:g24URVg0OFbNUTx9qqY1IRZ9D9z3iPyi5zKhQZpNwpA= github.com/go-sql-driver/mysql v1.4.1/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w= github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= -github.com/gofrs/uuid v3.2.0+incompatible h1:y12jRkkFxsd7GpqdSZ+/KCs/fJbqpEXSGd4+jfEaewE= -github.com/gofrs/uuid v3.2.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM= github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= github.com/gogo/protobuf v1.2.0/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= -github.com/golang/lint v0.0.0-20180702182130-06c8688daad7/go.mod h1:tluoj9z5200jBnyusfRPU2LqT6J+DAorxEvtC7LHB+E= github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= -github.com/golang/protobuf v1.2.0 h1:P3YflyNX/ehuJFLhxviNdFxQPkGK5cDcApsge1SqnvM= github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.3.1 h1:YF8+flBXS5eO826T4nzqPrxfhQThhXl0YzfuUPu4SBg= +github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/snappy v0.0.0-20180518054509-2e65f85255db/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= github.com/google/go-cmp v0.2.0 h1:+dTQ8DZQJz0Mb/HjFlkptS1FeQ4cWSnN941F8aEG4SQ= github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= -github.com/google/go-github v17.0.0+incompatible/go.mod h1:zLgOLi98H3fifZn+44m+umXrS52loVEgC2AApnigrVQ= -github.com/google/go-querystring v1.0.0/go.mod h1:odCYkC5MyYFN7vkCjXpyrEuKhc/BUO6wN/zVPAxq5ck= github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs= github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= -github.com/googleapis/gax-go v2.0.0+incompatible/go.mod h1:SFVmujtThgffbyetf+mdk2eWhX2bMyUtNHzFKcPA9HY= github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg= github.com/gorilla/context v1.1.1/go.mod h1:kBGZzfjB9CEq2AlWe17Uuf7NDRt0dE0s8S51q0aT7Yg= github.com/gorilla/mux v1.6.2/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs= -github.com/gregjones/httpcache v0.0.0-20180305231024-9cad4c3443a7/go.mod h1:FecbI9+v66THATjSRHfNgh1IVFe/9kFxbXtjV0ctIMA= -github.com/grpc-ecosystem/grpc-gateway v1.5.0/go.mod h1:RSKVYQBd5MCa4OVpNdGskqpgL2+G+NZTnrVHpWWfpdw= -github.com/grpc-ecosystem/grpc-gateway v1.6.2/go.mod h1:RSKVYQBd5MCa4OVpNdGskqpgL2+G+NZTnrVHpWWfpdw= 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/jellevandenhooff/dkim v0.0.0-20150330215556-f50fe3d243e1/go.mod h1:E0B/fFc00Y+Rasa88328GlI/XbtyysCtTHZS8h7IrBU= -github.com/jinzhu/gorm v1.9.2 h1:lCvgEaqe/HVE+tjAR2mt4HbbHAZsQOv3XAZiEZV37iw= -github.com/jinzhu/gorm v1.9.2/go.mod h1:Vla75njaFJ8clLU1W44h34PjIkijhjHIYnZxMqCdxqo= +github.com/jinzhu/gorm v1.9.8 h1:n5uvxqLepIP2R1XF7pudpt9Rv8I3m7G9trGxJVjLZ5k= +github.com/jinzhu/gorm v1.9.8/go.mod h1:bdqTT3q6dhSph2K3pWxrHP6nqxuAp2yQ3KFtc3U3F84= github.com/jinzhu/inflection v0.0.0-20180308033659-04140366298a h1:eeaG9XMUvRBYXJi4pg1ZKM7nxc5AfXfojeLLW7O5J3k= github.com/jinzhu/inflection v0.0.0-20180308033659-04140366298a/go.mod h1:h+uFLlag+Qp1Va5pdKtLDYj+kHp5pxUVkryuEj+Srlc= github.com/jinzhu/now v1.0.0 h1:6WV8LvwPpDhKjo5U9O6b4+xdG/jTXNPwlDme/MTo8Ns= @@ -84,11 +87,14 @@ github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFB github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI= github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= -github.com/kr/pty v1.1.3/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= 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/lib/pq v1.0.0 h1:X5PMW56eZitiTeO7tKzZxFCSpbFZJtkMMooicw2us9A= -github.com/lib/pq v1.0.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= +github.com/lib/pq v1.1.0 h1:/5u4a+KGJptBRqGzPvYQL9p0d/tPR4S31+Tnzj9lEO4= +github.com/lib/pq v1.1.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= +github.com/mailru/easyjson v0.0.0-20180823135443-60711f1a8329/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= +github.com/mailru/easyjson v0.0.0-20190312143242-1de009706dbe h1:W/GaMY0y69G4cFlmsC6B9sbuo2fP8OFP1ABjt4kPz+w= +github.com/mailru/easyjson v0.0.0-20190312143242-1de009706dbe/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= +github.com/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-sqlite3 v1.10.0 h1:jbhqpg7tQe4SupckyijYiy0mJJ/pRyHvXf7JdWK860o= @@ -102,104 +108,97 @@ github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRW github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= github.com/onsi/ginkgo v1.7.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= github.com/onsi/gomega v1.4.3/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY= -github.com/openzipkin/zipkin-go v0.1.1/go.mod h1:NtoC/o8u3JlF1lSlyPNswIbeQH9bJTmOf0Erfk+hxe8= -github.com/openzipkin/zipkin-go v0.1.3/go.mod h1:NtoC/o8u3JlF1lSlyPNswIbeQH9bJTmOf0Erfk+hxe8= github.com/openzipkin/zipkin-go v0.1.6/go.mod h1:QgAqvLzwWbR/WpD4A3cGpPtJrZXNIiJc5AZX7/PBEpw= github.com/pierrec/lz4 v2.0.5+incompatible/go.mod h1:pdkljMzZIN41W+lC3N2tnIh5sFi+IEE17M5jbnwPHcY= github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pkg/errors v0.8.1 h1:iURUrRGxPUNPdy5/HRSm+Yj6okJ6UtLINN0Q9M4+h3I= +github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= -github.com/prometheus/client_golang v0.8.0/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw= github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw= github.com/prometheus/client_golang v0.9.3-0.20190127221311-3c4408c8b829/go.mod h1:p2iRAGwDERtqlqzRXnrOVns+ignqQo//hLXqYxZYVNs= github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo= github.com/prometheus/client_model v0.0.0-20190115171406-56726106282f/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo= -github.com/prometheus/common v0.0.0-20180801064454-c7de2306084e/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro= github.com/prometheus/common v0.2.0/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4= -github.com/prometheus/procfs v0.0.0-20180725123919-05ee40e3a273/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/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/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/testify v1.2.2 h1:bSDNvY7ZPG5RlJ8otE/7V6gMiyenm9RtJ7IUVIAoJ1w= github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= -github.com/tarm/serial v0.0.0-20180830185346-98f6abe2eb07/go.mod h1:kDXzergiv9cbyO7IOYJZWg1U88JhDg3PB6klq9Hg2pA= +github.com/stretchr/testify v1.3.0 h1:TivCn/peBQ7UY8ooIcPgZFpTNSz0Q2U6UrFlUfqbe0Q= +github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= +github.com/swaggo/gin-swagger v1.1.0 h1:ZI6/82S07DkkrMfGKbJhKj1R+QNTICkeAJP06pU36pU= +github.com/swaggo/gin-swagger v1.1.0/go.mod h1:FQlm07YuT1glfN3hQiO11UQ2m39vOCZ/aa3WWr5E+XU= +github.com/swaggo/swag v1.4.0/go.mod h1:hog2WgeMOrQ/LvQ+o1YGTeT+vWVrbi0SiIslBtxKTyM= +github.com/swaggo/swag v1.5.0 h1:haK8VG3hj+v/c8hQ4f3U+oYpkdI/26m9LAUTXHOv+2U= +github.com/swaggo/swag v1.5.0/go.mod h1:+xZrnu5Ut3GcUkKAJm9spnOooIS1WB1cUOkLNPrvrE0= +github.com/ugorji/go v1.1.2/go.mod h1:hnLbHMwcvSihnDhEfx2/BzKp2xb0Y+ErdfYcrs9tkJQ= github.com/ugorji/go v1.1.4 h1:j4s+tAvLfL3bZyefP2SEWmhBzmuIlH/eqNuPdFPgngw= github.com/ugorji/go v1.1.4/go.mod h1:uQMGLiO92mf5W77hV/PUCpI3pbzQx3CRekS0kk+RGrc= -go.opencensus.io v0.18.0/go.mod h1:vKdFvxhtzZ9onBp9VKHK8z/sRpBMnKAsufL7wlDrCOA= -go.opencensus.io v0.19.1/go.mod h1:gug0GbSHa8Pafr0d2urOSgoXHZ6x/RUlaiT0d9pqb4A= -go.opencensus.io v0.19.2/go.mod h1:NO/8qkisMZLZ1FCsKNqtJPwc8/TaclWyY0B6wcYNg9M= -go4.org v0.0.0-20180809161055-417644f6feb5/go.mod h1:MkTOUMDaeVYJUOUsaDXIhWPZYa1yOyC1qaOBpL57BhE= -golang.org/x/build v0.0.0-20190314133821-5284462c4bec/go.mod h1:atTaCNAy0f16Ah5aV1gMSwgiKVHwu/JncqDpuRr7lS4= +github.com/ugorji/go/codec v0.0.0-20181209151446-772ced7fd4c2/go.mod h1:VFNgLljTbGfSG7qAOspJ7OScBnGdDN/yBr0sguwnwf0= +github.com/ugorji/go/codec v0.0.0-20190204201341-e444a5086c43 h1:BasDe+IErOQKrMVXab7UayvSlIpiyGwRvuX3EKYY7UA= +github.com/ugorji/go/codec v0.0.0-20190204201341-e444a5086c43/go.mod h1:iT03XoTwV7xq/+UGwKO3UbC1nNNlopQiY61beSdrtOA= +github.com/ugorji/go/codec v0.0.0-20190320090025-2dc34c0b8780 h1:vG/gY/PxA3v3l04qxe3tDjXyu3bozii8ulSlIPOYKhI= +github.com/ugorji/go/codec v0.0.0-20190320090025-2dc34c0b8780/go.mod h1:iT03XoTwV7xq/+UGwKO3UbC1nNNlopQiY61beSdrtOA= +github.com/urfave/cli v1.20.0/go.mod h1:70zkFmudgCuE/ngEzBv17Jvp/497gISqfk5gWijbERA= +go.opencensus.io v0.20.1/go.mod h1:6WKK9ahsWS3RSO+PY9ZHZUfv2irvY6gN279GOPZjmmk= golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= -golang.org/x/crypto v0.0.0-20181030102418-4d3f4d9ffa16/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20190325154230-a5d413f7728c h1:Vj5n4GlwjmQteupaxJ9+0FNOmBrHfq7vN4btdGoDZgI= golang.org/x/crypto v0.0.0-20190325154230-a5d413f7728c/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= -golang.org/x/lint v0.0.0-20180702182130-06c8688daad7/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-20181217174547-8f45f776aaf1/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-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/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-20181029044818-c44066c5c816/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/net v0.0.0-20181106065722-10aee1819953/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-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190125091013-d26f9f9a57f3/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/net v0.0.0-20190311183353-d8887717615a h1:oWX7TPOiFAMXLq8o0ikBYfCJVlRHBcsciT5bXOrH628= golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190322120337-addf6b3196f6/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c h1:uOCk1iQW6Vc18bnC13MfzScl+wdKBmM9Y9kU7Z83/lw= +golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= -golang.org/x/oauth2 v0.0.0-20181017192945-9dcd33a902f4/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= -golang.org/x/oauth2 v0.0.0-20181203162652-d668ce993890/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= -golang.org/x/perf v0.0.0-20180704124530-6e6d33e29852/go.mod h1:JLpeXjPJfIyPr5TlbXLkXWLhP8nz10XfvxElABhCtcw= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6 h1:bjcUS9ztw9kFmmIxJInhon/0Is3p+EHBKNgquIzo1OI= golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20181029174526-d69651ed3497/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-20181218192612-074acd46bca6/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 h1:DH4skfRX4EBpamg7iV4ZlCpblAHI6s6TDM39bFZumv8= golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190322080309-f49334f85ddc h1:4gbWbmmPFp4ySWICouJl6emP0MyS31yy9SrTlAGFT+g= +golang.org/x/sys v0.0.0-20190322080309-f49334f85ddc/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/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/time v0.0.0-20180412165947-fbb02b2291d2/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-20181219222714-6e267b5cc78e/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-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= golang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= -google.golang.org/api v0.0.0-20180910000450-7ca32eb868bf/go.mod h1:4mhQ8q/RsB7i+udVvVy5NUi08OU8ZlA0gRVgrF7VFY0= -google.golang.org/api v0.0.0-20181030000543-1d582fd0359e/go.mod h1:4mhQ8q/RsB7i+udVvVy5NUi08OU8ZlA0gRVgrF7VFY0= -google.golang.org/api v0.0.0-20181220000619-583d854617af/go.mod h1:4mhQ8q/RsB7i+udVvVy5NUi08OU8ZlA0gRVgrF7VFY0= -google.golang.org/api v0.2.0/go.mod h1:IfRCZScioGtypHNTlz3gFk67J8uePVW7uDTBzXuIkhU= -google.golang.org/api v0.3.0/go.mod h1:IuvZyQh8jgscv8qWfQ4ABd8m7hEudgBFM/EdhA3BnXw= +golang.org/x/tools v0.0.0-20190322203728-c1a832b0ad89 h1:iWXXYN3edZ3Nd/7I6Rt1sXrWVmhF9bgVtlEJ7BbH124= +golang.org/x/tools v0.0.0-20190322203728-c1a832b0ad89/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +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.2.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= -google.golang.org/appengine v1.3.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= google.golang.org/appengine v1.4.0 h1:/wp5JvzpHIxhs/dumFmF7BXTf3Z+dd4uXta4kVyO508= google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= -google.golang.org/genproto v0.0.0-20180831171423-11092d34479b/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= -google.golang.org/genproto v0.0.0-20181029155118-b69ba1387ce2/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= -google.golang.org/genproto v0.0.0-20181219182458-5a97ab628bfb/go.mod h1:7Ep/1NZk928CDR8SjdVbjWNpdIf6nzjE3BTgJDr2Atg= google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= -google.golang.org/grpc v1.14.0/go.mod h1:yo6s7OP7yaDglbqo1J04qKzAhqBH6lvTonzMVmEdcZw= -google.golang.org/grpc v1.16.0/go.mod h1:0JHn/cJsOMiMfNA9+DeHDlAU7KAAB5GDlYFpa9MZMio= +google.golang.org/genproto v0.0.0-20190404172233-64821d5d2107/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= google.golang.org/grpc v1.17.0/go.mod h1:6QZJwpn2B+Zp71q/5VxRsJ6NXXVCE5NRUHRo+f3cWCs= google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw= @@ -211,13 +210,10 @@ gopkg.in/go-playground/assert.v1 v1.2.1 h1:xoYuJVE7KT85PYWrN730RguIQO0ePzVRfFMXa gopkg.in/go-playground/assert.v1 v1.2.1/go.mod h1:9RXL0bg/zibRAgZUYszZSwO/z8Y/a8bDuhia5mkpMnE= gopkg.in/go-playground/validator.v8 v8.18.2 h1:lFB4DoMU6B626w8ny76MV7VX6W2VHct2GVOI3xgiMrQ= gopkg.in/go-playground/validator.v8 v8.18.2/go.mod h1:RX2a/7Ha8BgOhfk7j780h4/u/RRjR0eouCJSH80/M2Y= -gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw= gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw= gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.2 h1:ZCJp+EgiOT7lHqUV2J862kp8Qj64Jo6az82+3Td9dZw= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= -grpc.go4.org v0.0.0-20170609214715-11d0a25b4919/go.mod h1:77eQGdRu53HpSqPFJFmuJdjuHRquDANNeA4x7B8WQ9o= honnef.co/go/tools v0.0.0-20180728063816-88497007e858/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= -honnef.co/go/tools v0.0.0-20180920025451-e3ad64cb4ed3/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= diff --git a/routes/file/fileEndpoints.go b/routes/file/fileEndpoints.go index afdaa41..3980279 100644 --- a/routes/file/fileEndpoints.go +++ b/routes/file/fileEndpoints.go @@ -1,46 +1,405 @@ package file import ( - "github.com/gin-gonic/gin" + "fmt" "net/http" + "path/filepath" + "strconv" + + "github.com/gin-gonic/gin" + + "git.rwth-aachen.de/acs/public/villas/villasweb-backend-go/common" ) -func FilesRegister(r *gin.RouterGroup) { - r.GET("/", filesReadEp) - r.POST("/", fileRegistrationEp) // NEW in API - r.PUT("/:FileID", fileUpdateEp) // NEW in API - r.GET("/:FileID", fileReadEp) - r.DELETE("/:FileID", fileDeleteEp) +func RegisterFileEndpoints(r *gin.RouterGroup){ + r.GET("/", GetFiles) + r.POST ("/", AddFile) + r.GET("/:fileID", GetFile) + r.PUT("/:fileID", UpdateFile) + r.DELETE("/:fileID", DeleteFile) + //r.GET("/:simulationID/visualizations/:visualizationID/widgets/:widgetID/files", GetFilesOfWidget) + //r.POST ("/:simulationID/visualizations/:visualizationID/widgets/:widgetID/file", AddFileToWidget) + //r.GET("/:simulationID/visualizations/:visualizationID/widgets/:widgetID/file", GetFileOfWidget) + //r.PUT("/:simulationID/visualizations/:visualizationID/widgets/:widgetID/file", UpdateFileOfWidget) + //r.DELETE("/:simulationID/visualizations/:visualizationID/widgets/:widgetID/file", DeleteFileOfWidget) } -func filesReadEp(c *gin.Context) { - allFiles, _, _ := FindAllFiles() - serializer := FilesSerializerNoAssoc{c, allFiles} - c.JSON(http.StatusOK, gin.H{ - "files": serializer.Response(), - }) + + +// GetFiles godoc +// @Summary Get all files of a specific model or widget +// @ID GetFiles +// @Tags files +// @Produce json +// @Success 200 {array} common.FileResponse "File parameters requested by user" +// @Failure 401 "Unauthorized Access" +// @Failure 403 "Access forbidden." +// @Failure 404 "Not found" +// @Failure 500 "Internal server error" +// @Param originType query string true "Set to model for files of model, set to widget for files of widget" +// @Param originID query int true "ID of either model or widget of which files are requested" +// @Router /files [get] +func GetFiles(c *gin.Context) { + + // TODO if originType == "model" --> GetFilesOfModel, if originType == "vis" --> GetFilesOfWidget + } -func fileRegistrationEp(c *gin.Context) { +// AddFile godoc +// @Summary Add a file to a specific model or widget +// @ID AddFile +// @Tags files +// @Produce json +// @Accept text/plain +// @Accept png +// @Accept jpeg +// @Accept gif +// @Accept model/x-cim +// @Accept model/x-cim.zip +// @Success 200 "OK" +// @Failure 401 "Unauthorized Access" +// @Failure 403 "Access forbidden." +// @Failure 404 "Not found" +// @Failure 500 "Internal server error" +// @Param inputFile formData file true "File to be uploaded" +// @Param originType query string true "Set to model for files of model, set to widget for files of widget" +// @Param originID query int true "ID of either model or widget of which files are requested" +// @Router /files [post] +func AddFile(c *gin.Context){ + // TODO if originType == "model" --> AddFileToModel, if originType == "vis" --> AddFileToWidget +} + +// GetFile godoc +// @Summary Download a file +// @ID GetFile +// @Tags files +// @Produce text/plain +// @Produce png +// @Produce jpeg +// @Produce gif +// @Produce model/x-cim +// @Produce model/x-cim.zip +// @Success 200 "OK" +// @Failure 401 "Unauthorized Access" +// @Failure 403 "Access forbidden." +// @Failure 404 "Not found" +// @Failure 500 "Internal server error" +// @Param fileID path int true "ID of the file to download" +// @Router /files/{fileID} [get] +func GetFile(c *gin.Context){ + // TODO +} + +// UpdateFile godoc +// @Summary Update a file +// @ID UpdateFile +// @Tags files +// @Produce json +// @Accept text/plain +// @Accept png +// @Accept jpeg +// @Accept gif +// @Accept model/x-cim +// @Accept model/x-cim.zip +// @Success 200 "OK" +// @Failure 401 "Unauthorized Access" +// @Failure 403 "Access forbidden." +// @Failure 404 "Not found" +// @Failure 500 "Internal server error" +// @Param fileID path int true "ID of the file to update" +// @Router /files/{fileID} [put] +func UpdateFile(c *gin.Context){ + + //TODO parse this info based on fileID parameter + simulationID := 1 + modelID := 1 + widgetID := 1 + + + // Extract file from PUT request form + err := c.Request.ParseForm() + if err != nil { + errormsg := fmt.Sprintf("Bad request. Get form error: %s", err.Error()) + c.JSON(http.StatusBadRequest, gin.H{ + "error": errormsg, + }) + return; + } + + 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; + } + + filename := filepath.Base(file_header.Filename) + filetype := file_header.Header.Get("Content-Type") // TODO make sure this is properly set in file header + size := file_header.Size + foldername := getFolderName(simulationID, modelID, widgetID) + + err = modifyFileOnDisc(file_header, filename, foldername, uint(size), false) + if err != nil { + errormsg := fmt.Sprintf("Internal Server Error. Error saving file: %s", err.Error()) + c.JSON(http.StatusInternalServerError, gin.H{ + "error": errormsg, + }) + return + } + + saveFileInDB(c, filename, foldername, filetype, uint(size), widgetID, modelID, false) +} + + +// DeleteFile godoc +// @Summary Delete a file +// @ID DeleteFile +// @Tags files +// @Produce json +// @Success 200 "OK" +// @Failure 401 "Unauthorized Access" +// @Failure 403 "Access forbidden." +// @Failure 404 "Not found" +// @Failure 500 "Internal server error" +// @Param fileID path int true "ID of the file to update" +// @Router /files/{fileID} [delete] +func DeleteFile(c *gin.Context){ + // TODO +} + + +func GetFilesOfModel(c *gin.Context) { + + simulationID, modelID, err := getRequestParams(c) + if err != nil{ + return + } + + // Find files' properties in DB and return in HTTP response, no change to DB + allFiles, _, err := FindFiles(c, -1, modelID, simulationID) + + if common.ProvideErrorResponse(c, err) == false { + serializer := common.FilesSerializerNoAssoc{c, allFiles} + c.JSON(http.StatusOK, gin.H{ + "files": serializer.Response(), + }) + } + +} + + +func AddFileToModel(c *gin.Context) { + + simulationID, modelID, err := getRequestParams(c) + if err != nil{ + return + } + + // Save file locally and register file in DB, HTTP response is set by this method + RegisterFile(c,-1, modelID, simulationID) + +} + +func CloneFileOfModel(c *gin.Context) { + c.JSON(http.StatusOK, gin.H{ "message": "NOT implemented", }) + } -func fileUpdateEp(c *gin.Context) { + +func GetFileOfModel(c *gin.Context) { + + simulationID, modelID, err := getRequestParams(c) + if err != nil{ + return + } + + // Read file from disk and return in HTTP response, no change to DB + ReadFile(c, -1, modelID, simulationID) +} + + +func UpdateFileOfModel(c *gin.Context) { + + //simulationID, modelID, err := getRequestParams(c) + //if err != nil{ + // return + //} + + // Update file locally and update file entry in DB, HTTP response is set by this method + //UpdateFile(c,-1, modelID, simulationID) +} + +func DeleteFileOfModel(c *gin.Context) { + + //simulationID, modelID, err := getRequestParams(c) + //if err != nil{ + // return + //} + + // Delete file from disk and remove entry from DB, HTTP response is set by this method + //DeleteFile(c, -1, modelID, simulationID) + + +} + +func GetFilesOfWidget(c *gin.Context) { + + simulationID, widgetID, err := getRequestParams(c) + if err != nil{ + return + } + + // Find files' properties in DB and return in HTTP response, no change to DB + allFiles, _, err := FindFiles(c, widgetID, -1, simulationID) + + if common.ProvideErrorResponse(c, err) == false { + serializer := common.FilesSerializerNoAssoc{c, allFiles} + c.JSON(http.StatusOK, gin.H{ + "files": serializer.Response(), + }) + } + +} + + +func AddFileToWidget(c *gin.Context) { + + simulationID, widgetID, err := getRequestParams(c) + if err != nil{ + return + } + + // Save file locally and register file in DB, HTTP response is set by this method + RegisterFile(c,widgetID, -1, simulationID) + +} + +func CloneFileOfWidget(c *gin.Context) { + c.JSON(http.StatusOK, gin.H{ "message": "NOT implemented", }) + } -func fileReadEp(c *gin.Context) { - c.JSON(http.StatusOK, gin.H{ - "message": "NOT implemented", - }) + +func GetFileOfWidget(c *gin.Context) { + + simulationID, widgetID, err := getRequestParams(c) + if err != nil{ + return + } + + // Read file from disk and return in HTTP response, no change to DB + ReadFile(c, widgetID, -1, simulationID) } -func fileDeleteEp(c *gin.Context) { - c.JSON(http.StatusOK, gin.H{ - "message": "NOT implemented", - }) + +func UpdateFileOfWidget(c *gin.Context) { + + //simulationID, widgetID, err := getRequestParams(c) + //if err != nil{ + // return + //} + // + //// Update file locally and update file entry in DB, HTTP response is set by this method + //UpdateFile(c,widgetID, -1, simulationID) } + + +func DeleteFileOfWidget(c *gin.Context) { + + //simulationID, widgetID, err := getRequestParams(c) + //if err != nil{ + // return + //} + // + //// Delete file from disk and remove entry from DB, HTTP response is set by this method + //DeleteFile(c, widgetID, -1, simulationID) + + +} + + +// local functions + +//func filesReadEp(c *gin.Context) { +// // Database query +// allFiles, _, err := FindAllFiles() +// +// if common.ProvideErrorResponse(c, err) == false { +// serializer := FilesSerializerNoAssoc{c, allFiles} +// c.JSON(http.StatusOK, gin.H{ +// "files": serializer.Response(), +// }) +// } +// +//} +// +// +// +//func fileUpdateEp(c *gin.Context) { +// c.JSON(http.StatusOK, gin.H{ +// "message": "NOT implemented", +// }) +//} +// +//func fileReadEp(c *gin.Context) { +// var err error +// var file common.File +// fileID := c.Param("FileID") +// desc := c.GetHeader("X-Request-FileDesc") +// desc_b, _ := strconv.ParseBool(desc) +// +// userID := 1 // TODO obtain ID of user making the request +// +// //check if description of file or file itself shall be returned +// if desc_b { +// file, err = FindFile(userID, fileID) +// if common.ProvideErrorResponse(c, err) == false { +// serializer := FileSerializerNoAssoc{c, file} +// c.JSON(http.StatusOK, gin.H{ +// "file": serializer.Response(), +// }) +// } +// +// +// } else { +// //TODO: return file itself +// } +//} +// +//func fileDeleteEp(c *gin.Context) { +// c.JSON(http.StatusOK, gin.H{ +// "message": "NOT implemented", +// }) +//} + + +func getRequestParams(c *gin.Context) (int, int, error){ + simulationID, err := strconv.Atoi(c.Param("SimulationID")) + + if err != nil { + errormsg := fmt.Sprintf("Bad request. No or incorrect format of simulation ID") + c.JSON(http.StatusBadRequest, gin.H{ + "error": errormsg, + }) + return -1, -1, err + } + + var subID int + subID, err = common.GetModelID(c) + if err != nil{ + subID, err = common.GetWidgetID(c) + if err != nil { + return -1, -1, err + } + } + + return simulationID, subID, err +} \ No newline at end of file diff --git a/routes/file/fileQueries.go b/routes/file/fileQueries.go index 6c977ed..766296c 100644 --- a/routes/file/fileQueries.go +++ b/routes/file/fileQueries.go @@ -1,19 +1,352 @@ package file import ( + "fmt" + "io" + "mime/multipart" + "net/http" + "os" + "path/filepath" + "strconv" + + "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/routes/model" + "git.rwth-aachen.de/acs/public/villas/villasweb-backend-go/routes/simulation" + "git.rwth-aachen.de/acs/public/villas/villasweb-backend-go/routes/widget" ) -func FindAllFiles() ([]common.File, int, error) { +//func FindAllFiles() ([]common.File, int, error) { +// db := common.GetDB() +// var files []common.File +// err := db.Find(&files).Error +// if err != nil { +// // print error message to screen +// fmt.Println(fmt.Errorf("DB Error in FindAllFiles(): %q", err).Error()) +// } +// return files, len(files), err +//} +// +//func FindUserFiles(user *common.User) ([]common.File, int, error) { +// db := common.GetDB() +// var files []common.File +// err := db.Model(user).Related(&files, "Files").Error +// return files, len(files), err +//} +// +//func FindFile(userID int, fileID string) ( common.File, error) { +// var file common.File +// db := common.GetDB() +// fileID_i, _ := strconv.Atoi(fileID) +// +// err := db.First(&file, fileID_i).Error +// +// return file, err +// +//} + +func FindFiles(c *gin.Context, widgetID int, modelID int, simulationID int) ([]common.File, int, error){ db := common.GetDB() var files []common.File - err := db.Find(&files).Error + var err error + + if widgetID != -1 { + var w common.Widget + err = db.First(&w, widgetID).Error + if err != nil { + return files, 0, err + } + err = db.Model(&w).Related(&files, "Files").Error + if err != nil { + return files, 0, err + } + + } else if modelID != -1 { + var m common.Model + err = db.First(&m, modelID).Error + if err != nil { + return files, 0, err + } + err = db.Model(&m).Related(&files, "Files").Error + if err != nil { + return files, 0, err + } + + } + return files, len(files), err } -func FindUserFiles(user *common.User) ([]common.File, int, error) { +func FindFileByPath(path string) (common.File, error) { + var file common.File db := common.GetDB() - var files []common.File - err := db.Model(user).Related(&files, "Files").Error - return files, len(files), err + err := db.Where("Path = ?", path).Find(file).Error + + return file, err +} + +func RegisterFile(c *gin.Context, widgetID int, modelID int, simulationID int){ + + // 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; + } + + // Obtain properties of file + filetype := file_header.Header.Get("Content-Type") // TODO make sure this is properly set in file header + filename := filepath.Base(file_header.Filename) + foldername := getFolderName(simulationID, modelID, widgetID) + size := file_header.Size + + // Check if simulation and widget or model exist in DB + _, err = simulation.FindSimulation(simulationID) + if common.ProvideErrorResponse(c, err) { + return + } + + if modelID != -1 { + _, err = model.FindModel(modelID) + if common.ProvideErrorResponse(c, err) { + return + } + } else if widgetID != -1 { + _, err = widget.FindWidget(widgetID) + if common.ProvideErrorResponse(c, err) { + return + } + } + + // Save file to local disc (NOT DB!) + err = modifyFileOnDisc(file_header, filename, foldername, uint(size), true) + if err != nil { + errormsg := fmt.Sprintf("Internal Server Error. Error saving file: %s", err.Error()) + c.JSON(http.StatusInternalServerError, gin.H{ + "error": errormsg, + }) + return + } + + // Add File object with parameters to DB + saveFileInDB(c, filename, foldername, filetype, uint(size), widgetID, modelID, true) + +} + + + +func ReadFile(c *gin.Context, widgetID int, modelID int, simulationID int){ + + //contentType := c.GetHeader("Content-Type") + + //TODO currently returns first file it finds in DB + + db := common.GetDB() + var fileInDB common.File + if widgetID != -1 { + // get associated Widget + var wdgt common.Widget + err := db.First(&wdgt, modelID).Error + if common.ProvideErrorResponse(c, err) { + return + } + err = db.Model(&wdgt).Related(&fileInDB).Error + if common.ProvideErrorResponse(c, err) { + return + } + + } else if modelID != -1 { + + // get associated Simulation Model + var model common.Model + err := db.First(&model, modelID).Error + if common.ProvideErrorResponse(c, err) { + return + } + err = db.Model(&model).Related(&fileInDB).Error + if common.ProvideErrorResponse(c, err) { + return + } + } + + //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="+fileInDB.Name ) + //c.Header("Content-Type", contentType) + c.File(fileInDB.Path) + + c.JSON(http.StatusOK, gin.H{ + "message": "OK.", + }) +} + + +func saveFileInDB(c *gin.Context, filename string, foldername string, filetype string, size uint, widgetID int, modelID int, createObj bool) { + + filesavepath := filepath.Join(foldername, filename) + + // get last modify time of target file + fileinfo, err := os.Stat(filesavepath) + if err != nil { + errormsg := fmt.Sprintf("Internal Server Error. Error stat on file: %s", err.Error()) + c.JSON(http.StatusInternalServerError, gin.H{ + "error": errormsg, + }) + return + } + modTime := fileinfo.ModTime() + + // Create file object for Database + var fileObj common.File + fileObj.Size = uint(size) + fileObj.Type = filetype + fileObj.Path = filesavepath + fileObj.Date = modTime + fileObj.Name = filename + fileObj.ImageHeight = 0 + fileObj.ImageWidth = 0 + + // Check if file shall be associated with Widget or Simulation Model + db := common.GetDB() + if widgetID != -1 { + + if createObj { + // associate to Widget + var wdgt common.Widget + err := db.First(&wdgt, widgetID).Error + if common.ProvideErrorResponse(c, err) { + return + } + err = db.Model(&wdgt).Association("Files").Append(&fileObj).Error + } else { + // update file obj in DB + fileInDB, err := FindFileByPath(filesavepath) + if common.ProvideErrorResponse(c, err){ + return + } + + err = db.Model(&fileInDB).Where("Path = ?", filesavepath).Updates(map[string]interface{}{"Size": fileObj.Size, "Date": fileObj.Date, "ImageHeight": fileObj.ImageHeight, "ImageWidth": fileObj.ImageWidth}).Error + + } + + if common.ProvideErrorResponse(c, err) == false { + c.JSON(http.StatusOK, gin.H{ + "message": "OK.", + "fileID": fileObj.ID, + }) + return + } + + } + if modelID != -1 { + + if createObj { + // associate to Simulation Model + db := common.GetDB() + var model common.Model + err := db.First(&model, modelID).Error + if common.ProvideErrorResponse(c, err) { + return + } + err = db.Model(&model).Association("Files").Append(&fileObj).Error + } else { + // update file obj in DB + fileInDB, err := FindFileByPath(filesavepath) + if common.ProvideErrorResponse(c, err){ + return + } + + err = db.Model(&fileInDB).Where("Path = ?", filesavepath).Updates(map[string]interface{}{"Size": fileObj.Size, "Date": fileObj.Date, "ImageHeight": fileObj.ImageHeight, "ImageWidth": fileObj.ImageWidth}).Error + } + + if common.ProvideErrorResponse(c, err) == false { + c.JSON(http.StatusOK, gin.H{ + "message": "OK.", + "fileID": fileObj.ID, + }) + return + } + } +} + +func modifyFileOnDisc(file_header *multipart.FileHeader, filename string, foldername string, size uint, createFile bool) error { + + filesavepath := filepath.Join(foldername, filename) + var err error + + if createFile { + // Ensure folder with name foldername exists + err = os.MkdirAll(foldername, os.ModePerm) + } else { + // test if file exists + _, err = os.Stat(filesavepath) + } + if err != nil { + return err + } + + var open_options int + if createFile { + // create file it not exists, file MUST not exist + open_options = os.O_RDWR|os.O_CREATE|os.O_EXCL + } else { + open_options = os.O_RDWR + } + + fileTarget , err := os.OpenFile(filesavepath, open_options, 0666) + if err != nil { + return err + } + defer fileTarget.Close() + + // Save file to target path + uploadedFile, err := file_header.Open() + if err != nil { + return err + } + defer uploadedFile.Close() + + var uploadContent = make([]byte, 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 { + return err + } + + } + return err +} + + +func getFolderName(simulationID int, modelID int, widgetID int) string { + base_foldername := "files/" + elementname := "" + elementid := 0 + if modelID == -1{ + elementname = "/widget_" + elementid = widgetID + } else { + elementname = "/model_" + elementid = modelID + } + + + foldername := base_foldername + "simulation_"+ strconv.Itoa(simulationID) + elementname + strconv.Itoa(elementid) + "/" + return foldername } \ No newline at end of file diff --git a/routes/file/fileSerializer.go b/routes/file/fileSerializer.go deleted file mode 100644 index 16d85ae..0000000 --- a/routes/file/fileSerializer.go +++ /dev/null @@ -1,53 +0,0 @@ -package file - -import ( - "github.com/gin-gonic/gin" - - "git.rwth-aachen.de/acs/public/villas/villasweb-backend-go/common" -) - -// File/s Serializers - -type FilesSerializerNoAssoc struct { - Ctx *gin.Context - Files []common.File -} - -func (self *FilesSerializerNoAssoc) Response() []FileResponseNoAssoc { - response := []FileResponseNoAssoc{} - for _, files := range self.Files { - serializer := FileSerializerNoAssoc{self.Ctx, files} - response = append(response, serializer.Response()) - } - return response -} - -type FileSerializerNoAssoc struct { - Ctx *gin.Context - common.File -} - -type FileResponseNoAssoc struct { - Name string `json:"Name"` - ID uint `json:"FileID"` - Path string `json:"Path"` - Type string `json:"Type"` - Size uint `json:"Size"` - H uint `json:"ImageHeight"` - W uint `json:"ImageWidth"` - // Date -} - -func (self *FileSerializerNoAssoc) Response() FileResponseNoAssoc { - response := FileResponseNoAssoc{ - Name: self.Name, - ID: self.ID, - Path: self.Path, - Type: self.Type, - Size: self.Size, - H: self.ImageHeight, - W: self.ImageWidth, - // Date - } - return response -} \ No newline at end of file diff --git a/routes/model/modelEndpoints.go b/routes/model/modelEndpoints.go new file mode 100644 index 0000000..84a58ab --- /dev/null +++ b/routes/model/modelEndpoints.go @@ -0,0 +1,355 @@ +package model + +import ( + "fmt" + "net/http" + "strconv" + + "git.rwth-aachen.de/acs/public/villas/villasweb-backend-go/common" + "git.rwth-aachen.de/acs/public/villas/villasweb-backend-go/routes/simulator" + + "github.com/gin-gonic/gin" +) + +func RegisterModelEndpoints(r *gin.RouterGroup){ + r.GET("/", GetModels) + r.POST("/", AddModel) + //r.POST("/:modelID", CloneModel) + r.PUT("/:modelID", UpdateModel) + r.GET("/:modelID", GetModel) + r.DELETE("/:modelID", DeleteModel) + //r.PUT("/:modelID/simulator", UpdateSimulator) + //r.GET("/:modelID/simulator", GetSimulator) + //r.POST("/:modelID/signals/:direction", UpdateSignals) + //r.GET("/:modelID/signals/:direction", GetSignals) +} + +// GetModels godoc +// @Summary Get all models of simulation +// @ID GetModels +// @Produce json +// @Tags models +// @Success 200 {array} common.ModelResponse "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 GetModels(c *gin.Context) { + + simID, err := common.GetSimulationID(c) + if err != nil { + return + } + + allModels, _, err := FindAllModels(simID) + if common.ProvideErrorResponse(c, err) { + return + } + + serializer := common.ModelsSerializer{c, allModels} + c.JSON(http.StatusOK, gin.H{ + "models": serializer.Response(), + }) +} + +// AddModel godoc +// @Summary Add a model to a simulation +// @ID AddModel +// @Accept json +// @Produce json +// @Tags models +// @Param inputModel body common.ModelResponse true "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 AddModel(c *gin.Context) { + + simID, err := common.GetSimulationID(c) + if err != nil { + return + } + + var m Model + err = c.BindJSON(&m) + 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.addToSimulation(simID) + if common.ProvideErrorResponse(c, err) == false { + c.JSON(http.StatusOK, gin.H{ + "message": "OK.", + }) + } +} + +func CloneModel(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.", + }) + + +} + +// UpdateModel godoc +// @Summary Update a model +// @ID UpdateModel +// @Tags models +// @Accept json +// @Produce json +// @Param inputModel body common.ModelResponse true "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 UpdateModel(c *gin.Context) { + + modelID, err := common.GetModelID(c) + if err != nil { + return + } + + var m Model + err = c.BindJSON(&m) + 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.UpdateModel(modelID) + if common.ProvideErrorResponse(c, err) == false { + c.JSON(http.StatusOK, gin.H{ + "message": "OK.", + }) + } + +} + +// GetModel godoc +// @Summary Get a model +// @ID GetModel +// @Tags models +// @Produce json +// @Success 200 {object} common.ModelResponse "Requested 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 GetModel(c *gin.Context) { + + modelID, err := common.GetModelID(c) + if err != nil { + return + } + + m, err := FindModel(modelID) + if common.ProvideErrorResponse(c, err) { + return + } + + serializer := common.ModelSerializer{c, m} + c.JSON(http.StatusOK, gin.H{ + "model": serializer.Response(), + }) +} + +// DeleteModel godoc +// @Summary Delete a model +// @ID DeleteModel +// @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 DeleteModel(c *gin.Context) { + + c.JSON(http.StatusOK, gin.H{ + "message": "Not implemented.", + }) +} + + +func GetSimulator(c *gin.Context) { + + modelID, err := common.GetModelID(c) + if err != nil { + return + } + + m, err := FindModel(modelID) + if common.ProvideErrorResponse(c, err) { + return + } + + smtr, err := simulator.FindSimulator(int(m.SimulatorID)) + if common.ProvideErrorResponse(c, err) { + return + } + + serializer := common.SimulatorSerializer{c, smtr} + c.JSON(http.StatusOK, gin.H{ + "simulator": serializer.Response(), + }) +} + + +func UpdateSimulator(c *gin.Context) { + + // simulator ID as parameter of Query, e.g. simulations/:SimulationID/models/:ModelID/simulator?simulatorID=42 + simulatorID, err := strconv.Atoi(c.Query("simulatorID")) + if err != nil { + errormsg := fmt.Sprintf("Bad request. No or incorrect simulator ID") + c.JSON(http.StatusBadRequest, gin.H{ + "error": errormsg, + }) + return + } + + modelID, err := common.GetModelID(c) + if err != nil { + return + } + + smtr, err := simulator.FindSimulator(simulatorID) + if common.ProvideErrorResponse(c, err) { + return + } + + _m, err := FindModel(modelID) + if common.ProvideErrorResponse(c, err) { + return + } + + var m = Model{_m} + err = m.UpdateSimulator(&smtr) + if common.ProvideErrorResponse(c, err) == false { + c.JSON(http.StatusOK, gin.H{ + "message": "OK", + }) + } + +} + + +func UpdateSignals(c *gin.Context) { + + modelID, err := common.GetModelID(c) + if err != nil { + return + } + + _m, err := FindModel(modelID) + if common.ProvideErrorResponse(c, err) { + return + } + + direction := c.Param("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 + } + + var sigs []common.Signal + err = c.BindJSON(&sigs) + if err != nil { + errormsg := "Bad request. Error binding form data to JSON: " + err.Error() + c.JSON(http.StatusBadRequest, gin.H{ + "error": errormsg, + }) + return + } + + // Add signals to model and remove all existing Signals of the requested direction (if any) + var m = Model{_m} + err = m.UpdateSignals(sigs, direction) + if common.ProvideErrorResponse(c, err) == false { + c.JSON(http.StatusOK, gin.H{ + "message": "OK.", + }) + } +} + +func GetSignals(c *gin.Context) { + + modelID, err := common.GetModelID(c) + if err != nil { + return + } + + m, err := FindModel(modelID) + if common.ProvideErrorResponse(c, err) { + return + } + + direction := c.Param("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 + } + + var signals []common.Signal + if direction == "in" { + signals = m.InputMapping + } else { + signals = m.OutputMapping + } + + c.JSON(http.StatusOK, gin.H{ + "signals": signals, + }) +} \ No newline at end of file diff --git a/routes/model/modelQueries.go b/routes/model/modelQueries.go new file mode 100644 index 0000000..d48ddfe --- /dev/null +++ b/routes/model/modelQueries.go @@ -0,0 +1,79 @@ +package model + +import ( + "git.rwth-aachen.de/acs/public/villas/villasweb-backend-go/common" + "git.rwth-aachen.de/acs/public/villas/villasweb-backend-go/routes/simulation" +) + +type Model struct{ + common.Model +} + +func FindAllModels(simID int) ([]common.Model, int, error) { + db := common.GetDB() + var models []common.Model + sim, err := simulation.FindSimulation(simID) + if err != nil { + return models, 0, err + } + + err = db.Order("ID asc").Model(sim).Related(&models, "Models").Error + + return models, len(models), err +} + +func FindModel(modelID int) (common.Model, error){ + db := common.GetDB() + var m common.Model + err := db.First(&m, modelID).Error + return m, err +} + +func (m *Model) addToSimulation(simID int) error { + db := common.GetDB() + sim, err := simulation.FindSimulation(simID) + if err != nil { + return err + } + + err = db.Create(m).Error + if err != nil { + return err + } + + err = db.Model(&sim).Association("Models").Append(m).Error + return err +} + +func (m *Model) UpdateModel(modelID int) error { + db := common.GetDB() + model_to_update, err := FindModel(modelID) + if err != nil { + return err + } + // only Name and Start Params can be updated directly by the user + err = db.Model(&model_to_update).Updates(map[string]interface{}{"Name": m.Name, "StartParameters": m.StartParameters}).Error + return err +} + + +func (m *Model) UpdateSimulator(simulator *common.Simulator) error { + db := common.GetDB() + err := db.Model(m).Association("Simulator").Replace(simulator).Error + return err +} + +func (m *Model) UpdateSignals(signals []common.Signal, direction string) error { + + db := common.GetDB() + var err error + + if direction == "in" { + err = db.Model(m).Select("InputMapping").Update("InputMapping", signals).Error + } else { + err = db.Model(m).Select("OutputMapping").Update("OutputMapping", signals).Error + } + + return err + +} \ No newline at end of file diff --git a/routes/project/projectEndpoints.go b/routes/project/projectEndpoints.go deleted file mode 100644 index a5fac47..0000000 --- a/routes/project/projectEndpoints.go +++ /dev/null @@ -1,46 +0,0 @@ -package project - -import ( - "github.com/gin-gonic/gin" - "net/http" -) - -func ProjectsRegister(r *gin.RouterGroup) { - r.GET("/", projectsReadEp) - r.POST("/", projectRegistrationEp) - r.PUT("/:ProjectID", projectUpdateEp) - r.GET("/:ProjectID", projectReadEp) - r.DELETE("/:ProjectID", projectDeleteEp) -} - -func projectsReadEp(c *gin.Context) { - allProjects, _, _ := FindAllProjects() - serializer := ProjectsSerializerNoAssoc{c, allProjects} - c.JSON(http.StatusOK, gin.H{ - "projects": serializer.Response(), - }) -} - -func projectRegistrationEp(c *gin.Context) { - c.JSON(http.StatusOK, gin.H{ - "message": "NOT implemented", - }) -} - -func projectUpdateEp(c *gin.Context) { - c.JSON(http.StatusOK, gin.H{ - "message": "NOT implemented", - }) -} - -func projectReadEp(c *gin.Context) { - c.JSON(http.StatusOK, gin.H{ - "message": "NOT implemented", - }) -} - -func projectDeleteEp(c *gin.Context) { - c.JSON(http.StatusOK, gin.H{ - "message": "NOT implemented", - }) -} diff --git a/routes/project/projectQueries.go b/routes/project/projectQueries.go deleted file mode 100644 index c42cb5b..0000000 --- a/routes/project/projectQueries.go +++ /dev/null @@ -1,28 +0,0 @@ -package project - -import ( - "git.rwth-aachen.de/acs/public/villas/villasweb-backend-go/common" -) - -func FindAllProjects() ([]common.Project, int, error) { - db := common.GetDB() - var projects []common.Project - err := db.Find(&projects).Error - return projects, len(projects), err -} - -func FindUserProjects(user *common.User) ([]common.Project, int, error) { - db := common.GetDB() - var projects []common.Project - err := db.Model(user).Related(&projects, "Projects").Error - return projects, len(projects), err -} - -func FindVisualizationProject(visualization *common.Visualization) (common.Project, int, error) { - db := common.GetDB() - var project common.Project - err := db.Model(visualization).Related(&project, "Projects").Error - return project, 1, err -} - - diff --git a/routes/project/projectSerializer.go b/routes/project/projectSerializer.go deleted file mode 100644 index 753a79a..0000000 --- a/routes/project/projectSerializer.go +++ /dev/null @@ -1,41 +0,0 @@ -package project - -import ( - "github.com/gin-gonic/gin" - - "git.rwth-aachen.de/acs/public/villas/villasweb-backend-go/common" -) - -type ProjectsSerializerNoAssoc struct { - Ctx *gin.Context - Projects []common.Project -} - -func (self *ProjectsSerializerNoAssoc) Response() []ProjectResponseNoAssoc { - response := []ProjectResponseNoAssoc{} - for _, project := range self.Projects { - serializer := ProjectSerializerNoAssoc{self.Ctx, project} - response = append(response, serializer.Response()) - } - return response -} - -type ProjectSerializerNoAssoc struct { - Ctx *gin.Context - common.Project -} - -type ProjectResponseNoAssoc struct { - Name string `json:"Name"` - ID uint `json:"ProjectID"` -} - -func (self *ProjectSerializerNoAssoc) Response() ProjectResponseNoAssoc { - response := ProjectResponseNoAssoc{ - Name: self.Name, - ID: self.ID, - } - return response -} - - diff --git a/routes/signal/signalEndpoints.go b/routes/signal/signalEndpoints.go deleted file mode 100644 index 3fde498..0000000 --- a/routes/signal/signalEndpoints.go +++ /dev/null @@ -1,4 +0,0 @@ -package signal - - -//TODO extend API with signal endpoints \ No newline at end of file diff --git a/routes/signal/signalQueries.go b/routes/signal/signalQueries.go deleted file mode 100644 index 65499f1..0000000 --- a/routes/signal/signalQueries.go +++ /dev/null @@ -1,3 +0,0 @@ -package signal - -//TODO extend API with signal endpoints \ No newline at end of file diff --git a/routes/signal/signalSerializer.go b/routes/signal/signalSerializer.go deleted file mode 100644 index 65499f1..0000000 --- a/routes/signal/signalSerializer.go +++ /dev/null @@ -1,3 +0,0 @@ -package signal - -//TODO extend API with signal endpoints \ No newline at end of file diff --git a/routes/simulation/simulationEndpoints.go b/routes/simulation/simulationEndpoints.go index 00a6c73..b1ea962 100644 --- a/routes/simulation/simulationEndpoints.go +++ b/routes/simulation/simulationEndpoints.go @@ -1,47 +1,258 @@ package simulation import ( - "github.com/gin-gonic/gin" + "git.rwth-aachen.de/acs/public/villas/villasweb-backend-go/routes/user" "net/http" + + "github.com/gin-gonic/gin" + + "git.rwth-aachen.de/acs/public/villas/villasweb-backend-go/common" ) -func SimulationsRegister(r *gin.RouterGroup) { - r.GET("/", simulationsReadEp) - r.POST("/", simulationRegistrationEp) - r.PUT("/:SimulationID", simulationUpdateEp) - r.GET("/:SimulationID", simulationReadEp) - r.DELETE("/:SimulationID", simulationDeleteEp) +func RegisterSimulationEndpoints(r *gin.RouterGroup){ + r.GET("/", GetSimulations) + r.POST("/", AddSimulation) + //r.POST("/:simulationID", CloneSimulation) + 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) } -func simulationsReadEp(c *gin.Context) { +// 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) { + + //TODO Identify user who is issuing the request and return only those simulations that are known to the user + allSimulations, _, _ := FindAllSimulations() - serializer := SimulationsSerializerNoAssoc{c, allSimulations} + serializer := common.SimulationsSerializer{c, allSimulations} c.JSON(http.StatusOK, gin.H{ "simulations": serializer.Response(), }) } -func simulationRegistrationEp(c *gin.Context) { +// AddSimulation godoc +// @Summary Add a simulation +// @ID AddSimulation +// @Accept json +// @Produce json +// @Tags simulations +// @Param inputModel body common.ModelResponse 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) { + + +} + +func CloneSimulation(c *gin.Context) { c.JSON(http.StatusOK, gin.H{ "message": "NOT implemented", }) } -func simulationUpdateEp(c *gin.Context) { +// 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) { c.JSON(http.StatusOK, gin.H{ "message": "NOT implemented", }) } -func simulationReadEp(c *gin.Context) { +// 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) { + + simID, err := common.GetSimulationID(c) + if err != nil { + return + } + + sim, err := FindSimulation(simID) + if common.ProvideErrorResponse(c, err) { + return + } + + serializer := common.SimulationSerializer{c, sim} + 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) { c.JSON(http.StatusOK, gin.H{ "message": "NOT implemented", }) } -func simulationDeleteEp(c *gin.Context) { + +// 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) { + + simID, err := common.GetSimulationID(c) + if err != nil { + return + } + + sim, err := FindSimulation(simID) + if common.ProvideErrorResponse(c, err) { + return + } + + // Find all users of simulation + allUsers, _, err := user.FindAllUsersSim(&sim) + if common.ProvideErrorResponse(c, err) { + return + } + + serializer := common.UsersSerializer{c, allUsers} c.JSON(http.StatusOK, gin.H{ - "message": "NOT implemented", + "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) { + simID, err := common.GetSimulationID(c) + if err != nil { + return + } + + sim, err := FindSimulation(simID) + if common.ProvideErrorResponse(c, err) { + return + } + + querydata := c.Request.URL.Query() + + username := querydata.Get("username") + + u, err := user.FindUserByName(username) + if common.ProvideErrorResponse(c, err) { + return + } + + err = user.AddUserToSim(&sim, &u) + 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) { + simID, err := common.GetSimulationID(c) + if err != nil { + return + } + + sim, err := FindSimulation(simID) + if common.ProvideErrorResponse(c, err) { + return + } + + querydata := c.Request.URL.Query() + + username := querydata.Get("username") + + err = user.RemoveUserFromSim(&sim, username) + if common.ProvideErrorResponse(c, err) { + return + } + + c.JSON(http.StatusOK, gin.H{ + "message": "OK.", + }) +} + + diff --git a/routes/simulation/simulationQueries.go b/routes/simulation/simulationQueries.go index 58b88ce..382128e 100644 --- a/routes/simulation/simulationQueries.go +++ b/routes/simulation/simulationQueries.go @@ -4,16 +4,27 @@ import ( "git.rwth-aachen.de/acs/public/villas/villasweb-backend-go/common" ) +type Simulation struct{ + common.Simulation +} + func FindAllSimulations() ([]common.Simulation, int, error) { db := common.GetDB() var simulations []common.Simulation - err := db.Find(&simulations).Error + err := db.Order("ID asc").Find(&simulations).Error return simulations, len(simulations), err } func FindUserSimulations(user *common.User) ([]common.Simulation, int, error) { db := common.GetDB() var simulations []common.Simulation - err := db.Model(user).Related(&simulations, "Simulations").Error + err := db.Order("ID asc").Model(user).Related(&simulations, "Simulations").Error return simulations, len(simulations), err } + +func FindSimulation(simID int) (common.Simulation, error) { + db := common.GetDB() + var sim common.Simulation + err := db.First(&sim, simID).Error + return sim, err +} diff --git a/routes/simulation/simulationSerializer.go b/routes/simulation/simulationSerializer.go deleted file mode 100644 index b967b12..0000000 --- a/routes/simulation/simulationSerializer.go +++ /dev/null @@ -1,43 +0,0 @@ -package simulation - -import ( - "github.com/gin-gonic/gin" - - "git.rwth-aachen.de/acs/public/villas/villasweb-backend-go/common" -) - -type SimulationsSerializerNoAssoc struct { - Ctx *gin.Context - Simulations []common.Simulation -} - -func (self *SimulationsSerializerNoAssoc) Response() []SimulationResponseNoAssoc { - response := []SimulationResponseNoAssoc{} - for _, simulation := range self.Simulations { - serializer := SimulationSerializerNoAssoc{self.Ctx, simulation} - response = append(response, serializer.Response()) - } - return response -} - -type SimulationSerializerNoAssoc struct { - Ctx *gin.Context - common.Simulation -} - -type SimulationResponseNoAssoc struct { - Name string `json:"Name"` - ID uint `json:"SimulationID"` - Running bool `json:"Running"` - //StartParams postgres.Jsonb `json:"Starting Parameters"` -} - -func (self *SimulationSerializerNoAssoc) Response() SimulationResponseNoAssoc { - response := SimulationResponseNoAssoc{ - Name: self.Name, - ID: self.ID, - Running: self.Running, - //StartParams: self.StartParameters, - } - return response -} diff --git a/routes/simulation/simulation_test.go b/routes/simulation/simulation_test.go new file mode 100644 index 0000000..fb9c9b3 --- /dev/null +++ b/routes/simulation/simulation_test.go @@ -0,0 +1,127 @@ +package simulation + +import ( + "encoding/json" + "fmt" + "net/http" + "net/http/httptest" + "testing" + + "github.com/gin-gonic/gin" + "github.com/stretchr/testify/assert" + + "git.rwth-aachen.de/acs/public/villas/villasweb-backend-go/common" +) + + +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 msgUsers = common.ResponseMsgUsers{ + Users: myUsers, +} + + +var simulationA = common.SimulationResponse{ + Name: "Simulation_A", + ID: 1, + Running: false, +} + +var simulationB = common.SimulationResponse{ + Name: "Simulation_B", + ID: 2, + Running: false, +} + +var mySimulations = []common.SimulationResponse{ + simulationA, + simulationB, +} + +var msgSimulations = common.ResponseMsgSimulations{ + Simulations: mySimulations, +} + +var msgSimulation = common.ResponseMsgSimulation{ + Simulation: simulationA, +} + +// Test /simulation endpoints +func TestSimulationEndpoints(t *testing.T) { + + db := common.DummyInitDB() + defer db.Close() + common.DummyPopulateDB(db) + + + router := gin.Default() + api := router.Group("/api") + RegisterSimulationEndpoints(api.Group("/simulations")) + + msgOKjson, err := json.Marshal(msgOK) + if err !=nil { + panic(err) + } + + msgUsersjson, err := json.Marshal(msgUsers) + 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) + } + + // test GET simulations/ + testEndpoint(t, router, "/api/simulations/", "GET", "", 200, string(msgSimulationsjson)) + + // test GET simulations/:SimulationID + testEndpoint(t, router, "/api/simulations/1", "GET", "", 200, string(msgSimulationjson)) + + // test GET simulations/:SimulationID/users + testEndpoint(t, router, "/api/simulations/1/users", "GET", "", 200, string(msgUsersjson)) + + // test DELETE simulations/:SimulationID/user + testEndpoint(t, router, "/api/simulations/1/user?username=User_A", "DELETE", "", 200, string(msgOKjson)) + + // test PUT simulations/:SimulationID/user + testEndpoint(t, router, "/api/simulations/1/user?username=User_A", "PUT", "", 200, string(msgOKjson)) + + // TODO add more tests +} + + +func testEndpoint(t *testing.T, router *gin.Engine, url string, method string, body string, expected_code int, expected_response string ) { + w := httptest.NewRecorder() + req, _ := http.NewRequest(method, url, nil) + router.ServeHTTP(w, req) + assert.Equal(t, expected_code, w.Code) + fmt.Println(w.Body.String()) + assert.Equal(t, expected_response, w.Body.String()) +} \ No newline at end of file diff --git a/routes/simulationmodel/simulationmodelEnpoints.go b/routes/simulationmodel/simulationmodelEnpoints.go deleted file mode 100644 index 7f41593..0000000 --- a/routes/simulationmodel/simulationmodelEnpoints.go +++ /dev/null @@ -1,60 +0,0 @@ -package simulationmodel - -import ( - "github.com/gin-gonic/gin" - "net/http" -) - -func SimulationModelsRegister(r *gin.RouterGroup) { - r.GET("/", simulationmodelsReadEp) - r.POST("/", simulationmodelRegistrationEp) - r.PUT("/:SimulationModelID", simulationmodelUpdateEp) - r.GET("/:SimulationModelID", simulationmodelReadEp) - r.DELETE("/:SimulationModelID", simulationmodelDeleteEp) - r.GET("/:SimulationModelID/file", simulationmodelReadFileEp) // NEW in API - r.PUT("/:SimulationModelID/file", simulationmodelUpdateFileEp) // NEW in API -} - -func simulationmodelsReadEp(c *gin.Context) { - allSimulationModels, _, _ := FindAllSimulationModels() - serializer := SimulationModelsSerializerNoAssoc{c, allSimulationModels} - c.JSON(http.StatusOK, gin.H{ - "simulationmodels": serializer.Response(), - }) -} - -func simulationmodelRegistrationEp(c *gin.Context) { - c.JSON(http.StatusOK, gin.H{ - "message": "NOT implemented", - }) -} - -func simulationmodelUpdateEp(c *gin.Context) { - c.JSON(http.StatusOK, gin.H{ - "message": "NOT implemented", - }) -} - -func simulationmodelReadEp(c *gin.Context) { - c.JSON(http.StatusOK, gin.H{ - "message": "NOT implemented", - }) -} - -func simulationmodelDeleteEp(c *gin.Context) { - c.JSON(http.StatusOK, gin.H{ - "message": "NOT implemented", - }) -} - -func simulationmodelReadFileEp(c *gin.Context) { - c.JSON(http.StatusOK, gin.H{ - "message": "NOT implemented", - }) -} - -func simulationmodelUpdateFileEp(c *gin.Context) { - c.JSON(http.StatusOK, gin.H{ - "message": "NOT implemented", - }) -} \ No newline at end of file diff --git a/routes/simulationmodel/simulationmodelQueries.go b/routes/simulationmodel/simulationmodelQueries.go deleted file mode 100644 index 0eaf308..0000000 --- a/routes/simulationmodel/simulationmodelQueries.go +++ /dev/null @@ -1,12 +0,0 @@ -package simulationmodel - -import ( - "git.rwth-aachen.de/acs/public/villas/villasweb-backend-go/common" -) - -func FindAllSimulationModels() ([]common.SimulationModel, int, error) { - db := common.GetDB() - var simulationmodels []common.SimulationModel - err := db.Find(&simulationmodels).Error - return simulationmodels, len(simulationmodels), err -} diff --git a/routes/simulationmodel/simulationmodelSerializer.go b/routes/simulationmodel/simulationmodelSerializer.go deleted file mode 100644 index fdee9f7..0000000 --- a/routes/simulationmodel/simulationmodelSerializer.go +++ /dev/null @@ -1,52 +0,0 @@ -package simulationmodel - -import ( - "github.com/gin-gonic/gin" - - "git.rwth-aachen.de/acs/public/villas/villasweb-backend-go/common" -) - -type SimulationModelsSerializerNoAssoc struct { - Ctx *gin.Context - SimulationModels []common.SimulationModel -} - -func (self *SimulationModelsSerializerNoAssoc) Response() []SimulationModelResponseNoAssoc { - response := []SimulationModelResponseNoAssoc{} - for _, simulationmodel := range self.SimulationModels { - serializer := SimulationModelSerializerNoAssoc{self.Ctx, simulationmodel} - response = append(response, serializer.Response()) - } - return response -} - -type SimulationModelSerializerNoAssoc struct { - Ctx *gin.Context - common.SimulationModel -} - -type SimulationModelResponseNoAssoc struct { - Name string `json:"Name"` - OutputLength int `json:"OutputLength"` - InputLength int `json:"InputLength"` - BelongsToSimulationID uint `json:"BelongsToSimulationID"` - BelongsToSimulatorID uint `json:"BelongsToSimulatiorID"` - //StartParams postgres.Jsonb `json:"Starting Parameters"` - //Output Mapping - //Input Mapping -} - -func (self *SimulationModelSerializerNoAssoc) Response() SimulationModelResponseNoAssoc { - response := SimulationModelResponseNoAssoc{ - Name: self.Name, - OutputLength: self.OutputLength, - InputLength: self.InputLength, - BelongsToSimulationID: self.BelongsToSimulationID, - BelongsToSimulatorID: self.BelongsToSimulatorID, - //StartParams: self.StartParameters, - //InputMapping - //OutputMapping - } - return response -} - diff --git a/routes/simulator/simulatorEndpoints.go b/routes/simulator/simulatorEndpoints.go index d8ce7ca..ccb0127 100644 --- a/routes/simulator/simulatorEndpoints.go +++ b/routes/simulator/simulatorEndpoints.go @@ -1,53 +1,134 @@ package simulator import ( - "github.com/gin-gonic/gin" "net/http" + + "github.com/gin-gonic/gin" + + "git.rwth-aachen.de/acs/public/villas/villasweb-backend-go/common" ) -func SimulatorsRegister(r *gin.RouterGroup) { - r.GET("/", simulatorsReadEp) - r.POST("/", simulatorRegistrationEp) - r.PUT("/:SimulatorID", simulatorUpdateEp) - r.GET("/:SimulatorID", simulatorReadEp) - r.DELETE("/:SimulatorID", simulatorDeleteEp) - r.POST("/:SimulatorID", simulatorSendActionEp) +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) } -func simulatorsReadEp(c *gin.Context) { +// 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) { allSimulators, _, _ := FindAllSimulators() - serializer := SimulatorsSerializer{c, allSimulators} + serializer := common.SimulatorsSerializer{c, allSimulators} c.JSON(http.StatusOK, gin.H{ "simulators": serializer.Response(), }) } -func simulatorRegistrationEp(c *gin.Context) { +// 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", }) } -func simulatorUpdateEp(c *gin.Context) { + +// 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", }) } -func simulatorReadEp(c *gin.Context) { +// 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", }) } -func simulatorDeleteEp(c *gin.Context) { +// 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", }) } -func simulatorSendActionEp(c *gin.Context) { +// 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", }) -} \ No newline at end of file +} + + diff --git a/routes/simulator/simulatorQueries.go b/routes/simulator/simulatorQueries.go index 92220d9..b52285c 100644 --- a/routes/simulator/simulatorQueries.go +++ b/routes/simulator/simulatorQueries.go @@ -7,7 +7,14 @@ import ( func FindAllSimulators() ([]common.Simulator, int, error) { db := common.GetDB() var simulators []common.Simulator - err := db.Find(&simulators).Error + err := db.Order("ID asc").Find(&simulators).Error return simulators, len(simulators), err } +func FindSimulator(simulatorID int) (common.Simulator, error) { + db := common.GetDB() + var simulator common.Simulator + err := db.First(&simulator, simulatorID).Error + return simulator, err +} + diff --git a/routes/simulator/simulatorSerializer.go b/routes/simulator/simulatorSerializer.go deleted file mode 100644 index 7307238..0000000 --- a/routes/simulator/simulatorSerializer.go +++ /dev/null @@ -1,52 +0,0 @@ -package simulator - -import ( - "time" - - "github.com/gin-gonic/gin" - - "git.rwth-aachen.de/acs/public/villas/villasweb-backend-go/common" -) - -type SimulatorsSerializer struct { - Ctx *gin.Context - Simulators []common.Simulator -} - -func (self *SimulatorsSerializer) Response() []SimulatorResponse { - response := []SimulatorResponse{} - for _, simulator := range self.Simulators { - serializer := SimulatorSerializer{self.Ctx, simulator} - response = append(response, serializer.Response()) - } - return response -} - -type SimulatorSerializer struct { - Ctx *gin.Context - common.Simulator -} - -type SimulatorResponse struct { - UUID string `json:"UUID"` - Host string `json:"Host"` - ModelType string `json:"ModelType"` - Uptime int `json:"Uptime"` - State string `json:"State"` - StateUpdateAt time.Time `json:"StateUpdateAt"` - // Properties - // Raw Properties -} - -func (self *SimulatorSerializer) Response() SimulatorResponse { - - response := SimulatorResponse{ - UUID: self.UUID, - Host: self.Host, - ModelType: self.Modeltype, - Uptime: self.Uptime, - State: self.State, - StateUpdateAt: self.StateUpdateAt, - } - return response -} diff --git a/routes/user/userEndpoints.go b/routes/user/userEndpoints.go index c680a8e..5a65fc5 100644 --- a/routes/user/userEndpoints.go +++ b/routes/user/userEndpoints.go @@ -8,6 +8,8 @@ import ( "net/http" "strconv" "time" + + "git.rwth-aachen.de/acs/public/villas/villasweb-backend-go/common" ) // TODO: the signing secret must be environmental variable @@ -20,19 +22,37 @@ type tokenClaims struct { jwt.StandardClaims } +type AuthResponse struct{ + Success bool `json:"success"` + Message string `json:"message"` + Token string `json:"token"` +} + // `/authenticate` endpoint does not require Authentication func VisitorAuthenticate(r *gin.RouterGroup) { r.POST("", authenticationEp) } -func UsersRegister(r *gin.RouterGroup) { - r.POST("", userRegistrationEp) - r.PUT("/:UserID", userUpdateEp) - r.GET("", usersReadEp) - r.GET("/:UserID", userReadEp) - r.DELETE("/:UserID", userDeleteEp) +func RegisterUserEndpoints(r *gin.RouterGroup) { + r.POST("", addUser) + r.PUT("/:UserID", updateUser) + r.GET("", getUsers) + r.GET("/:UserID", getUser) + r.DELETE("/:UserID", deleteUser) } +// authenticationEp godoc +// @Summary Authentication for user +// @ID authenticationEp +// @Accept json +// @Produce json +// @Tags users +// @Param inputUser body user.Credentials true "Credentials of user" +// @Success 200 {object} user.AuthResponse "JSON web token and message" +// @Failure 401 "Unauthorized Access" +// @Failure 404 "Not found" +// @Failure 422 "Unprocessable entity." +// @Router /authenticate [post] func authenticationEp(c *gin.Context) { // Bind the response (context) with the Credentials struct @@ -101,7 +121,18 @@ func authenticationEp(c *gin.Context) { }) } -func usersReadEp(c *gin.Context) { +// GetUsers godoc +// @Summary Get all users +// @ID GetUsers +// @Produce json +// @Tags users +// @Success 200 {array} common.UserResponse "Array of users" +// @Failure 401 "Unauthorized Access" +// @Failure 403 "Access forbidden." +// @Failure 404 "Not found" +// @Failure 500 "Internal server error" +// @Router /users [get] +func getUsers(c *gin.Context) { //// dummy TODO: check in the middleware if the user is authorized //authorized := false //// TODO: move this redirect in the authentication middleware @@ -110,13 +141,26 @@ func usersReadEp(c *gin.Context) { //return //} allUsers, _, _ := FindAllUsers() - serializer := UsersSerializer{c, allUsers} + serializer := common.UsersSerializer{c, allUsers} c.JSON(http.StatusOK, gin.H{ "users": serializer.Response(true), }) } -func userRegistrationEp(c *gin.Context) { +// AddUser godoc +// @Summary Add a user +// @ID AddUser +// @Accept json +// @Produce json +// @Tags users +// @Param inputUser body common.UserResponse true "User to be added" +// @Success 200 "OK." +// @Failure 401 "Unauthorized Access" +// @Failure 403 "Access forbidden." +// @Failure 404 "Not found" +// @Failure 500 "Internal server error" +// @Router /users [post] +func addUser(c *gin.Context) { // Bind the response (context) with the User struct var newUser User @@ -163,13 +207,39 @@ func userRegistrationEp(c *gin.Context) { }) } -func userUpdateEp(c *gin.Context) { +// UpdateUser godoc +// @Summary Update a user +// @ID UpdateUser +// @Tags users +// @Accept json +// @Produce json +// @Param inputUser body common.UserResponse true "User to be updated" +// @Success 200 "OK." +// @Failure 401 "Unauthorized Access" +// @Failure 403 "Access forbidden." +// @Failure 404 "Not found" +// @Failure 500 "Internal server error" +// @Param userID path int true "User ID" +// @Router /users/{userID} [put] +func updateUser(c *gin.Context) { c.JSON(http.StatusOK, gin.H{ "message": "NOT implemented", }) } -func userReadEp(c *gin.Context) { +// GetUser godoc +// @Summary Get user +// @ID GetUser +// @Produce json +// @Tags users +// @Success 200 {object} common.UserResponse "User requested by user" +// @Failure 401 "Unauthorized Access" +// @Failure 403 "Access forbidden." +// @Failure 404 "Not found" +// @Failure 500 "Internal server error" +// @Param userID path int true "User ID" +// @Router /users/{userID} [get] +func getUser(c *gin.Context) { var user User id, _ := strconv.ParseInt(c.Param("UserID"), 10, 64) @@ -180,13 +250,25 @@ func userReadEp(c *gin.Context) { return } - serializer := UserSerializer{c, user.User} + serializer := common.UserSerializer{c, user.User} c.JSON(http.StatusOK, gin.H{ "user": serializer.Response(false), }) } -func userDeleteEp(c *gin.Context) { +// DeleteUser godoc +// @Summary Delete a user +// @ID DeleteUser +// @Tags users +// @Produce json +// @Success 200 "OK." +// @Failure 401 "Unauthorized Access" +// @Failure 403 "Access forbidden." +// @Failure 404 "Not found" +// @Failure 500 "Internal server error" +// @Param userID path int true "User ID" +// @Router /users/{userID} [delete] +func deleteUser(c *gin.Context) { c.JSON(http.StatusOK, gin.H{ "message": "NOT implemented", }) diff --git a/routes/user/userQueries.go b/routes/user/userQueries.go index 4c45c94..82d3613 100644 --- a/routes/user/userQueries.go +++ b/routes/user/userQueries.go @@ -7,6 +7,46 @@ import ( func FindAllUsers() ([]common.User, int, error) { db := common.GetDB() var users []common.User - err := db.Find(&users).Error + err := db.Order("ID asc").Find(&users).Error return users, len(users), err } + +func FindAllUsersSim(sim *common.Simulation) ([]common.User, int, error) { + db := common.GetDB() + var users []common.User + err := db.Order("ID asc").Model(sim).Related(&users, "Users").Error + return users, len(users), err +} + +func FindUserByName(username string) (common.User, error){ + db := common.GetDB() + var user common.User + err := db.Where("Username = ?", username).Find(&user).Error + return user, err +} + +func AddUserToSim(sim *common.Simulation, user *common.User) error { + db := common.GetDB() + err := db.Model(sim).Association("Users").Append(user).Error + return err +} + +func RemoveUserFromSim(sim *common.Simulation, username string) error { + db := common.GetDB() + + user, err := FindUserByName(username) + if err != nil { + return err + } + + // remove user from simulation + err = db.Model(sim).Association("Users").Delete(&user).Error + if err != nil { + return err + } + + // remove simulation from user + err = db.Model(&user).Association("Simulations").Delete(sim).Error + + return err +} \ No newline at end of file diff --git a/routes/user/userSerializer.go b/routes/user/userSerializer.go deleted file mode 100644 index f208bd0..0000000 --- a/routes/user/userSerializer.go +++ /dev/null @@ -1,73 +0,0 @@ -package user - -import ( - "github.com/gin-gonic/gin" - - "git.rwth-aachen.de/acs/public/villas/villasweb-backend-go/common" - "git.rwth-aachen.de/acs/public/villas/villasweb-backend-go/routes/file" - "git.rwth-aachen.de/acs/public/villas/villasweb-backend-go/routes/project" - "git.rwth-aachen.de/acs/public/villas/villasweb-backend-go/routes/simulation" -) - -type UsersSerializer struct { - Ctx *gin.Context - Users []common.User -} - -func (self *UsersSerializer) Response(assoc bool) []UserResponse { - response := []UserResponse{} - for _, user := range self.Users { - serializer := UserSerializer{self.Ctx, user} - response = append(response, serializer.Response(assoc)) - } - return response -} - -type UserSerializer struct { - Ctx *gin.Context - common.User -} - -type UserResponse struct { - Username string `json:"Username"` - Password string `json:"Password"` // XXX: this is the hashed pw - Role string `json:"Role"` - Mail string `json:"Mail"` - Projects []project.ProjectResponseNoAssoc - Simulations []simulation.SimulationResponseNoAssoc - Files []file.FileResponseNoAssoc -} - -func (self *UserSerializer) Response(assoc bool) UserResponse { - - response := UserResponse{ - Username: self.Username, - Password: self.Password, - Role: self.Role, - Mail: self.Mail, - } - - // Associated models MUST NOT called with assoc=true otherwise we - // will have an infinite loop due to the circular dependencies - if assoc { - - // TODO: maybe all those should be made in one transaction - projects, _, _ := project.FindUserProjects(&self.User) - projectsSerializer := - project.ProjectsSerializerNoAssoc{self.Ctx, projects} - - simulations, _, _ := simulation.FindUserSimulations(&self.User) - simulationsSerializer := - simulation.SimulationsSerializerNoAssoc{self.Ctx, simulations} - - files, _, _ := file.FindUserFiles(&self.User) - filesSerializer := file.FilesSerializerNoAssoc{self.Ctx, files} - - // Add the associated models to the response - response.Projects = projectsSerializer.Response() - response.Simulations = simulationsSerializer.Response() - response.Files = filesSerializer.Response() - } - - return response -} diff --git a/routes/visualization/visualizationEndpoints.go b/routes/visualization/visualizationEndpoints.go index bfd5a52..0a19230 100644 --- a/routes/visualization/visualizationEndpoints.go +++ b/routes/visualization/visualizationEndpoints.go @@ -1,46 +1,211 @@ package visualization import ( - "github.com/gin-gonic/gin" "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 VisualizationsRegister(r *gin.RouterGroup) { - r.GET("/", visualizationsReadEp) - r.POST("/", visualizationRegistrationEp) - r.PUT("/:VisualizationID", visualizationUpdateEp) - r.GET("/:VisualizationID", visualizationReadEp) - r.DELETE("/:VisualizationID", visualizationDeleteEp) +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) + } -func visualizationsReadEp(c *gin.Context) { - allVisualizations, _, _ := FindAllVisualizations() - serializer := VisualizationsSerializer{c, allVisualizations} +// 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 := common.GetSimulationID(c) + if err != nil { + return + } + + sim, err := simulation.FindSimulation(simID) + if common.ProvideErrorResponse(c, err) { + return + } + + allVisualizations, _, _ := FindAllVisualizationsOfSim(&sim) + serializer := common.VisualizationsSerializer{c, allVisualizations} c.JSON(http.StatusOK, gin.H{ "visualizations": serializer.Response(), }) } -func visualizationRegistrationEp(c *gin.Context) { +// 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) { + + simID, err := common.GetSimulationID(c) + if err != nil { + return + } + + sim, err := simulation.FindSimulation(simID) + if common.ProvideErrorResponse(c, err) { + return + } + + var vis common.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 = AddVisualizationToSim(&sim, &vis) + 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", }) } -func visualizationUpdateEp(c *gin.Context) { +// 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) { + + simID, err := common.GetSimulationID(c) + if err != nil { + return + } + + sim, err := simulation.FindSimulation(simID) + if common.ProvideErrorResponse(c, err) { + return + } + + visID, err := common.GetVisualizationID(c) + if err != nil { + return + } + + var vis common.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 + } + + err = UpdateVisualizationOfSim(&sim, vis, visID) + 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) { + + simID, err := common.GetSimulationID(c) + if err != nil { + return + } + + sim, err := simulation.FindSimulation(simID) + if common.ProvideErrorResponse(c, err) { + return + } + + visID, err := common.GetVisualizationID(c) + if err != nil { + return + } + + visualization, err := FindVisualizationOfSim(&sim, visID) + if common.ProvideErrorResponse(c, err) { + return + } + + serializer := common.VisualizationSerializer{c, 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", }) } -func visualizationReadEp(c *gin.Context) { - c.JSON(http.StatusOK, gin.H{ - "message": "NOT implemented", - }) -} -func visualizationDeleteEp(c *gin.Context) { - c.JSON(http.StatusOK, gin.H{ - "message": "NOT implemented", - }) -} diff --git a/routes/visualization/visualizationQueries.go b/routes/visualization/visualizationQueries.go index a1f97db..0f20f7d 100644 --- a/routes/visualization/visualizationQueries.go +++ b/routes/visualization/visualizationQueries.go @@ -4,9 +4,46 @@ import ( "git.rwth-aachen.de/acs/public/villas/villasweb-backend-go/common" ) -func FindAllVisualizations() ([]common.Visualization, int, error) { +func FindAllVisualizationsOfSim(sim *common.Simulation) ([]common.Visualization, int, error) { db := common.GetDB() - var visualization []common.Visualization - err := db.Find(&visualization).Error - return visualization, len(visualization), err + var visualizations []common.Visualization + err := db.Order("ID asc").Model(sim).Related(&visualizations, "Visualizations").Error + return visualizations, len(visualizations), err } + +func FindVisualizationOfSim(sim *common.Simulation, visID int) (common.Visualization, error) { + db := common.GetDB() + var vis common.Visualization + err := db.Model(sim).Where("ID = ?", visID).Related(&vis, "Visualizations").Error + return vis, err +} + +func AddVisualizationToSim(sim * common.Simulation, vis * common.Visualization) error { + db := common.GetDB() + + // Add visualization to DB + err := db.Create(vis).Error + if err != nil { + return err + } + + // Add association with simulation + err = db.Model(sim).Association("Visualizations").Append(vis).Error + return err + +} + +func UpdateVisualizationOfSim(sim * common.Simulation, vis common.Visualization, visID int) error { + db := common.GetDB() + + // Get visualization of simulation that matches with ID (= visualization to be updated) + var vis_old common.Visualization + err := db.Model(sim).Where("ID = ?", visID).Related(&vis_old, "Visualizations").Error + if err != nil { + return err + } + + // Update visualization in DB (only name and grid can be updated) + err = db.Model(&vis_old).Updates(map[string]interface{}{"Name": vis.Name, "Grid": vis.Grid}).Error + return err +} \ No newline at end of file diff --git a/routes/visualization/visualizationSerializer.go b/routes/visualization/visualizationSerializer.go deleted file mode 100644 index 968b21e..0000000 --- a/routes/visualization/visualizationSerializer.go +++ /dev/null @@ -1,59 +0,0 @@ -package visualization - -import ( - "github.com/gin-gonic/gin" - - "git.rwth-aachen.de/acs/public/villas/villasweb-backend-go/common" - "git.rwth-aachen.de/acs/public/villas/villasweb-backend-go/routes/project" - "git.rwth-aachen.de/acs/public/villas/villasweb-backend-go/routes/widget" -) - -type VisualizationsSerializer struct { - Ctx *gin.Context - Visualizations []common.Visualization -} - -func (self *VisualizationsSerializer) Response() []VisualizationResponse { - response := []VisualizationResponse{} - for _, visualization := range self.Visualizations { - serializer := VisualizationSerializer{self.Ctx, visualization} - response = append(response, serializer.Response()) - } - return response -} - -type VisualizationSerializer struct { - Ctx *gin.Context - common.Visualization -} - -type VisualizationResponse struct { - Name string `json:"Name"` - UserID uint `json:"UserID"` - Grid int `json:"Grid"` - ProjectID uint `json:"ProjectID"` - Project project.ProjectResponseNoAssoc - Widgets []widget.WidgetResponse -} - -func (self *VisualizationSerializer) Response() VisualizationResponse { - - // TODO: maybe all those should be made in one transaction - p, _, _ := project.FindVisualizationProject(&self.Visualization) - projectSerializer := project.ProjectSerializerNoAssoc{self.Ctx, p} - - w, _, _:= widget.FindVisualizationWidgets(&self.Visualization) - widgetsSerializer := widget.WidgetsSerializer{self.Ctx, w} - - - response := VisualizationResponse{ - Name: self.Name, - UserID: self.UserID, - Grid: self.Grid, - ProjectID: self.ProjectID, - Project: projectSerializer.Response(), - Widgets: widgetsSerializer.Response(), - } - return response -} - diff --git a/routes/widget/widgetEndpoints.go b/routes/widget/widgetEndpoints.go index 950ec4d..be08605 100644 --- a/routes/widget/widgetEndpoints.go +++ b/routes/widget/widgetEndpoints.go @@ -1,3 +1,287 @@ package widget -// TODO add new API endpoints for widgets +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" + "git.rwth-aachen.de/acs/public/villas/villasweb-backend-go/routes/visualization" +) + +func RegisterWidgetEndpoints(r *gin.RouterGroup){ + r.GET("/", GetWidgets) + r.POST("/", AddWidget) + //r.POST("/:widgetID", CloneWidget) + r.PUT("/:widgetID", UpdateWidget) + r.GET("/:widgetID", GetWidget) + r.DELETE("/:widgetID", DeleteWidget) +} + +// GetWidgets godoc +// @Summary Get all widgets of visualization +// @ID GetWidgets +// @Produce json +// @Tags widgets +// @Success 200 {array} common.WidgetResponse "Array of widgets to which belong to visualization" +// @Failure 401 "Unauthorized Access" +// @Failure 403 "Access forbidden." +// @Failure 404 "Not found" +// @Failure 500 "Internal server error" +// @Param visualizationID query int true "Visualization ID" +// @Router /widgets [get] +func GetWidgets(c *gin.Context) { + + simID, err := common.GetSimulationID(c) + if err != nil { + return + } + + sim, err := simulation.FindSimulation(simID) + if common.ProvideErrorResponse(c, err) { + return + } + + visID, err := common.GetVisualizationID(c) + if err != nil { + return + } + + vis, err := visualization.FindVisualizationOfSim(&sim, visID) + if common.ProvideErrorResponse(c, err) { + return + } + + widgets,_, err := FindWidgetsOfVisualization(&vis) + if common.ProvideErrorResponse(c, err) { + return + } + + serializer := common.WidgetsSerializer{c, widgets} + c.JSON(http.StatusOK, gin.H{ + "widgets": serializer.Response(), + }) +} + +// AddWidget godoc +// @Summary Add a widget to a visualization +// @ID AddWidget +// @Accept json +// @Produce json +// @Tags widgets +// @Param inputWidget body common.WidgetResponse true "Widget to be added incl. ID of visualization" +// @Success 200 "OK." +// @Failure 401 "Unauthorized Access" +// @Failure 403 "Access forbidden." +// @Failure 404 "Not found" +// @Failure 500 "Internal server error" +// @Router /widgets [post] +func AddWidget(c *gin.Context) { + + simID, err := common.GetSimulationID(c) + if err != nil { + return + } + + sim, err := simulation.FindSimulation(simID) + if common.ProvideErrorResponse(c, err) { + return + } + + visID, err := common.GetVisualizationID(c) + if err != nil { + return + } + + vis, err := visualization.FindVisualizationOfSim(&sim, visID) + if common.ProvideErrorResponse(c, err) { + return + } + + var widget_input common.Widget + err = c.BindJSON(&widget_input) + if err != nil { + errormsg := "Bad request. Error binding form data to JSON: " + err.Error() + c.JSON(http.StatusBadRequest, gin.H{ + "error": errormsg, + }) + return + } + + err = AddWidgetToVisualization(&vis, &widget_input) + if common.ProvideErrorResponse(c, err) == false { + c.JSON(http.StatusOK, gin.H{ + "message": "OK.", + }) + } + + +} + +func CloneWidget(c *gin.Context) { + c.JSON(http.StatusOK, gin.H{ + "message": "NOT implemented", + }) +} + +// UpdateWidget godoc +// @Summary Update a widget +// @ID UpdateWidget +// @Tags widgets +// @Accept json +// @Produce json +// @Param inputWidget body common.WidgetResponse true "Widget to be updated" +// @Success 200 "OK." +// @Failure 401 "Unauthorized Access" +// @Failure 403 "Access forbidden." +// @Failure 404 "Not found" +// @Failure 500 "Internal server error" +// @Param widgetID path int true "Widget ID" +// @Router /widgets/{widgetID} [put] +func UpdateWidget(c *gin.Context) { + simID, err := common.GetSimulationID(c) + if err != nil { + return + } + + sim, err := simulation.FindSimulation(simID) + if common.ProvideErrorResponse(c, err) { + return + } + + visID, err := common.GetVisualizationID(c) + if err != nil { + return + } + + vis, err := visualization.FindVisualizationOfSim(&sim, visID) + if common.ProvideErrorResponse(c, err) { + return + } + + widgetID, err := common.GetWidgetID(c) + if err != nil { + return + } + + var widget_input common.Widget + err = c.BindJSON(&widget_input) + if err != nil { + errormsg := "Bad request. Error binding form data to JSON: " + err.Error() + c.JSON(http.StatusBadRequest, gin.H{ + "error": errormsg, + }) + return + } + + err = UpdateWidgetOfVisualization(&vis, widget_input, widgetID) + if common.ProvideErrorResponse(c, err) == false { + c.JSON(http.StatusOK, gin.H{ + "message": "OK", + }) + } +} + +// GetWidget godoc +// @Summary Get a widget +// @ID GetWidget +// @Tags widgets +// @Produce json +// @Success 200 {object} common.WidgetResponse "Requested widget." +// @Failure 401 "Unauthorized Access" +// @Failure 403 "Access forbidden." +// @Failure 404 "Not found" +// @Failure 500 "Internal server error" +// @Param widgetID path int true "Widget ID" +// @Router /widgets/{widgetID} [get] +func GetWidget(c *gin.Context) { + + simID, err := common.GetSimulationID(c) + if err != nil { + return + } + + sim, err := simulation.FindSimulation(simID) + if common.ProvideErrorResponse(c, err) { + return + } + + visID, err := common.GetVisualizationID(c) + if err != nil { + return + } + + vis, err := visualization.FindVisualizationOfSim(&sim, visID) + if common.ProvideErrorResponse(c, err) { + return + } + + widgetID, err := common.GetWidgetID(c) + if err != nil { + return + } + + widget, err := FindWidgetOfVisualization(&vis, widgetID) + serializer := common.WidgetSerializer{c, widget} + c.JSON(http.StatusOK, gin.H{ + "widget": serializer.Response(), + }) +} + + +// DeleteWidget godoc +// @Summary Delete a widget +// @ID DeleteWidget +// @Tags widgets +// @Produce json +// @Success 200 "OK." +// @Failure 401 "Unauthorized Access" +// @Failure 403 "Access forbidden." +// @Failure 404 "Not found" +// @Failure 500 "Internal server error" +// @Param widgetID path int true "Widget ID" +// @Router /widgets/{widgetID} [delete] +func DeleteWidget(c *gin.Context) { + + // simID, err := GetSimulationID(c) + // if err != nil { + // return + // } + // + // sim, err := queries.FindSimulation(simID) + // if common.ProvideErrorResponse(c, err) { + // return + // } + // + // visID, err := GetVisualizationID(c) + // if err != nil { + // return + // } + // + // visualization, err := queries.FindVisualizationOfSim(&sim, visID) + // if common.ProvideErrorResponse(c, err) { + // return + // } + // + // widgetID, err := GetWidgetID(c) + // if err != nil { + // return + // } + // + // widget, err := queries.FindWidgetOfVisualization(&visualization, widgetID) + // if common.ProvideErrorResponse(c, err) { + // return + // } + + // TODO delete files of widget in DB and on disk + + + // TODO Delete widget itself + association with visualization + + c.JSON(http.StatusOK, gin.H{ + "message": "NOT implemented", + }) +} + + diff --git a/routes/widget/widgetQueries.go b/routes/widget/widgetQueries.go index 3d64294..2278622 100644 --- a/routes/widget/widgetQueries.go +++ b/routes/widget/widgetQueries.go @@ -7,8 +7,61 @@ import ( func FindVisualizationWidgets(visualization *common.Visualization) ([]common.Widget, int, error) { db := common.GetDB() var widgets []common.Widget - err := db.Model(visualization).Related(&widgets, "Widgets").Error + err := db.Order("ID asc").Model(visualization).Related(&widgets, "Widgets").Error return widgets, len(widgets), err } +func FindWidget(widgetID int) (common.Widget, error){ + db := common.GetDB() + var w common.Widget + err := db.First(&w, widgetID).Error + return w, err +} + + +func FindWidgetsOfVisualization(vis * common.Visualization) ([]common.Widget, int, error) { + db := common.GetDB() + var widgets []common.Widget + err := db.Order("ID asc").Model(vis).Related(&vis, "Widgets").Error + return widgets, len(widgets), err +} + +func FindWidgetOfVisualization(visualization *common.Visualization, widgetID int) (common.Widget, error){ + db := common.GetDB() + var widget common.Widget + err := db.Model(visualization).Where("ID = ?", widgetID).Related(&widget, "Widgets").Error + return widget, err +} + + +func AddWidgetToVisualization(vis *common.Visualization, widget_input * common.Widget) error { + + db := common.GetDB() + + // Add widget to DB + err := db.Create(widget_input).Error + if err != nil { + return err + } + + // Add association with visualization + err = db.Model(vis).Association("Widgets").Append(widget_input).Error + return err +} + +func UpdateWidgetOfVisualization(vis * common.Visualization, widget_input common.Widget, widgetID int) error { + db := common.GetDB() + + // Get widget of visualization that matches with ID (= widget to be updated) + var widget_old common.Widget + err := db.Model(vis).Where("ID = ?", widgetID).Related(&widget_old, "Widgets").Error + if err != nil { + return err + } + + // Update widget in DB + err = db.Model(&widget_old).Updates(map[string]interface{}{"Name": widget_input.Name, "Type": widget_input.Type, "MinHeight": widget_input.MinHeight, "MinWidth": widget_input.MinWidth, "Height": widget_input.Height, "Width": widget_input.Width, "X": widget_input.X, "Y": widget_input.Y, "Z": widget_input.Z, "CustomProperties": widget_input.CustomProperties}).Error + return err + +} \ No newline at end of file diff --git a/routes/widget/widgetSerializer.go b/routes/widget/widgetSerializer.go deleted file mode 100644 index cb9a629..0000000 --- a/routes/widget/widgetSerializer.go +++ /dev/null @@ -1,60 +0,0 @@ -package widget - -import ( - "github.com/gin-gonic/gin" - - "git.rwth-aachen.de/acs/public/villas/villasweb-backend-go/common" -) - -type WidgetsSerializer struct { - Ctx *gin.Context - Widgets []common.Widget -} - -func (self *WidgetsSerializer) Response() []WidgetResponse { - response := []WidgetResponse{} - for _, widget := range self.Widgets { - serializer := WidgetSerializer{self.Ctx, widget} - response = append(response, serializer.Response()) - } - return response -} - -type WidgetSerializer struct { - Ctx *gin.Context - common.Widget -} - -type WidgetResponse struct { - Name string `json:"Name"` - Type string `json:"Type"` - Width uint `json:"Width"` - Height uint `json:"Height"` - MinWidth uint `json:"MinWidth"` - MinHeight uint `json:"MinHeight"` - X int `json:"X"` - Y int `json:"Y"` - Z int `json:"Z"` - VisualizationID uint `json:"VisualizationID"` - IsLocked bool `json:"IsLocked"` - //CustomProperties -} - -func (self *WidgetSerializer) Response() WidgetResponse { - - response := WidgetResponse{ - Name: self.Name, - Type: self.Type, - Width: self.Width, - Height: self.Height, - MinWidth: self.MinWidth, - MinHeight: self.MinHeight, - X: self.X, - Y: self.Y, - Z: self.Z, - VisualizationID: self.VisualizationID, - IsLocked: self.IsLocked, - //CustomProperties - } - return response -} diff --git a/start.go b/start.go index fb94766..7be1f85 100644 --- a/start.go +++ b/start.go @@ -1,18 +1,37 @@ package main import ( - "git.rwth-aachen.de/acs/public/villas/villasweb-backend-go/common" "git.rwth-aachen.de/acs/public/villas/villasweb-backend-go/routes/file" - "git.rwth-aachen.de/acs/public/villas/villasweb-backend-go/routes/project" + "github.com/gin-gonic/gin" + "github.com/swaggo/gin-swagger" + "github.com/swaggo/gin-swagger/swaggerFiles" + + "git.rwth-aachen.de/acs/public/villas/villasweb-backend-go/common" + _ "git.rwth-aachen.de/acs/public/villas/villasweb-backend-go/doc/autoapi" // apidocs folder is generated by Swag CLI, you have to import it + "git.rwth-aachen.de/acs/public/villas/villasweb-backend-go/routes/model" "git.rwth-aachen.de/acs/public/villas/villasweb-backend-go/routes/simulation" - "git.rwth-aachen.de/acs/public/villas/villasweb-backend-go/routes/simulationmodel" "git.rwth-aachen.de/acs/public/villas/villasweb-backend-go/routes/simulator" "git.rwth-aachen.de/acs/public/villas/villasweb-backend-go/routes/user" "git.rwth-aachen.de/acs/public/villas/villasweb-backend-go/routes/visualization" - - "github.com/gin-gonic/gin" + "git.rwth-aachen.de/acs/public/villas/villasweb-backend-go/routes/widget" ) +// @title VILLASweb Backend API +// @version 2.0 +// @description This is the API of the VILLASweb Backend +// @description WORK IN PROGRESS! PLEASE BE PATIENT! + +// @description This documentation is auto-generated based on the API documentation in the code. +// @description The tool https://github.com/swaggo/swag is used to auto-generate API docs for gin. + +// @contact.name Sonja Happ +// @contact.email sonja.happ@eonerc.rwth-aachen.de + +// @license.name GNU GPL 3.0 +// @license.url http://www.gnu.de/documents/gpl-3.0.en.html + +// @host localhost:4000 +// @BasePath /api/v1 func main() { // Testing db := common.DummyInitDB() @@ -31,13 +50,15 @@ func main() { api.Use(user.Authentication(true)) - user.UsersRegister(api.Group("/users")) - file.FilesRegister(api.Group("/files")) - project.ProjectsRegister(api.Group("/projects")) - simulation.SimulationsRegister(api.Group("/simulations")) - simulationmodel.SimulationModelsRegister(api.Group("/models")) - simulator.SimulatorsRegister(api.Group("/simulators")) - visualization.VisualizationsRegister(api.Group("/visualizations")) + simulation.RegisterSimulationEndpoints(api.Group("/simulations")) + model.RegisterModelEndpoints(api.Group("/models")) + visualization.RegisterVisualizationEndpoints(api.Group("/visualizations")) + widget.RegisterWidgetEndpoints(api.Group("/widgets")) + file.RegisterFileEndpoints(api.Group("/files")) + user.RegisterUserEndpoints(api.Group("/users")) + simulator.RegisterSimulatorEndpoints(api.Group("/simulators")) + + r.GET("swagger/*any", ginSwagger.WrapHandler(swaggerFiles.Handler)) // server at port 4000 to match frontend's redirect path r.Run(":4000")