Skip to content

Latest commit

 

History

History
414 lines (328 loc) · 13.1 KB

File metadata and controls

414 lines (328 loc) · 13.1 KB

StagePass — Codebase Guide

A complete guide to the StagePass codebase. Read this before touching anything.


What is StagePass?

StagePass is a movie ticket booking web app — think BookMyShow. Users can browse movies, pick a showtime, select seats, and make a booking. It is intentionally built with a number of bugs, security issues, and code quality problems as a workshop demo for AI-assisted SDLC improvement.


Tech Stack

Layer Technology
Frontend React 18, TypeScript, Tailwind CSS, Vite
Backend Node.js, Express, TypeScript
Database SQLite (via better-sqlite3)
Auth JWT (jsonwebtoken)
Monorepo pnpm workspaces
Shared types @stagepass/common package

Repository Structure

stagepass/
├── package.json               # Root — workspace scripts (dev, build, seed)
├── pnpm-workspace.yaml        # Declares packages/*  as workspaces
├── .gitignore
│
├── packages/
│   ├── common/                # Shared TypeScript types used by both server and web
│   │   └── src/index.ts
│   │
│   ├── server/                # Express REST API
│   │   ├── package.json
│   │   ├── tsconfig.json
│   │   ├── stagepass.db       # SQLite database file (generated by seed)
│   │   └── src/
│   │       ├── index.ts       # App entry — mounts routes, starts server on :3001
│   │       ├── db.ts          # DB connection + schema creation
│   │       ├── seed.ts        # Seeds 25 movies, 880+ showtimes, seats, users
│   │       └── routes/
│   │           ├── auth.ts        # POST /api/auth/login, /signup + JWT middleware
│   │           ├── movies.ts      # GET /api/movies, /movies/:id, /movies/meta/genres
│   │           ├── showtimes.ts   # GET /api/showtimes/movie/:id, /showtimes/:id
│   │           ├── seats.ts       # GET /api/seats/showtime/:id
│   │           └── bookings.ts    # GET /api/bookings, POST /api/bookings
│   │
│   └── web/                   # React frontend
│       ├── package.json
│       ├── tsconfig.json
│       ├── vite.config.ts     # Proxies /api → localhost:3001
│       ├── tailwind.config.js
│       ├── index.html
│       └── src/
│           ├── main.tsx           # React root
│           ├── App.tsx            # Router, Header, AuthProvider wiring
│           ├── AuthContext.tsx    # Auth state: user, token, login, signup, logout
│           ├── index.css          # Global styles (intentionally ugly)
│           └── pages/
│               ├── Home.tsx               # Movie grid + search + genre filter
│               ├── MovieDetail.tsx        # Movie info + showtime picker
│               ├── SeatSelection.tsx      # Interactive seat grid
│               ├── BookingConfirmation.tsx # Post-booking screen
│               ├── MyBookings.tsx         # User's booking history
│               └── Login.tsx              # Login / signup form
│
└── docs/
    ├── CODE_BASE_GUIDE.md          # This file
    ├── ISSUES_IN_THE_CODEBASE.md   # Catalogue of 33 known issues
    ├── PLAN_OF_ACTION.md           # High-level 5-phase improvement plan
    └── INCREMENTAL_CHANGE_PLAN.md  # Step-by-step commit plan with test specs

Getting Started

Prerequisites

  • Node.js ≥ 18
  • pnpm (npm install -g pnpm)

Install dependencies

pnpm install

Seed the database

pnpm seed
# Creates packages/server/stagepass.db with:
# - 25 movies (real posters via TMDB)
# - 880+ showtimes across 10 venues over 8 days
# - 75,000+ seats
# - 3 users
# - 6 pre-existing bookings

Run the app

pnpm dev
# Web:  http://localhost:3000
# API:  http://localhost:3001

Run packages individually

pnpm dev:server    # API only
pnpm dev:web       # Frontend only

Demo credentials

