Persistence and auth

Budget: 1-2 weeks.

Why this matters

SessionStore in models.py is an in-memory Python dict. Restart the server and everything a user built is gone. There are no user accounts, so every visitor shares the same nginx basic-auth credential. This is the single largest gap between the current POC and anything you can charge for.

This is also the chapter where your backend instincts carry you the furthest. You already know SQL, you already know how to model domains. What is new is the Python async plus SQLAlchemy 2.0 patterns, and wiring authentication into FastAPI.

What to learn

TopicTime
Postgres refresher: transactions, indexes, JSONB for VRP payloads1 day
SQLAlchemy 2.0 async sessions, or SQLModel if you prefer Pydantic2-3 days
Alembic migrations: autogenerate, edit, apply, roll back1 day
Pick and set up a managed auth provider (Clerk, Supabase Auth, WorkOS)1-2 days
JWT verification as a FastAPI dependency0.5 day
Repository layer that scopes every query by user_id1-2 days
Design the schema (users, sessions, scenarios, solutions)0.5 day
Replace SessionStore and write the isolation integration test2-3 days

Resources

Exercise

Design and implement the schema for users, sessions, scenarios, and solutions. Keep the VRP request and solution payloads as JSONB. Replace SessionStore with a repository that reads and writes Postgres. Add a FastAPI dependency that resolves a JWT to a user_id and injects it into every request. Write one integration test that proves a user cannot read another user's scenario even if they guess the ID.

Schema sketch

create table users (
  id uuid primary key,
  email text unique not null,
  created_at timestamptz not null default now()
);

create table sessions (
  id uuid primary key,
  user_id uuid not null references users(id),
  name text not null,
  created_at timestamptz not null default now()
);

create table scenarios (
  id uuid primary key,
  session_id uuid not null references sessions(id),
  name text not null,
  request jsonb not null,
  original_request jsonb not null,
  created_at timestamptz not null default now()
);

create table solutions (
  id uuid primary key,
  scenario_id uuid not null references scenarios(id),
  status text not null,
  payload jsonb,
  created_at timestamptz not null default now()
);

create index on sessions (user_id);
create index on scenarios (session_id);
create index on solutions (scenario_id);