Files
maziggy 00251fe808 feat(orca-cloud): pair via RFC 8628 device flow, replacing the paste-based sign-in
OrcaSlicer shipped a first-class external-app pairing API (OAuth 2.0 Device
Authorization Grant), so the Supabase-PKCE copy-paste flow is replaced end to
end. Connecting is now: click Connect, approve a short code on the Orca Cloud
settings page, done — no redirect, no callback paste, no client secret, works
from a LAN IP / localhost / behind a proxy.

Backend: services/orca_cloud.py rewritten to device-code request + poll (the
four RFC outcomes) + refresh_token grant + introspection + external sync pull;
routes expose /device/start and /device/poll (device_code kept server-side in
the reused orca_cloud_pending_* columns, no migration). Requests sync:read
(read-only feature). Prod endpoint by default, ORCA_CLOUD_API_BASE overrides
to staging. Wired the shared httpx client (fixes a per-request socket leak).

Frontend: device-code connect UI + api client methods; all 11 locales updated.
2026-07-17 08:16:35 +02:00

87 lines
3.0 KiB
Python

"""Schemas for Orca Cloud device-pairing auth + profile sync endpoints."""
from typing import Literal
from pydantic import BaseModel, Field
class OrcaDeviceStartResponse(BaseModel):
"""Returned by ``POST /orca-cloud/device/start``. The frontend shows
``user_code`` and a clickable/QR ``verification_uri_complete``; the user
approves in their Orca Cloud settings. The ``device_code`` itself is a
secret and stays server-side — it is deliberately NOT in this response."""
user_code: str = Field(..., description="Short code the user confirms on the approval page")
verification_uri: str = Field(..., description="Approval page URL")
verification_uri_complete: str = Field(..., description="Approval page URL with the code pre-filled")
interval: int = Field(..., description="Seconds the frontend should wait between poll calls")
expires_in: int = Field(..., description="Seconds until this pairing attempt expires")
# Poll outcomes surfaced to the frontend. ``authorization_pending`` /
# ``slow_down`` mean keep polling; ``access_denied`` / ``expired_token`` are
# terminal (restart the flow); ``complete`` means paired.
OrcaDevicePollStatus = Literal[
"authorization_pending",
"slow_down",
"access_denied",
"expired_token",
"complete",
]
class OrcaDevicePollResponse(BaseModel):
"""Returned by ``POST /orca-cloud/device/poll`` — one poll attempt."""
status: OrcaDevicePollStatus
connected: bool = False
email: str | None = None
user_id: str | None = None
class OrcaAuthStatusResponse(BaseModel):
"""Connection status for the Orca Cloud tab."""
connected: bool
email: str | None = None
user_id: str | None = None
class OrcaProfileMeta(BaseModel):
"""A single profile, shaped to match the Bambu Cloud ``SlicerSetting``
schema so the frontend can render Orca profiles with the existing
Bambu Cloud visual components (cards, filter bar, grouping). Per-source
differences (Orca's IDs are UUIDs not Bambu's ``PFU...`` prefix; Orca
types are ``machine`` / ``process`` / ``filament`` whereas Bambu uses
``printer`` / ``process`` / ``filament``) are normalized at the route
layer before the response leaves the backend."""
setting_id: str
name: str
type: str
version: str | None = None
user_id: str | None = None
updated_time: str | None = None
is_custom: bool = True
class OrcaProfileListResponse(BaseModel):
"""Groups Orca profiles by type, matching ``SlicerSettingsResponse``."""
filament: list[OrcaProfileMeta] = []
printer: list[OrcaProfileMeta] = []
process: list[OrcaProfileMeta] = []
class OrcaProfileDetail(BaseModel):
"""Single profile's full content, shaped to match ``SlicerSettingDetail``
so the frontend's detail modal can render it without translation."""
setting_id: str
name: str
type: str
version: str | None = None
base_id: str | None = None
update_time: str | None = None
setting: dict