Email:    john@example.com
Password: password123

Database Schema

The SQLite database is created automatically on first run by packages/server/src/db.ts.

┌─────────┐       ┌───────────┐       ┌───────────┐
│  users  │       │  movies   │       │ showtimes │
│─────────│       │───────────│       │───────────│
│ id      │       │ id        │──────▶│ movie_id  │
│ name    │       │ title     │       │ id        │
│ email   │       │ genre     │       │ date      │
│ password│       │ duration  │       │ time      │
└─────────┘       │ rating    │       │ venue     │
     │            │ poster_url│       │ price     │
     │            │ synopsis  │       │ total_seats│
     │            │ director  │       └───────────┘
     │            │ cast_mbrs │             │
     │            │ release_dt│         ┌──▼────┐
     │            │ language  │         │ seats │
     │            └───────────┘         │───────│
     │                                  │ id    │
     │         ┌──────────┐             │ show..│
     └────────▶│ bookings │             │ row   │
               │──────────│             │ number│
               │ id       │             │is_book│
               │ user_id  │             └───────┘
               │ showtime_│
               │ seats    │  ← comma-separated seat IDs e.g. "A1,A2,A3"
               │ total_amt│
               │ book_date│
               └──────────┘

Key design decisions (and their problems)

  • seats in bookings is stored as a comma-separated string ("A1,A2,A3") — not a join table. This makes querying which seats belong to a booking cumbersome.
  • password is stored as plain text — no hashing. (Security issue 3.1)
  • rating and synopsis and cast_members are nullable — the app doesn't handle nulls. (Crash bug 1.1)

API Reference

Base URL: http://localhost:3001

All protected routes require: Authorization: Bearer <token>

Auth

Method Endpoint Auth Description
POST /api/auth/login No Login with email + password. Returns { token, user }
POST /api/auth/signup No Create account. Returns { token, user }

Login request body:

{ "email": "john@example.com", "password": "password123" }

Token payload:

{ "userId": 1, "iat": 1234567890, "exp": 1234654290 }

Movies

Method Endpoint Auth Description
GET /api/movies No All movies. Supports ?genre=Action and ?search=inception
GET /api/movies/:id No Single movie by ID
GET /api/movies/meta/genres No List of distinct genres

Movie object:

{
  "id": 1,
  "title": "Inception",
  "genre": "Sci-Fi",
  "duration": 148,
  "rating": 8.8,
  "poster_url": "https://image.tmdb.org/t/p/w500/...",
  "synopsis": "A thief who...",
  "director": "Christopher Nolan",
  "cast_members": "Leonardo DiCaprio, Joseph Gordon-Levitt",
  "release_date": "2010-07-16",
  "language": "English"
}

Showtimes

Method Endpoint Auth Description
GET /api/showtimes/movie/:movieId No All showtimes for a movie, ordered by date + time
GET /api/showtimes/:id No Single showtime by ID

Showtime object:

{
  "id": 42,
  "movie_id": 1,
  "date": "2026-02-20",
  "time": "7:00 PM",
  "venue": "PVR IMAX - Forum Mall",
  "price": 350,
  "total_seats": 120
}

Seats

Method Endpoint Auth Description
GET /api/seats/showtime/:showtimeId No All seats for a showtime

Seat object:

{
  "id": 1201,
  "showtime_id": 42,
  "row": "D",
  "number": 5,
  "is_booked": 0
}

is_booked is 0 (available) or 1 (booked). SQLite stores booleans as integers.


Bookings

Method Endpoint Auth Description
GET /api/bookings Yes All bookings for the logged-in user
POST /api/bookings Yes Create a new booking

POST request body:

{
  "showtimeId": 42,
  "seatIds": [1201, 1202, 1203],
  "totalAmount": 1050
}

GET response (with joined movie + showtime data):

