# Quota cache sync (admin HTTP API)

Internal admin endpoints refresh **quota-related Redis caches** from Cloud SQL after you change rows in Postgres. They mirror `internal/quota` sync helpers (`SyncOrgInfoAfterChange`, `SyncUserInfoAfterChange`, `SyncPriceAfterChange`).

## Authentication

Same contract as `POST /admin/credit/topup`:

- Header **`X-Admin-Key`**: must match environment variable **`ADMIN_API_KEY`**.
- If **`ADMIN_API_KEY`** is unset, every admin route (including these) returns **404** with `{"error":"admin endpoints disabled"}`.
- Wrong key → **401** `{"error":"unauthorized"}`.

Use **HTTPS** in production; never send the admin key over untrusted networks.

## Endpoints

All are **`POST`**, **`Content-Type: application/json`**, **30s** server-side timeout per request.

### 1. Refresh organization quota cache

**`POST /admin/quota/cache/sync/organization`**

Body:

```json
{
  "organization_id": "org-uuid-or-id"
}
```

Success **200**:

```json
{
  "ok": true,
  "organization_id": "org-uuid-or-id"
}
```

### 2. Refresh user quota cache

**`POST /admin/quota/cache/sync/user`**

Body:

```json
{
  "user_id": "user-id"
}
```

Success **200**:

```json
{
  "ok": true,
  "user_id": "user-id"
}
```

Implementation detail: this reloads the user’s quota hash from Postgres (`max_daily_user_limit`, **`enable_limit`** as `"true"` / `"false"`).

- **`enable_limit=false`**: only updates the user Hash; **does not** create or update daily bucket keys. Existing daily keys are left in place (not deleted).
- **`enable_limit=true`**: requires **`max_daily_user_limit` > 0**; otherwise **400** `enable_limit=true requires max_daily_user_limit > 0` and Redis is not partially updated. On success, **`SCAN`s Redis for `daily:user:{user_id}:*`**, updates each existing daily bucket to the current limit with per-date TTL, and **sets the current and next calendar day** buckets (same dates as `price-refresh` / API daily quota; avoids `enable_limit` false→true with only today’s bucket and tomorrow fail-open).
- If the user row no longer exists, deletes the user hash **and** all matching daily bucket keys.

See also: [每日限额支持一键管理](../../.cursor/每日限额支持一键管理.md).

**`POST /admin/quota/cache/sync/model-price`**

Body:

```json
{
  "model_id": 42
}
```

`model_id` must be a **positive** integer (matches `models.model_id`).

Success **200**:

```json
{
  "ok": true,
  "model_id": 42
}
```

## Errors

| HTTP | Typical cause |
|------|----------------|
| 400 | Missing `organization_id` / `user_id` / invalid `model_id`, or invalid JSON |
| 404 | Admin disabled (`ADMIN_API_KEY` empty) |
| 401 | Missing or wrong `X-Admin-Key` |
| 405 | Non-POST |
| 503 | Quota service not wired (should not happen in normal `cmd/api` startup) |
| 500 | Sync failed (DB/Redis error); check server logs and `quota_app_event` if reporter is enabled |

## Related environment variables

| Variable | Role |
|----------|------|
| `ADMIN_API_KEY` | Enables admin routes and auth |
| `DATABASE_URL` / Cloud SQL | Source for sync reads |
| `REDIS_ADDR` | Target for cache writes |
| `QUOTA_APP_ERRORS_CONFIG_FILE` | Optional overlay for quota numeric error registry (see `internal/config`) |
| `PUBSUB_QUOTA_FLOW_TOPIC` + `GCP_PROJECT_ID` | Optional `FlowPublisher` for quota flow messages (business publish is Day2+) |

## Example (curl)

```bash
export ADMIN_KEY='your-admin-api-key'
export BASE='https://your-api-host'

curl -sS -X POST "$BASE/admin/quota/cache/sync/organization" \
  -H "X-Admin-Key: $ADMIN_KEY" \
  -H "Content-Type: application/json" \
  -d '{"organization_id":"org-123"}'
```
