Skip to content

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

  1. Frontend attaches Authorization: Bearer <token> via the axios instance in lib/api.ts (setAuthToken is called whenever the auth token changes).
  2. Every protected endpoint depends on get_current_user (in auth.py), which resolves the token to a User and, on first sign-in, creates the User + its Company and seeds two sample pipelines.
  3. Read endpoints call get_company(db, user) to scope queries by company_id.
  4. Write endpoints call require_premium_company(db, user) — which is get_company plus a check on Company.is_premium. If the workspace is free it raises HTTP 402 with the paywall message.
  5. 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 SequenceRun rows, no real email sent.
  • Stripe — real if STRIPE_SECRET_KEY is set; otherwise a "simulate successful payment" button flips the workspace to premium directly.