diff --git a/app/api/bridges.go b/app/api/bridges.go index e9fe792..c82a0ca 100644 --- a/app/api/bridges.go +++ b/app/api/bridges.go @@ -12,6 +12,10 @@ func Bridges(r gin.IRoutes) { return config.BridgeRepository().FetchAll(ctxOf(c)) })) + r.GET("/:id", handler(func(c *gin.Context) (interface{}, error) { + return config.BridgeRepository().Find(ctxOf(c), intParam(c, "id")) + })) + r.POST("", handler(func(c *gin.Context) (interface{}, error) { var body struct { Driver models.DriverKind `json:"driver"` diff --git a/app/api/presets.go b/app/api/presets.go new file mode 100644 index 0000000..6682d21 --- /dev/null +++ b/app/api/presets.go @@ -0,0 +1,83 @@ +package api + +import ( + "git.aiterp.net/lucifer/new-server/app/config" + "git.aiterp.net/lucifer/new-server/models" + "github.com/gin-gonic/gin" +) + +func ColorPresets(r gin.IRoutes) { + r.GET("", handler(func(c *gin.Context) (interface{}, error) { + return config.ColorPresetRepository().FetchAll(ctxOf(c)) + })) + + r.GET("/:id", handler(func(c *gin.Context) (interface{}, error) { + return config.ColorPresetRepository().Find(ctxOf(c), intParam(c, "id")) + })) + + r.POST("", handler(func(c *gin.Context) (interface{}, error) { + var body struct { + Name string `json:"name"` + ColorString string `json:"colorString"` + } + err := parseBody(c, &body) + if err != nil { + return nil, err + } + + newColor, err := models.ParseColorValue(body.ColorString) + if err != nil { + return nil, err + } + + preset := models.ColorPreset{ + Name: body.Name, + Value: newColor, + } + + preset.Validate() + err = config.ColorPresetRepository().Save(ctxOf(c), &preset) + if err != nil { + return nil, err + } + + return preset, nil + })) + + r.PUT("/:id", handler(func(c *gin.Context) (interface{}, error) { + var body struct { + Name *string `json:"name"` + ColorString *string `json:"colorString"` + } + err := parseBody(c, &body) + if err != nil { + return nil, err + } + + preset, err := config.ColorPresetRepository().Find(ctxOf(c), intParam(c, "id")) + if err != nil { + return nil, err + } + + if body.Name != nil { + preset.Name = *body.Name + } + + if body.ColorString != nil { + newColor, err := models.ParseColorValue(*body.ColorString) + if err != nil { + return nil, err + } + + preset.Value = newColor + } + + preset.Validate() + err = config.ColorPresetRepository().Save(ctxOf(c), &preset) + if err != nil { + return nil, err + } + + return preset, nil + })) +} diff --git a/app/config/driver.go b/app/config/driver.go index 2ec8fa7..51fa613 100644 --- a/app/config/driver.go +++ b/app/config/driver.go @@ -15,9 +15,9 @@ func DriverProvider() models.DriverProvider { defer dpMutex.Unlock() if dp == nil { - dp = &drivers.MapBasedDriverProvider{Data: map[models.DriverKind]models.Driver{ + dp = drivers.DriverMap{ models.DTNanoLeaf: &nanoleaf.Driver{}, - }} + } } return dp diff --git a/app/config/repo.go b/app/config/repo.go index 53b65a4..b251e22 100644 --- a/app/config/repo.go +++ b/app/config/repo.go @@ -7,6 +7,7 @@ import ( var ( bRepo models.BridgeRepository + cpRepo models.ColorPresetRepository dRepo models.DeviceRepository ehRepo models.EventHandlerRepository ) @@ -19,6 +20,14 @@ func BridgeRepository() models.BridgeRepository { return bRepo } +func ColorPresetRepository() models.ColorPresetRepository { + if cpRepo == nil { + cpRepo = &mysql.ColorPresetRepo{DBX: DBX()} + } + + return cpRepo +} + func DeviceRepository() models.DeviceRepository { if dRepo == nil { panic("panik") diff --git a/app/server.go b/app/server.go index f13657d..ba1f6ea 100644 --- a/app/server.go +++ b/app/server.go @@ -17,6 +17,7 @@ func StartServer() { apiGin := ginny.Group("/api") api.Bridges(apiGin.Group("/bridges")) + api.ColorPresets(apiGin.Group("/color-presets")) api.DriverKinds(apiGin.Group("/driver-kinds")) api.Events(apiGin.Group("/events")) diff --git a/cmd/bridgetest/main.go b/cmd/bridgetest/main.go index 055bc02..06dc50b 100644 --- a/cmd/bridgetest/main.go +++ b/cmd/bridgetest/main.go @@ -65,7 +65,7 @@ func main() { _ = driver.Publish(context.Background(), bridge, devices) - ch := make(chan models.Event) + ch := config.EventChannel go func() { err := driver.Run(context.Background(), bridge, ch) if err != nil { diff --git a/cmd/goose/main.go b/cmd/goose/main.go index b02c383..a0eadbe 100644 --- a/cmd/goose/main.go +++ b/cmd/goose/main.go @@ -4,11 +4,13 @@ import ( "git.aiterp.net/lucifer/new-server/app/config" "github.com/pressly/goose" "log" + "time" ) func main() { db := config.DBX().DB + log.Printf("Target version: %s",time.Now().Format("20060102150405")) log.Printf("Database: %s:%d/%s", config.MySqlHost(), config.MySqlPort(), config.MySqlSchema()) log.Printf("Authenticating as: %s", config.MySqlUsername()) diff --git a/internal/drivers/provider.go b/internal/drivers/provider.go index 4490640..c3763b4 100644 --- a/internal/drivers/provider.go +++ b/internal/drivers/provider.go @@ -2,15 +2,13 @@ package drivers import "git.aiterp.net/lucifer/new-server/models" -type MapBasedDriverProvider struct { - Data map[models.DriverKind]models.Driver -} +type DriverMap map[models.DriverKind]models.Driver -func (m *MapBasedDriverProvider) Provide(kind models.DriverKind) (models.Driver, error) { - if m.Data[kind] == nil { +func (m DriverMap) Provide(kind models.DriverKind) (models.Driver, error) { + if m[kind] == nil { return nil, models.ErrUnknownDriver } - return m.Data[kind], nil + return m[kind], nil } diff --git a/internal/mysql/bridgerepo.go b/internal/mysql/bridgerepo.go index 172fe58..926b9d6 100644 --- a/internal/mysql/bridgerepo.go +++ b/internal/mysql/bridgerepo.go @@ -22,7 +22,7 @@ func (b *BridgeRepo) Find(ctx context.Context, id int) (models.Bridge, error) { func (b *BridgeRepo) FetchAll(ctx context.Context) ([]models.Bridge, error) { bridges := make([]models.Bridge, 0, 8) - err := b.DBX.GetContext(ctx, bridges, "SELECT * FROM bridge") + err := b.DBX.GetContext(ctx, &bridges, "SELECT * FROM bridge") if err != nil { return nil, dbErr(err) } diff --git a/internal/mysql/presetrepo.go b/internal/mysql/presetrepo.go new file mode 100644 index 0000000..296fcc1 --- /dev/null +++ b/internal/mysql/presetrepo.go @@ -0,0 +1,99 @@ +package mysql + +import ( + "context" + "git.aiterp.net/lucifer/new-server/models" + "github.com/jmoiron/sqlx" +) + +type presetRecord struct { + ID int `db:"id"` + Name string `db:"name"` + Hue float64 `db:"hue"` + Saturation float64 `db:"saturation"` + Kelvin int `db:"kelvin"` +} + +type ColorPresetRepo struct { + DBX *sqlx.DB +} + +func (c *ColorPresetRepo) Find(ctx context.Context, id int) (models.ColorPreset, error) { + var record presetRecord + err := c.DBX.GetContext(ctx, &record, "SELECT * FROM color_preset WHERE id = ?", id) + if err != nil { + return models.ColorPreset{}, dbErr(err) + } + + return c.fromRecords(record)[0], nil +} + +func (c *ColorPresetRepo) FetchAll(ctx context.Context) ([]models.ColorPreset, error) { + records := make([]presetRecord, 0, 16) + err := c.DBX.SelectContext(ctx, &records, "SELECT * FROM color_preset") + if err != nil { + return nil, dbErr(err) + } + + return c.fromRecords(records...), nil +} + +func (c *ColorPresetRepo) Save(ctx context.Context, preset *models.ColorPreset) error { + if preset.ID > 0 { + _, err := c.DBX.ExecContext( + ctx, + "UPDATE color_preset SET name = ?, hue = ?, saturation = ?, kelvin = ? WHERE id = ?", + preset.Name, preset.Value.Hue, preset.Value.Saturation, preset.Value.Kelvin, preset.ID, + ) + + if err != nil { + return dbErr(err) + } + } else { + rs, err := c.DBX.ExecContext( + ctx, + "INSERT INTO color_preset (name, hue, saturation, kelvin) VALUES (?, ?, ?, ?)", + preset.Name, preset.Value.Hue, preset.Value.Saturation, preset.Value.Kelvin, + ) + + if err != nil { + return dbErr(err) + } + + id, err := rs.LastInsertId() + if err != nil { + return dbErr(err) + } + + preset.ID = int(id) + } + + return nil +} + +func (c *ColorPresetRepo) Delete(ctx context.Context, preset *models.ColorPreset) error { + _, err := c.DBX.ExecContext(ctx, "DELETE FROM color_preset WHERE id = ?", preset.ID) + if err != nil { + return dbErr(err) + } + + preset.ID = 0 + return nil +} + +func (c *ColorPresetRepo) fromRecords(records ...presetRecord) []models.ColorPreset { + newList := make([]models.ColorPreset, len(records), len(records)) + for i, record := range records { + newList[i] = models.ColorPreset{ + ID: record.ID, + Name: record.Name, + Value: models.ColorValue{ + Hue: record.Hue, + Saturation: record.Saturation, + Kelvin: record.Kelvin, + }, + } + } + + return newList +} diff --git a/models/colorpreset.go b/models/colorpreset.go new file mode 100644 index 0000000..c2b3f71 --- /dev/null +++ b/models/colorpreset.go @@ -0,0 +1,26 @@ +package models + +import ( + "context" + "strings" +) + +type ColorPreset struct { + ID int `json:"id"` + Name string `json:"name"` + Value ColorValue `json:"value"` +} + +type ColorPresetRepository interface { + Find(ctx context.Context, id int) (ColorPreset, error) + FetchAll(ctx context.Context) ([]ColorPreset, error) + Save(ctx context.Context, preset *ColorPreset) error + Delete(ctx context.Context, preset *ColorPreset) error +} + +func (c *ColorPreset) Validate() { + c.Name = strings.Trim(c.Name, " \t\n") + if len(c.Name) == 0 { + c.Name = c.Value.String() + } +} diff --git a/scripts/20210523175111_color_preset.sql b/scripts/20210523175111_color_preset.sql new file mode 100644 index 0000000..108d9e6 --- /dev/null +++ b/scripts/20210523175111_color_preset.sql @@ -0,0 +1,17 @@ +-- +goose Up +-- +goose StatementBegin +CREATE TABLE color_preset +( + id INT NOT NULL AUTO_INCREMENT, + hue DOUBLE NOT NULL, + saturation DOUBLE NOT NULL, + kelvin INT NOT NULL, + + PRIMARY KEY (id) +); +-- +goose StatementEnd + +-- +goose Down +-- +goose StatementBegin +DROP TABLE color_preset; +-- +goose StatementEnd diff --git a/scripts/20210523175425_color_preset_name.sql b/scripts/20210523175425_color_preset_name.sql new file mode 100644 index 0000000..8b7ead9 --- /dev/null +++ b/scripts/20210523175425_color_preset_name.sql @@ -0,0 +1,11 @@ +-- +goose Up +-- +goose StatementBegin +ALTER TABLE color_preset + ADD COLUMN name VARCHAR(255) NOT NULL DEFAULT 'Unnamed'; +-- +goose StatementEnd + +-- +goose Down +-- +goose StatementBegin +ALTER TABLE color_preset + DROP COLUMN name; +-- +goose StatementEnd