Surface Google, email, phone, and Telegram on login (mocked), polish the brand panel and fixes for Button/Menu, and keep the backend spec sheet aligned. Co-authored-by: Cursor <cursoragent@cursor.com>
7.1 KiB
Backend overview
Living spec for the shared platform API consumed by Gishen-B2B. This repo implements mocks typed to this contract; the backend service is consolidated later from B2B, Ecom, Mobile, and Admin spec sheets.
Workspace: /Users/kirukib/Desktop/Yaltopia Project/Gishen-B2B
Base URL (future)
https://api.gishenpharmacy.org/v1
Mock adapters in this repo live under src/lib/api/ and src/mocks/.
Tenancy
- Every authenticated request (except public registration) is scoped to an organisation via
orgIdon the session or explicit path prefix. - Org lifecycle:
pending_activation→active→suspended|closed. - Pending orgs: SUPER_USER may read status and submitted registration; cannot create members, packages, or migration commits until Admin activates.
X-Org-Id: org_01H...
For single-org sessions, orgId is implicit from JWT/session. Multi-org users (rare in B2B) pass X-Org-Id.
Authentication
| Mode (this phase) | Description |
|---|---|
| Mock demo | /login offers Google, email, phone, Telegram (UI mocked) plus a test-user dropdown; any path sets session via demo_user_id |
| Production (future) | Google OIDC, email magic-link/OTP, SMS OTP, Telegram Login Widget / Mini App — see endpoints/auth.md |
Demo personas: user_super, user_hr, user_fin, user_mem — see GET /v1/auth/demo-profiles.
Session shape
See entities/session-user.md. Includes avatar_url, roles[], and active_role for multi-role demo users.
JWT claims (future)
{
"sub": "usr_01H...",
"org_id": "org_01H...",
"roles": ["HR_ADMIN", "FINANCE"],
"active_role": "HR_ADMIN",
"locale": "am",
"iat": 1710000000,
"exp": 1710003600
}
RBAC matrix
| Resource / action | SUPER_USER | HR_ADMIN | FINANCE | MEMBER |
|---|---|---|---|---|
Own profile (/v1/me/profile) |
✓ | ✓ | ✓ | ✓ |
| Org profile (commercial read) | ✓ | ✓ (active only) | ✓ (active only) | — |
| Org registration submit | ✓ (public path) | — | — | — |
| Departments CRUD | ✓ | ✓ | read | — |
| Members CRUD / import / invite | ✓ | ✓ | read metadata | own row |
| Pharmacy ID card (print / QR payload) | ✓ | ✓ | — | own only |
| Packages CRUD | ✓ | ✓ | read | own assignment |
| Migration jobs | ✓ | ✓ | read history | — |
| Finance spend / statements | ✓ | read aggregates | ✓ | — |
| Finance approvals | ✓ | — | ✓ | — |
| Member allowance / orders | ✓ | — | — | ✓ |
| Prescriptions (clinical) | ✓ full | withheld | withheld | own only |
| Settings / verification | ✓ | ✓ | — | — |
Clinical withhold: HR_ADMIN and FINANCE never receive prescription images, medicine line items, dosages, prescriber notes, or diagnosis-related fields. Aggregates (e.g. “Rx order — ETB 450, category: chronic”) may appear in finance spend. SUPER_USER bypasses withhold for support/disputes.
Error model
All errors return:
{
"error": {
"code": "VALIDATION_FAILED",
"message": "Human-readable summary",
"details": [
{ "field": "email", "code": "INVALID_FORMAT", "message": "..." }
],
"request_id": "req_01H..."
}
}
Standard HTTP status codes
| Status | Usage |
|---|---|
| 400 | Validation failed, malformed JSON |
| 401 | Missing or expired session |
| 403 | Authenticated but role/org forbids action; clinical withhold |
| 404 | Resource not found or not visible in tenant |
| 409 | Conflict (duplicate member, job already committed) |
| 422 | Business rule violation (org pending, credit exceeded) |
| 429 | Rate limit |
| 500 | Internal error |
Error codes (catalog)
| Code | HTTP | Description |
|---|---|---|
UNAUTHENTICATED |
401 | No valid session |
FORBIDDEN |
403 | Role cannot perform action |
CLINICAL_WITHHELD |
403 | Clinical field requested by HR/Finance |
ORG_PENDING |
422 | Org not yet activated by Admin |
ORG_SUSPENDED |
422 | Org suspended |
VALIDATION_FAILED |
400 | Field-level validation |
DUPLICATE_MEMBER |
409 | Phone/email/employee_id conflict |
CREDIT_LIMIT_EXCEEDED |
422 | Would exceed org credit |
MIGRATION_JOB_LOCKED |
409 | Job already committed or rolled back |
INVITE_EXPIRED |
422 | Invite token expired |
NOT_FOUND |
404 | Generic not found |
Pagination
List endpoints accept:
?page=1&page_size=25&sort=-created_at&q=search
Response envelope:
{
"data": [ ... ],
"pagination": {
"page": 1,
"page_size": 25,
"total_items": 142,
"total_pages": 6
}
}
Default page_size: 25. Max: 100.
Filtering & date ranges
List endpoints support search + filters (portal toolbar / modal when >3 filters):
| Domain | Query params (common) |
|---|---|
| Members | q, status, department_id, package_id, member_type, has_dependants |
| Finance spend | from, to, department_id, category, coverage, member_id, approval_status |
| Statements | year, status |
| Migration | status, dataset_type, has_errors, created_by |
| Packages / departments | q, status / site |
?from=2026-01-01&to=2026-01-31&department_id=dept_01H...
ISO 8601 dates in org timezone (default Africa/Addis_Ababa).
Pharmacy ID card (platform contract)
Members carry a printable verification card used at Gishen pharmacy / Ecom checkout:
| Field | Purpose |
|---|---|
avatar_url |
Photo on card + portal UI |
card_id |
Human-readable card number (e.g. GSH-1042) |
verification_id |
Org verification token encoded in QR |
QR payload (canonical):
gishen://member/{member_id}?v={verification_id}
Admin / POS scans the QR to resolve the member for covered purchase. Clinical detail is not on the card. See endpoints/members.md and ../coordination/gishen-admin.md.
File uploads
Multipart for Rx images and migration files:
POST /v1/...
Content-Type: multipart/form-data
Max file size (Rx page): 10 MB. Accepted: image/jpeg, image/png, application/pdf.
Migration uploads: 50 MB .xlsx, .xls, .csv.
Idempotency
Mutating endpoints that create billing or migration commits accept:
Idempotency-Key: <uuid>
Duplicate keys within 24h return the original response.
Webhooks / events
Domain events published to platform bus; see events/README.md. B2B emits and consumes events for org registration, migration, and prescriptions.
Environment (Vercel frontend)
| Variable | Purpose |
|---|---|
NEXT_PUBLIC_API_BASE_URL |
Platform API base (mock or real) |
NEXT_PUBLIC_MOCK_AUTH |
true for demo role selector |
NEXT_PUBLIC_DEFAULT_LOCALE |
en |
Document production URL in this file when first Vercel deploy is finalized.