A full-stack wealth management platform for Registered Investment Advisors (RIAs). Advisors log in to a firm-scoped console that surfaces their practice structure, household relationships, client portfolios, and planning data.
| Layer | Technology |
|---|---|
| API | FastAPI + SQLModel (SQLAlchemy 2 async) + asyncpg |
| Database | PostgreSQL (Docker Compose) + Alembic migrations |
| Auth | JWT (HS256, 12h) — separate advisor and client flows |
| UI | Next.js 16 (App Router) + React 19 + Tailwind CSS v4 |
| Package mgmt | uv (Python) · pnpm (Node) |
cp .env.example .envThe defaults in .env.example work for local development out of the box.
docker compose up -dPostgres listens on localhost:5432. Data persists in a named Docker volume.
uv syncmake migrate # applies all Alembic migrations
make seed # inserts Summit Wealth Advisors + 5 sample householdsThe seed creates Summit Wealth Advisors (RIA, Denver CO) with two practices, six advisors, and five realistic retirement-planning households. All seed accounts use the password password.
make run # FastAPI on http://localhost:8000Interactive API docs: http://localhost:8000/docs
make ui-install # install Node dependencies (first time only)
make ui-dev # Next.js dev server on http://localhost:3000Visit http://localhost:3000/login and sign in with any seeded advisor account, for example:
| Password | |
|---|---|
jcarter@summitwealthadvisors.example.com |
password |
lpark@summitwealthadvisors.example.com |
password |
rchen@summitwealthadvisors.example.com |
password |
After login you land on the Firm Structure dashboard — firm overview, KPI cards, and a browsable grid of practices and advisors.
# Full reset — wipe tables, re-migrate, re-seed
make reset && make migrate && make seed
# Generate a new migration after editing a model
uv run alembic revision --autogenerate -m "describe the change"
# Roll back one migration
uv run alembic downgrade -1
# Add a Python dependency
uv add <package>
# Add a UI dependency
cd ui && pnpm add <package>app/ FastAPI application
main.py App factory + router registration
config.py Settings (reads .env)
database.py Async engine + session dependency
security.py JWT + bcrypt
authz.py Firm-scoped authorization (Scope dataclass)
models/ SQLModel table models + Pydantic schemas
crud/ Generic CRUDBase + per-entity instances
routers/ One APIRouter per entity
alembic/ Database migrations
versions/ Hand-written raw-SQL migration files
scripts/
seed.py Dev seed — 2 firms, 2 practices, 7 advisors, 7 households
ui/ Next.js 16 frontend (App Router)
app/ Pages and Server Actions
login/ Advisor login
dashboard/ Firm Structure landing (post-login)
portal/ Client portal (post-login)
components/
app-shell/ Sidebar + authenticated layout
firm-structure/ Firm overview page components
auth/ Login forms
landing/ Public marketing page
lib/
format.ts AUM formatting, initials, token helpers
types.ts Shared TypeScript interfaces
design/ Design system
tokens.css CSS custom properties (single source of truth)
tailwind.config.js Tailwind v3 config (mirrors tokens.css)
references/ Drop screenshot mockups here for design sessions
- Auth — cookie-session; the advisor JWT is stored in an
httpOnlycookie (advisor_session). The Next.js proxy (ui/proxy.ts) does a fast JWT-payload check before rendering protected routes; the page itself re-validates againstGET /api/v1/auth/me. - Authorization — every API request is scoped to a
firm_id. Advisors only see households they are assigned to viaadvisor_assignment. - Enum fields — all enum-typed SQLModel columns use
sa_column=Column(String(N))to avoid PostgreSQL native enum type conflicts with VARCHAR+CHECK columns in the schema. - Migrations — hand-written raw SQL (
op.execute()) to preserve CHECK constraints, GIN/partial indexes, and DDL comments. Never autogenerate for production.