Skip to content

Multi-user rentals with copy limits, bans and an admin panel - #174

Draft
devin-ai-integration[bot] wants to merge 8 commits into
mainfrom
devin/1786670285-multi-user-rentals
Draft

Multi-user rentals with copy limits, bans and an admin panel#174
devin-ai-integration[bot] wants to merge 8 commits into
mainfrom
devin/1786670285-multi-user-rentals

Conversation

@devin-ai-integration

Copy link
Copy Markdown

Summary

Turns the single-user demo into a multi-user rental store: users sign in with just an email, the catalog is shared with a limited number of copies per movie, and an admin panel (admin/admin123) manages users, bans and the catalog.

The core change is the rentals model. Today rentals(id UNIQUE, price) has no notion of a user and a return deletes the row, so there is no history and no way to count copies. Now:

users(email PK, display_name, banned, ban_reason, created_at)
rentals(id SERIAL, user_email, movie_id, price, rented_at, returned_at)  -- returned_at IS NULL == active
redemptions(id SERIAL, user_email, good_deed, status, created_at)

A return sets returned_at, so rows survive as history, and availability is copies - count(active rentals for that movie). The worker no longer drops the rentals table on boot; both api and worker call database.EnsureSchema (idempotent) and migrate the legacy table.

Auth lives in the existing Go api (per the plan review): a base64url payload + HMAC-SHA256 signature in movies_session / movies_admin cookies, signed with SESSION_SECRET. Because rent (Java) and catalog (Node) must trust the same cookies, all three charts get the same sessionSecret value.

Rentals are still async through Kafka, which used to mean a rejected rental just vanished into the worker's logs. rent now asks the api synchronously before publishing, so the UI gets an immediate 409:

// POST /rent
email = SessionCookie.email(request)          // 401 if unsigned/absent
check = rentCheck.check(email, catalogID, "rent")   // GET api:8080/internal/rent-check
if (!check.allowed()) return 409 check.reason();    // banned / already rented / no copies left
publish("rentals", {user_email, movie_id})          // 202

The worker re-runs the same checks inside the insert transaction, since the pre-check is advisory and two requests can race for the last copy. Note the payload carries no price: the worker looks it up with catalog.Lookup(movieID) and charges what the catalog says, so a client can't pick its own price.

Movies gained a copies field (seeded 1-4; missing/invalid falls back to DefaultCopies = 3), and the catalog service gained admin-only POST/PUT/DELETE /catalog.

The frontend is rewritten from the class component with the hardcoded Cindy session into a functional app: login, "my movies" with returns, history, availability badges/sold-out, the banned meme screen with a good-deed form, and /admin (users + histories + ban/unban + redemption queue + catalog CRUD). Store re-fetches /me on mount and after every action so a user banned mid-session lands on the meme screen without a reload.

Validated end to end on Okteto in a dedicated namespace (devin-multiuser-rentals): okteto test e2e passes 8/8, plus a browser pass over the whole flow with two concurrent users and the admin panel.

Note: SESSION_SECRET, ADMIN_USERNAME and ADMIN_PASSWORD are plain chart values (demo defaults okteto-movies-demo / admin / admin123) — fine for the demo, worth promoting to a Secret before anything real.

Link to Devin session: https://app.devin.ai/sessions/04a3cbd2609a47509c195b95cc573235
Requested by: @rberrelleza

rberrelleza and others added 4 commits August 14, 2026 01:18
Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
…he store

Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
@rberrelleza rberrelleza self-assigned this Aug 14, 2026
@devin-ai-integration

Copy link
Copy Markdown
Author
Original prompt from Ramiro

@okteto/movies i want to update the application so that multiple users can rent movies . the catalog is shared, and we should have a limited number of rentals allowed per movie. an administrator should be able to see the history of each user, and ban them if they misbehave. each user should be able to log in with their email (no password needed, its a demo app), see the history of their movies, return the movies rented, and rent more movies. if the user is banned, we should show a funny meme, and ask them to perform a good deed to be unbanned. i also want to be able to manage the catalog from the web, via an admin panel (username admin, password admin123), so i can add, edit and remove movies.
Before implementing anything, investigate and propose a plan for me to review first.

@devin-ai-integration

Copy link
Copy Markdown
Author

🤖 Devin AI Engineer

I'll be helping with this pull request! Here's what you should know:

✅ I will automatically:

  • Address comments on this PR that start with 'DevinAI' or '@devin'.
  • Look at CI failures and help fix them

Note: I can only respond to comments from users who have write access to this repository.

⚙️ Control Options:

  • Disable automatic comment, CI, and merge conflict monitoring

@devin-ai-integration

Copy link
Copy Markdown
Author

E2E validation on Okteto (namespace devin-multiuser-rentals)

Deployed this branch with okteto deploy --wait and validated it two ways:

  • API suite: okteto test e2e — 8/8 passing (shared inventory, copy exhaustion, return restoring availability, history persistence, anonymous rejection, admin history/ban/redemption, catalog CRUD).
  • Browser pass: two concurrent user sessions + the admin panel, covering rent → shared inventory → sold-out → return → ban → meme → good deed → forgiveness → catalog add/edit/delete.

Two bugs found in the browser pass and fixed in abc265d:

  1. Rental history always showed $0.00 — the price came from the client and defaulted to 0. The worker now charges the catalog price (catalog.Lookup) and the price is gone from the Kafka payload; the e2e suite asserts it.
  2. A user banned mid-session kept seeing the store until a full reload — Store now re-fetches /me on mount and after each action.

Both re-verified in the browser after redeploying. Note that a tab still running the pre-deploy JS bundle keeps the old behavior until a hard reload — expected for a hashed static bundle.

rberrelleza and others added 4 commits August 14, 2026 02:57
Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant