A small REST API for managing support tickets, built with FastAPI and PostgreSQL.
- Python 3.12, FastAPI, psycopg, Pydantic v2, raw parameterized SQL
git clone https://github.com/yl3847/Laminar-Interview.git
cd Laminar-Interview
python3 -m venv venv
source venv/bin/activate
pip install -r requirements.txt# Create the database (if it doesn't exist)
createdb laminar_exercise
# Apply the schema
psql -d laminar_exercise -f schema.sqlTo use a custom database URL:
export DATABASE_URL="postgresql://user:password@localhost:5432/laminar_exercise"uvicorn main:app --reloadcurl -s http://localhost:8000/health | python3 -m json.toolcurl -s -X POST http://localhost:8000/tickets \
-H "Content-Type: application/json" \
-d '{
"customer_id": "cust-101",
"subject": "Cannot log in",
"priority": "high",
"status": "open",
"created_by": "alice@example.com"
}' | python3 -m json.toolcurl -s -X POST http://localhost:8000/tickets \
-H "Content-Type: application/json" \
-d '{
"customer_id": "cust-101",
"subject": "Test",
"priority": "critical",
"status": "open",
"created_by": "alice@example.com"
}' | python3 -m json.toolcurl -s "http://localhost:8000/tickets?customer_id=cust-101" | python3 -m json.toolcurl -s "http://localhost:8000/tickets?customer_id=cust-101&status=open&priority=high" | python3 -m json.toolcurl -s -X PATCH http://localhost:8000/tickets/1 \
-H "Content-Type: application/json" \
-d '{"status": "resolved"}' | python3 -m json.toolcurl -s -X PATCH http://localhost:8000/tickets/999 \
-H "Content-Type: application/json" \
-d '{"status": "closed"}' | python3 -m json.toolcurl -s "http://localhost:8000/customers/cust-101/tickets/summary" | python3 -m json.toolBIGINT GENERATED ALWAYS AS IDENTITYforid— the modern PostgreSQL standard overSERIAL.customer_idisVARCHARrather than an integer foreign key — keeps the service self-contained without requiring a customers table.CHECKconstraints onpriorityandstatusenforce validity at the database level, not just the application level.TIMESTAMPTZfor timestamps to store timezone-aware values.
- Four composite indexes on
(customer_id, [status,] [priority,] created_at DESC)directly mirror the four query shapes the API supports: filter by customer only, customer + status, customer + priority, and all three. A composite index starting withcustomer_idalso serves as the basic customer lookup, so no separate single-column index oncustomer_idis needed.
- Pydantic
Literaltypes (Literal["low", "medium", "high", "urgent"]) handle enum validation declaratively — no manual@field_validatorboilerplate. EmailStrvalidatescreated_byformat automatically.- FastAPI's
Query(...)with typedOptional[Status]/Optional[Priority]parameters validates query string values before they reach any SQL. - All SQL values go through psycopg
%sparameterized queries — no f-string interpolation of user input.
GET /customers/{customer_id}/tickets/summaryuses a path parameter rather than a query string, which reads more naturally as a resource and avoids a route conflict withGET /tickets.PATCH /tickets/{id}updates onlystatusand touchesupdated_at— minimal surface area for the stated requirement.- Pydantic validation failures return FastAPI's standard 422 response with field-level detail; no custom error handler needed for an interview scope.
- Connection pooling with
psycopg_poolinstead of opening a new connection per request. - Pagination (
limit/offset) onGET /tickets. - A
customerstable with a proper foreign key ontickets.customer_id. - Structured logging and request IDs for traceability.
- Integration tests with a real test database using
pytestandhttpx.