You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
333 lines
7.3 KiB
333 lines
7.3 KiB
package api
|
|
|
|
import (
|
|
"context"
|
|
"git.aiterp.net/lucifer/new-server/app/config"
|
|
"git.aiterp.net/lucifer/new-server/app/services/publisher"
|
|
"git.aiterp.net/lucifer/new-server/models"
|
|
"github.com/gin-gonic/gin"
|
|
"log"
|
|
"strconv"
|
|
"time"
|
|
)
|
|
|
|
func fetchDevices(ctx context.Context, fetchStr string) ([]models.Device, error) {
|
|
kind, value := models.ParseFetchString(fetchStr)
|
|
return config.DeviceRepository().FetchByReference(ctx, kind, value)
|
|
}
|
|
|
|
func Devices(r gin.IRoutes) {
|
|
r.GET("", handler(func(c *gin.Context) (interface{}, error) {
|
|
devices, err := config.DeviceRepository().FetchByReference(ctxOf(c), models.RKAll, "")
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return withSceneState(devices), nil
|
|
}))
|
|
|
|
r.GET("/:fetch", handler(func(c *gin.Context) (interface{}, error) {
|
|
devices, err := fetchDevices(ctxOf(c), c.Param("fetch"))
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return withSceneState(devices), nil
|
|
}))
|
|
|
|
r.PUT("", handler(func(c *gin.Context) (interface{}, error) {
|
|
var body []struct {
|
|
Fetch string `json:"fetch"`
|
|
SetState models.NewDeviceState `json:"setState"`
|
|
}
|
|
err := parseBody(c, &body)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
set := make(map[int]bool)
|
|
changed := make([]models.Device, 0, 64)
|
|
for _, job := range body {
|
|
devices, err := fetchDevices(ctxOf(c), job.Fetch)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
if len(devices) == 0 {
|
|
return []models.Device{}, nil
|
|
}
|
|
|
|
for i := range devices {
|
|
if set[devices[i].ID] {
|
|
continue
|
|
}
|
|
|
|
err := devices[i].SetState(job.SetState)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
set[devices[i].ID] = true
|
|
changed = append(changed, devices[i])
|
|
}
|
|
}
|
|
|
|
config.PublishChannel <- changed
|
|
|
|
go func() {
|
|
for _, device := range changed {
|
|
err := config.DeviceRepository().Save(context.Background(), &device, models.SMState)
|
|
if err != nil {
|
|
log.Println("Failed to save device for state:", err)
|
|
continue
|
|
}
|
|
}
|
|
}()
|
|
|
|
return withSceneState(changed), nil
|
|
}))
|
|
|
|
r.PUT("/:fetch", handler(func(c *gin.Context) (interface{}, error) {
|
|
update := models.DeviceUpdate{}
|
|
err := parseBody(c, &update)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
devices, err := fetchDevices(ctxOf(c), c.Param("fetch"))
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
if len(devices) == 0 {
|
|
return []models.Device{}, nil
|
|
}
|
|
|
|
for i := range devices {
|
|
devices[i].ApplyUpdate(update)
|
|
|
|
err := config.DeviceRepository().Save(context.Background(), &devices[i], models.SMProperties)
|
|
if err != nil {
|
|
log.Println("Failed to save device for state:", err)
|
|
continue
|
|
}
|
|
}
|
|
|
|
return withSceneState(devices), nil
|
|
}))
|
|
|
|
r.PUT("/:fetch/state", handler(func(c *gin.Context) (interface{}, error) {
|
|
state := models.NewDeviceState{}
|
|
err := parseBody(c, &state)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
devices, err := fetchDevices(ctxOf(c), c.Param("fetch"))
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
if len(devices) == 0 {
|
|
return []models.Device{}, nil
|
|
}
|
|
|
|
for i := range devices {
|
|
err := devices[i].SetState(state)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
}
|
|
|
|
config.PublishChannel <- devices
|
|
|
|
err = config.DeviceRepository().SaveMany(ctxOf(c), models.SMState, devices)
|
|
if err != nil {
|
|
log.Println("Failed to save devices states")
|
|
}
|
|
|
|
return withSceneState(devices), nil
|
|
}))
|
|
|
|
r.PUT("/:fetch/tags", handler(func(c *gin.Context) (interface{}, error) {
|
|
var body struct {
|
|
Add []string `json:"add"`
|
|
Remove []string `json:"remove"`
|
|
}
|
|
err := parseBody(c, &body)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
devices, err := fetchDevices(ctxOf(c), c.Param("fetch"))
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
if len(devices) == 0 {
|
|
return []models.Device{}, nil
|
|
}
|
|
|
|
for i := range devices {
|
|
device := &devices[i]
|
|
|
|
for _, tag := range body.Add {
|
|
found := false
|
|
for _, tag2 := range device.Tags {
|
|
if tag == tag2 {
|
|
found = true
|
|
break
|
|
}
|
|
}
|
|
|
|
if !found {
|
|
device.Tags = append(device.Tags, tag)
|
|
}
|
|
}
|
|
for _, tag := range body.Remove {
|
|
index := -1
|
|
for i, tag2 := range device.Tags {
|
|
if tag == tag2 {
|
|
index = i
|
|
}
|
|
}
|
|
if index == -1 {
|
|
continue
|
|
}
|
|
|
|
device.Tags = append(device.Tags[:index], device.Tags[index+1:]...)
|
|
}
|
|
|
|
err = config.DeviceRepository().Save(ctxOf(c), device, models.SMTags)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
}
|
|
|
|
return withSceneState(devices), nil
|
|
}))
|
|
|
|
r.PUT("/:fetch/scene", handler(func(c *gin.Context) (interface{}, error) {
|
|
var body models.DeviceSceneAssignment
|
|
err := parseBody(c, &body)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
devices, err := fetchDevices(ctxOf(c), c.Param("fetch"))
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
if len(devices) == 0 {
|
|
return []models.Device{}, nil
|
|
}
|
|
|
|
var scene *models.Scene
|
|
if body.SceneName != "" {
|
|
scene, err = config.SceneRepository().FindName(ctxOf(c), body.SceneName)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
if body.DurationMS < 0 {
|
|
body.DurationMS = 0
|
|
}
|
|
body.StartTime = time.Now()
|
|
body.SceneID = scene.ID
|
|
} else {
|
|
scene, err = config.SceneRepository().Find(ctxOf(c), body.SceneID)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
if body.DurationMS < 0 {
|
|
body.DurationMS = 0
|
|
}
|
|
body.StartTime = time.Now()
|
|
body.SceneName = scene.Name
|
|
}
|
|
|
|
pushMode := c.Query("push") == "true"
|
|
for i := range devices {
|
|
if pushMode {
|
|
devices[i].SceneAssignments = append(devices[i].SceneAssignments, body)
|
|
} else {
|
|
devices[i].SceneAssignments = []models.DeviceSceneAssignment{body}
|
|
}
|
|
}
|
|
config.PublishChannel <- devices
|
|
|
|
err = config.DeviceRepository().SaveMany(ctxOf(c), 0, devices)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return withSceneState(devices), nil
|
|
}))
|
|
|
|
r.DELETE("/:fetch/scene", handler(func(c *gin.Context) (interface{}, error) {
|
|
devices, err := fetchDevices(ctxOf(c), c.Param("fetch"))
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
if len(devices) == 0 {
|
|
return []models.Device{}, nil
|
|
}
|
|
|
|
for i := range devices {
|
|
devices[i].SceneAssignments = []models.DeviceSceneAssignment{}
|
|
}
|
|
config.PublishChannel <- devices
|
|
|
|
err = config.DeviceRepository().SaveMany(ctxOf(c), 0, devices)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return withSceneState(devices), nil
|
|
}))
|
|
|
|
r.DELETE("/:fetch", handler(func(c *gin.Context) (interface{}, error) {
|
|
id, err := strconv.Atoi(c.Param("fetch"))
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
device, err := config.DeviceRepository().Find(ctxOf(c), id)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
if c.Query("forgetmenot") != "true" {
|
|
bridge, err := config.BridgeRepository().Find(ctxOf(c), device.BridgeID)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
driver, err := config.DriverProvider().Provide(bridge.Driver)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
forgettableDriver, ok := driver.(models.ForgettableDriver)
|
|
if !ok {
|
|
return nil, models.ErrCannotForget
|
|
}
|
|
err = forgettableDriver.ForgetDevice(ctxOf(c), bridge, *device)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
}
|
|
|
|
err = config.DeviceRepository().Delete(ctxOf(c), device)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return device, nil
|
|
}))
|
|
}
|
|
|
|
func withSceneState(devices []models.Device) []models.Device {
|
|
res := make([]models.Device, 0, len(devices))
|
|
for _, device := range devices {
|
|
device.SceneState = publisher.Global().SceneState(device.ID)
|
|
res = append(res, device)
|
|
}
|
|
|
|
return res
|
|
}
|