feat: merge opus comfyui and login updates

This commit is contained in:
boos
2026-05-21 16:32:04 +02:00
parent c16479da7c
commit 9c84759498
105 changed files with 20160 additions and 1034 deletions
@@ -0,0 +1,88 @@
## ADDED Requirements
### Requirement: `GET /api/models/token-candidates` is registered
The admin router SHALL register `GET /api/models/token-candidates`
behind the standard admin auth layer. The handler SHALL return a JSON
object with exactly four top-level keys: `models`,
`modelsWithoutToken`, `modelsMissingTokenGroups`, `endpointTypesByModel`.
#### Scenario: Endpoint returns 200 with the four-key shape
- **WHEN** an authenticated admin sends `GET /api/models/token-candidates`
- **THEN** the response is HTTP 200 with a JSON body containing keys
`models`, `modelsWithoutToken`, `modelsMissingTokenGroups`, and
`endpointTypesByModel` — no extra top-level keys, no missing keys
#### Scenario: Unauthenticated request is rejected
- **WHEN** a request without a valid admin token reaches the endpoint
- **THEN** the admin auth layer returns 401 before the handler runs
### Requirement: `models` lists every (model, token) candidate
The `models` field SHALL be an object whose keys are model names and
whose values are arrays of candidate descriptors. A `(model, token)`
combination SHALL appear in the `models` array for that model exactly
when ALL of the following hold:
- a row in `model_availability` reports `available = 1` for the model
on the token's account
- the token's `enabled` is truthy (NULL treated as enabled)
- the token's `value_status` is `'ready'` (NULL treated as ready)
- the account's `status = 'active'`
- the site's `status` is `'active'` (NULL treated as active)
Each candidate descriptor SHALL contain: `accountId`, `tokenId`,
`tokenName`, `isDefault` (boolean), `username`, `siteId`, `siteName`.
Within a single model's array, descriptors SHALL be deduplicated by
`tokenId`.
#### Scenario: Active token with available model appears
- **WHEN** account 5 (active, site active) has token 11 (enabled,
ready) and `model_availability(account=5, model='gpt-4')` is
available
- **THEN** `models['gpt-4']` includes a descriptor with
`accountId = 5, tokenId = 11`
#### Scenario: Disabled token is excluded
- **WHEN** account 5 has token 11 with `enabled = 0`
- **THEN** no descriptor with `tokenId = 11` appears in `models` for
any model
#### Scenario: Duplicate tokens in source data deduped
- **WHEN** the underlying join produces two rows for the same
`(model, tokenId)` pair (e.g., joins fan out)
- **THEN** the descriptor appears exactly once in
`models[model_name]`
### Requirement: `modelsWithoutToken` surfaces accounts missing tokens
The `modelsWithoutToken` field SHALL be an object keyed by model name.
For each `(account, model)` combination where the model is available
on the active account but no qualifying token (per the `models`
requirement) exists for that account, the handler SHALL include a
descriptor `{ accountId, username, siteId, siteName }` under that
model name. Descriptors SHALL be deduplicated by `accountId` within
each model's array.
#### Scenario: Account with available model but no enabled tokens
- **WHEN** account 8 reports `gpt-4` available but has zero rows in
`account_tokens` (or all rows are disabled)
- **THEN** `modelsWithoutToken['gpt-4']` includes a descriptor with
`accountId = 8`, and that account does NOT appear in `models['gpt-4']`
#### Scenario: Account with token does not appear here
- **WHEN** account 5 reports `gpt-4` available and has a usable token
- **THEN** account 5 does NOT appear in `modelsWithoutToken['gpt-4']`
### Requirement: `modelsMissingTokenGroups` and `endpointTypesByModel` are empty
The handler SHALL return `modelsMissingTokenGroups` and
`endpointTypesByModel` as empty JSON objects (`{}`). The frontend
contract permits empty objects and these fields SHALL NOT be
populated until a marketplace-cache or pricing-catalog source is
added to the backend.
#### Scenario: Both fields are empty objects
- **WHEN** the handler returns successfully under any state of the
database
- **THEN** `response.modelsMissingTokenGroups === {}` and
`response.endpointTypesByModel === {}`