package onboarding import ( "context" "data-terminal-cloud/internal/devicebinding" "data-terminal-cloud/internal/devicereset" "data-terminal-cloud/internal/idempotency" "errors" ) // This is a DEVREQ1 adapter for the existing reset transaction, not an owner // authorization shortcut. The account must release ownership before reset. func (s *Service) DeviceResetState(ctx context.Context, d Device) (map[string]any, error) { if !deviceAllowed(d) || d.Mode != "physical_confirm" { return nil, errAccess } if err := devicebinding.RequireUnboundForReset(ctx, s.pool, d.ID); err != nil { return nil, resetError(err) } var counter int64 if err := s.pool.QueryRow(ctx, `SELECT factory_reset_counter FROM device WHERE id=$1 AND tenant_id=$2`, d.ID, d.TenantID).Scan(&counter); err != nil { return nil, err } return map[string]any{"reset_counter": counter, "binding_generation": d.Generation}, nil } func (s *Service) DeviceFactoryReset(ctx context.Context, d Device, requestID string, raw []byte, in devicereset.Input) (devicereset.Response, error) { if !deviceAllowed(d) || d.Mode != "physical_confirm" { return devicereset.Response{}, errAccess } service, err := devicereset.NewService(s.pool, idempotency.NewExecutor(s.pool), s.audit) if err != nil { return devicereset.Response{}, err } // The reset ID lives in the signed and persisted body. HTTP requests may use // fresh nonce/request IDs while retrying an uncertain result after a reboot. response, _, err := service.Reset(ctx, controlIdentity(d), "factory-reset:"+in.ResetID, requestID, raw, in) return response, resetError(err) } func resetError(err error) error { switch { case err == nil: return nil case errors.Is(err, devicebinding.ErrConfirmationRequired): return fail(409, "DEVICE_RESET_AUTHORIZATION_REQUIRED", "请先由当前账号授权解绑,再恢复出厂") case errors.Is(err, devicereset.ErrCallInProgress): return fail(409, "DEVICE_RESET_CALL_IN_PROGRESS", "设备正在通话,请结束后再恢复出厂") case errors.Is(err, devicereset.ErrFirmwareInProgress): return fail(409, "DEVICE_RESET_FIRMWARE_IN_PROGRESS", "设备正在升级,暂时不能恢复出厂") case errors.Is(err, devicereset.ErrPendingTranscripts): return fail(409, "DEVICE_RESET_PENDING_TRANSCRIPTS", "设备仍有未上传的聊天记录") case errors.Is(err, devicereset.ErrResetCounterConflict): return fail(409, "DEVICE_RESET_COUNTER_CONFLICT", "设备重置计数器已变化") case errors.Is(err, devicereset.ErrStateConflict): return fail(409, "DEVICE_RESET_STATE_CONFLICT", "设备状态已变化,请重新确认") case errors.Is(err, devicereset.ErrInvalid): return fail(400, "INVALID_RESET_REQUEST", "设备重置参数不正确") case errors.Is(err, idempotency.ErrKeyConflict): return fail(409, "IDEMPOTENCY_KEY_CONFLICT", "同一重置请求不能修改内容") case errors.Is(err, idempotency.ErrInProgress): return fail(409, "IDEMPOTENCY_IN_PROGRESS", "设备重置请求仍在处理中") default: return err } }