CineSuper Weekend Task Jain School of Future Technology 📊 Task Status
Database Management Systems · Compulsory Weekend Task

Build CineSuper,
your own mini OTT platform

Design a real PostgreSQL database on Supabase, connect it to a movie website, and deploy it live on GitHub Pages. This is a compulsory weekend task: complete every phase at home over the weekend, in order, and push at every checkpoint.

🗄️ Supabase · PostgreSQL 🌐 HTML · CSS · JavaScript 🚀 GitHub Pages 🔐 Row Level Security
13Phases (0–12)
8Push checkpoints
~7 hrsOver one weekend
25Marks
Overview

What you will build

CineSuper is a movie catalogue like a mini Netflix. Every student builds it with their own database and their own GitHub repo, and deploys it at a public link.

01

Browse

Grid of movies with poster, year, language and genre.

02

Search & filter

Search by title, filter by genre.

03

Details & reviews

Open a movie to see its description and reviews.

04

Write reviews

Add a 1 to 5 star review saved to the database.

Tech stack

LayerToolWhy
DatabaseSupabase (PostgreSQL)Real SQL database in the browser, free, nothing to install
FrontendHTML, CSS, JavaScriptNo frameworks, no build step
Code & hostingGitHub + GitHub PagesVersion history and a free public URL

Learning outcomes

  • Design tables with primary keys, foreign keys and CHECK constraints
  • Write DDL (CREATE, ALTER), DML (INSERT, UPDATE, DELETE) and DCL (GRANT)
  • Query with JOIN, GROUP BY, HAVING, subqueries and views
  • Secure data with Row Level Security (RLS)
  • Connect a web page to a cloud database and deploy it publicly

Timeline

Saturday

Phases 0–6 · ~3.5 hrs

Git and VS Code setup, repo, database, security, queries

Sunday morning

Phases 7–11 · ~2 hrs

Frontend, testing, GitHub Pages, README

Sunday evening

Phase 12 · ~1.5 hrs

Personalisation and final submission

Read first

Completion rules

Compulsory weekend task. No push, no marks.Every student must complete all phases at home over the weekend. There is no lab session for this project, and work that exists only on your laptop or in Supabase is not evaluated.
Working on your ownRead each step fully before doing it, use Troubleshooting when something breaks, and start on Saturday so you have time to recover from mistakes.
  • Every student has their own Supabase project and their own public GitHub repo named cinesuper-<jsoft-id>.
  • Every Push checkpoint in this guide must appear as a separate commit in your repo history (8 in total).
  • “Push” means saving a commit with Git and sending it to GitHub from the VS Code terminal. Phase 0 shows how to install Git and VS Code and gives the push routine you repeat at every checkpoint.
  • The live link must load movies from your Supabase project on the evaluation day.
  • The site footer must show your name, JSOFT ID and Jain School of Future Technology.
  • Copied repos (same data, same history, no personalisation) get zero.

Use the My push progress panel on the left to tick off each checkpoint as you push it. It is saved in your browser.

Phase 0 · Setup

Accounts, Git, VS Code and your repo

You set up your tools and your repo first, so every later phase is saved as a commit and pushed to GitHub from the VS Code terminal. Do this once; it takes about 45 minutes.

Step 0.1 — GitHub account

  1. Go to github.com and click Sign up.
  2. Use your college email and a professional username, for example ravi-cse24.
  3. Verify your email.

Step 0.2 — Supabase account

  1. Go to supabase.com and click Start your project.
  2. Choose Continue with GitHub and authorise it.

Step 0.3 — Install VS Code

  1. Go to code.visualstudio.com and download the installer for your computer.
  2. Windows: run the installer and accept the defaults. On the Select Additional Tasks screen tick Add "Open with Code" action and Add to PATH.
    Mac: open the downloaded zip and drag Visual Studio Code into Applications.
  3. Open VS Code. Click the Extensions icon on the left (four squares), search Live Server (by Ritwick Dey) and click Install. You will use it in Phase 9.

Step 0.4 — Install Git

WINDOWS

Git for Windows

Download from git-scm.com/downloads/win (64-bit installer) and run it. Click Next on every screen, except:
• Default editor: choose Use Visual Studio Code as Git's default editor
• Initial branch name: choose Override and type main

MAC

Command Line Tools

Open the Terminal app, type git --version and press Enter. If Git is missing, a window asks to install the Command Line Tools: click Install and wait. Run git --version again.

Restart VS Code after installing GitClose every VS Code window and open it again, otherwise its terminal will not find Git.

