118 lines
3.7 KiB
Go
118 lines
3.7 KiB
Go
package area
|
|
|
|
import (
|
|
"context"
|
|
"crypto/rand"
|
|
"encoding/hex"
|
|
"fmt"
|
|
"strings"
|
|
"time"
|
|
"yovision.local/sense/app/sense/admission"
|
|
"yovision.local/sense/app/sense/identity"
|
|
)
|
|
|
|
type Service struct {
|
|
store Store
|
|
profile func(string, string) (admission.Profile, error)
|
|
now func() time.Time
|
|
}
|
|
|
|
func NewService(store Store) *Service {
|
|
return &Service{store: store, profile: admission.VerifiedProfile, now: time.Now}
|
|
}
|
|
func (s *Service) Create(ctx context.Context, actor identity.Principal, deviceID, profileToken, name, kind, direction string, points []Point) (Area, error) {
|
|
profile, err := s.profile(deviceID, profileToken)
|
|
if err != nil {
|
|
return Area{}, err
|
|
}
|
|
if err := ValidateGeometry(kind, direction, points); err != nil {
|
|
return Area{}, err
|
|
}
|
|
name = strings.TrimSpace(name)
|
|
if name == "" {
|
|
return Area{}, fmt.Errorf("名称不能为空")
|
|
}
|
|
now := s.now().UTC()
|
|
item := Area{ID: newID(), DeviceID: deviceID, ProfileToken: profileToken, Width: profile.Width, Height: profile.Height, Name: name, Kind: kind, Direction: direction, Points: points, Enabled: true, Version: 1, CreatedAt: now, UpdatedAt: now}
|
|
if err := s.store.Save(ctx, item, 0); err != nil {
|
|
return Area{}, err
|
|
}
|
|
identity.RecordAudit(ctx, actor.UserID, "area.create", item.ID, "success", map[string]any{"version": 1, "kind": kind})
|
|
return item, nil
|
|
}
|
|
func (s *Service) Update(ctx context.Context, actor identity.Principal, id, name, direction string, points []Point, expected int64, recalibrate bool) (Area, error) {
|
|
current, err := s.store.Get(ctx, id)
|
|
if err != nil {
|
|
return Area{}, err
|
|
}
|
|
if current.Version != expected {
|
|
return Area{}, ErrConflict
|
|
}
|
|
profile, err := s.profile(current.DeviceID, current.ProfileToken)
|
|
if err != nil {
|
|
return Area{}, err
|
|
}
|
|
resolutionChanged := profile.Width != current.Width || profile.Height != current.Height
|
|
if resolutionChanged && !recalibrate {
|
|
current.RecalibrationRequired = true
|
|
current.Version++
|
|
current.UpdatedAt = s.now().UTC()
|
|
if err := s.store.Save(ctx, current, expected); err != nil {
|
|
return Area{}, err
|
|
}
|
|
return current, nil
|
|
}
|
|
if err := ValidateGeometry(current.Kind, direction, points); err != nil {
|
|
return Area{}, err
|
|
}
|
|
current.Name = strings.TrimSpace(name)
|
|
current.Direction = direction
|
|
current.Points = points
|
|
current.Width = profile.Width
|
|
current.Height = profile.Height
|
|
current.RecalibrationRequired = false
|
|
current.Version++
|
|
current.UpdatedAt = s.now().UTC()
|
|
if err := s.store.Save(ctx, current, expected); err != nil {
|
|
return Area{}, err
|
|
}
|
|
identity.RecordAudit(ctx, actor.UserID, "area.publish", id, "success", map[string]any{"version": current.Version, "recalibrated": recalibrate})
|
|
return current, nil
|
|
}
|
|
func (s *Service) Disable(ctx context.Context, actor identity.Principal, id string, expected int64) (Area, error) {
|
|
item, err := s.store.Get(ctx, id)
|
|
if err != nil {
|
|
return Area{}, err
|
|
}
|
|
if item.Version != expected {
|
|
return Area{}, ErrConflict
|
|
}
|
|
item.Enabled = false
|
|
item.Version++
|
|
item.UpdatedAt = s.now().UTC()
|
|
if err := s.store.Save(ctx, item, expected); err != nil {
|
|
return Area{}, err
|
|
}
|
|
identity.RecordAudit(ctx, actor.UserID, "area.disable", id, "success", map[string]any{"version": item.Version})
|
|
return item, nil
|
|
}
|
|
func (s *Service) List(ctx context.Context, deviceID string) ([]Area, error) {
|
|
items, err := s.store.List(ctx, deviceID)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
for i := range items {
|
|
if profile, profileErr := s.profile(items[i].DeviceID, items[i].ProfileToken); profileErr == nil && (profile.Width != items[i].Width || profile.Height != items[i].Height) {
|
|
items[i].RecalibrationRequired = true
|
|
}
|
|
}
|
|
return items, nil
|
|
}
|
|
func newID() string {
|
|
b := make([]byte, 16)
|
|
if _, err := rand.Read(b); err != nil {
|
|
panic(err)
|
|
}
|
|
return "area_" + hex.EncodeToString(b)
|
|
}
|