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/internal/lerrors" "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, lerrors.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 }