package store import ( "context" "crypto/sha256" "encoding/base64" "errors" "strconv" "time" "yovision/sense/internal/device" ) var ( ErrETagMismatch = errors.New("device ETag mismatch") ErrIdempotencyConflict = errors.New("idempotency key body conflict") ErrDuplicateSerialNumber = errors.New("duplicate device serial number") ) type ControlProjectionVersions struct { QuotaSourceVersion *int64 `json:"quota_source_version"` AreaPolicySourceVersion *int64 `json:"area_policy_source_version"` SyncedAt *time.Time `json:"synced_at"` } // ControlDevice is deliberately safe to serialize. It contains configured // booleans, never endpoint, credential or profile-token values. type ControlDevice struct { ID string `json:"id"` TenantID string `json:"tenant_id"` SiteID string `json:"site_id"` SerialNumber string `json:"serial_number"` Name string `json:"name"` Modality device.Modality `json:"modality"` Capabilities []device.Capability `json:"capabilities"` AreaID string `json:"area_id"` DesiredState device.DesiredState `json:"desired_state"` ActualState device.ActualState `json:"actual_state"` AdapterStatus string `json:"adapter_status"` EndpointConfigured bool `json:"endpoint_configured"` CredentialConfigured bool `json:"credential_configured"` Generation int64 `json:"generation"` ObservedGeneration int64 `json:"observed_generation"` Converged bool `json:"converged"` FailureCount int `json:"failure_count"` NextAttemptAt *time.Time `json:"next_attempt_at,omitempty"` LastErrorCode *string `json:"last_error_code,omitempty"` ProjectionVersions ControlProjectionVersions `json:"projection_versions"` CreatedAt time.Time `json:"created_at"` UpdatedAt time.Time `json:"updated_at"` ResourceVersion int64 `json:"-"` } type ControlSiteQuota struct { Status string UsedVideoChannels int MaxVideoChannels *int AvailableVideoChannels *int OverLimit bool SourceVersion *int64 SyncedAt *time.Time } type ControlListFilter struct { Limit int AfterCreated *time.Time AfterDeviceID string Modality *device.Modality Capability *device.Capability DesiredState *device.DesiredState ActualState *device.ActualState } type ControlDevicePage struct { Items []ControlDevice HasMore bool Quota ControlSiteQuota } type IdempotencyScope struct { PrincipalID string TenantID string SiteID string Operation string Key string RequestHash [sha256.Size]byte TraceID string } type ControlCreateRequest struct { Scope IdempotencyScope Device device.Device } type ControlCreateResult struct { Device ControlDevice AcceptedAt time.Time TraceID string ETag string Location string Replay bool } type ControlPatch struct { Name *string AreaID *string EndpointRef *string CredentialRef *string ProfileToken *string } type ControlMutationResult struct { Device ControlDevice AcceptedAt time.Time TraceID string ETag string } type ControlBatchItem struct { DeviceID string ETag string DesiredState device.DesiredState } type ControlBatchRequest struct { Scope IdempotencyScope Reason string Items []ControlBatchItem } type ControlBatchItemResult struct { DeviceID string `json:"device_id"` Status string `json:"status"` ErrorCode *string `json:"error_code"` Message *string `json:"message"` Generation *int64 `json:"generation"` } type ControlBatchOperation struct { ID string `json:"id"` TenantID string `json:"-"` SiteID string `json:"-"` Status string `json:"status"` SubmittedAt time.Time `json:"submitted_at"` CompletedAt *time.Time `json:"completed_at"` Results []ControlBatchItemResult `json:"results"` TraceID string `json:"trace_id"` Replay bool `json:"-"` } type ControlRepository interface { ListControlDevices(context.Context, string, string, ControlListFilter) (ControlDevicePage, error) CreateControlDevice(context.Context, ControlCreateRequest) (ControlCreateResult, error) GetControlDevice(context.Context, string, string, string) (ControlDevice, error) PatchControlDevice(context.Context, string, string, string, string, ControlPatch) (ControlMutationResult, error) SetControlDesiredState(context.Context, string, string, string, string, device.DesiredState) (ControlMutationResult, error) BatchSetControlDesiredState(context.Context, ControlBatchRequest) (ControlBatchOperation, error) GetControlOperation(context.Context, string, string) (ControlBatchOperation, error) } func DeviceETag(deviceID string, resourceVersion int64) string { digest := sha256.Sum256([]byte(deviceID + "\x00" + strconv.FormatInt(resourceVersion, 10))) return `"` + base64.RawURLEncoding.EncodeToString(digest[:18]) + `"` }