The OAuth flow opens a popup window that redirects to /admin/gdrive-oauth-callback,
but this route and page were missing. Add the callback view that extracts the
authorization code from the URL and sends it back to the opener window via
postMessage, completing the OAuth authorization flow.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Define SoraObjectStorage interface abstracting S3 and GDrive backends
- Implement SoraGDriveStorage with OAuth2 and Service Account auth
- Add SoraStorageRouter to route requests based on active profile provider
- Add GDrive OAuth handler for authorization flow (start + callback)
- Extend profile model with provider, auth_type, and GDrive-specific fields
- Update frontend UI with provider selection, dynamic form fields, and i18n
- Migrate API paths from /sora-s3 to /sora-storage (old paths preserved)
- All existing S3 functionality remains backward compatible
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
CTE snapshot in READ COMMITTED can cause concurrent requests during
period reset to lose one increment. Inline CASE in single UPDATE
ensures PostgreSQL re-evaluates all expressions with latest row data.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Use NOW() AT TIME ZONE 'UTC' to ensure consistent timestamps
regardless of database timezone setting
- Clear quota_period_start when manually resetting quota so a
fresh period starts from next usage
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Support daily/weekly automatic quota reset using a rolling period
anchored to first usage. Implements lazy reset via atomic CTE SQL
in IncrementQuotaUsed - no cron job needed.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Remove frontend badge/tooltip and backend counting/query infrastructure
for client affinity. Core client affinity scheduling logic is preserved.
Removed:
- GatewayCache injection in AccountHandler
- GetAccountAffinityCount/Batch/Clients methods and interface
- AffinityClientInfo struct and ClientAffinityTTL function
- /affinity-clients API endpoint and route
- Reverse index (account_affinity:*) Lua scripts
- Frontend blue badge, tooltip, and lazy-load logic
- affinity_client_count field from types and response
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
PR #723 changed all OAuth 401 to use SetTempUnschedulable instead of SetError,
and PR #761 added 401 escalation logic in tryTempUnschedulable. Both caused
non-OpenAI accounts to frequently become temporarily unschedulable.
- HandleUpstreamError case 401: limit temp-unschedulable to OpenAI OAuth only,
other platforms (Anthropic/Gemini/Antigravity) revert to original SetError
- tryTempUnschedulable: skip 401 escalation for Antigravity, let its
applyErrorPolicy temp_unschedulable_rules handle 401 naturally
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
The reverse index account_affinity:{accountID} was using the same 1h
EXPIRE as the forward index. But the forward key EXPIRE refreshes on
every client request, while the reverse key only refreshes when a
request is routed to that specific account. This caused reverse keys
to expire prematurely.
Fix: set reverse key EXPIRE to 24h as safety net, rely on
ZREMRANGEBYSCORE for member-level expiration. Also clean up empty
keys after member cleanup.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Backend: expand IsClientAffinityEnabled() to support all Anthropic
accounts (OAuth/SetupToken/APIKey), not just OAuth/SetupToken
- Frontend: show client affinity toggle for all Anthropic accounts
- Frontend: fix save logic to properly chain extra field updates
- Antigravity accounts explicitly excluded from client affinity
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Backend: expand IsClientAffinityEnabled() to support all Anthropic
accounts (OAuth/SetupToken/APIKey), not just OAuth/SetupToken
- Frontend: show client affinity toggle for all Anthropic accounts
- Frontend: fix save logic to properly chain extra field updates
- Antigravity accounts explicitly excluded from client affinity
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Backend: remove count > 0 filter in GetAccountAffinityCountBatch so
accounts with 0 bound clients are included in the response
- Frontend: change badge condition from affinityClientCount > 0 to
account.client_affinity_enabled so the badge always shows for accounts
with affinity enabled
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Add reverse Redis index (account_affinity:{accountID}) to track which
clients are bound to each account via affinity scheduling. Display a
blue badge with client count in the admin account list, with a hover
tooltip showing client details (groupID:clientID + TTL).
Backend: reverse index in Lua scripts, batch query methods, new
GET /accounts/:id/affinity-clients endpoint.
Frontend: blue badge + lazy-loaded tooltip in AccountStatusIndicator.
- Fix bulk edit: send 0 instead of null/NaN to clear load_factor
- Fix edit modal: explicit NaN check instead of implicit falsy
- Fix create modal: use ?? instead of || for load_factor
- Add load_factor upper limit validation (max 10000)
- Add //go:build unit tag and self-contained intPtrHelper in test
- Add design intent comments on WaitPlan.MaxConcurrency