PHP · MySQL · HTML/CSS/JS · IS351, USP · Group 25
A cinema booking system with three roles: admin, staff, and customer. The brief was registration, schedules, seats, payment-facing dashboards, and access that stays inside each role.
What it does
Customers register, pick a showing, and book a seat if it is free. They see their own bookings. Staff and admin get separate dashboards for movies, schedules, and users. Nobody uses a screen that is not theirs.
Roles
Admin — users, movies, schedules, bookings.
Staff — day-to-day movie and schedule work.
Customer — register, book, view own tickets.
Stack
PHP 8, MySQL then PostgreSQL on Render, HTML/CSS/JavaScript. Composer for the Google client and PHPMailer. Local work ran in VS Code on PHP’s built-in server.
Security the course asked for
Passwords hashed. Prepared statements on every query. CSRF tokens. Input cleaned before it hits the page. Sessions regenerated and timed out. Cookies HttpOnly, Secure, SameSite. XSS escaped with htmlspecialchars.
Login is local or Google OAuth. Email OTP sits on top as 2FA. Roles follow least privilege.
Build order
Database first. Then registration, login, RBAC, booking, Google login, 2FA. Security work followed the IS351 lab sequence, not a polish pass at the end.






What broke
- The PHP server needed
-t publicor paths died. - Google’s redirect URI had to match the callback exactly.
- Gmail would not send OTP until an app password was set.
- Render did not see PHP until
render.yamland a Procfile were in the repo.
What I would keep
One role per dashboard. Prepared statements from the first query. OAuth as an extra door, not the only door.