{ZB}Ziyad Alboghdady

Case study

Task Management API

A containerised REST API where cross-user access is structurally impossible, slow work runs in the background, and the AI endpoint is tested for being right, not only well-formed.

Stack.NET 9 Web API · C# · EF Core · PostgreSQL 16 · Docker Compose · Supabase Auth · Hangfire · OpenRouter LLM
RoleSole engineer
When2026
SourceGitHub repository

The problem

A task API is only interesting once more than one person uses it. Then three things get hard: proving who the caller is, guaranteeing that a caller can never touch another user's rows, and doing slow work without making the caller wait for it. All three are easy to get almost right — and “almost right” on an authorisation boundary is indistinguishable from broken.

What I did & decided

  • Split the service into Controllers → Services → Repositories, with storage behind one interface (ITasksRepository, implemented over EF Core and PostgreSQL).
  • Made Supabase the identity provider and this API a pure resource server: it never stores a password and never signs a token, it only verifies them — in SupabaseAuthMiddleware, so the controllers contain no authentication code at all.
  • Chose a network round-trip over a local signature check. It is slower, but a revoked token is rejected on the next request instead of staying valid until it expires. When Supabase is unreachable the API returns 503, not 401: an outage is not the caller's fault.
  • Made ownership a required parameter on every repository query, taken from the verified token, never from the request body. Asking for someone else's task returns 404, not 403 — a 403 would confirm that the id exists.
  • Moved slow work out of the request: POST /api/reports answers 202 with a status URL in milliseconds, and Hangfire runs two chained steps with two retries and 5 s / 10 s backoff. A report whose retries run out is marked failed, never left pending, and a cron heartbeat every minute flags anything stuck for more than two minutes.
  • Added an AI endpoint, POST /api/tasks/draft, that turns a messy note into a suggested task. The note travels in the user message, never inside the system prompt (the prompt-injection defence); invalid output gets one repair attempt, then a 422. A provider outage is a 503 with Retry-After, because those two failures need different reactions from the client.

What came of it

  • Cross-user access is structurally impossible, not policed: the query cannot be written without an owner.
  • Three PowerShell scripts check the README's claims against a running instance: 16 checks for authentication and ownership, 19 for the background jobs, and the AI checks.
  • A report measured 8.2 seconds from creation to completion — after the request that started it had already returned.
  • The eval suite scored 6 of 8 cases fully correct (18 of 20 checks). Both failures are date arithmetic, so the README says what I would change: resolve dates in C#, not in the model.
  • docker compose up starts the API (with its job worker) and PostgreSQL 16 together.

Next case: Car Rental Management System