small fixed, device repo and some device api
This commit is contained in:
@@ -11,10 +11,10 @@ type BridgeRepo struct {
|
||||
}
|
||||
|
||||
func (b *BridgeRepo) Find(ctx context.Context, id int) (models.Bridge, error) {
|
||||
var bridge models.Bridge
|
||||
var bridge models.Bridge
|
||||
err := b.DBX.GetContext(ctx, &bridge, "SELECT * FROM bridge WHERE id = ?", id)
|
||||
if err != nil {
|
||||
return models.Bridge{}, dbErr(err)
|
||||
if err != nil {
|
||||
return models.Bridge{}, dbErr(err)
|
||||
}
|
||||
|
||||
return bridge, nil
|
||||
@@ -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.SelectContext(ctx, &bridges, "SELECT * FROM bridge")
|
||||
if err != nil {
|
||||
return nil, dbErr(err)
|
||||
}
|
||||
|
||||
@@ -0,0 +1,313 @@
|
||||
package mysql
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"git.aiterp.net/lucifer/new-server/models"
|
||||
sq "github.com/Masterminds/squirrel"
|
||||
"github.com/jmoiron/sqlx"
|
||||
"log"
|
||||
"strings"
|
||||
)
|
||||
|
||||
type deviceRecord struct {
|
||||
ID int `db:"id"`
|
||||
BridgeID int `db:"bridge_id"`
|
||||
InternalID string `db:"internal_id"`
|
||||
Icon string `db:"icon"`
|
||||
Name string `db:"name"`
|
||||
Capabilities string `db:"capabilities"`
|
||||
ButtonNames string `db:"button_names"`
|
||||
}
|
||||
|
||||
type deviceStateRecord struct {
|
||||
DeviceID int `db:"device_id"`
|
||||
Hue float64 `db:"hue"`
|
||||
Saturation float64 `db:"saturation"`
|
||||
Kelvin int `db:"kelvin"`
|
||||
Power bool `db:"power"`
|
||||
Intensity float64 `db:"intensity"`
|
||||
}
|
||||
|
||||
type devicePropertyRecord struct {
|
||||
DeviceID int `db:"device_id"`
|
||||
Key string `db:"prop_key"`
|
||||
Value string `db:"prop_value"`
|
||||
IsUser bool `db:"is_user"`
|
||||
}
|
||||
|
||||
type deviceTagRecord struct {
|
||||
DeviceID int `db:"device_id"`
|
||||
TagName string `db:"tag_name"`
|
||||
}
|
||||
|
||||
type DeviceRepo struct {
|
||||
DBX *sqlx.DB
|
||||
}
|
||||
|
||||
func (r *DeviceRepo) Find(ctx context.Context, id int) (*models.Device, error) {
|
||||
var device deviceRecord
|
||||
err := r.DBX.GetContext(ctx, &device, "SELECT * FROM device WHERE id = ?", id)
|
||||
if err != nil {
|
||||
return nil, dbErr(err)
|
||||
}
|
||||
|
||||
return r.populateOne(ctx, device)
|
||||
}
|
||||
|
||||
func (r *DeviceRepo) FetchByReference(ctx context.Context, kind models.ReferenceKind, value string) ([]models.Device, error) {
|
||||
var err error
|
||||
records := make([]deviceRecord, 0, 8)
|
||||
|
||||
switch kind {
|
||||
case models.RKDeviceID:
|
||||
err = r.DBX.SelectContext(ctx, &records, "SELECT * FROM device WHERE id=?", value)
|
||||
case models.RKBridgeID:
|
||||
err = r.DBX.SelectContext(ctx, &records, "SELECT * FROM device WHERE bridge_id=?", value)
|
||||
case models.RKTag:
|
||||
err = r.DBX.SelectContext(ctx, &records, "SELECT device.* FROM device JOIN device_tag dt ON device.id = dt.device_id WHERE dt.tag_name=?", value)
|
||||
case models.RKAll:
|
||||
err = r.DBX.SelectContext(ctx, &records, "SELECT device.* FROM device")
|
||||
default:
|
||||
log.Println("Unknown reference kind used for device fetch:", kind)
|
||||
return []models.Device{}, nil
|
||||
}
|
||||
if err != nil {
|
||||
return nil, dbErr(err)
|
||||
}
|
||||
|
||||
return r.populate(ctx, records)
|
||||
}
|
||||
|
||||
func (r *DeviceRepo) Save(ctx context.Context, device *models.Device) error {
|
||||
tx, err := r.DBX.Beginx()
|
||||
if err != nil {
|
||||
return dbErr(err)
|
||||
}
|
||||
defer tx.Rollback()
|
||||
|
||||
record := deviceRecord{
|
||||
ID: device.ID,
|
||||
BridgeID: device.BridgeID,
|
||||
InternalID: device.InternalID,
|
||||
Icon: device.Icon,
|
||||
Name: device.Name,
|
||||
Capabilities: strings.Join(models.DeviceCapabilitiesToStrings(device.Capabilities), ","),
|
||||
ButtonNames: strings.Join(device.ButtonNames, ","),
|
||||
}
|
||||
|
||||
if device.ID > 0 {
|
||||
_, err := tx.NamedExecContext(ctx, `
|
||||
UPDATE device SET
|
||||
internal_id = :internal_id,
|
||||
icon = :icon,
|
||||
name = :name,
|
||||
capabilities = :capabilities,
|
||||
button_names = :button_names
|
||||
WHERE id=:id
|
||||
`, record)
|
||||
if err != nil {
|
||||
return dbErr(err)
|
||||
}
|
||||
|
||||
// Let's just be lazy for now, optimize later if need be.
|
||||
_, err = tx.ExecContext(ctx, "DELETE FROM device_tag WHERE device_id=?", record.ID)
|
||||
if err != nil {
|
||||
return dbErr(err)
|
||||
}
|
||||
_, err = tx.ExecContext(ctx, "DELETE FROM device_property WHERE device_id=?", record.ID)
|
||||
if err != nil {
|
||||
return dbErr(err)
|
||||
}
|
||||
} else {
|
||||
res, err := tx.NamedExecContext(ctx, `
|
||||
INSERT INTO device (bridge_id, internal_id, icon, name, capabilities, button_names)
|
||||
VALUES (:bridge_id, :internal_id, :icon, :name, :capabilities, :button_names)
|
||||
`, record)
|
||||
if err != nil {
|
||||
return dbErr(err)
|
||||
}
|
||||
|
||||
lastID, err := res.LastInsertId()
|
||||
if err != nil {
|
||||
return dbErr(err)
|
||||
}
|
||||
|
||||
record.ID = int(lastID)
|
||||
device.ID = int(lastID)
|
||||
}
|
||||
|
||||
for _, tag := range device.Tags {
|
||||
_, err := tx.ExecContext(ctx, "INSERT INTO device_tag (device_id, tag_name) VALUES (?, ?)", record.ID, tag)
|
||||
if err != nil {
|
||||
return dbErr(err)
|
||||
}
|
||||
}
|
||||
|
||||
for key, value := range device.UserProperties {
|
||||
_, err := tx.ExecContext(ctx, "INSERT INTO device_property (device_id, prop_key, prop_value, is_user) VALUES (?, ?, ?, 1)",
|
||||
record.ID, key, value,
|
||||
)
|
||||
if err != nil {
|
||||
return dbErr(err)
|
||||
}
|
||||
}
|
||||
|
||||
for key, value := range device.DriverProperties {
|
||||
j, err := json.Marshal(value)
|
||||
if err != nil {
|
||||
// Eh, it'll get filled by the driver anyway
|
||||
continue
|
||||
}
|
||||
|
||||
_, err = tx.ExecContext(ctx, "INSERT INTO device_property (device_id, prop_key, prop_value, is_user) VALUES (?, ?, ?, 0)",
|
||||
record.ID, key, string(j),
|
||||
)
|
||||
if err != nil {
|
||||
// Return err here anyway, it might put the tx in a bad state to ignore it.
|
||||
return dbErr(err)
|
||||
}
|
||||
}
|
||||
|
||||
_, err = tx.NamedExecContext(ctx, `
|
||||
REPLACE INTO device_state(device_id, hue, saturation, kelvin, power, intensity)
|
||||
VALUES (:device_id, :hue, :saturation, :kelvin, :power, :intensity)
|
||||
`, deviceStateRecord{
|
||||
DeviceID: record.ID,
|
||||
Hue: device.State.Color.Hue,
|
||||
Saturation: device.State.Color.Saturation,
|
||||
Kelvin: device.State.Color.Kelvin,
|
||||
Power: device.State.Power,
|
||||
Intensity: device.State.Intensity,
|
||||
})
|
||||
if err != nil {
|
||||
return dbErr(err)
|
||||
}
|
||||
|
||||
return tx.Commit()
|
||||
}
|
||||
|
||||
func (r *DeviceRepo) Delete(ctx context.Context, device *models.Device) error {
|
||||
_, err := r.DBX.ExecContext(ctx, "DELETE FROM device WHERE Id=?", device.ID)
|
||||
if err != nil {
|
||||
return dbErr(err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (r *DeviceRepo) populateOne(ctx context.Context, record deviceRecord) (*models.Device, error) {
|
||||
records, err := r.populate(ctx, []deviceRecord{record})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &records[0], nil
|
||||
}
|
||||
|
||||
func (r *DeviceRepo) populate(ctx context.Context, records []deviceRecord) ([]models.Device, error) {
|
||||
if len(records) == 0 {
|
||||
return []models.Device{}, nil
|
||||
}
|
||||
|
||||
ids := make([]int, 0, len(records))
|
||||
for _, record := range records {
|
||||
ids = append(ids, record.ID)
|
||||
}
|
||||
|
||||
tagsQuery, tagsArgs, err := sq.Select("*").From("device_tag").Where(sq.Eq{"device_id": ids}).ToSql()
|
||||
if err != nil {
|
||||
return nil, dbErr(err)
|
||||
}
|
||||
propsQuery, propsArgs, err := sq.Select("*").From("device_property").Where(sq.Eq{"device_id": ids}).ToSql()
|
||||
if err != nil {
|
||||
return nil, dbErr(err)
|
||||
}
|
||||
stateQuery, stateArgs, err := sq.Select("*").From("device_state").Where(sq.Eq{"device_id": ids}).ToSql()
|
||||
if err != nil {
|
||||
return nil, dbErr(err)
|
||||
}
|
||||
|
||||
states := make([]deviceStateRecord, 0, len(records))
|
||||
props := make([]devicePropertyRecord, 0, len(records)*8)
|
||||
tags := make([]deviceTagRecord, 0, len(records)*4)
|
||||
|
||||
err = r.DBX.SelectContext(ctx, &states, stateQuery, stateArgs...)
|
||||
if err != nil {
|
||||
return nil, dbErr(err)
|
||||
}
|
||||
err = r.DBX.SelectContext(ctx, &props, propsQuery, propsArgs...)
|
||||
if err != nil {
|
||||
return nil, dbErr(err)
|
||||
}
|
||||
err = r.DBX.SelectContext(ctx, &tags, tagsQuery, tagsArgs...)
|
||||
if err != nil {
|
||||
return nil, dbErr(err)
|
||||
}
|
||||
|
||||
devices := make([]models.Device, 0, len(records))
|
||||
for _, record := range records {
|
||||
device := models.Device{
|
||||
ID: record.ID,
|
||||
BridgeID: record.BridgeID,
|
||||
InternalID: record.InternalID,
|
||||
Icon: record.Icon,
|
||||
Name: record.Name,
|
||||
ButtonNames: strings.Split(record.ButtonNames, ","),
|
||||
DriverProperties: make(map[string]interface{}, 8),
|
||||
UserProperties: make(map[string]string, 8),
|
||||
Tags: make([]string, 0, 8),
|
||||
}
|
||||
|
||||
caps := make([]models.DeviceCapability, 0, 16)
|
||||
for _, capStr := range strings.Split(record.Capabilities, ",") {
|
||||
caps = append(caps, models.DeviceCapability(capStr))
|
||||
}
|
||||
device.Capabilities = caps
|
||||
|
||||
for _, state := range states {
|
||||
if state.DeviceID == record.ID {
|
||||
device.State = models.DeviceState{
|
||||
Power: state.Power,
|
||||
Color: models.ColorValue{
|
||||
Hue: state.Hue,
|
||||
Saturation: state.Saturation,
|
||||
Kelvin: state.Kelvin,
|
||||
},
|
||||
Intensity: state.Intensity,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
driverProps := make(map[string]json.RawMessage, 8)
|
||||
for _, prop := range props {
|
||||
if prop.DeviceID == record.ID {
|
||||
if prop.IsUser {
|
||||
device.UserProperties[prop.Key] = prop.Value
|
||||
} else {
|
||||
driverProps[prop.Key] = json.RawMessage(prop.Value)
|
||||
}
|
||||
}
|
||||
}
|
||||
if len(driverProps) > 0 {
|
||||
j, err := json.Marshal(driverProps)
|
||||
if err != nil {
|
||||
return nil, dbErr(err)
|
||||
}
|
||||
err = json.Unmarshal(j, &device.DriverProperties)
|
||||
if err != nil {
|
||||
return nil, dbErr(err)
|
||||
}
|
||||
}
|
||||
|
||||
for _, tag := range tags {
|
||||
if tag.DeviceID == record.ID {
|
||||
device.Tags = append(device.Tags, tag.TagName)
|
||||
}
|
||||
}
|
||||
|
||||
devices = append(devices, device)
|
||||
}
|
||||
|
||||
return devices, nil
|
||||
}
|
||||
Reference in New Issue
Block a user