Last verified against the code: 2 October 2026,
main@7009b470(AFAWA WEEP dashboards). This document describes the system that is actually deployed onimuka.co. Proposals and target designs live elsewhere:NARRATIVE_ALIGNED_ARCHITECTURE_REDESIGN.md(redesign proposal) anddocs/product-review.md(product direction). The page-by-page inventory isfrontend/FEATURES.md.
Imuka Access is a web platform that connects African SMEs (entrepreneurs) with investors, supported by experts (advisory services), partners (organisations that source deals and refer members) and an Imuka admin team that vets everything before it goes public. Since September 2026 it is also the digital backbone for the AFAWA WEEP programme (300 women-led SMEs in Uganda, Nigeria and Burundi).
It is one React single-page application talking to one FastAPI service over a JSON API, backed by one PostgreSQL database, all run with Docker Compose behind Nginx on a single VPS.
Integrations that need production credentials before they do anything: Persona (KYC), SMTP (email —
in-app notifications work without it), Flutterwave (payments), PostHog (analytics, also consent-gated) and the
AI provider (AI_PROVIDER defaults to none).
| Container | What it runs | Notes |
|---|---|---|
nginx |
Nginx 1.25 | TLS termination (Let's Encrypt certs synced by scripts/sync-letsencrypt-certs.sh), /api/v1 → backend, everything else → frontend. index.html is served no-cache so deploys don't strand old sessions |
frontend |
Static Vite build served by Nginx | React 18 + TypeScript, Ant Design 6, TanStack Query, React Router 7 |
backend |
Uvicorn + FastAPI | On start it waits for Postgres, runs alembic upgrade head, optionally creates the first admin (BOOTSTRAP_ADMIN_*), then reports healthy |
postgres |
PostgreSQL 16 | The only system of record. Holds legacy tables migrated from MySQL and the newer canonical_* tables |
redis, minio |
Provisioned | No application code uses them today. Files are stored in Postgres or the uploads volume (see §6) |
woodpecker-*, gh-runner, docker-dind |
CI helpers | Optional, defined in the same compose file |
Background jobs run inside the API process (no separate worker): saved-search digests every 15 minutes and wallet reconciliation nightly. This assumes a single backend replica.
frontend/app/
├─ routes/App.tsx all routes (~320 path entries incl. ~120 legacy redirects), role guards
├─ features/
│ ├─ public/ marketing pages, deals/opportunities/programmes boards, programme form, books, events
│ ├─ auth/ login, 3-step signup (role → details → interests), password reset, admin login
│ ├─ member/ role dashboards and every member tool (deals, data room, discussions, wallet…)
│ ├─ partner/ partner portal (own shell)
│ ├─ events/ hosted-event creation and ticketing
│ └─ admin/ admin console incl. AFAWA programme tracker and operations
├─ lib/ API clients, currency, SEO, afawa.ts (screening), linkage.ts (matching)
├─ stores/ auth session, active organisation, cart
└─ components/ shared layout, AI assistant, cookie consent, dashboard UI kit
Route guards: RequireAdmin, RequireMember (any signed-in non-admin; admins bounce to /admin),
RequirePartner, and OrgGate (entrepreneurs/investors must have an organisation first). A required,
non-dismissable onboarding overlay keeps new members on their dashboard until the membership application
(and, for entrepreneurs, company + first deal) is done — and the server enforces the same gates.
Language: English in the code. French (and other languages) come from the third-party GTranslate machine-translation widget in the public header and the workspace header. There is no translation catalogue.
backend/app/
├─ api/v1/endpoints/ 44 routers (auth, auth_mfa, deals, broker, partner, admin, admin_ops, kyc, …)
├─ services/ ~50 domain services (deal_pipeline, data_room_nda, deal_commitments,
│ deal_questions, investor_updates, mfa_service, audit_service, wallet_service…)
├─ repositories/ SQL access per domain
├─ models/ SQLAlchemy models (legacy.py, canonical.py, organizations, wallet, …)
├─ integrations/ flutterwave.py (server-side payment verification)
└─ core/ config, security (JWT, password hashing), dependencies
backend/alembic/versions/ 37 migrations; a fresh database migrates from empty
Layering is endpoint → service → repository / SQL. Several newer services use SQL text queries directly. Database errors are no longer turned into empty lists (R2): a failed query surfaces as an error.
| Role | How they get in | What unlocks |
|---|---|---|
| Entrepreneur | Self-signup → in-app membership application (acknowledged, no approval gate) → company → first deal | Deal creation, data room, discussions, investor updates |
| Investor | Self-signup → application → admin approval | Messaging, Q&A, pledges, data-room viewing, portfolio |
| Expert | Self-signup → application → admin approval | Bookings, engagements, marketplace bids, public directory listing |
| Partner | Public application → admin approval → 7-day single-use setup link → profile + terms overlay | Sourcing deals, opportunities, referrals, commissions (if a rate is set), hosted events |
| Admin | Created by a super admin (or bootstrap) | Admin console; scopes: super / program / finance / support |
/admin/audit.One PostgreSQL database with two generations of tables:
| Generation | Examples | Status |
|---|---|---|
| Legacy (from the CodeIgniter/MySQL system) | imuka_entrepreneur, imuka_investors, business_experts, imuka_businesses, investment_deals, investment_offers, administrators, countries, industry_categories |
Still the identity tables for members. Old deals stay here, read-only in the stage tracker |
| Canonical (new) | canonical_deals, deal_milestones, canonical_membership_applications, canonical_wallets, canonical_ledger_entries, canonical_audit_events, canonical_user_profiles, canonical_investor_preferences |
All new deals, approvals, wallets and audit |
| Feature tables | organizations, org_members, org_documents, data_room_files, data_room_views, data-room NDAs, deal commitments, deal questions, investor updates, partner_accounts, partner_referrals, partner_commissions, program_applications, kyc_verifications, notifications, disputes, reconciliation_runs, hosted_events, event_ticket_types, expert_tasks, expert_bids, expert_assignments, session_meetings |
Live |
Files. Organisation-vault and data-room documents are stored inside Postgres (base64 text, 10 MB limit).
Other uploads (images, attachments) go to the uploads volume. There is no application-level encryption of
file contents; protection is TLS in transit, access checks, the stamped no-download viewer and the access log.
Money is stored in minor units as 64-bit integers with an explicit currency per record (17 supported display currencies; amounts are never mixed across currencies).
Milestone evidence is submitted by the owner and verified by Imuka at /admin/milestones. Partner-sourced deals
are the same records tagged with partner_id and go through the same queue. Closing a deal no longer credits
the founder's wallet (F06); a partner commission accrues only if an admin has set a rate.
Screening rules (frontend/app/lib/afawa.ts): applicant is a woman (proxy for 50%+ women ownership, confirmed
at interview), 5–300 full-time employees, formally registered, address recognised as Uganda/Nigeria/Burundi.
Any unanswered criterion sends the application to manual review. "Investment-ready" = eligible + seeking funds
/member/linkages ranks published opportunities, approved experts and live deals against the member's
sector, country and business-description keywords (sector 50, country 30, up to 4 shared keywords × 5;
threshold 30), each with a written reason. Computed in the browser; recommendations are not stored.
| Step | How |
|---|---|
| Lint / type-check / unit tests | npm run lint, npx tsc --noEmit, npm test (Vitest) in frontend/; ruff/pytest in backend/ (~170 tests in 31 files, against a real migrated Postgres) |
| End-to-end | Playwright, 17 specs in e2e/tests (seeds its own accounts) |
| CI | .github/workflows/ci.yml on a self-hosted runner (lint, tests, e2e); Woodpecker pipelines in .woodpecker/ as an alternative |
| Production deploy | Push to main → deploy.yml on the self-hosted runner → docker compose up -d --build --wait → health check. The deploy watcher (scripts/deploy-watcher.sh, polls origin/main) is the fallback when Actions minutes run out. Host: /opt/imuka, systemd unit imuka |
| Staging | http://207.180.201.109:8080/, checkout /opt/imuka-bugfix, branch dev, own database and uploads. As of 2 Oct 2026 dev is behind main (it lacks the September review fixes, NDA/pledges/2FA/audit and the AFAWA work) |
npm run build.