feat: expand companion platform, device controls, and memoir workflows
ci / verify (push) Canceled after 0s

Add customer and operator login, BLE provisioning, device fleet controls, call memory, memoir generation, and refreshed family and maintenance portals. Include API contracts, migrations, firmware sources, tests, assets, and deployment documentation.
This commit is contained in:
Codex Local
2026-10-08 10:06:44 +08:00
parent 98fb2965d0
commit bf1233d5df
861 changed files with 94614 additions and 3209 deletions
+29 -10
View File
@@ -2,6 +2,7 @@ package onboarding
import (
"context"
"data-terminal-cloud/internal/devicefleet"
"encoding/json"
"errors"
"fmt"
@@ -27,7 +28,8 @@ type Error struct {
Code, Message, Recovery string
}
func (e *Error) Error() string { return e.Code }
func (e *Error) Error() string { return e.Code }
func (e *Error) HTTPStatus() int { return e.Status }
func fail(status int, code, message string) *Error {
return &Error{Status: status, Code: code, Message: message, Recovery: "NONE"}
}
@@ -40,22 +42,30 @@ type LegacyVerifier interface {
Verify(*http.Request) (deviceauth.Identity, error)
}
type Options struct {
PauseClaims bool
TrustedProxyCIDRs []string
Enabled bool
MasterKey, EntryURL, ProviderMasterKey string
AllowLocalHTTP bool
TokenVerifier identity.TokenVerifier
LegacyVerifier LegacyVerifier
Registrar hardwareprovision.Registrar
SaaSCaptchaDisabled bool
Captcha CaptchaProvider
PauseClaims bool
TrustedProxyCIDRs []string
Enabled bool
MasterKey, EntryURL, ProviderMasterKey string
AllowLocalHTTP bool
TokenVerifier identity.TokenVerifier
LegacyVerifier LegacyVerifier
Registrar hardwareprovision.Registrar
// Compatibility harness only; production defaults to server registration.
LegacyDeviceRegistration bool
ActivationEnabled bool
InstanceID, ProductKey, ProductSecret, BotID, Transport, MinimumSDKVersion string
SMS SMSProvider
WeChat WeChatProvider
WeChatAppID, WeChatAppSecret string
SMSLoginEnabled bool
SelfSignupTenantIDs []string
Logger *slog.Logger
}
type Service struct {
Fleet *devicefleet.Service
operatorLogin bool
pool *pgxpool.Pool
redis *redis.Client
crypt cryptor
@@ -81,6 +91,9 @@ func NewService(pool *pgxpool.Pool, redisClient *redis.Client, options Options)
if options.Logger == nil {
options.Logger = slog.Default()
}
if options.WeChat == nil && options.WeChatAppID != "" && options.WeChatAppSecret != "" {
options.WeChat = newWeChatClient(options.WeChatAppID, options.WeChatAppSecret)
}
if options.ActivationEnabled && (len(options.ProductSecret) < 16 || options.InstanceID == "" || options.ProductKey == "" || options.BotID == "") {
return nil, errors.New("onboarding activation requires configured provider")
}
@@ -97,6 +110,12 @@ func NewService(pool *pgxpool.Pool, redisClient *redis.Client, options Options)
if options.ActivationEnabled && providerCrypt.aead == nil {
return nil, errors.New("onboarding activation requires provider credential master key")
}
if options.ActivationEnabled && options.Registrar == nil && !options.LegacyDeviceRegistration {
options.Registrar, err = hardwareprovision.NewHTTPRegistrar("", options.ProductSecret, 20*time.Second)
if err != nil {
return nil, err
}
}
return &Service{pool: pool, redis: redisClient, crypt: crypt, providerCrypt: providerCrypt, options: options, now: time.Now, audit: audit.NewRepository(), outbox: outbox.NewStore(pool)}, nil
}
func (s *Service) Enabled() bool { return s != nil && s.options.Enabled }
@@ -153,7 +172,7 @@ func (s *Service) state(ctx context.Context, tx pgx.Tx, d Device) (State, error)
}
st := State{DeviceID: d.FactoryID, PlatformDeviceID: d.PublicID, Revision: d.Revision, Generation: d.Generation, BindingState: "UNBOUND", ActivationState: "NOT_STARTED", NextAction: "WAIT_BIND"}
var bound, pending bool
err = tx.QueryRow(ctx, `SELECT EXISTS(SELECT 1 FROM device_binding WHERE device_id=$1::uuid AND unbound_at IS NULL),EXISTS(SELECT 1 FROM device_binding_claim WHERE device_id=$1::uuid AND status='PENDING' AND expires_at>now())`, d.ID).Scan(&bound, &pending)
err = tx.QueryRow(ctx, `SELECT (EXISTS(SELECT 1 FROM device_binding WHERE device_id=$1::uuid AND unbound_at IS NULL) OR EXISTS(SELECT 1 FROM device_account_binding WHERE device_id=$1::uuid AND released_at IS NULL)),EXISTS(SELECT 1 FROM device_binding_claim WHERE device_id=$1::uuid AND status='PENDING' AND expires_at>now())`, d.ID).Scan(&bound, &pending)
if err != nil {
return st, err
}