A deliberately small Flask + SQLAlchemy watchlist API where the domain is trivial on purpose -- the real engineering problem is getting authentication, per-user ownership scoping, and JWT lifecycle correct under boundary, normalization, and concurrent-request conditions, not building a movie database.
Nobody has started this yet — be first.
Movie list shows the oldest movies first
A watchlist app's whole value is letting you glance at what you just added. Right now GET /api/movies returns the user's oldest entries first. For anyone with more than a page of movies, the thing they added five seconds ago is buried at the bottom of the last page instead of sitting at the top -- every session starts with "did my add actually work?" followed by paging all the way to the end to check. It's the kind of bug that doesn't crash anything, just quietly makes the app feel broken and unpolished from the very first real use.
About this project
A small Flask + SQLAlchemy API for a personal movie watchlist -- register, log in, add movies, mark them watched, rate them 1-10, page and filter the list. The domain is intentionally trivial: there is no external movie data source wired in by default and no complex business rules. The entire point of the project is auth, per-user ownership, and JWT lifecycle management done correctly, with nothing else competing for the reader's attention.
Every single-resource movie endpoint routes through one shared function, get_owned_movie_or_404(), which filters by id and user_id in the same query -- so a movie owned by someone else is literally indistinguishable, at the database level, from an id that doesn't exist, and there is no code path where "found the row, but it's not yours" can leak as a 403 or a 200. The seven injected tickets sit around that same discipline from different angles: a rating validator that's off-by-one against its own documented 1-10 scale, a missing ordering direction that inverts an entire list, a filter parameter that's declared but never read -- and, the two that actually require careful reasoning rather than a one-line fix, an email-normalization mismatch between register() and login() that silently and permanently locks out any user who ever typed their email in a different case, and a genuine time-of-check-to-time-of-use race between the uniqueness pre-check and the commit in register() that lets two concurrent signups for the same email crash the losing request instead of cleanly reporting 409.
Movies can never be saved with a perfect rating of 10
The rating scale is documented and enforced everywhere else in this app as 1-10 (see MovieEntryUpdateSchema, the model column, and every existing test that mentions "rating"). But creating a new entry with the single most meaningful value on that scale -- a perfect 10 -- fails validation. A user rating their favorite movie ever gets a 422 on the one input the UI told them was valid. This is exactly the kind of "the app is broken" bug that generates confused support messages and bad reviews, because it happens on the happy path, not an edge case anyone would think to avoid.
Seven tickets -- four bugs and three enhancements -- are deliberately injected into an otherwise fully working, tested codebase (33 passing tests in the project's own suite, none of which exercise any of the seven bugs, by construction). Four are one- or two-line defects an experienced developer should spot in minutes; three are missing features with the surrounding route, schema, or scaffolding already in place so only the actual logic is left to write. Every ticket has its own dedicated pytest test, isolated from the other six, that goes green the moment the fix is correct.