Capstone: Full Review and What's Next
- Recap the full 25-lesson build arc from scoping through deployment, security hardening, and the production debugging story
- Identify single-tenancy as the build's first deliberate scope limit and describe what multi-tenancy would require to add
- Identify mock review ingestion as the build's second deliberate scope limit and describe the real path to live Google/Yelp/Facebook API integration
- Name at least three additional extensions beyond multi-tenancy and real API integration (real-time updates, a shared rate-limit store, webhook event deduplication, AI-assisted response drafting)
- Articulate why the transferable skill from this track is the build-and-debug process, not the specific stack choices
What You Built
Twenty-five lessons ago this was an empty repository and an idea: a dashboard that pulls Google, Yelp, and Facebook reviews into one place a business owner can actually respond from. It is now a real, working application -- with auth, a real schema, a billed core feature, transactional email, a test suite, a live deployment, monitoring, two security-hardening passes, and a production bug found and fixed the way real production bugs get found and fixed. Worth pausing on that, because it is easy to lose the shape of the whole thing lesson by lesson. Here is the arc, end to end.
The Full Build, Section by Section
Scoping and setup defined what the product actually does -- and just as importantly, what it deliberately does not try to do yet -- before any code existed, then scaffolded the Next.js, Drizzle, Supabase, and Tailwind foundation the rest of the build sits on.
Git workflow established how changes move through the project safely, the same discipline a real engineering team uses, not a one-off script.
Auth wired up Supabase Auth end to end -- sign-up, sign-in, session refresh -- the foundation Lesson 22 came back to audit once the rest of the app existed around it.
Schema and data access designed the Drizzle schema and the query layer everything else reads and writes through.
The core feature -- ingestion, UI, state, and wiring -- is the actual product: reviews coming in, a dashboard to filter and sort them, and the ability to respond to one.
Stripe billing and webhooks turned Free/Pro into a real distinction enforced by real subscription state, which is exactly the system Lesson 24's debugging story lives inside.
Resend email added the negative-review alert and weekly digest -- the parts of the product that reach a business owner without them having to remember to check.
Polish, testing, deployment, and monitoring took the app from "works on my machine" to a real URL with a real test suite behind it and someone watching when it breaks.
Section 8 -- Security and Hardening (Lessons 22-23, yours) audited every auth check in the app for whether it was real or decorative, then added the rate limiting and input validation that stop a legitimate request from becoming an abusive one.
Section 9 -- The Debugging Story (Lesson 24) walked a real production incident from a vague support ticket to a root cause in Stripe's documented lack of webhook delivery-order guarantees, a fix, and a first fix attempt that was itself wrong until a test caught it.
That is a complete, if compact, SaaS product -- and a complete, if compact, picture of what it actually takes to ship and maintain one.
Two Honest Gaps -- and Why They're Gaps on Purpose
Two things in this build are deliberately not real, and it is worth being direct about both rather than letting them pass as finished.
Gap 1: Single-Tenant, Not Multi-Tenant
lib/auth.ts says it outright in a comment: "This app is single-tenant: one business per account." Every owner gets exactly one row in businesses, found by a single WHERE owner_id = ... lookup. That was the right scope for a 25-lesson build -- it let every lesson from schema design through the debugging story focus on one thing at a time without also threading "which business is this for" through every layer. A real reputation-management SaaS, the kind that sells to agencies managing reviews for a dozen client locations, needs multi-tenancy: one owner account with many businesses underneath it, a business-switcher in the dashboard, and -- critically -- every single query, Server Action, and API route from Lessons 22-23's audit re-walked with "does this scope to the right business, not just the right owner" as the new question. The ownership joins this track built are the right shape to extend; they just need an extra business-selection layer in front of them.
Gap 2: Mock Ingestion, Not Real Platform APIs
app/api/reviews/ingest/route.ts says this just as directly: "In production this is what a scheduled job (cron / webhook) would call after pulling new reviews from the Google Places API, Yelp Fusion API, or Facebook Graph API. Each of those requires business verification and/or app review that isn't feasible for this reference build." The endpoint, its Zod validation, and its dedupe-on-conflict behavior are all real and are the actual integration point a real poller would call -- there just isn't a real poller behind it, because getting verified API access to three separate review platforms is its own multi-week process completely orthogonal to learning how to build the product around it. Taking this further means: registering for each platform's developer program, going through their verification/review process, building a scheduled job (Vercel Cron, same pattern as the weekly digest) that polls each API on a schedule, mapping each platform's response shape into the existing ingestSchema, and POSTing to the same endpoint this track already built and tested. The hard part -- what happens once a review lands -- is done. The hard-in-a-different-way part -- getting real reviews to land -- is the next project.
Other Directions Worth Naming
- Real-time updates -- the dashboard currently reflects new reviews on page load/revalidation; a Supabase real-time subscription would push a new review into the UI the moment
app/api/reviews/ingest/route.tsinserts it. - A shared rate-limit store -- Lesson 23 covered exactly why
lib/rate-limit.ts's in-memoryMapdoes not coordinate across serverless instances; swapping it for Upstash Redis or Vercel KV behind the same function signature is a contained, well-scoped next step. - Event-id deduplication on the webhook -- Lesson 24's "what this doesn't fix" section named this directly: idempotent processing keyed on Stripe's event id, on top of the ordering guard already shipped.
- AI-assisted response drafting -- given a review's text and rating, draft a suggested response a business owner can edit before sending, rather than starting from a blank textarea.
The Real Skill This Track Was Teaching
None of the 25 lessons were really about Next.js, Drizzle, Stripe, or Resend specifically -- those are this build's particular choices, and a different stack would have taught the same underlying things. What this track was actually about: scoping a real product down to something buildable, building it in a sequence where each piece sits on a tested foundation, auditing your own security assumptions instead of trusting that they are fine, and debugging a production incident by reading the actual code path and the actual platform documentation instead of guessing -- including catching your own first fix when it is wrong, because you wrote a test that demanded it be right. Those are the skills that transfer to the next product, on a different stack, with different requirements. The dashboard you built is the artifact. The process you just practiced is the actual takeaway.
That is the track. Go build the next one.
- The app is intentionally single-tenant (one business per owner, per lib/auth.ts) -- multi-tenancy would mean adding a business-switcher and re-auditing every query from Lessons 22-23 to scope by business, not just owner
- Review ingestion is real and tested end to end, but the data source is mocked -- Google Places, Yelp Fusion, and Facebook Graph API access all require business verification/app review not feasible inside a tutorial
- The actual integration point for real platform data already exists and was already built: POST to app/api/reviews/ingest/route.ts with the existing ingestSchema shape
- Lesson 23's rate limiter and Lesson 24's webhook fix both documented their own known gaps explicitly (cross-instance coordination, event-id deduplication) rather than hiding them, which is itself a practice worth carrying forward
- The transferable skill across this track is the process -- scoping, building on tested foundations, auditing your own auth assumptions, and debugging from real evidence rather than guesses -- not the specific choice of Next.js, Drizzle, Stripe, or Resend