Skip to content

Spec: Simple support ticketing system (React + FastAPI + Postgres, containers) #1

Description

@uukrogh

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.

No activity

Activity on this issue will appear here.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions