127 lines
3.9 KiB
Go
127 lines
3.9 KiB
Go
package device
|
|
|
|
import (
|
|
"context"
|
|
"crypto/rand"
|
|
"encoding/hex"
|
|
"fmt"
|
|
"strings"
|
|
"time"
|
|
|
|
"yovision.local/sense/app/sense/identity"
|
|
)
|
|
|
|
type Service struct {
|
|
store Store
|
|
vault *CredentialVault
|
|
now func() time.Time
|
|
}
|
|
|
|
func NewService(store Store, vault *CredentialVault) *Service {
|
|
return &Service{store: store, vault: vault, now: time.Now}
|
|
}
|
|
|
|
func (s *Service) Create(ctx context.Context, actor identity.Principal, name, location, modality string, capabilities []string) (Device, error) {
|
|
name = strings.TrimSpace(name)
|
|
if name == "" {
|
|
return Device{}, fmt.Errorf("设备名称不能为空")
|
|
}
|
|
if modality == "" {
|
|
modality = ModalityVideo
|
|
}
|
|
adapter := AdapterNotReady
|
|
if modality == ModalityVideo {
|
|
adapter = AdapterReady
|
|
}
|
|
now := s.now().UTC()
|
|
item := Device{ID: newID(), Name: name, Location: strings.TrimSpace(location), Modality: modality, Capabilities: capabilities, Status: StatusPending, AdapterStatus: adapter, Version: 1, CreatedAt: now, UpdatedAt: now}
|
|
if err := s.store.Create(ctx, item); err != nil {
|
|
return Device{}, err
|
|
}
|
|
identity.RecordAudit(ctx, actor.UserID, "device.create", item.ID, "success", map[string]any{"modality": modality})
|
|
return item, nil
|
|
}
|
|
func (s *Service) Get(ctx context.Context, id string) (Device, error) {
|
|
item, err := s.store.Get(ctx, id)
|
|
if err == nil {
|
|
item.CredentialConfigured = len(item.CredentialCiphertext) > 0
|
|
item.CredentialCiphertext = nil
|
|
}
|
|
return item, err
|
|
}
|
|
func (s *Service) List(ctx context.Context, filter ListFilter) (Page, error) {
|
|
page, err := s.store.List(ctx, filter)
|
|
for i := range page.Items {
|
|
page.Items[i].CredentialCiphertext = nil
|
|
}
|
|
return page, err
|
|
}
|
|
func (s *Service) Update(ctx context.Context, actor identity.Principal, id, name, location string, capabilities []string, expected int64) (Device, error) {
|
|
item, err := s.store.Get(ctx, id)
|
|
if err != nil {
|
|
return Device{}, err
|
|
}
|
|
item.Name = strings.TrimSpace(name)
|
|
item.Location = strings.TrimSpace(location)
|
|
item.Capabilities = capabilities
|
|
item.Version++
|
|
item.UpdatedAt = s.now().UTC()
|
|
if item.Name == "" {
|
|
return Device{}, fmt.Errorf("设备名称不能为空")
|
|
}
|
|
if err := s.store.Update(ctx, item, expected); err != nil {
|
|
return Device{}, err
|
|
}
|
|
identity.RecordAudit(ctx, actor.UserID, "device.update", id, "success", map[string]any{"version": item.Version})
|
|
item.CredentialConfigured = len(item.CredentialCiphertext) > 0
|
|
item.CredentialCiphertext = nil
|
|
return item, nil
|
|
}
|
|
func (s *Service) SetCredential(ctx context.Context, actor identity.Principal, id, username, password string) (Device, error) {
|
|
if strings.TrimSpace(username) == "" || password == "" {
|
|
return Device{}, fmt.Errorf("用户名和密码不能为空")
|
|
}
|
|
item, err := s.store.Get(ctx, id)
|
|
if err != nil {
|
|
return Device{}, err
|
|
}
|
|
ciphertext, err := s.vault.Encrypt(username, password)
|
|
if err != nil {
|
|
return Device{}, err
|
|
}
|
|
expected := item.Version
|
|
item.CredentialCiphertext = ciphertext
|
|
item.CredentialConfigured = true
|
|
item.Version++
|
|
item.UpdatedAt = s.now().UTC()
|
|
if err := s.store.Update(ctx, item, expected); err != nil {
|
|
return Device{}, err
|
|
}
|
|
identity.RecordAudit(ctx, actor.UserID, "device.credential.update", id, "success", map[string]any{"configured": true})
|
|
item.CredentialCiphertext = nil
|
|
return item, nil
|
|
}
|
|
func (s *Service) Disable(ctx context.Context, actor identity.Principal, id string, expected int64) (Device, error) {
|
|
item, err := s.store.Get(ctx, id)
|
|
if err != nil {
|
|
return Device{}, err
|
|
}
|
|
item.Status = StatusDisabled
|
|
item.Version++
|
|
item.UpdatedAt = s.now().UTC()
|
|
if err := s.store.Update(ctx, item, expected); err != nil {
|
|
return Device{}, err
|
|
}
|
|
identity.RecordAudit(ctx, actor.UserID, "device.disable", id, "success", nil)
|
|
item.CredentialConfigured = len(item.CredentialCiphertext) > 0
|
|
item.CredentialCiphertext = nil
|
|
return item, nil
|
|
}
|
|
func newID() string {
|
|
b := make([]byte, 16)
|
|
if _, err := rand.Read(b); err != nil {
|
|
panic(err)
|
|
}
|
|
return "dev_" + hex.EncodeToString(b)
|
|
}
|