docs: complete code-reality alignment for remaining docs + reconcile issue set
Remaining docs updated to match code (the docs that the first pass had not covered):
- Flows: Chat, Referral, Rating, Registration, Google OAuth, Negotiation, Payout,
Trezor Safekeeping — corrected endpoints, socket events, status enums, auth gaps
- API Reference: User API, Trezor API — admin route prefix/verb/status corrections,
added undocumented endpoints (ton-proof challenge, profile email verify,
GET /trezor/account, POST /trezor/verify-operation)
- Data Models: Chat, Notification, Payment, PointTransaction, User — corrected
enums (PaymentProvider, escrowState, PointTransaction.type, User.status),
90-day notification TTL, soft-delete semantics, wallet fields
Trezor "zero frontend" finding (audit C31/C32) corrected as STALE:
- Verified current code HAS a full frontend Trezor implementation (admin/trezor
page, TrezorSettingsView, trezorConnector via @trezor/connect-web,
TrezorSignDialog, actions/trezor.ts building the {message,signature} object)
- Fixed Trezor Safekeeping Flow doc (removed false "no frontend" warnings)
- Reclassified ISSUE-012 as invalid/superseded with explanation
Issue set reconciled to a single canonical numbering (ISSUE-001..054):
- Adopted the comprehensive 51-issue set (long-slug, fully indexed)
- Removed 35 superseded short-slug duplicates from the first pass
- Removed a duplicate ISSUE-046 file
- Added 3 issues the 51-set lacked: ISSUE-052 (completed-not-counted-in-stats),
ISSUE-053 (axios 401-only interceptor), ISSUE-054 (rate limiter counts all attempts)
- Regenerated Issues Index: 53 open (14 critical, 39 major) + 1 invalid
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -5,9 +5,9 @@ tags: [api, admin, reference]
|
||||
|
||||
# Admin API
|
||||
|
||||
> **Last updated:** 2026-05-29 — aligned with code (see [Doc vs Code Audit Report](../09%20-%20Audits/Doc%20vs%20Code%20Audit%20Report%20-%202026-05-29.md))
|
||||
> **Last updated:** 2026-05-29 — aligned with code (see Doc vs Code Audit Report)
|
||||
|
||||
There is no single `/api/admin` namespace — admin-only endpoints are scattered across the service routers. This page catalogs them in one place. All require `Bearer JWT` with `req.user.role === 'admin'`. The two enforcement patterns are:
|
||||
There is no single `/api/admin` namespace — admin-only endpoints are scattered across the service routers. This page catalogs them in one place. All require `Bearer JWT` with `req.user.role === 'admin'` unless explicitly noted otherwise. The two enforcement patterns are:
|
||||
|
||||
- Middleware: `authorizeRoles('admin')` after `authenticateToken` (used by the dispute, data-cleanup, blog routers).
|
||||
- Inline check inside the handler: `if (req.user.role !== 'admin') return 403` (used by user, points, payment routes).
|
||||
@@ -16,27 +16,31 @@ There is no single `/api/admin` namespace — admin-only endpoints are scattered
|
||||
|
||||
See full descriptions in [[User API]].
|
||||
|
||||
> **Path note:** The frontend and backend both use `/api/users/admin/*` (plural). The singular `/api/user/admin/*` paths for create/delete/status/role/list are **unreachable** — they are not mounted in the backend. Use `/api/users/admin/*` for all user-management calls.
|
||||
|
||||
| Endpoint | Action |
|
||||
| --- | --- |
|
||||
| `POST /api/user/admin/create` | Create user with role/status |
|
||||
| `DELETE /api/user/admin/:userId` | Soft delete user — sets `status='deleted'` (admins cannot delete each other) |
|
||||
| `PATCH /api/user/admin/:userId/status` | Activate / suspend |
|
||||
| `PATCH /api/user/admin/:userId/toggle-status` | Flip active flag |
|
||||
| `PATCH /api/user/admin/:userId/role` | Change role |
|
||||
| `GET /api/user/admin/list` | Paginated directory + stats |
|
||||
| `GET /api/user/admin/:userId/dependencies` | Pre-delete dependency check |
|
||||
| `POST /api/users/admin/create` | Create user with role/status |
|
||||
| `DELETE /api/users/admin/:userId` | Soft delete user — sets `status='deleted'` (admins cannot delete each other) |
|
||||
| `PATCH /api/users/admin/:userId/status` | Activate / suspend |
|
||||
| `PATCH /api/users/admin/:userId/toggle-status` | Flip active flag |
|
||||
| `PATCH /api/users/admin/:userId/role` | Change role |
|
||||
| `GET /api/users/admin/list` | Paginated directory + stats |
|
||||
| `GET /api/users/admin/:userId/dependencies` | Pre-delete dependency check |
|
||||
| `GET /api/users/admin/stats` | Aggregate user analytics |
|
||||
| `GET /api/users/admin/:userId` | Full user detail (admin view) |
|
||||
| `PUT /api/users/admin/:userId` | Mass update user |
|
||||
| `PUT /api/users/admin/update/:email` | Mass update by email |
|
||||
| `PATCH /api/users/admin/:userId/password` | Force password reset (wipes refresh tokens) |
|
||||
| `POST /api/users/admin/:userId/resend-verification` | Resend verification email |
|
||||
| `POST /api/users/admin/:userId/resend-verification` | Resend verification email (legacy route — uses 8-digit codes) |
|
||||
|
||||
> **Verification code length:** The endpoint `POST /api/users/admin/:userId/resend-verification` is served by the legacy userRoutes and generates **8-digit** codes. The new userController generates 6-digit codes and is reached via a different path. Both coexist; the legacy route takes precedence for this path.
|
||||
|
||||
**⚠️ KNOWN BUG — HTTP verb mismatch (status/role updates):** The frontend Redux actions for `updateUserStatus` and `updateUserRole` send `PUT` requests, but the backend registers these handlers under `PATCH`. These calls will receive `404 Method Not Found` responses until the frontend is corrected to use `PATCH`.
|
||||
|
||||
**⚠️ KNOWN BUG — Status value mismatch:** The frontend sends `'inactive'` and `'pending'` as status values when updating user status. The backend only accepts `'active'`, `'suspended'`, or `'deleted'`. Sending `'inactive'` or `'pending'` will be rejected or silently ignored.
|
||||
|
||||
**Hard vs. soft delete note:** The legacy route `DELETE /users/admin/:id` performs a **hard delete** (`findByIdAndDelete`). The current route `DELETE /api/user/admin/:userId` performs a **soft delete** (sets `status='deleted'`). Always use the current `/api/user/admin/:userId` route to preserve data integrity.
|
||||
**Hard vs. soft delete note:** The legacy route `DELETE /users/admin/:id` performs a **hard delete** (`findByIdAndDelete`). The current route `DELETE /api/users/admin/:userId` performs a **soft delete** (sets `status='deleted'`). Always use the current `/api/users/admin/:userId` route to preserve data integrity.
|
||||
|
||||
## Listing / marketplace moderation
|
||||
|
||||
@@ -80,6 +84,22 @@ See [[Payment API]].
|
||||
|
||||
**⚠️ Path correction:** Release/refund routes do **not** include a `/shkeeper/` segment. The correct paths are `/api/payment/:id/release`, `/api/payment/:id/release/confirm`, etc. (Previously documented incorrectly as `/api/payment/shkeeper/:id/…`.)
|
||||
|
||||
## Derived destinations & sweep
|
||||
|
||||
Frontend page: `/dashboard/admin/derived-destinations`. Backend registers 7 endpoints under `/api/payment/derived-destinations/*` with admin auth.
|
||||
|
||||
| Endpoint | Action |
|
||||
| --- | --- |
|
||||
| `GET /api/payment/derived-destinations` | List all derived destination addresses |
|
||||
| `POST /api/payment/derived-destinations/sweep/trigger` | Trigger a sweep across all destinations |
|
||||
| `POST /api/payment/derived-destinations/sweep/trigger/:id` | Trigger sweep for a single destination |
|
||||
| `GET /api/payment/derived-destinations/sweep/cron/status` | Get sweep cron job status |
|
||||
| `POST /api/payment/derived-destinations/sweep/cron/start` | Start the sweep cron job |
|
||||
| `POST /api/payment/derived-destinations/sweep/cron/stop` | Stop the sweep cron job |
|
||||
| `GET /api/payment/derived-destinations/sweep/history` | Sweep history log |
|
||||
|
||||
> Frontend action functions: `getDerivedDestinations`, `triggerSweep`, `triggerSingleSweep`, `getSweepCronStatus`, `startSweepCron`, `stopSweepCron`.
|
||||
|
||||
## Points (admin)
|
||||
|
||||
See [[Points API]].
|
||||
@@ -140,12 +160,58 @@ Router: [`backend/src/services/admin/dataCleanupRoutes.ts`](../../backend/src/se
|
||||
### GET /api/admin/scanner/status
|
||||
|
||||
**Description:** Returns the current state of the blockchain scanner / wallet monitor.
|
||||
**⚠️ SECURITY BUG — NO AUTHENTICATION:** Despite being mounted under `/api/admin/` and documented as admin-only, this endpoint has **no** `authenticateToken` or `authorizeRoles` guard. Any unauthenticated request can read scanner state.
|
||||
|
||||
> ⚠️ **NOT IMPLEMENTED:** The following endpoints do not exist in the codebase:
|
||||
> - `GET /api/admin/settings/confirmation-thresholds/history` — only the current-values `GET /api/admin/settings/confirmation-thresholds` and per-chain `PATCH /api/admin/settings/confirmation-thresholds/:chainId` exist.
|
||||
> - `POST /api/admin/rn/networks/reload` — the network registry cannot be reloaded at runtime via HTTP.
|
||||
> - `POST /api/admin/rn/networks/probe/:chainId` — no per-chain probe endpoint exists.
|
||||
> **⚠️ SECURITY BUG — NO AUTHENTICATION:** Despite being mounted under `/api/admin/`, this endpoint has **no** `authenticateToken` or `authorizeRoles` guard. Any unauthenticated request can read scanner state.
|
||||
|
||||
## Settings
|
||||
|
||||
### AML settings
|
||||
|
||||
> **⚠️ RUNTIME-ONLY PERSISTENCE:** `PATCH /api/admin/settings/aml` updates `process.env` at runtime only. Changes are **lost on server restart**. There is no frontend page for these endpoints.
|
||||
|
||||
| Endpoint | Auth | Action |
|
||||
| --- | --- | --- |
|
||||
| `GET /api/admin/settings/aml` | admin | Read current AML settings |
|
||||
| `PATCH /api/admin/settings/aml` | admin | Update AML settings (runtime only — not persisted to disk or DB) |
|
||||
|
||||
### Confirmation thresholds
|
||||
|
||||
Frontend page exists. Endpoints require admin auth.
|
||||
|
||||
| Endpoint | Action |
|
||||
| --- | --- |
|
||||
| `GET /api/admin/settings/confirmation-thresholds` | Get current confirmation thresholds for all chains |
|
||||
| `PATCH /api/admin/settings/confirmation-thresholds/:chainId` | Update threshold for a specific chain |
|
||||
|
||||
> **Not implemented:** `GET /api/admin/settings/confirmation-thresholds/history` — history endpoint does not exist. `POST /api/admin/rn/networks/reload` and `POST /api/admin/rn/networks/probe/:chainId` do not exist.
|
||||
|
||||
## Payments awaiting confirmation
|
||||
|
||||
Frontend page exists.
|
||||
|
||||
| Endpoint | Auth | Action |
|
||||
| --- | --- | --- |
|
||||
| `GET /api/admin/payments/awaiting-confirmation` | admin | List payments pending blockchain confirmation |
|
||||
|
||||
## RN network registry
|
||||
|
||||
Frontend page exists.
|
||||
|
||||
| Endpoint | Auth | Action |
|
||||
| --- | --- | --- |
|
||||
| `GET /api/admin/rn/networks` | admin | List all registered RN networks |
|
||||
|
||||
## Blog admin
|
||||
|
||||
Backend registers 5 blog admin endpoints, all guarded by `authorizeRoles('admin')`. Frontend has action functions calling each.
|
||||
|
||||
| Endpoint | Action |
|
||||
| --- | --- |
|
||||
| `GET /api/blog/admin/posts` | List all blog posts (admin view, includes drafts) |
|
||||
| `POST /api/blog/posts` | Create a new blog post |
|
||||
| `GET /api/blog/admin/posts/:id` | Get a single blog post (admin view) |
|
||||
| `PUT /api/blog/posts/:id` | Update a blog post |
|
||||
| `DELETE /api/blog/posts/:id` | Delete a blog post |
|
||||
|
||||
## Analytics
|
||||
|
||||
|
||||
@@ -69,13 +69,11 @@ Model: [[Chat]]. Real-time delivery happens over Socket.IO rooms named `chat-<ch
|
||||
**Auth required:** Bearer JWT (participant)
|
||||
**Errors:** `403` not a participant, `404` not found.
|
||||
|
||||
### PATCH /api/chat/:id/archive
|
||||
### PUT /api/chat/:id/archive
|
||||
|
||||
**Description:** Toggle archived state for the caller (per-user flag). Calling this endpoint on an already-archived chat **unarchives** it (toggle semantics).
|
||||
**Auth required:** Bearer JWT (participant)
|
||||
|
||||
> ⚠️ **KNOWN BUG** — The frontend `archiveConversation` helper sends `PUT /api/chat/:id/archive` but the backend route is registered as `PATCH`. The request will receive a `404` until the frontend is corrected to use `PATCH`.
|
||||
|
||||
### POST /api/chat/:id/participants
|
||||
|
||||
**Description:** Add a participant to a group chat.
|
||||
|
||||
@@ -79,7 +79,7 @@ Model: [[Dispute]]. A dispute references a [[PurchaseRequest]] plus optional [[P
|
||||
### GET /api/disputes/statistics
|
||||
|
||||
**Description:** Aggregated counts (open, by reason, average resolution time) for admin dashboards.
|
||||
**Auth required:** Bearer JWT (admin)
|
||||
**Auth required:** Bearer JWT (any authenticated user — backend applies `authenticateToken` only, no role restriction)
|
||||
**Response 200:** `{ success, data: { open, byReason, avgResolutionHours, ... } }`
|
||||
|
||||
### GET /api/disputes/:id
|
||||
|
||||
@@ -500,6 +500,16 @@ Same result shape as above, but for a single destination.
|
||||
}
|
||||
```
|
||||
|
||||
## Frontend PaymentProvider type
|
||||
|
||||
`src/types/payment.ts` defines `PaymentProvider` as:
|
||||
|
||||
```ts
|
||||
type PaymentProvider = 'request.network' | 'test' | 'other';
|
||||
```
|
||||
|
||||
> ⚠️ **Type gap (M37):** Despite both SHKeeper and the legacy wallet-direct (DePay/decentralized) flows being active in production, neither `'shkeeper'` nor `'decentralized'` appears in this union. Any frontend code that branches on `provider` will treat both as `'other'` or fall through a switch default. The backend stores the literal strings `"shkeeper"` and `"decentralized"` in the database; the mismatch exists only in the frontend type definition.
|
||||
|
||||
## Status model
|
||||
|
||||
[[Payment]] uses the statuses below across all providers:
|
||||
|
||||
@@ -3,6 +3,8 @@ title: Trezor API
|
||||
tags: [api, payments, trezor, safekeeping]
|
||||
---
|
||||
|
||||
> **Last updated:** 2026-05-29 — aligned with code (see Doc vs Code Audit Report)
|
||||
|
||||
# Trezor API
|
||||
|
||||
The Trezor API is mounted at `/api/trezor`. It is optional support for hardware-backed safekeeping and does not replace Request Network checkout, the funds ledger, or the broader Safe/multisig custody roadmap.
|
||||
@@ -80,10 +82,26 @@ Response:
|
||||
|
||||
## GET /api/trezor/account
|
||||
|
||||
Returns the caller's active Trezor registration summary.
|
||||
Returns the caller's active Trezor registration summary. If no Trezor has been registered for the authenticated user, returns `{ registered: false }` without an error.
|
||||
|
||||
Auth: bearer JWT
|
||||
|
||||
Response when registered:
|
||||
|
||||
```json
|
||||
{
|
||||
"success": true,
|
||||
"data": {
|
||||
"registered": true,
|
||||
"xpubFingerprint": "0x...",
|
||||
"registrationAddress": "0x...",
|
||||
"basePath": "m/44'/60'/0'",
|
||||
"deviceLabel": "Office Trezor",
|
||||
"nextAddressIndex": 3
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Response when absent:
|
||||
|
||||
```json
|
||||
@@ -148,7 +166,7 @@ Response:
|
||||
|
||||
## POST /api/trezor/verify-operation
|
||||
|
||||
Verifies a signed operation intent against the admin's registered Trezor safekeeping address.
|
||||
Admin-only standalone signature verification endpoint. Verifies a signed operation intent against the admin's registered Trezor safekeeping address without performing any release or refund. Use this to validate a Trezor proof before submitting it to the release/refund flow.
|
||||
|
||||
Auth: bearer JWT, admin
|
||||
|
||||
|
||||
@@ -5,6 +5,8 @@ tags: [api, user, reference]
|
||||
|
||||
# User API
|
||||
|
||||
> **Last updated:** 2026-05-29 — aligned with code (see [Doc vs Code Audit Report](../09%20-%20Audits/Doc%20vs%20Code%20Audit%20Report%20-%202026-05-29.md))
|
||||
|
||||
Two routers are mounted for users:
|
||||
|
||||
- `/api/user/*` - the new controller pattern in [`backend/src/services/user/userControllerRoutes.ts`](../../backend/src/services/user/userControllerRoutes.ts) wired to `userController`.
|
||||
@@ -75,29 +77,78 @@ Avatar upload is handled by the [[File API]]:
|
||||
|
||||
### GET /api/user/wallet-address
|
||||
|
||||
**Description:** Returns the caller's stored EVM wallet address (or `null`).
|
||||
**Description:** Returns the caller's stored wallet address plus its chain type and provider (each `null` if unset).
|
||||
**Auth required:** Bearer JWT
|
||||
**Response 200:** `{ "success": true, "data": { "walletAddress": "0x..." | null } }`
|
||||
**Response 200:**
|
||||
```json
|
||||
{
|
||||
"success": true,
|
||||
"data": {
|
||||
"walletAddress": "0x..." , // or null
|
||||
"walletType": "evm" , // "evm" | "ton" | null (the chain family)
|
||||
"walletProvider": "evm" // e.g. "evm" | "telegram-wallet" | null
|
||||
}
|
||||
}
|
||||
```
|
||||
(Earlier docs listed only `walletAddress`; the endpoint also returns `walletType` and `walletProvider`.)
|
||||
|
||||
### PATCH /api/user/wallet-address
|
||||
|
||||
**Description:** Verifies an EIP-191 signed message and stores `profile.walletAddress`. The server uses `ethers.verifyMessage(message, signature)` and rejects if the recovered address does not match.
|
||||
**Description:** Stores a verified wallet address. Supports **both EVM and TON**:
|
||||
- **EVM** (`walletType` omitted or not `'ton'`): the address must pass `ethers.isAddress`, and the body must include `signature` + `message`. The server runs `ethers.verifyMessage(message, signature)` (EIP-191) and rejects if the recovered address does not match.
|
||||
- **TON** (`walletType: 'ton'`): the address is validated against a TON address regex. An optional `tonProof` payload is verified via `verifyTonProof`; if valid, `profile.walletProofVerified` is set to `true` and `profile.walletProofTimestamp` is stamped.
|
||||
|
||||
On success the server writes `profile.walletAddress`, `profile.walletType` (`'evm'` or `'ton'`), `profile.walletProvider`, and `profile.walletProofVerified`.
|
||||
**Auth required:** Bearer JWT
|
||||
**Request body:**
|
||||
```ts
|
||||
{
|
||||
walletAddress: string; // 0x-prefixed 40-hex
|
||||
signature: string; // signed `message`
|
||||
message: string; // human-readable challenge text
|
||||
walletAddress: string; // EVM 0x-address, or TON address
|
||||
walletType?: "evm" | "ton"; // defaults to "evm"
|
||||
walletProvider?: string; // defaults to "telegram-wallet" for ton, "evm" otherwise
|
||||
// EVM only:
|
||||
signature?: string; // required for EVM — signed `message`
|
||||
message?: string; // required for EVM — human-readable challenge text
|
||||
// TON only:
|
||||
tonProof?: TonProofPayload; // optional; when valid sets walletProofVerified=true
|
||||
}
|
||||
```
|
||||
**Response 200:** `{ "success": true, "data": { "walletAddress": "0x..." } }`
|
||||
**Response 200:** `{ "success": true, "data": { "user": { /* sanitized user */ }, "walletProofVerified": boolean } }`
|
||||
**Errors:**
|
||||
- `400` missing fields, malformed address, signature mismatch
|
||||
- `400` missing/invalid fields, malformed address, EVM signature mismatch, invalid TON proof
|
||||
- `404` user not found
|
||||
|
||||
The legacy alias `PATCH /api/users/wallet-address` performs the same logic.
|
||||
|
||||
### POST /api/user/wallet-address/ton-proof/challenge
|
||||
|
||||
**Description:** Generates a TON proof nonce/challenge for TON wallet address verification. The returned challenge is then signed by the client and submitted for verification.
|
||||
**Auth required:** Bearer JWT
|
||||
**Response 200:** `{ "success": true, "data": { /* challenge/nonce payload */ } }`
|
||||
**Source:** Backend implements this endpoint for TON proof nonce generation.
|
||||
|
||||
## Email verification
|
||||
|
||||
### POST /api/user/profile/email/verify
|
||||
|
||||
**Description:** Re-verifies the caller's email address after an email change using a 6-digit code sent to the new address.
|
||||
**Auth required:** Bearer JWT
|
||||
**Request body:**
|
||||
```ts
|
||||
{
|
||||
code: string; // 6-digit verification code
|
||||
}
|
||||
```
|
||||
**Response 200:** `{ "success": true, "data": { /* updated user */ } }`
|
||||
**Source:** `axios.ts` defines this endpoint; used after email change flow.
|
||||
|
||||
### POST /api/user/profile/email/resend-verification
|
||||
|
||||
**Description:** Resends the 6-digit email verification code to the caller's (new) email address.
|
||||
**Auth required:** Bearer JWT
|
||||
**Response 200:** `{ "success": true }`
|
||||
**Source:** `axios.ts` defines this endpoint; used in email change / re-verification flow.
|
||||
|
||||
## Contacts and search
|
||||
|
||||
### GET /api/users/contacts
|
||||
@@ -122,7 +173,17 @@ The legacy alias `PATCH /api/users/wallet-address` performs the same logic.
|
||||
|
||||
## Admin: user management
|
||||
|
||||
These are duplicated across the two routers. The newer controller variants live under `/api/user/admin/*`; the legacy bodies live under `/api/users/admin/*`. All require `req.user.role === 'admin'` (the legacy routes check inline; the controller routes only check `authenticateToken` and the controller enforces the role).
|
||||
> **Note on the two admin route groups (prefix inconsistency).** There are TWO parallel admin route groups:
|
||||
> - **Singular `/api/user/admin/*`** — the NEW controller (`userControllerRoutes.ts` → `userController`). This is where create / delete / status / role / list / dependencies are actually *registered* on the new controller.
|
||||
> - **Plural `/api/users/admin/*`** — the LEGACY router (`userRoutes.ts`), which also mounts admin sub-routes (status, role, password, single-user fetch/update, resend-verification, stats).
|
||||
>
|
||||
> ⚠️ **The frontend consistently calls the PLURAL `/api/users/admin/*`** (see `frontend/src/lib/axios.ts`, all paths under `endpoints.users.admin.*`). So the singular create/delete/status/role/list paths below are *documented*, but in practice the frontend hits the legacy plural group. Both are listed; treat the plural group as the frontend-effective reality.
|
||||
>
|
||||
> ⚠️ **Note on HTTP verbs (KNOWN BUG):** The frontend `updateUserStatus` and `updateUserRole` calls (`frontend/src/actions/user.ts`) use **`PUT`** (`PUT /api/users/admin/:id/status`, `PUT /api/users/admin/:id/role`). The backend registers these as **`PATCH`** only (both the legacy and new routers). The verbs do not match — treat `PATCH` as the authoritative backend verb; the `PUT` calls will not route.
|
||||
>
|
||||
> ⚠️ **Note on status values (KNOWN BUG):** The frontend `updateUserStatus` TypeScript type is `'active' | 'inactive' | 'pending'`. The backend `User.status` enum is `'active' | 'suspended' | 'deleted'`. So:
|
||||
> - `'inactive'` and `'pending'` are **rejected/ignored** by the backend (the new controller only applies `status` when it is one of `active`/`suspended`/`deleted`).
|
||||
> - `'suspended'` — the actually-usable suspend value — is **missing from the frontend type**, so the admin UI cannot send it.
|
||||
|
||||
### POST /api/user/admin/create
|
||||
|
||||
@@ -144,31 +205,49 @@ These are duplicated across the two routers. The newer controller variants live
|
||||
**Response 201:** `{ success, data: { user } }`
|
||||
**Errors:** `400` missing fields, `403` non-admin, `409` email exists.
|
||||
|
||||
### DELETE /api/user/admin/:userId
|
||||
### DELETE /api/user/admin/:userId (new controller — SOFT delete)
|
||||
|
||||
**Description:** Hard-delete a user. Prevents self-deletion and deleting other admins.
|
||||
**Description:** **Soft-delete** — sets `status = 'deleted'` via `findByIdAndUpdate` (the user document is retained). Only blocks **self-deletion** (`userId === req.user.id`).
|
||||
**Auth required:** Bearer JWT (admin)
|
||||
**Response 200:** `{ success, data: { deletedUserId } }`
|
||||
**Errors:** `400` self-delete, `403` admin-on-admin, `404` not found.
|
||||
**Errors:** `400` self-delete, `404` not found.
|
||||
|
||||
### PATCH /api/user/admin/:userId/status
|
||||
> ⚠️ **Behavior diverges from the legacy DELETE — and a privilege concern.** The new controller’s soft-delete does **NOT** block an admin from deleting *other* admins (it only blocks deleting yourself). By contrast, the legacy `DELETE /api/users/admin/:id` (below) is a **HARD delete** (`findByIdAndDelete`, removes the document) and **does** block admin-on-admin deletion. The two endpoints behave differently in both deletion semantics (soft vs hard) and authorization (self-only vs admin-on-admin block).
|
||||
|
||||
**Description:** Activate / suspend a user.
|
||||
### DELETE /api/users/admin/:id (legacy router — HARD delete)
|
||||
|
||||
**Description:** **Hard-delete** — permanently removes the user document via `findByIdAndDelete`. Blocks deleting other admins.
|
||||
**Auth required:** Bearer JWT (admin)
|
||||
**Request body:** `{ isActive: boolean; reason?: string }`
|
||||
**Response 200:** `{ success, data: { user: { _id, isActive, statusUpdatedAt } } }`
|
||||
**Errors:** `403` admin-on-admin, `404` not found.
|
||||
|
||||
### PATCH /api/user/admin/:userId/status (and legacy PATCH /api/users/admin/:id/status)
|
||||
|
||||
**Description:** Update a user's status and/or email-verified flag. Registered on the new controller as `/api/user/admin/:userId/status`; the legacy plural `/api/users/admin/:id/status` is what the frontend actually calls.
|
||||
**Auth required:** Bearer JWT (admin)
|
||||
**Request body:**
|
||||
```ts
|
||||
{
|
||||
status?: "active" | "suspended" | "deleted"; // applied only if one of these three values
|
||||
isEmailVerified?: boolean; // new controller also accepts this — sets User.isEmailVerified
|
||||
reason?: string;
|
||||
}
|
||||
```
|
||||
The new controller only writes `status` when it is exactly `active`, `suspended`, or `deleted`; any other value (e.g. the frontend's `inactive`/`pending`) is silently ignored. It additionally accepts an `isEmailVerified` boolean to flip the user's email-verified flag.
|
||||
**Response 200:** `{ success, data: { user } }` (sanitized user without password)
|
||||
**⚠️ Frontend discrepancy (KNOWN BUG):** Frontend calls this with the `PUT` verb and sends `status: 'active' | 'inactive' | 'pending'`; the backend registers `PATCH` and only honors `active`/`suspended`/`deleted`. See the admin routing note above.
|
||||
|
||||
### PATCH /api/user/admin/:userId/toggle-status
|
||||
|
||||
**Description:** Flip active/suspended without explicit body.
|
||||
**Auth required:** Bearer JWT (admin)
|
||||
|
||||
### PATCH /api/user/admin/:userId/role
|
||||
### PATCH /api/users/admin/:userId/role
|
||||
|
||||
**Description:** Change a user's role.
|
||||
**Auth required:** Bearer JWT (admin)
|
||||
**Request body:** `{ role: "buyer" | "seller" | "admin"; reason?: string }`
|
||||
**Errors:** `400` invalid role.
|
||||
**Frontend discrepancy:** Frontend calls this with `PUT` verb; backend only accepts `PATCH`.
|
||||
|
||||
### GET /api/user/admin/list
|
||||
|
||||
@@ -184,8 +263,9 @@ These are duplicated across the two routers. The newer controller variants live
|
||||
|
||||
### GET /api/users/admin/stats
|
||||
|
||||
**Description:** Aggregated user stats — total/active/verified counts, role distribution, activity buckets (24h / 7d / 30d).
|
||||
**Description:** Aggregated user stats — total/active/verified counts, role distribution, activity buckets (24h / 7d / 30d). (Undocumented previously.)
|
||||
**Auth required:** Bearer JWT (admin)
|
||||
**Note:** No frontend UI actually consumes this. The endpoint path exists in `axios.ts` (`endpoints.users.admin.stats`), but the admin overview computes its figures client-side from `getPurchaseRequests()`, not from this endpoint.
|
||||
|
||||
### GET /api/users/admin/:userId
|
||||
|
||||
@@ -210,10 +290,12 @@ These are duplicated across the two routers. The newer controller variants live
|
||||
|
||||
### POST /api/users/admin/:userId/resend-verification
|
||||
|
||||
**Description:** Regenerate the 8-digit email verification code and re-send the verification email.
|
||||
**Description:** Regenerate the email verification code and re-send the verification email.
|
||||
**Auth required:** Bearer JWT (admin)
|
||||
**Errors:** `400` user already verified.
|
||||
|
||||
> ⚠️ **Email code length inconsistency.** The legacy `userRoutes.ts` generates an **8-digit** code (`10000000 + Math.random() * 90000000`), while the new `userController` (used by `POST /api/user/profile/email/verify` and the email-change flow) generates a **6-digit** code (`crypto.randomInt(100000, 1000000)`). Code length therefore depends on which path issued it.
|
||||
|
||||
## Address book
|
||||
|
||||
Source: [`backend/src/services/address/addressRoutes.ts`](../../backend/src/services/address/addressRoutes.ts), model: [[Address]].
|
||||
|
||||
Reference in New Issue
Block a user