A production-shaped Flask CRUD API for contacts and groups, built on the application-factory + Blueprints pattern -- where a marshmallow validation contract, a SQLAlchemy relationship loading strategy, and CSV bulk-import idempotency are the real engineering surface, not the CRUD itself.
Nobody has started this yet — be first.
Single-character contact names are rejected
A support rep or a user importing/entering contacts with a one-character name (an initial like "Q", "X", a single-letter nickname, or certain transliterated names) gets a 422 "name must not be blank" error on data that is very obviously not blank. This is exactly the kind of validation bug that generates confused support tickets ("your form is broken, I typed a name!") and erodes trust in the API's error messages -- the message promises one rule ("not blank") while the code enforces a stricter, undocumented one.
About this project
Contact Book is a small, production-shaped CRUD service for contacts (name, phone, email) organized into groups, with substring search and group filtering. It is built with the Flask pattern ( in , unbound / extensions bound via , / registered with their own URL prefixes) so the project stays readable as it grows, backed by SQLAlchemy models with a genuine many-to-many <-> relationship, marshmallow schemas for input validation and output shaping, and a central error handler that turns every failure mode -- validation errors, 404s, and unexpected exceptions alike -- into one consistent JSON shape.
Contact search only matches when the term is in name AND email AND phone
Search is one of the headline features in the README (?search= matches "substring on name/email/phone"). As shipped right now, a search only returns a contact if the search term appears in all three fields (name, email, and phone) at once. Since most contacts don't have a phone number, or have one that obviously doesn't contain letters from someone's name, this means search returns nothing for almost every real query. Any user relying on search to find a contact -- the single most common thing a contact-book user does -- gets empty results and assumes the person isn't in their contact list at all. This is a silent, severe regression: no error is raised, the API just returns [].
create_app()app/__init__.pydbmigrateinit_app()contacts_bpgroups_bpContactGroupThe interesting engineering problems live below the CRUD surface, not in it: a marshmallow validate.Length boundary that has to actually agree with the error message sitting right next to it; a SQLAlchemy relationship lazy= loading strategy that silently turns a single list endpoint into an N+1 query storm as the table grows, invisible in any response-body diff and only catchable by counting real SQL statements; pagination that has to slice an already-filtered, already-ordered query rather than re-querying from scratch, with total reflecting the filtered count rather than the page size; a CSV bulk-import endpoint that must avoid creating the same missing group twice when two rows in one batch reference it, a check-then-act bookkeeping problem even inside a single-threaded request; and a uniqueness rule (case-insensitive group names) that the database's own unique=True constraint does not enforce, so the application layer has to.
Seven tickets (four bugs, three enhancements) are deliberately injected into an otherwise working, fully-tested codebase -- from a one-character validation boundary and a flipped and_/or_ search combinator, through an off-by-one CSV row-number counter and a missing lazy='selectin' batching strategy, up to a from-scratch pagination helper and a stateful, duplicate-safe group auto-creation flow. Every ticket has a dedicated pytest test that goes green once the fix is correct, and two of them (the search operator flip and the CSV row-number bug) also collaterally break the project's own pre-existing test suite -- exactly what a real regression looks like, and a useful signal that the underlying cause, not just the narrow dedicated test, has actually been fixed.
http://localhost:5000