Architecture¶
Stack¶
| Layer | Choice |
|---|---|
| Backend | FastAPI, SQLAlchemy 2.0 (typed Mapped[...] models), Pydantic v2 |
| Database | SQLite locally (backend/crm.db), Postgres in production |
| Auth | Dev-login (Authorization: Bearer dev:<email>) locally; Supabase JWT (JWKS ES256/RS256, or HS256) in production |
| Billing | Stripe checkout + webhook; a "simulate payment" fallback when unconfigured |
| Frontend | React 18, Vite 6, Mantine 7, TanStack Query, React Router |
| Repo | C:\Users\dell\Desktop\NavAse Universal CRM |
| Ports (local) | API 8083, frontend 5178 |
Repository layout¶
backend/app
main.py FastAPI app, CORS, router registration, /health, /pricing-plan
config.py Settings (env-driven): dev_auth, cors, supabase, stripe
database.py SQLAlchemy engine, Base, get_db() session dependency
models.py Company · User · Contact · Interaction · FollowUp · Sequence · SequenceRun
schemas.py Pydantic request/response models
auth.py get_current_user, require_admin, require_super_admin,
get_company, require_premium_company (the paywall)
routers/
me.py current user + workspace, settings updates
contacts.py contact CRUD, serialize()
pipeline.py board view (contacts grouped by stage)
followups.py follow-up CRUD, state logic, .ics export
agenda.py the daily agenda queue (overdue / today / dormant)
reports.py pipeline / conversion / activity summaries
team.py agent management, routing
sequences.py sequence CRUD + runs (mocked sends)
billing.py Stripe checkout, plan info, simulate-payment
webhooks.py /webhooks/stripe
admin.py super-admin cross-workspace company list + premium toggle
services/
dormancy.py is_dormant(), days_since_activity(), recommendation()
ics.py follow_up_ics() — builds an .ics calendar file
demo_seed.py seed_demo_data() — two sample verticals on first sign-in
billing.py Stripe helpers
seed.py standalone: wipe + seed a demo workspace and 3 logins
frontend/src
main.tsx providers: Mantine, TanStack Query, Router, Auth, Upgrade
App.tsx all routes, role gating
theme.ts Mantine theme (indigo primary, Inter)
api/
endpoints.ts typed API functions
types.ts shared response types
lib/api.ts axios instance, setAuthToken(), 402 -> upgrade modal
context/
AuthContext.tsx token, sign-in/out
UpgradeContext.tsx opens the upgrade modal on a 402
components/
AppLayout.tsx shell: sidebar nav + header
ProtectedRoute.tsx AuthedRoute, RequireRole, RoleRedirect
DemoBanner.tsx the "you're on demo data" banner
MetricCard.tsx, PremiumButton.tsx
pages/ dashboard (agenda) · pipeline · contacts · contact detail ·
follow-ups · reports · sequences · team · settings · billing ·
companies (super-admin) · landing · login · pricing
Request flow¶
- Frontend attaches
Authorization: Bearer <token>via the axios instance inlib/api.ts(setAuthTokenis called whenever the auth token changes). - Every protected endpoint depends on
get_current_user(inauth.py), which resolves the token to aUserand, on first sign-in, creates theUser+ itsCompanyand seeds two sample pipelines. - Read endpoints call
get_company(db, user)to scope queries bycompany_id. - Write endpoints call
require_premium_company(db, user)— which isget_companyplus a check onCompany.is_premium. If the workspace is free it raises HTTP 402 with the paywall message. - The frontend's axios error interceptor catches 402 and opens the upgrade
modal (
UpgradeContext).
Multi-tenancy¶
There is no row-level security in the database. Isolation is enforced in
application code: every query filters on company_id, and the company_id
always comes from the authenticated user's Company, never from the request
body. See Auth and multi-tenancy.
What's mocked¶
- Sequence sends — recorded as
SequenceRunrows, no real email sent. - Stripe — real if
STRIPE_SECRET_KEYis set; otherwise a "simulate successful payment" button flips the workspace to premium directly.