What this is
A simple support helpdesk: Submitters raise Tickets; Agents work them through a status lifecycle until Closed. React SPA + FastAPI + PostgreSQL, everything running in Docker Compose; ticket data persists in a named Docker volume.
Stack
- Frontend: React + Vite; state via Context API + useReducer (no extra state deps)
- API: FastAPI, SQLAlchemy 2 ORM, Alembic migrations, JWT auth (bcrypt via passlib)
- Database: PostgreSQL — persistent named volume
pgdata
- Serving: nginx serves the built SPA on :8080 (static only, no proxy); API on :8000 with CORS for the SPA origin; Postgres internal-only
- Build: multi-stage Dockerfiles for web and api
Domain
- Ticket — a support problem or request raised by a Submitter. Fields: title, description, category, priority, status, optional assignee, created/updated timestamps. No attachments or custom fields in V1. Nothing is ever hard-deleted.
- Status — Open → In Progress → Resolved → Closed.
- Priority — Low, Medium, High, Critical.
- Category — chosen from a maintained list; five initially: Billing, Technical, Account, Feature Request, General. Agents can add new categories via API only (no UI in V1).
- Assignee — optional Agent on a ticket. Assignment/reassignment is agent-only; submitters cannot request an agent.
- Comment — public message on a Ticket; visible to the ticket's Submitter and all Agents.
- Note — private message on a Ticket; visible only to Agents.
- Submitter — self-registers via the web UI.
- Agent — provisioned by an existing Agent via single-use invite code (no email in V1).
Status workflow
- Forward moves always legal, skips included (e.g. Open → Resolved).
- Reopen: from Resolved or Closed, always lands on Open. Legal for the ticket's Submitter (own tickets) and any Agent.
- No other backwards moves.
- Withdrawal: a Submitter may close their own Open ticket.
- Every status change and assignment change is recorded in a ticket event log (who/what/when) — the intended future source for webhooks (no webhook endpoints in V1).
Roles and permissions
| Action |
Submitter |
Agent |
| Register |
self |
via invite code |
| View / comment |
own tickets |
all tickets |
| Create ticket |
✓ |
✓ |
| Note |
✗ |
✓ |
| Move status |
✗ |
✓ (per workflow) |
| Withdraw (close own Open ticket) |
✓ |
— |
| Reopen (Resolved/Closed → Open) |
own tickets |
any ticket |
| Assign / reassign |
✗ |
✓ |
| Add category |
✗ |
✓ (API only) |
| Delete anything |
✗ |
✗ |
Accounts and auth
- Account: unique email (login), display name, password (bcrypt via passlib), role.
- JWT: single access token, 1-hour expiry, in an
HttpOnly; SameSite=Strict cookie; no refresh token; expiry → login screen. No separate CSRF token in V1 — SameSite carries the protection (noted limitation).
- First Agent: a documented seed command creates the first Agent at setup.
API surface (nested resources)
- Auth: register, login, logout, current user
GET /tickets — list, role-scoped (Submitters: own; Agents: all); filters: status, priority, category, assignee, date range; ILIKE title search; offset pagination (default 20, max 100)
POST /tickets — create (title, description, category, priority)
GET /tickets/{id} — detail, visibility per role
PATCH /tickets/{id}/status — transition per workflow matrix
PATCH /tickets/{id}/assignee — agent-only
GET|POST /tickets/{id}/comments — public
GET|POST /tickets/{id}/notes — agents only
GET /tickets/{id}/events — status/assignment event log
GET /categories; POST /categories — agent-only
- Invites: create (agent-only), consume at registration
GET /health
Frontend
Auth pages (register, login); role-aware ticket list; agent queue with filter controls; ticket detail (description, metadata, timeline with events, comments, notes section for agents, role-appropriate status controls, assignee picker for agents); invite panel for agents.
Infrastructure
- Docker Compose (v1-compatible file):
web (nginx static, :8080), api (:8000, CORS origin env-configurable), db (Postgres, internal, named volume pgdata), db-test (Postgres, throwaway volume, tests only)
- Alembic migrations run on api startup
Testing
- pytest API integration suite against
db-test (auth, CRUD, transition matrix, permissions, filters, pagination); test users created through the API so the JWT cookie flow stays under test
- ~3 Playwright E2E flows against the running Compose stack: (1) Submitter register → create → view; (2) Agent login → note → assign → resolve; (3) Submitter attempts an agent-only action → 403
- No unit tests, no CI in V1
Out of scope (V1)
Deletion, email/notifications, webhook endpoints, CSRF tokens, refresh tokens, category-management UI, attachments, custom fields, full-text search, CI, submitter-requested assignment.
What this is
A simple support helpdesk: Submitters raise Tickets; Agents work them through a status lifecycle until Closed. React SPA + FastAPI + PostgreSQL, everything running in Docker Compose; ticket data persists in a named Docker volume.
Stack
pgdataDomain
Status workflow
Roles and permissions
Accounts and auth
HttpOnly; SameSite=Strictcookie; no refresh token; expiry → login screen. No separate CSRF token in V1 — SameSite carries the protection (noted limitation).API surface (nested resources)
GET /tickets— list, role-scoped (Submitters: own; Agents: all); filters: status, priority, category, assignee, date range; ILIKE title search; offset pagination (default 20, max 100)POST /tickets— create (title, description, category, priority)GET /tickets/{id}— detail, visibility per rolePATCH /tickets/{id}/status— transition per workflow matrixPATCH /tickets/{id}/assignee— agent-onlyGET|POST /tickets/{id}/comments— publicGET|POST /tickets/{id}/notes— agents onlyGET /tickets/{id}/events— status/assignment event logGET /categories;POST /categories— agent-onlyGET /healthFrontend
Auth pages (register, login); role-aware ticket list; agent queue with filter controls; ticket detail (description, metadata, timeline with events, comments, notes section for agents, role-appropriate status controls, assignee picker for agents); invite panel for agents.
Infrastructure
web(nginx static, :8080),api(:8000, CORS origin env-configurable),db(Postgres, internal, named volumepgdata),db-test(Postgres, throwaway volume, tests only)Testing
db-test(auth, CRUD, transition matrix, permissions, filters, pagination); test users created through the API so the JWT cookie flow stays under testOut of scope (V1)
Deletion, email/notifications, webhook endpoints, CSRF tokens, refresh tokens, category-management UI, attachments, custom fields, full-text search, CI, submitter-requested assignment.