{
  "id": 7,
  "showtime_id": 42,
  "seats": "1201,1202,1203",
  "total_amount": 1050,
  "booking_date": "2026-02-18T11:45:00",
  "movie_title": "Inception",
  "venue": "PVR IMAX - Forum Mall",
  "show_date": "2026-02-20",
  "show_time": "7:00 PM"
}

Frontend Architecture

Routing

Defined in App.tsx. All routes are public except My Bookings, which redirects to /login if the user is not authenticated (handled inside the component, not via a route guard).

/                         → Home.tsx
/movie/:id                → MovieDetail.tsx
/seats/:showtimeId        → SeatSelection.tsx
/booking-confirmation/:id → BookingConfirmation.tsx
/bookings                 → MyBookings.tsx
/login                    → Login.tsx

Auth Flow

Managed by AuthContext.tsx. It exposes { user, token, login, signup, logout } to the entire app via React Context.

User submits login form
  → Login.tsx calls login(email, password)
  → AuthContext POSTs to /api/auth/login
  → On success: sets user + token in React state
  → token is NOT persisted to localStorage (bug — page refresh logs you out)

Protected API calls pass the token in the Authorization header:

headers: { 'Authorization': `Bearer ${token}` }

Data Fetching

There is no data-fetching library (no React Query, no SWR). Every page calls fetch() directly in a useEffect. There is no caching, no retry logic, and no error handling.


Known Bugs (quick reference)

Full details in docs/ISSUES_IN_THE_CODEBASE.md.

# File Bug Trigger
1 MovieDetail.tsx:L44 cast_members.split() on null → white screen crash Click "Untitled Project X"
2 SeatSelection.tsx:L52-56 selectedSeats.splice() mutates state directly → seats don't deselect visually Select a seat, then click it again
3 MyBookings.tsx:L29-33 setLoading(false) only called if data.length > 0 → infinite spinner Log in as a new user with no bookings
4 Home.tsx:L44-49 while(Date.now() - start < 2000) blocks main thread → UI freezes 2s Type anything in the search box

Seed Data

The seed script (packages/server/src/seed.ts) populates the database with realistic data.

Movies (25 total): Real films with TMDB poster images. Mix of English, Hindi, Telugu, Korean, Japanese language films. One movie ("Untitled Project X") intentionally has null for rating, synopsis, and cast_members to trigger crash bugs.

Showtimes: Each movie gets 3–6 shows per day across 10 venues over 8 days. Venues include INOX, PVR IMAX, Cinepolis, Regal, and Miraj. Prices range from ₹150 to ₹650. IMAX screens have 120 seats; standard screens have 80.

Seats: Every showtime has a full seat grid: rows A–H (standard) or A–L (IMAX), 10 seats per row. ~15–35% of seats are randomly pre-booked.

Users:

john@example.com  / password123  (has 4 pre-existing bookings)
jane@example.com  / password123  (has 2 pre-existing bookings)
raj@example.com   / password123  (has no bookings — triggers infinite spinner bug)

Warning: Running pnpm seed deletes all existing data first. Do not run against a database with real bookings.


Common Tasks

Add a new API route

  1. Create packages/server/src/routes/myroute.ts
  2. Define a Router and export it as default
  3. Mount it in packages/server/src/index.ts:
    import myRouter from './routes/myroute';
    app.use('/api/myroute', myRouter);

Add a new page

  1. Create packages/web/src/pages/MyPage.tsx
  2. Add a route in App.tsx:
    <Route path="/my-page" element={<MyPage />} />

Add a shared type

  1. Add the interface to packages/common/src/index.ts
  2. Import it in either package:
    import type { MyType } from '@stagepass/common';

Reset the database

rm packages/server/stagepass.db
pnpm seed

Scripts Reference

Command What it does
pnpm install Install all workspace dependencies
pnpm dev Start server (:3001) + web (:3000) in parallel
pnpm dev:server Start API server only
pnpm dev:web Start frontend only
pnpm build Build all packages
pnpm seed Wipe DB and reseed with fresh data