Step 0.5 — Open the VS Code terminal and tell Git who you are

  1. In VS Code, open the menu Terminal → New Terminal (shortcut Ctrl + `, the key below Esc). A terminal opens at the bottom.
  2. Check Git works. You should see a version number such as git version 2.47.0.
    Terminal
    git --version
  3. Set your name and the same email you used for GitHub. Type each line, change the values, press Enter. Nothing is printed when it works.
    Terminal (run once)
    git config --global user.name "Your Full Name"
    git config --global user.email "your-github-email@example.com"
    git config --global init.defaultBranch main
  4. Sign in to GitHub inside VS Code: click the Accounts icon (person, bottom-left) → Sign in with GitHub → allow it in the browser. VS Code will then handle your git push logins.

Step 0.6 — Create your repository on GitHub

  1. On GitHub click + (top right) → New repository.
  2. Name: cinesuper-<your-jsoft-id>, for example cinesuper-jsoft26451. All lowercase. Your JSOFT ID is the one shown next to your name in the submission form.
  3. Visibility: Public (GitHub Pages is free only for public repos).
  4. Tick Add a README file, then click Create repository.
  5. On the repo page click the green <> Code button → HTTPS tab → copy the URL. It looks like https://github.com/ravi-cse24/cinesuper-jsoft26451.git

Step 0.7 — Clone the repo to your laptop

“Clone” downloads your GitHub repo into a folder on your laptop that stays connected to GitHub.

  1. In the VS Code terminal, go to your Documents folder and clone. Paste your URL from Step 0.6 (right-click in the terminal to paste).
    Terminal
    cd ~/Documents
    git clone https://github.com/YOUR-USERNAME/cinesuper-YOUR-JSOFT-ID.git
    You should see Cloning into 'cinesuper-…'… done.
  2. Open the folder in VS Code: File → Open Folder… → Documents → cinesuper-<jsoft-id> → Select Folder / Open. If asked “Do you trust the authors?”, click Yes, I trust the authors.
  3. Open a fresh terminal (Terminal → New Terminal). It now starts inside your repo. Check it:
    Terminal
    git status
    It should say On branch main and Your branch is up to date with 'origin/main'.
Always work inside this folderEvery file in this project goes inside cinesuper-<jsoft-id>. Every Git command is run from a terminal opened in this folder. If Git says not a git repository, you are in the wrong folder.

Step 0.8 — How to create files and folders in VS Code

  1. In the Explorer panel (left, top icon) hover over your folder name and click the New File icon (page with +).
  2. Type the file name and press Enter. Typing a / creates a folder too: database/01_tables.sql creates the database folder and the file inside it.
  3. Paste the content and press Ctrl + S (Mac: Cmd + S) to save. An unsaved file shows a ● on its tab, and Git cannot see unsaved changes.
  4. To add screenshots, save or drag the image files into the screenshots folder in the Explorer.

By the end of the project your folder will look like this:

Repository structure
cinesuper-<jsoft-id>/
├── README.md
├── index.html
├── style.css
├── config.js
├── app.js
├── database/
│   ├── 01_tables.sql
│   ├── 02_seed.sql
│   ├── 03_security.sql
│   ├── 04_view.sql
│   ├── 05_queries.sql
│   └── 06_personalisation.sql
└── screenshots/

Step 0.9 — The push routine (use it at every checkpoint)

Every PUSH box in this guide means: save your files, then run these three commands in the VS Code terminal. Only the commit message changes, and each PUSH box gives you its exact message. For example, at PUSH 2 you run:

Terminal · push routine
git add .
# Use the exact message shown in each PUSH box
git commit -m "Phase 3: create tables"
git push
Do not type “Phase X” or copy this example at every checkpointYour 8 commit messages must match the PUSH boxes exactly (Phase 0: repo setup, Phase 3: create tables … Phase 12: personalisation). They are checked automatically, and a missing or wrong message counts as a missing checkpoint.
CommandWhat it does
git statusShows changed and new files (red = not added yet, green = added)
git add .Puts all changed files in this folder into the next commit (the dot means “everything here”)
git commit -m "…"Saves a snapshot on your laptop with a message
git pushSends your commits to GitHub
git log --onelineLists your commits (press q to exit)
First pushThe first time you run git push, VS Code or Git asks you to sign in to GitHub in the browser. Click Allow / Authorize. After that it remembers you.
PUSH 1

In VS Code create the empty file screenshots/.gitkeep (Git does not save empty folders, so this keeps the folder) and save it. Then run:

Terminal
git add .
git commit -m "Phase 0: repo setup"
git push

Refresh your repo on GitHub: you should see the screenshots folder.

Phase 1

Create your Supabase project

  1. On the Supabase dashboard click New project. If asked, create an Organization with your name on the Free plan.
  2. Fill in:
    • Project name: cinesuper-<your-jsoft-id>
    • Database password: Generate a password, copy and save it in a notepad file
    • Region: the one closest to India (Mumbai / South Asia if listed)
  3. Click Create new project and wait 1 to 2 minutes.
CheckpointThe project dashboard opens with a healthy status.
Free projects pauseSupabase pauses free projects after about a week without activity. If your site stops working, log in and click Restore project. Open your dashboard the day before evaluation.
Phase 2

Understand the database design

Three tables, two foreign keys: one genre has many movies, and one movie has many reviews.

TableStoresExample row
genresMovie categoriesAction
moviesMovie detailsDrishyam, 2013, Malayalam, 160 min
reviewsRatings and comments per movieAnjali gave Drishyam 5 stars

ER diagram

erDiagram
    GENRES ||--o{ MOVIES : "categorises"
    MOVIES ||--o{ REVIEWS : "receives"
    GENRES {
        bigint id PK
        text name "UNIQUE NOT NULL"
    }
    MOVIES {
        bigint id PK
        text title "NOT NULL"
        int release_year "CHECK 1900-2100"
        text language "NOT NULL"
        int duration_min "CHECK > 0"
        text description
        text poster_url
        bigint genre_id FK
        timestamptz created_at
    }
    REVIEWS {
        bigint id PK
        bigint movie_id FK
        text reviewer_name "NOT NULL"
        int rating "CHECK 1-5"
        text comment
        timestamptz created_at
    }
Record notebookDraw this ER diagram in your record. You will add your own column to it in Phase 12.
Phase 3 · DDL

Create the tables

  1. In Supabase, open SQL Editor from the left sidebar.
  2. Click New query.
  3. Paste Script 1 and click Run (or Ctrl + Enter).
database/01_tables.sql
genres
ColumnTypeRulesMeaning
idbigintPrimary key, auto-numberUnique number for each genre
nametextNot null, uniqueGenre name, e.g. Action
movies
ColumnTypeRulesMeaning
idbigintPrimary key, auto-numberUnique number for each movie
titletextNot nullMovie name
release_yearintBetween 1900 and 2100Year of release
languagetextNot nullMalayalam, Tamil, English …
duration_minintGreater than 0Running time in minutes
descriptiontextOptionalShort story summary
poster_urltextOptionalLink to the poster image
genre_idbigintForeign key → genres.id, set null on deleteWhich genre the movie belongs to
created_attimestamptzDefault now()When the row was added
reviews
ColumnTypeRulesMeaning
idbigintPrimary key, auto-numberUnique number for each review
movie_idbigintNot null, foreign key → movies.id, cascade on deleteWhich movie is reviewed
reviewer_nametextNot nullWho wrote the review
ratingintNot null, between 1 and 5Star rating
commenttextOptionalReview text
created_attimestamptzDefault now()When the review was written

One genre has many movies (genres.id → movies.genre_id). One movie has many reviews (movies.id → reviews.movie_id).

Checkpoint“Success. No rows returned.” Table Editor shows genres, movies and reviews.

What each keyword means

KeywordMeaning
generated always as identityAuto-increment id (1, 2, 3 …)
primary keyUnique identifier of each row
not nullValue is compulsory
uniqueNo two rows share the value
check (…)Only values meeting the condition are allowed
references genres(id)Foreign key to an existing genre
on delete set nullDeleting a genre leaves its movies with no genre
on delete cascadeDeleting a movie deletes its reviews
PUSH 2

In VS Code create database/01_tables.sql, paste Script 1 and save. Then run the push routine:

Terminal
git add .
git commit -m "Phase 3: create tables"
git push
Phase 4 · DML

Insert sample data

Open a new query, paste Script 2 and run it once.

database/02_seed.sql
genres · 6 rows
idname
1Action
2Drama
3Comedy
4Sci-Fi
5Thriller
6Biography
movies · 13 rows
idtitlerelease_yearlanguageduration_mingenre_iddescription
1Drishyam2013Malayalam1605 (Thriller)A cable operator goes to any length to protect his family after an accidental crime.
2Premam2015Malayalam1562 (Drama)George falls in love three times, from school days to adulthood.
3Bangalore Days2014Malayalam1713 (Comedy)Three cousins from Kerala chase their dreams in Bangalore.
4Minnal Murali2021Malayalam1581 (Action)A village tailor gets superpowers after being struck by lightning.
5Manjummel Boys2024Malayalam1355 (Thriller)Friends from Kochi race to rescue one of them trapped in the Guna Caves.
6Vikram2022Tamil1741 (Action)A special agent investigates masked murders linked to a drug cartel.
7962018Tamil1582 (Drama)Two school sweethearts meet again at a reunion, twenty-two years later.
8Jai Bhim2021Tamil1642 (Drama)A lawyer fights for justice for a tribal woman whose husband goes missing in custody.
9Memento2000English1135 (Thriller)A man with short-term memory loss hunts his wife's killer using notes and tattoos.
10The Dark Knight2008English1521 (Action)Batman faces the Joker, a criminal who wants to plunge Gotham into chaos.
11Inception2010English1484 (Sci-Fi)A thief who steals secrets through dreams is asked to plant an idea instead.
12Interstellar2014English1694 (Sci-Fi)Astronauts travel through a wormhole to find a new home for humanity.
13Oppenheimer2023English1806 (Biography)The story of J. Robert Oppenheimer and the creation of the atomic bomb.
reviews · 8 rows
idmovie_idreviewer_nameratingcomment
11 (Drishyam)Anjali5 ★★★★★Georgekutty is a genius!
21 (Drishyam)Arjun5 ★★★★★Best thriller ever.
32 (Premam)Sreya4 ★★★★Malar teacher steals the show.
45 (Manjummel Boys)Akhil5 ★★★★★Gripping survival story.
56 (Vikram)Fathima4 ★★★★Mass climax!
612 (Interstellar)Nikhil5 ★★★★★Mind-blowing science.
711 (Inception)Arjun4 ★★★★Need to watch it twice.
810 (The Dark Knight)Akhil5 ★★★★★The Joker is legendary.

poster_url and created_at are not shown. The id numbers match if you ran each script once; they can differ if you re-ran a script.

CheckpointTable Editor shows 6 genres, 13 movies (5 Malayalam, 3 Tamil, 5 Christopher Nolan) and 8 reviews.
Notice the subquery(select id from genres where name = 'Action') looks up IDs by name instead of hard-coding them.
PUSH 3

Create database/02_seed.sql with Script 2 and save. Then push:

Terminal
git add .
git commit -m "Phase 4: sample data"
git push
Phase 5 · DCL

Security: RLS and permissions

The website talks to the database from the browser with a public key. These rules let everyone read and add valid reviews, and stop anyone from updating or deleting.

database/03_security.sql
Row Level Security policies
PolicyTableActionWhoRule
Public can read genresgenresSELECTanon, authenticatedEveryone can read every row
Public can read moviesmoviesSELECTanon, authenticatedEveryone can read every row
Public can read reviewsreviewsSELECTanon, authenticatedEveryone can read every row
Public can add reviewsreviewsINSERTanon, authenticatedRating 1–5, name 1–50 characters, comment up to 500 characters
What the website (public key) is allowed to do
Tableanon / authenticated canCannot
genresSELECTINSERT, UPDATE, DELETE
moviesSELECTINSERT, UPDATE, DELETE
reviewsSELECT, INSERTUPDATE, DELETE

RLS is switched on for all three tables. Anything without a policy is blocked.

CheckpointFour policies in total appear under Authentication → Policies: one read policy each on genres and movies, and a read policy plus an insert policy on reviews.
Never disable RLSIf data does not show on the site, a policy or grant is missing. Re-run Script 3 instead of turning security off.
PUSH 4

Create database/03_security.sql with Script 3, save it, and put a screenshot of the policies page in screenshots/. Then push:

Terminal
git add .
git commit -m "Phase 5: RLS policies"
git push
Phase 6

View and practice queries

Step 6.1 — Average-rating view

database/04_view.sql
select * from movie_ratings order by avg_rating desc nulls last; · 13 rows
idtitleavg_ratingreview_count
1Drishyam5.02
5Manjummel Boys5.01
10The Dark Knight5.01
12Interstellar5.01
2Premam4.01
6Vikram4.01
11Inception4.01
3Bangalore DaysNULL0
4Minnal MuraliNULL0
796NULL0
8Jai BhimNULL0
9MementoNULL0
13OppenheimerNULL0

Movies with no reviews still appear (LEFT JOIN) with avg_rating = NULL and review_count = 0. Rows with the same rating may appear in a different order.

security_invoker = on makes the view obey the RLS rules of whoever queries it. LEFT JOIN keeps movies with zero reviews.

Step 6.2 — Part A: run these and screenshot each output

database/05_queries.sql
Q1 · Movies with genre name · 13 rows
titlerelease_yeargenre
Drishyam2013Thriller
Premam2015Drama
Bangalore Days2014Comedy
Minnal Murali2021Action
Manjummel Boys2024Thriller
Vikram2022Action
962018Drama
Jai Bhim2021Drama
Memento2000Thriller
The Dark Knight2008Action
Inception2010Sci-Fi
Interstellar2014Sci-Fi
Oppenheimer2023Biography
Q2 · Movies per genre · 6 rows
nametotal_movies
Action3
Drama3
Thriller3
Sci-Fi2
Comedy1
Biography1
Q3 · Longer than 150 minutes · 10 rows
titleduration_min
Drishyam160
Premam156
Bangalore Days171
Minnal Murali158
Vikram174
96158
Jai Bhim164
The Dark Knight152
Interstellar169
Oppenheimer180
Q4 · Malayalam movies after 2015 · 2 rows
titlerelease_year
Minnal Murali2021
Manjummel Boys2024
Q5 · Average rating at least 4.5 · 4 rows
titleavg_rating
Drishyam5.0
Manjummel Boys5.0
The Dark Knight5.0
Interstellar5.0
Q6 · Movies with no reviews · 6 rows
title
Bangalore Days
Minnal Murali
96
Jai Bhim
Memento
Oppenheimer
Q7 · Longest movie · 1 row
titleduration_min
Oppenheimer180
Q8 · Update a description
result
UPDATE 1 — Interstellar's description is changed. Run select title, description from movies where title = 'Interstellar'; to see it.

Row order can differ for queries without ORDER BY, and for ties. The values must match.

Step 6.3 — Part B: write your own (mandatory)

  1. All reviews with the movie title (JOIN).
  2. Number of movies per language.
  3. The newest movie in the database.
  4. Movies whose title starts with M (LIKE).
  5. Delete one review; screenshot the table before and after.
PUSH 5

Create database/04_view.sql (Script 4) and database/05_queries.sql (Q1–Q8 + your Part B), and put all query screenshots in screenshots/. Then push:

Terminal
git add .
git commit -m "Phase 6: view and queries"
git push
Phase 7

Get your API keys

  1. In Supabase, click Connect at the top of the project dashboard, or open Project Settings → API Keys.
  2. Copy the Project URL, like https://abcdxyz.supabase.co.
  3. Copy the Publishable key (older projects call it the anon public key).
  4. Save both in your notepad file.
Only the public keyThe publishable/anon key is safe in frontend code because RLS protects the data. Never put the secret or service_role key in your website.
Phase 8

Build the frontend

In VS Code, with your cinesuper-<jsoft-id> repo folder open, create these four files at the top level of the folder (next to README.md). Names are case-sensitive.

Step 8.1 — config.js (your own keys)

config.js
// Replace both values with YOUR Supabase details from Phase 7
const SUPABASE_URL = "https://YOUR-PROJECT-ID.supabase.co";
const SUPABASE_KEY = "YOUR-PUBLISHABLE-OR-ANON-KEY";

Step 8.2 — index.html (edit the footer with your name and JSOFT ID)

index.html
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>CineSuper - Mini OTT</title>
  <link rel="stylesheet" href="style.css">
</head>
<body>

  <header>
    <h1>🎬 CineSuper</h1>
    <p>Your mini OTT movie library</p>
  </header>

  <section class="controls">
    <input id="search" type="text" placeholder="🔍 Search movies...">
    <select id="genreFilter">
      <option value="">All Genres</option>
    </select>
  </section>

  <p id="status">Loading movies...</p>

  <main id="movieGrid" class="grid"></main>

  <!-- Movie detail popup -->
  <div id="modal" class="modal hidden">
    <div class="modal-content">
      <button id="closeModal" class="close">&times;</button>
      <div id="movieDetail"></div>

      <h3>Reviews</h3>
      <div id="reviewList"></div>

      <h3>Add your review</h3>
      <form id="reviewForm">
        <input id="reviewerName" type="text" placeholder="Your name" maxlength="50" required>
        <select id="reviewRating" required>
          <option value="">Select rating</option>
          <option value="5">⭐⭐⭐⭐⭐ (5)</option>
          <option value="4">⭐⭐⭐⭐ (4)</option>
          <option value="3">⭐⭐⭐ (3)</option>
          <option value="2">⭐⭐ (2)</option>
          <option value="1">⭐ (1)</option>
        </select>
        <textarea id="reviewComment" placeholder="Write your comment" maxlength="500" required></textarea>
        <button type="submit">Submit Review</button>
      </form>
    </div>
  </div>

  <footer>
    <p>Built by <strong>YOUR NAME</strong> · JSOFT ID: <strong>YOUR JSOFT ID</strong></p>
    <p>Jain School of Future Technology · Faculty: Sathish Kumar M</p>
    <p class="powered">Powered by Supabase</p>
  </footer>

  <!-- Order matters: 1) Supabase library  2) your keys  3) your app -->
  <script src="https://cdn.jsdelivr.net/npm/@supabase/supabase-js@2"></script>
  <script src="config.js"></script>
  <script src="app.js"></script>
</body>
</html>

Step 8.3 — style.css

style.css
* { box-sizing: border-box; margin: 0; padding: 0; }

body {
  font-family: "Segoe UI", Arial, sans-serif;
  background: #0f0f14;
  color: #f1f1f1;
  min-height: 100vh;
}

header { text-align: center; padding: 30px 16px 10px; }
header h1 { font-size: 2.4rem; color: #e50914; }
header p  { color: #aaa; margin-top: 4px; }

.controls {
  display: flex; gap: 12px; justify-content: center;
  flex-wrap: wrap; padding: 16px;
}
.controls input, .controls select {
  padding: 10px 14px; border-radius: 8px;
  border: 1px solid #333; background: #1c1c24; color: #fff;
  font-size: 1rem; min-width: 220px;
}

#status { text-align: center; color: #888; margin: 6px 0 12px; }

.grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(170px, 1fr));
  gap: 18px; padding: 0 24px 40px;
  max-width: 1200px; margin: 0 auto;
}

.card {
  background: #1c1c24; border-radius: 10px; overflow: hidden;
  cursor: pointer; transition: transform 0.2s, box-shadow 0.2s;
}
.card:hover { transform: scale(1.04); box-shadow: 0 8px 20px rgba(229, 9, 20, 0.35); }
.card img { width: 100%; aspect-ratio: 2 / 3; object-fit: cover; display: block; }
.card-body { padding: 10px; }
.card-body h3 { font-size: 1rem; margin-bottom: 4px; }
.card-body p  { font-size: 0.8rem; color: #aaa; }
.stars { color: #fbbf24 !important; margin-top: 4px; }

.modal {
  position: fixed; inset: 0; background: rgba(0, 0, 0, 0.8);
  display: flex; align-items: center; justify-content: center;
  padding: 16px; z-index: 10;
}
.hidden { display: none; }
.modal-content {
  background: #1c1c24; border-radius: 12px;
  max-width: 600px; width: 100%; max-height: 90vh; overflow-y: auto;
  padding: 24px; position: relative;
}
.close {
  position: absolute; top: 10px; right: 14px;
  background: none; border: none; color: #fff; font-size: 1.8rem; cursor: pointer;
}
.modal-content h2 { color: #e50914; margin-bottom: 6px; }
.modal-content h3 { margin: 20px 0 10px; }
.meta { color: #aaa; font-size: 0.9rem; margin-bottom: 10px; }

.review { background: #26262f; padding: 10px 12px; border-radius: 8px; margin-bottom: 8px; }
.review strong { color: #fbbf24; }
.review small  { color: #777; display: block; margin-top: 4px; }

#reviewForm { display: flex; flex-direction: column; gap: 10px; }
#reviewForm input, #reviewForm select, #reviewForm textarea {
  padding: 10px; border-radius: 8px;
  border: 1px solid #333; background: #0f0f14; color: #fff; font-size: 0.95rem;
}
#reviewForm textarea { min-height: 70px; resize: vertical; }
#reviewForm button {
  padding: 12px; border: none; border-radius: 8px;
  background: #e50914; color: #fff; font-weight: bold; cursor: pointer;
}
#reviewForm button:hover { background: #b20710; }

footer {
  text-align: center; padding: 20px; line-height: 1.7;
  color: #888; font-size: 0.85rem; border-top: 1px solid #222;
}
footer strong { color: #e5e5e5; }
footer .powered { color: #555; font-size: 0.75rem; }

Step 8.4 — app.js (connects the page to Supabase)

app.js
// 1. Connect to Supabase
const db = supabase.createClient(SUPABASE_URL, SUPABASE_KEY);

// 2. Page elements
const grid        = document.getElementById("movieGrid");
const statusEl    = document.getElementById("status");
const searchInput = document.getElementById("search");
const genreFilter = document.getElementById("genreFilter");
const modal       = document.getElementById("modal");
const movieDetail = document.getElementById("movieDetail");
const reviewList  = document.getElementById("reviewList");
const reviewForm  = document.getElementById("reviewForm");

let movies = [];
let ratingsMap = {};
let currentMovieId = null;

// Stops users injecting HTML/JS through reviews (safe in text and attributes)
function escapeHtml(text) {
  return String(text ?? "")
    .replace(/&/g, "&amp;")
    .replace(/</g, "&lt;")
    .replace(/>/g, "&gt;")
    .replace(/"/g, "&quot;")
    .replace(/'/g, "&#39;");
}

function showError(error) {
  console.error(error);
  statusEl.textContent = "❌ Error: " + error.message;
}

// 3. SELECT id, name FROM genres ORDER BY name
async function loadGenres() {
  const { data, error } = await db.from("genres").select("id, name").order("name");
  if (error) return showError(error);
  data.forEach((g) => {
    const opt = document.createElement("option");
    opt.value = g.id;
    opt.textContent = g.name;
    genreFilter.appendChild(opt);
  });
}

// 4. SELECT * FROM movie_ratings (the view)
async function loadRatings() {
  const { data, error } = await db.from("movie_ratings").select("id, avg_rating, review_count");
  if (error) return console.error(error);
  ratingsMap = {};
  data.forEach((r) => (ratingsMap[r.id] = r));
}

// 5. SELECT movies JOIN genres, with search and filter
async function loadMovies() {
  statusEl.textContent = "Loading movies...";

  let query = db
    .from("movies")
    .select("id, title, release_year, language, duration_min, description, poster_url, genres(name)")
    .order("release_year", { ascending: false });

  const search = searchInput.value.trim();
  const genreId = genreFilter.value;
  if (search)  query = query.ilike("title", `%${search}%`);
  if (genreId) query = query.eq("genre_id", genreId);

  const { data, error } = await query;
  if (error) return showError(error);

  movies = data;
  statusEl.textContent = data.length ? `${data.length} movie(s) found` : "No movies found 😕";
  grid.innerHTML = data.map(movieCard).join("");
  document.querySelectorAll(".card").forEach((card) =>
    card.addEventListener("click", () => openMovie(Number(card.dataset.id)))
  );
}

function movieCard(m) {
  const r = ratingsMap[m.id];
  const stars = r && r.avg_rating ? `⭐ ${r.avg_rating} (${r.review_count})` : "No ratings yet";
  return `
    <div class="card" data-id="${m.id}">
      <img src="${escapeHtml(m.poster_url)}" alt="${escapeHtml(m.title)}">
      <div class="card-body">
        <h3>${escapeHtml(m.title)}</h3>
        <p>${m.release_year} · ${escapeHtml(m.language)} · ${escapeHtml(m.genres?.name)}</p>
        <p class="stars">${stars}</p>
      </div>
    </div>`;
}

// 6. Movie detail popup
async function openMovie(id) {
  const m = movies.find((x) => x.id === id);
  if (!m) return;
  currentMovieId = id;
  movieDetail.innerHTML = `
    <h2>${escapeHtml(m.title)}</h2>
    <p class="meta">${m.release_year} · ${escapeHtml(m.language)} · ${escapeHtml(m.genres?.name)} · ${m.duration_min} min</p>
    <p>${escapeHtml(m.description)}</p>`;
  modal.classList.remove("hidden");
  await loadReviews(id);
}

// 7. SELECT * FROM reviews WHERE movie_id = ?
async function loadReviews(movieId) {
  reviewList.innerHTML = "<p>Loading reviews...</p>";
  const { data, error } = await db
    .from("reviews")
    .select("reviewer_name, rating, comment, created_at")
    .eq("movie_id", movieId)
    .order("created_at", { ascending: false });

  if (error) {
    reviewList.innerHTML = `<p>Error: ${escapeHtml(error.message)}</p>`;
    return;
  }
  reviewList.innerHTML = data.length
    ? data.map((r) => `
        <div class="review">
          <strong>${"⭐".repeat(r.rating)}</strong> — ${escapeHtml(r.reviewer_name)}
          <p>${escapeHtml(r.comment)}</p>
          <small>${new Date(r.created_at).toLocaleString()}</small>
        </div>`).join("")
    : "<p>No reviews yet. Be the first!</p>";
}

// 8. INSERT INTO reviews
reviewForm.addEventListener("submit", async (e) => {
  e.preventDefault();
  const newReview = {
    movie_id: currentMovieId,
    reviewer_name: document.getElementById("reviewerName").value.trim(),
    rating: Number(document.getElementById("reviewRating").value),
    comment: document.getElementById("reviewComment").value.trim(),
  };
  const { error } = await db.from("reviews").insert(newReview);
  if (error) {
    alert("Could not save review: " + error.message);
    return;
  }
  reviewForm.reset();
  await loadReviews(currentMovieId);
  await loadRatings();
  await loadMovies();
});

// 9. Close popup
document.getElementById("closeModal").addEventListener("click", () => modal.classList.add("hidden"));
modal.addEventListener("click", (e) => { if (e.target === modal) modal.classList.add("hidden"); });
document.addEventListener("keydown", (e) => { if (e.key === "Escape") modal.classList.add("hidden"); });

// 10. Search (waits until typing stops) and genre filter
let typingTimer;
searchInput.addEventListener("input", () => {
  clearTimeout(typingTimer);
  typingTimer = setTimeout(loadMovies, 300);
});
genreFilter.addEventListener("change", loadMovies);

// 11. Start
async function init() {
  await loadGenres();
  await loadRatings();
  await loadMovies();
}
init();

JavaScript to SQL (learn this for the viva)

Supabase JavaScriptEquivalent SQL
db.from("genres").select("id, name")SELECT id, name FROM genres
.order("name")ORDER BY name
.select("…, genres(name)")JOIN genres ON genres.id = movies.genre_id
.ilike("title", "%drish%")WHERE title ILIKE '%drish%'
.eq("genre_id", 3)WHERE genre_id = 3
db.from("reviews").insert({…})INSERT INTO reviews (…) VALUES (…)
Phase 9

Test on your laptop

Test the Phase 8 files before you push them. Double-click index.html to open it in Chrome, or in VS Code install Live Server → right-click index.html → Open with Live Server.

  • 13 movies appear with posters
  • Genre dropdown lists 6 genres
  • Typing in shows Inception, Interstellar and Minnal Murali (search ignores case)
  • Clicking a movie opens the popup with its reviews
  • A submitted review appears immediately in the popup
  • The same review appears in Supabase → Table Editor → reviews
Something broken?Press F12 → Console, read the red error and check Troubleshooting.
PUSH 6

Check that index.html, style.css, config.js and app.js are at the top level of your repo folder (next to README.md, not inside database/). Then push:

Terminal
git add .
git commit -m "Phase 8: frontend"
git push
Phase 10

Deploy with GitHub Pages

  1. In your repo, open Settings → Pages (left sidebar).
  2. Under Build and deployment → Source, choose Deploy from a branch.
  3. Branch: main, folder: / (root) → Save.
  4. Wait 1 to 3 minutes and refresh. GitHub shows your live link: https://YOUR-USERNAME.github.io/cinesuper-YOUR-JSOFT-ID/
  5. Open the link on your phone and submit one review from the phone.
CheckpointMovies load on the live link, and the phone review appears in Supabase.
Phase 11

README with your live link

Open README.md in VS Code, replace everything with this, fill in your details and save.

README.md
# 🎬 CineSuper — Mini OTT Movie Database

**Live Demo:** https://YOUR-USERNAME.github.io/cinesuper-YOUR-JSOFT-ID/

**Student:** YOUR NAME | **JSOFT ID:** YOUR JSOFT ID
**Institution:** Jain School of Future Technology
**Course:** Database Management Systems | **Faculty:** Sathish Kumar M

## Tech Stack
- Supabase (PostgreSQL) – database
- HTML, CSS, JavaScript – frontend
- GitHub Pages – hosting

## Database
- genres (id, name)
- movies (id, title, release_year, language, duration_min, description, poster_url, genre_id → genres)
- reviews (id, movie_id → movies, reviewer_name, rating 1–5, comment, created_at)
- View: movie_ratings (average rating per movie)
- SQL scripts: see the `database/` folder

## Features
- Browse, search and filter movies
- Movie details with reviews
- Add a review (saved to the database)
- Row Level Security enabled

## My Personalisation
- New movies added: …
- New column: …
- Extra feature: …
PUSH 7

Save README.md, then push:

Terminal
git add .
git commit -m "Phase 11: README and live link"
git push
Phase 12 · Mandatory

Personalise your project

Every repo must be different. Unchanged copies get zero for this part.

12.1

Add 5 movies

Your own INSERT statements, with at least one new genre (Horror, Romance or Documentary).

12.2

Add a column

In the database and visible on the site.

12.3

Add a feature

Pick one from the table below.

12.4

New theme colour

Replace the #e50914 red in style.css.

12.2 — Column options

ColumnShow it
imdb_rating numeric(3,1)On the card
director textIn the popup
is_trending boolean default falseA 🔥 badge on trending cards
age_rating text check (age_rating in ('U','UA','A'))On the card
database/06_personalisation.sql
-- example
alter table movies add column director text;
update movies set director = 'Jeethu Joseph' where title = 'Drishyam';
update movies set director = 'Christopher Nolan'
where title in ('Memento', 'The Dark Knight', 'Inception', 'Interstellar', 'Oppenheimer');

Then add the column to the .select(...) in loadMovies() and display it in movieCard() or openMovie().

12.3 — Feature options

LevelFeature
EasyLanguage filter dropdown
EasySort by Newest / Oldest / Top Rated
MediumTop 5 Rated strip at the top (from the view)
Mediumwatchlist table + Add to Watchlist button
HardSupabase Auth: only logged-in users can review
HardReal posters uploaded to Supabase Storage
New tables need security tooFor the watchlist option, run alter table watchlist enable row level security;, add select and insert policies, and grant select, insert on watchlist to anon, authenticated;, just like Script 3 did for reviews. Without them you get permission denied or violates row-level security policy.
Auth optionTo allow only logged-in users to review, drop the "Public can add reviews" policy and recreate it with to authenticated instead of to anon, authenticated.
PUSH 8

Create database/06_personalisation.sql with your INSERT, ALTER and UPDATE statements, save your changed frontend files, add screenshots of the new features, and fill the README's My Personalisation section. Then push:

Terminal
git add .
git commit -m "Phase 12: personalisation"
git push
Completion

Push verification

A project counts as complete only when all 8 checkpoint commits are visible in the repo's history and the live link works. Open your repo → click commits above the file list to see it.

#Commit messageMust contain
1Phase 0: repo setupREADME.md, screenshots/
2Phase 3: create tablesdatabase/01_tables.sql
3Phase 4: sample datadatabase/02_seed.sql
4Phase 5: RLS policiesdatabase/03_security.sql + policies screenshot
5Phase 6: view and queries04_view.sql, 05_queries.sql + query screenshots
6Phase 8: frontendindex.html, style.css, config.js, app.js
7Phase 11: README and live linkREADME with live link, name, JSOFT ID
8Phase 12: personalisation06_personalisation.sql + updated frontend + screenshots

Deadlines

End of Saturday

Commits 1–5

Database, security and queries pushed. Commit dates are visible in your history.

Sunday afternoon

Commits 6–7

Frontend pushed and the live link works on your phone.

Final deadline

Commit 8 · Sunday night

All 8 commits by the deadline your faculty announces. Commits after it are not evaluated.

How faculty verifies in the next class (2 minutes per student)

  1. Open the repo's commit history: 8 phase commits present.
  2. Open the live link, add a test review, confirm it appears.
  3. Student shows that review in their own Supabase Table Editor.
  4. Footer shows name, JSOFT ID and Jain School of Future Technology.
  5. The Phase 12 column and feature are visible on the site.
Submit

Submission checklist

  • Live GitHub Pages link
  • GitHub repo link (cinesuper-<jsoft-id>, public)
  • All 8 checkpoint commits in the history
  • All SQL scripts in database/
  • Screenshots in screenshots/: tables, RLS policies, Q1–Q8, Part B, new movies, new column, extra feature
  • ER diagram including your new column, uploaded to screenshots/
  • README filled with name, JSOFT ID, live link and personalisation

Submit your project

When everything above is ready, submit your repo link and live link in the submission form. Submit only once. The form accepts one submission per JSOFT ID. After you submit, fixes you push to GitHub are picked up automatically, so you never need to submit again. If your repo or live link changes, tell your faculty.

📝 Open the submission form ↗

Automatic checkingEvery 30 minutes your submission is checked automatically: repo name, the 8 phase commits, required files, the live link, your JSOFT ID in the footer, and that your site's Supabase database has your 18+ movies. Results and marks appear on the Task Status page. If it shows Incomplete, fix the listed problems, push, and the status updates on the next check.
Evaluation

Rubric · 25 marks

ComponentMarks
Schema: tables, PK, FK, constraints4
Sample data + own 5 movies3
RLS policies + grants3
SQL queries Part A + Part B with outputs5
Live site works: browse, search, filter, review4
Personalisation: new column + extra feature4
All 8 commits, README, ER diagram2
Total25

A short viva will be held in the class after the weekend; marks can be reduced if you cannot explain your own code.

Help

Troubleshooting

ProblemLikely causeFix
Stuck on “Loading movies...”JavaScript errorF12 → Console, read the red error
supabase is not definedScript order wrong or offlineSupabase CDN script must come before config.js and app.js
SUPABASE_URL is not definedconfig.js missing or misnamedFile must be exactly config.js, at the repo root
Invalid API key / 401Wrong keyRe-copy the publishable/anon key, no extra spaces or quotes
Failed to fetchWrong URL or paused projectCheck the URL; restore the project in Supabase
“0 movies found” but data existsMissing policy or grantRe-run Script 3
permission denied for tableMissing GRANTRe-run the grant lines in Scripts 3 and 4
new row violates row-level security policyReview failed the policyRating must be 1 to 5, name 1 to 50 characters, comment at most 500 characters
relation "movie_ratings" does not existScript 4 not runRun Script 4
relation "genres" already existsScript 1 run twiceIgnore it; the tables are already there
duplicate key value violates unique constraint "genres_name_key"Script 2 run twiceIgnore it if Table Editor shows 6 genres and 13 movies; otherwise start over (below)
more than one row returned by a subqueryMovies inserted twice, so a title now matches two rowsStart over (below)
GitHub Pages shows 404index.html not at root, or too soonMove files to the root; wait 2 to 3 minutes
Site shows the old versionBrowser cacheCtrl + Shift + R

Git and GitHub problems

Message or problemLikely causeFix
'git' is not recognized / command not found: gitGit not installed, or VS Code opened before installing itInstall Git (Step 0.4), close all VS Code windows and open again
Please tell me who you areName and email not setRun the two git config --global lines from Step 0.5
fatal: not a git repositoryTerminal is not in your repo folderFile → Open Folder → your cinesuper-<jsoft-id> folder, then Terminal → New Terminal
nothing to commit, working tree cleanFiles not saved, or already committedSave with Ctrl + S (no ● on tabs) and check git status; if already committed, just git push
Authentication failed / 403 / Permission deniedNot signed in, or pushing to someone else's repoSign in via the Accounts icon in VS Code (Step 0.5), push from the VS Code terminal, and check the repo is yours with git remote -v
! [rejected] … (fetch first)You changed something on the GitHub websiteRun git pull, then git push
LF will be replaced by CRLFWindows line-ending noticeHarmless, ignore it
Wrong commit message (for example Phase X: … copied literally)The message is not the one in the PUSH boxBefore pushing: git commit --amend -m "Phase 0: repo setup". Already pushed: add the correct one with git commit --allow-empty -m "Phase 0: repo setup", then git push. Use the message of the checkpoint you missed.
A file is missing on GitHubIt is outside the repo folder or was not savedMove it into cinesuper-<jsoft-id> in VS Code Explorer, save, run the push routine again

Start over cleanly

If your data is duplicated or broken, run this in a new query. It deletes all CineSuper tables and data in your project. Then run Scripts 1, 2, 3 and 4 again, once each, in order.

reset (do not save in repo)
drop view  if exists movie_ratings;
drop table if exists reviews, movies, genres cascade;
Prepare

Viva questions

  1. Difference between a primary key and a foreign key, shown in your schema.
  2. What happens to reviews when a movie is deleted, and why?
  3. Why does the movie_ratings view use LEFT JOIN, not INNER JOIN?
  4. Difference between WHERE and HAVING.
  5. What is Row Level Security, and what happens if it is disabled?
  6. Why is the publishable key safe in frontend code but the service_role key is not?
  7. What is a view, and how does it differ from a table?
  8. What does check (rating between 1 and 5) do if you insert 6?
  9. Convert db.from("movies").select("*").eq("language","Malayalam") into SQL.
  10. Which normal form is your schema in? Justify.
  11. One example each of DDL, DML and DCL from your project.
  12. Trace a review from the browser form to the database row.