Files
Arkie-Library-Frontend/docs/backend-wallet-favorites-production-fixes.md
TerryM ec98ff5a03
Some checks failed
Deploy Staging (terry-wallet-login) / deploy (push) Failing after 28s
fix: align favorites page with post adapter
2026-06-04 17:46:09 +08:00

334 lines
8.9 KiB
Markdown

# Backend fixes required for Wallet Login + Favorites production readiness
Date: 2026-06-04
Environment tested: `https://arkie-library-stag.com/apnew/api`
## Summary
Frontend has been updated to the new backend contract:
- Wallet login: `POST /api/auth/wallet/login` with `{ address }`
- Wallet session check: `GET /api/auth/wallet/me` with `Authorization: Bearer <token>`
- Favorites list/status: `GET /api/favorites` and `GET /api/favorites?ids=...`
- Favorite mutation: `POST /api/posts/{id}/favorite` with `{ add: true|false }`
Staging confirms the new login endpoint works, but favorite mutation currently accepts an invalid Bearer token. This must be fixed before production trust.
---
## Priority 0 — Fix favorite mutation authentication
### Current staging behavior
The following request currently returns `200 OK` even with an invalid token:
```bash
curl -i -X POST \
"https://arkie-library-stag.com/apnew/api/posts/8f4a571c-3477-4b05-91be-d85907048de5/favorite" \
-H "Content-Type: application/json" \
-H "Authorization: Bearer invalid-token" \
--data '{"add":true}'
```
Observed response:
```http
HTTP 200
{"ok":true}
```
### Required behavior
`POST /api/posts/{id}/favorite` must require a valid wallet JWT.
Invalid, missing, expired, malformed, or unverifiable tokens must return:
```http
HTTP 401 Unauthorized
```
Recommended response body:
```json
{
"error": "unauthorized"
}
```
### Acceptance tests
#### Missing token
```bash
curl -i -X POST \
"https://arkie-library-stag.com/apnew/api/posts/{postId}/favorite" \
-H "Content-Type: application/json" \
--data '{"add":true}'
```
Expected:
```http
HTTP 401
```
#### Invalid token
```bash
curl -i -X POST \
"https://arkie-library-stag.com/apnew/api/posts/{postId}/favorite" \
-H "Content-Type: application/json" \
-H "Authorization: Bearer invalid-token" \
--data '{"add":true}'
```
Expected:
```http
HTTP 401
```
#### Valid token
```bash
TOKEN=$(curl -s -X POST \
"https://arkie-library-stag.com/apnew/api/auth/wallet/login" \
-H "Content-Type: application/json" \
--data '{"address":"0x0000000000000000000000000000000000000001"}' \
| jq -r .token)
curl -i -X POST \
"https://arkie-library-stag.com/apnew/api/posts/{postId}/favorite" \
-H "Content-Type: application/json" \
-H "Authorization: Bearer $TOKEN" \
--data '{"add":true}'
```
Expected:
```http
HTTP 200
```
Response should include at least:
```json
{
"ok": true,
"favorited": true
}
```
Then cancel:
```bash
curl -i -X POST \
"https://arkie-library-stag.com/apnew/api/posts/{postId}/favorite" \
-H "Content-Type: application/json" \
-H "Authorization: Bearer $TOKEN" \
--data '{"add":false}'
```
Expected:
```json
{
"ok": true,
"favorited": false
}
```
---
## Priority 1 — Confirm wallet login security model
### Current contract
```http
POST /api/auth/wallet/login
Content-Type: application/json
{
"address": "0x..."
}
```
Response:
```json
{
"token": "<jwt>",
"wallet": "0x..."
}
```
This is what the frontend now uses.
### Production risk
This flow does not prove wallet ownership. Any client can submit any wallet address and receive a token for that address.
If wallet identity is only used for low-risk favorites, this may be acceptable as an MVP. If wallet identity will be used for user identity, permissions, membership, rewards, asset ownership, admin behavior, or anything security-sensitive, backend should require signature verification.
### Recommended secure production flow
If stronger security is required, backend should use nonce + signature:
1. `POST /api/auth/wallet/nonce` with `{ address }`
2. Backend returns a one-time message / nonce.
3. Frontend asks wallet to sign the message.
4. `POST /api/auth/wallet/verify` with `{ address, message, signature }`
5. Backend verifies recovered address equals requested address.
6. Backend issues JWT.
If backend decides to keep the simplified `{ address }` login, please explicitly confirm that this is an accepted production risk.
---
## Priority 2 — Normalize favorites response contract
Frontend currently supports the staging response shape, but the response must be made explicit and self-sufficient. The frontend renders favorites as plain strings and does not perform per-resource translation, slug-to-name lookup, category fetching, or localization fallback.
### `lang` semantics
`?lang=<ui-lang>` on `GET /api/favorites` is a **display resolution hint**, not a filter. It must NOT filter favorites by post language. A user who favorited Chinese and English posts must see both regardless of `lang`. `lang` only tells the backend which language to resolve display strings into.
**Current staging behavior is wrong**: sending `?lang=en` on staging returns zero items for users whose favorites are Chinese posts, and vice versa. Because of this, the frontend currently does NOT send `lang` on `GET /api/favorites`. Once the backend treats `lang` as a resolve hint instead of a filter, the frontend will send `lang` again so resolved strings come back in the user's UI language.
### Favorites list
```http
GET /api/favorites?lang=&limit=&page=&sort=&category=&q=
Authorization: Bearer <token>
```
Required production response:
```json
{
"items": [
{
"id": "...",
"title": "...",
"description": "...",
"type": "...",
"categoryId": 11,
"categorySlug": "official-assets",
"categoryName": "...",
"language": "...",
"sourceLanguage": "...",
"coverImage": "...",
"updatedAt": "...",
"publishedAt": "...",
"favoriteCount": 0,
"availability": "available"
}
],
"page": 1,
"limit": 24,
"total": 0
}
```
Fields that must be present and pre-resolved by the backend when `lang` is supplied:
- `title` — already in `lang`. If a translation does not exist, fall back to the post's source language.
- `description` — same rule as `title`.
- `categoryName` — localized category name for `lang`. Frontend must not look up categories by slug.
- `type` — a string the frontend can display directly. If you need both a raw type code and a label, add `typeLabel` and use that for display.
- `language` — a human-readable label for the post's source language, in `lang`. e.g. for `lang=zh-CN` a Chinese post returns `language: "中文"`. If you prefer to keep `language` as a code, add `languageLabel` and use it for display.
- `coverImage` — a usable image URL. The frontend will not fall back to attachment arrays.
- `updatedAt`, `publishedAt` — ISO timestamps.
- `favoriteCount` — optional but recommended.
- `availability``"available" | "unavailable"`.
`page`, `limit`, and `total` are needed for correct pagination.
The frontend must never need to: load `/api/categories`, parse `localizations` maps, walk `attachments`, or translate `type` / `language` codes for this page.
### Favorite status by ids
```http
GET /api/favorites?ids=id1,id2,id3
Authorization: Bearer <token>
```
Current staging response observed:
```json
{
"items": []
}
```
This works, but for frontend performance and clarity, recommended response is:
```json
{
"ids": ["id1", "id3"]
}
```
Meaning: only IDs that are already favorited by the current wallet user.
Frontend currently accepts both:
- `{ ids: string[] }`
- `{ items: Resource[] }`
But backend should document and standardize one shape.
---
## Priority 3 — Required status codes
Please standardize these responses:
| Case | Expected status |
| --- | --- |
| Missing Bearer token on protected endpoint | `401` |
| Invalid/expired Bearer token | `401` |
| Valid token but post ID does not exist | `404` |
| Invalid JSON body | `400` |
| Invalid `add` value | `400` |
| Successful favorite add/remove | `200` |
Protected endpoints:
- `GET /api/auth/wallet/me`
- `GET /api/favorites`
- `GET /api/favorites?ids=...`
- `POST /api/posts/{id}/favorite`
---
## Frontend compatibility notes
The frontend currently calls these staging paths through the same-origin prefix:
```txt
/apnew/api/auth/wallet/login
/apnew/api/auth/wallet/me
/apnew/api/favorites
/apnew/api/favorites?ids=...
/apnew/api/posts/{id}/favorite
```
In frontend source this is written as `/api/...`; staging build uses `VITE_API_PREFIX=/apnew`.
Please keep backend routes under `/api/...` behind the proxy.
---
## Final production checklist
Backend should confirm all of the following before production release:
- [ ] `POST /api/posts/{id}/favorite` rejects missing token with `401`.
- [ ] `POST /api/posts/{id}/favorite` rejects invalid token with `401`.
- [ ] `POST /api/posts/{id}/favorite` only changes favorites for the wallet from the validated JWT.
- [ ] `GET /api/favorites` requires a valid Bearer token.
- [ ] `GET /api/favorites?ids=...` requires a valid Bearer token, unless explicitly declared public/legacy.
- [ ] `GET /api/auth/wallet/me` validates token and returns the wallet address from the token.
- [ ] Backend explicitly confirms whether simplified `{ address }` login is acceptable for production, or switches to nonce/signature verification.