# PAG-PMS API

Base URL: `/api`

Production: [https://progressiveapi.kasoamarket.org/api](https://progressiveapi.kasoamarket.org/api)  
Health: [https://progressiveapi.kasoamarket.org/api/health](https://progressiveapi.kasoamarket.org/api/health)  
Docs: [https://progressiveapi.kasoamarket.org/api/docs](https://progressiveapi.kasoamarket.org/api/docs)

Envelope:

```json
{ "success": true, "data": {} }
```

```json
{ "success": false, "message": "You are not authorized to access this resource." }
```

Authentication: `Authorization: Bearer <accessToken>` **or** httpOnly `accessToken` cookie. Refresh tokens are httpOnly cookies (`refreshToken`). Interactive docs: `GET /api/docs`.

Roles: `SUPER_ADMIN`, `REGIONAL_COORDINATOR`, `CONSTITUENCY_COORDINATOR`, `MEMBER`.

Account statuses: `pending`, `active`, `rejected`, `suspended`, `deactivated`.

Scope: Super Admin is unrestricted. Regional Coordinators are bound to `regionId`. Constituency Coordinators are bound to `regionId` + `constituencyId`. Explicit query IDs outside that scope return **403**.

---

## Health

| Method | URL | Auth | Description |
|---|---|---|---|
| GET | `/health` | none | `{ status, env, time }` |

## Auth

| Method | URL | Auth | Body | Notes |
|---|---|---|---|---|
| POST | `/auth/register` | none | `fullName`, optional `email`, `phone` (Ghana), `ghanaCardId` (`GHA-XXXXXXXXX-X`), `securityQuestionId`, `securityAnswer`, `password` (≥8), optional `gender`, `dateOfBirth`, `address`, `regionId`, `constituencyId`, `requestedRole` | Email may be omitted or null. Empty strings are not stored. Real emails stay unique; multiple accounts without email are allowed. Members become `active`. Coordinator requests stay `pending` with `role=MEMBER`. Ghana Card IDs are unique. The generated `membershipId` is still created. Security answers are hashed and never returned. `role`/`status` in the body are ignored. |
| POST | `/auth/login` | none | `phone` (Ghana) or `email`, `password` | Sign-in screen uses phone. Rejects suspended/rejected/deactivated. |
| POST | `/auth/forgot-password` | none | `ghanaCardId` | Starts recovery. If the card is registered and a security question is set, returns that question plus a short-lived `challengeId`. Otherwise returns a generic message. |
| POST | `/auth/verify-security-answer` | none | `challengeId`, `answer` | Compares the hashed answer. Failed attempts are rate-limited and lock after 5 failures. Success returns a short-lived `resetToken`. |
| POST | `/auth/reset-password` | none | `resetToken`, `newPassword` (≥8) | Completes recovery and invalidates the token and existing sessions. |
| POST | `/auth/refresh` | refresh cookie | — | Rotates access + refresh tokens. |
| POST | `/auth/logout` | yes | — | Invalidates the current refresh token. |
| GET | `/auth/me` | yes (pending allowed) | — | Current user + Firebase custom token when configured. |
| PATCH | `/auth/fcm-token` | yes | `{ token }` | Registers an FCM device token. |

Errors: `400` validation, `401` invalid credentials/token, `403` blocked status, `409` duplicate email/phone/Ghana Card ID (Ghana numbers are unique across `024`, `233`, and `+233` formats), `429` recovery lockout.

## Users (staff)

| Method | URL | Roles | Notes |
|---|---|---|---|
| GET | `/users` | Super Admin, Regional, Constituency | Scoped list. Query: `q`, `role`, `status`, `regionId`, `constituencyId`. |
| GET | `/users/coordinators` | Super Admin | Regional and constituency coordinators. Query: `q`, `role`, `status`, `regionId`, `constituencyId`. |
| GET | `/users/pending` | Super Admin | Coordinator requests. |
| POST | `/users/:id/approve` | Super Admin | Body: `regionId` and/or `constituencyId`, optional `role`. |
| POST | `/users/:id/reject` | Super Admin | Body: optional `reason`. |
| POST | `/users/:id/suspend` | Super Admin | Sets `suspended`. |
| GET | `/users/:id` | authenticated + scope | |

## Members

| Method | URL | Roles | Notes |
|---|---|---|---|
| GET | `/members` | staff | Defaults to `role=MEMBER`. Same query as `/users`. |
| POST | `/members` | staff | `fullName`, optional `email`, `phone`, `ghanaCardId`, `securityQuestionId`, `securityAnswer`; Super Admin must send region + constituency. Coordinators are pinned to their assignment. Returns `temporaryPassword` when none is supplied. Ghana Card + security question are required so the member can use password recovery. |
| GET | `/members/:id` | authenticated + scope | Members may only read themselves. Responses include `ghanaCardId` and `hasSecurityQuestion`, never the security answer or hash. |
| PATCH | `/members/:id` | authenticated + scope | Members cannot change `regionId`, `constituencyId`, or `membershipStatus`. A member may add a missing Ghana Card ID and set their own security question. Staff may add missing Ghana Card / security details for accounts they can manage, but cannot overwrite an existing security question. Only Super Admin can change an existing Ghana Card ID. Responses never include the security answer or hash. |
| DELETE | `/members/:id` | Super Admin | Deactivates; does not hard-delete. |

## Regions

| Method | URL | Roles | Notes |
|---|---|---|---|
| GET | `/regions/public` | none | `{ name, code }` of active regions. |
| GET | `/regions` | active | Super Admin: all. Others: assigned region. |
| GET | `/regions/:id` | active + scope | Includes live `stats`. |
| POST | `/regions` | Super Admin | `name`, `code`, optional `description`, `coordinatorId`. |
| PATCH | `/regions/:id` | Super Admin | |
| POST | `/regions/:id/deactivate` | Super Admin | |

## Constituencies

| Method | URL | Roles | Notes |
|---|---|---|---|
| GET | `/constituencies/public` | none | Query `regionId` required. |
| GET | `/constituencies` | active | Optional `regionId` (403 if out of scope). |
| GET | `/constituencies/:id` | active + scope | Includes live `stats`. |
| POST | `/constituencies` | Super Admin | `name`, `code`, `regionId`, optional `description`, `phone`, `location`, `referralContact1`, `referralContact2`. |
| PATCH | `/constituencies/:id` | Super Admin | Coordinator assignment must match the constituency region. |

## Projects

Statuses: `PLANNING`, `IN_PROGRESS`, `COMPLETED`.

| Method | URL | Roles | Notes |
|---|---|---|---|
| GET | `/projects` | active | Scoped. Query: `status`, `regionId`, `constituencyId`. |
| POST | `/projects` | coordinators + Super Admin | Members cannot create. Scope is taken from the caller except Super Admin. |
| GET | `/projects/:id` | authenticated | Includes `updates` history. Viewing is organization-wide. |
| PATCH | `/projects/:id` | coordinators + Super Admin | Status changes are audited. |
| POST | `/projects/:id/updates` | Super Admin or coordinator of the project's region/constituency | `description`, `progressPercent` (0–100). ≥100 marks completed. Others receive 403. |

## Messages

Mongo is the authorization record. Firestore is mirrored when Admin credentials are set.

| Method | URL | Roles | Notes |
|---|---|---|---|
| GET | `/messages/threads` | active | Audience: `PARTY`, `REGION`, `CONSTITUENCY`, `DIRECT`. |
| POST | `/messages/threads` | active | Party-wide: Super Admin only. Members: `DIRECT` only. |
| GET | `/messages/threads/:id` | active + audience | 403 if outside audience. |
| POST | `/messages/threads/:id/messages` | active + audience | `{ text, attachments? }` |
| POST | `/messages/messages/:messageId/read` | active + audience | |

## Announcements

Audience: `PARTY`, `REGION`, `CONSTITUENCY`.

| Method | URL | Roles | Notes |
|---|---|---|---|
| GET | `/announcements` | active | Filtered to the caller’s audience. |
| POST | `/announcements` | coordinators + Super Admin | Party-wide requires Super Admin. |

## Notifications, reports, audit, dashboard, uploads

| Method | URL | Roles | Notes |
|---|---|---|---|
| GET | `/notifications` | authenticated | Own inbox. |
| POST | `/notifications/:id/read` | authenticated | |
| POST | `/notifications/read-all` | authenticated | |
| GET | `/dashboard/summary` | active | Organization-wide totals. `pendingApprovals` is Super Admin only (0 for others). Recent messages stay scoped. |
| GET | `/reports/membership` | active | |
| GET | `/reports/projects` | active | |
| GET | `/reports/export?type=` | active | `members`, `projects`, `regions` (admin), `constituencies`. CSV. |
| GET | `/audit-logs` | Super Admin | Query: `action`, `resource`. |
| POST | `/uploads` | staff | Multipart field `file`. JPEG/PNG/WebP/PDF. Returns `{ url }`. |
