Nobody has started this yet — be first.
Business impact
Users have no way to fix a typo'd target URL or extend/shorten an expiry without deleting the link and creating a brand new one under a different slug -- which breaks every place the old slug was already shared. Any real link-shortener needs "edit," not just "create and delete." This project's own caching design doc explicitly calls out "deleting or updating a ShortURL must invalidate (or update) its Redis entry" as one of exactly two write paths that touch the cache -- and right now only one of those two paths (delete) even exists.
Problem
PATCH /api/urls/<slug> has a route registered but it is a placeholder that raises NotImplementedError("PATCH /api/urls/<slug> is not implemented yet (see TICKET-10)").
Current behavior
Calling PATCH /api/urls/<slug> with any body currently raises NotImplementedError -- 500 in production.
Expected behavior
PATCH /api/urls/<slug> accepts a JSON body with any of target_url / expires_in_days, updates the matching fields on the row, and -- in the same request -- invalidates or refreshes the Redis cache entry so the very next GET /<slug> reflects the update rather than serving what was cached before the edit.
Steps to reproduce
Why this matters
This is the second of the two cache-invalidating write paths this project's design promises and the first one (DELETE) already had its own bug ticket for forgetting to invalidate. Getting PATCH right the first time means either calling invalidate_cache(slug) (safe, simple: next read just re-populates from the DB) or calling set_cached_url(...) with the new values directly (saves one DB round trip on the very next read, at the cost of duplicating the "what does a cache entry look like" logic in one more place) -- either is a legitimate choice, but the request must not return 200 while a stale cache entry survives it.
Suggested approach
Look at delete_url for the general request shape (look up by slug, 404 if missing), and at how create_short_url validates and applies expires_in_days. Reuse _validate_custom_slug/ expires_in_days parsing logic if it is a good fit rather than re-deriving it. Decide deliberately whether to invalidate-and-let-the-next-read-repopulate, or to update the cache entry directly -- and make sure whichever you choose actually runs before the response is returned, not after.
Acceptance criteria
Verification
pytest practice_tickets/tests/test_ticket_10_update_endpoint.py -v
Hints (0/2)
Try it without hints first — the reading is the exercise.
Working on this ticket
Work on a branch named for the ticket — that's what you'll submit.
Branch off your fork
Sign in to create it automatically, or do it yourself:
$git checkout -b feat/update-endpoint-cacheFix it and commit
Meet every acceptance criterion, and add a test that would have caught this.
Push the branch
$git push -u origin feat/update-endpoint-cacheSubmit it below
Paste your fork URL and the branch name, with a short write-up of the root cause.
Questions
Ask about anything unclear in the ticket — the maintainer and anyone who has solved it can answer. Please don't post full solutions.
Sign in to ask a question or reply.
Sign indocker-entrypoint.shcurl -X POST http://localhost:5000/api/urls \
-H "Content-Type: application/json" \
-d '{"target_url": "https://anthropic.com", "custom_slug": "claude"}'
# -> {"slug": "claude", "short_url": "http://localhost:5000/claude", "expires_at": null}
curl -i http://localhost:5000/claude # 302 redirect, records a click
make seed # or: docker compose exec web flask seed-db
If something is already listening on 5432/6379 locally, uncomment POSTGRES_HOST_PORT / REDIS_HOST_PORT in .env to remap the host side — the app always talks to db/redis over the compose network regardless. Run production-style (gunicorn, no dev server, no bind mount) with docker compose -f docker-compose.prod.yml up --build. GET /healthz checks both Postgres and Redis connectivity.
You do not need Docker running at all to work the tickets — the test suite uses TestingConfig (SQLite in-memory via StaticPool) and fakeredis instead of a real Postgres/Redis, so everything runs against a plain virtualenv:
python3 -m venv .venv && .venv/bin/pip install -r requirements-dev.txt
.venv/bin/pytest practice_tickets/tests/test_ticket_01_base62_roundtrip.py -v # a single ticket
./practice_tickets/run_tickets.sh # all 20, clean pass/fail summary
./practice_tickets/run_tickets.sh 05 13 16 # just a few, by number
All 20 tests fail at the start; as you fix each ticket in app/, its test (and only its test) should flip to passing. .venv/bin/pytest tests/ (via make test) runs the project's own main suite — some ticket bugs (TICKET-05, 07, 08, 09, 14 among others) also break assertions in that suite, since they are real bugs in shared code paths; fixing the ticket fixes those too, not a separate task.
Level 2
Implement a feature
Extend the system within its own patterns.