Database Systems Project — BCS-4B | NUCES Faisalabad
| Project Name | CampusBazaar |
| Course | Database Systems |
| Institution | FAST-NUCES, Faisalabad |
| Program | BS Computer Science — BCS-4B |
| Members | Ali Amir · Muhammad Zain |
| Database | PostgreSQL (hosted on Supabase) |
| Frontend | HTML · CSS · React (via CDN) |
| Deployment | Vercel |
- Project Overview
- Features & Capabilities
- Database Architecture
- UI Walkthrough
- Setup & Installation
- Running the Project
- Database Setup on Supabase
- Execution Guide
- Trigger Demonstrations
- Team Members
CampusBazaar is a full-stack web application designed as a marketplace exclusively for university campus members — students, faculty, and staff. It allows users to buy and sell everyday campus essentials such as textbooks, stationery, clothing, food, electronics, and utilities.
The core focus of this project is demonstrating real-world database concepts including:
- Relational schema design with proper normalisation
- IS-A inheritance pattern using shared primary keys
- Referential integrity through foreign key constraints
- Domain integrity through CHECK constraints
- Automated business logic using PL/pgSQL triggers
- Multi-table JOIN queries for data retrieval
- Row-level data filtering and sorting
This is not merely a demo — every user action (register, post listing, buy item, leave review) translates directly into real SQL operations executing on a live PostgreSQL database hosted on Supabase.
- Register as Student, Faculty, or Staff — each stored in their own sub-table
- IS-A inheritance: one master
userstable with three specialised child tables sharing the same primary key - Secure login that checks both email AND password against the database
- Separate admin login through the
adminstable - User profile page showing personal listing statistics pulled live from the database
- Post a listing with title, description, price, condition, category, and optional image URL
- Browse all active listings with real-time data from PostgreSQL
- Filter listings by category (Books, Food, Clothing, Utilities, Electronics, Stationery)
- Sort listings by newest, price ascending, or price descending
- Search listings by title or description
- Listing cards display condition badges (New / Like New / Used / Heavily Used)
- Sold listings visually greyed out and marked — cannot be purchased again
- Complete a purchase by selecting a meetup point
- Every purchase inserts a row into the
transactionstable with statuscompleted - Database trigger automatically marks the listing as
soldupon transaction completion - Transaction history page showing all purchases with buyer, listing, amount, and date
- Protection against buying your own listing — enforced at database level via trigger
- Protection against buying an already-sold listing — enforced at database level via trigger
- Leave a review only after a completed transaction — no fake reviews possible
- Star rating from 1 to 5 with an optional comment
- Reviews linked to transactions, not listings — full audit trail
- Reviews page displays all reviews with reviewer name, listing name, and date
- Tabbed view of all listings, all users, and all transactions
- Summary statistics: total listings, active listings, registered users, total revenue
- Real-time data pulled from the database with JOIN queries
- Status badges for at-a-glance monitoring
- Visual documentation of all 9 tables with column names, types, PK and FK labels
- All 4 PL/pgSQL trigger functions displayed with their actual SQL code
- Relationship map showing all foreign key connections between tables
- Designed specifically for instructor demonstration and academic review
ADMIN
|
moderates
|
USER ─────────────────────────── MESSAGE
| \──IS-A──► STUDENT
| \─IS-A──► FACULTY REVIEW
| \─IS-A─► STAFF |
| generates
│ (seller) |
└──────────► LISTING ──────► TRANSACTION ◄── (buyer) ── USER
|
belongs to
|
CATEGORY
| Table | Purpose | Key Constraint |
|---|---|---|
users |
Master table for all campus members | email UNIQUE |
students |
Student-specific details | Shared PK with users |
faculty |
Faculty-specific details | Shared PK with users |
staff |
Staff-specific details | Shared PK with users |
admins |
Platform administrators | Separate from users |
categories |
Listing categories | category_name UNIQUE |
listings |
Items for sale | price >= 0, status CHECK |
transactions |
Purchase records | amount >= 0, status CHECK |
reviews |
Post-purchase reviews | rating BETWEEN 1 AND 5 |
users (user_id PK)
├── students (user_id PK + FK → users)
├── faculty (user_id PK + FK → users)
└── staff (user_id PK + FK → users)
A student does not get a separate ID. Their user_id in the students table is the same as in users — this is the shared primary key pattern implementing IS-A inheritance in a relational database.
| Trigger | Event | Purpose |
|---|---|---|
trg_mark_listing_sold |
AFTER INSERT OR UPDATE ON transactions | Auto-marks listing as sold when transaction completes |
trg_prevent_self_purchase |
BEFORE INSERT ON transactions | Rejects purchase if buyer is the seller |
trg_prevent_duplicate_purchase |
BEFORE INSERT ON transactions | Rejects purchase if listing already sold |
trg_update_listing_timestamp |
BEFORE UPDATE ON listings | Auto-updates last_updated timestamp on any edit |
The landing page features a full-width hero section with the CampusBazaar branding, a live search bar, and three key statistics pulled from the database (active listings, items sold, categories). Below the hero, a horizontal category filter lets users browse by type. The listings grid displays all active items as cards, each showing the item emoji icon, condition badge, title, description, price, seller name and role, and a View button. The grid supports sorting and search filtering in real time.
The login page queries the admins table first, then the users table, matching on both email and password. If neither matches, an error message is shown in red — no automatic login for wrong credentials. The register page dynamically shows additional fields based on the selected role (degree program for students, department for faculty, role title for staff), then inserts into users and the appropriate sub-table in a single flow.
Clicking any listing card opens a full-detail modal showing the item image placeholder, category, title, price, condition and availability badges, full description, and a seller info card with name and role. If the item is available and the user is logged in, a meetup point input and Confirm Purchase button appear. Submitting the purchase triggers a real database INSERT into the transactions table, which fires the trigger that marks the listing as sold. The modal updates instantly.
A clean form with fields for title, description, price, category dropdown, condition dropdown, and optional image URL. Submitting the form inserts a new row into the listings table with the logged-in user's user_id as seller_id. Validation errors from the database (such as CHECK constraint violations) surface directly on screen.
Displays the logged-in user's name, role badge, email, and three statistics — active listings, sold listings, and total listings — all computed from a live database query filtered by seller_id. A refresh button re-fetches data from the database so sold items appear updated immediately after a transaction completes.
A full-width table showing every row in the transactions table, joined with listing titles and buyer names. Columns include transaction ID, listing, buyer, amount, meetup point, date, and status. This page directly demonstrates JOIN queries running on real data.
Shows all reviews from the reviews table joined with reviewer names and listing titles. Logged-in users who have completed transactions can submit a new review by selecting their transaction, choosing a star rating, and writing a comment. The review INSERT is rejected if the transaction ID does not belong to the reviewer — referential integrity in action.
A dedicated page for academic demonstration. Displays all 9 tables as visual cards with column names, data types, and PK/FK labels. Below the table grid, a relationships section maps all foreign key connections. Below that, all 4 trigger functions are shown with their actual PL/pgSQL source code and plain-English explanations of what each one does and when it fires.
Accessible only when logged in as admin. Shows four summary stat cards, then a tabbed table viewer for listings, users, and transactions. All data is fetched live from the database using JOIN queries. Status values display as colour-coded pills (green for active/completed, red for sold/cancelled, orange for pending).
You need the following before starting:
- A modern web browser (Chrome, Firefox, Edge)
- A Supabase account (free)
- A Vercel or Netlify account (free) for deployment
- No Node.js installation required — the project runs as a single HTML file
CampusBazaar/
├── index.html ← Complete frontend application
├── script.js
├── style.css
├── campusbazaar.sql ← Complete database setup script
└── README.md ← This file
- Go to supabase.com and sign in with GitHub
- Click New Project
- Fill in the following:
- Name:
campusbazaar - Database Password: choose a strong password and save it
- Region: Singapore or Mumbai (closest to Pakistan)
- Plan: Free
- Name:
- Click Create new project and wait 1-2 minutes for setup
- In your Supabase project, click SQL Editor in the left sidebar
- Click New Query
- Open
campusbazaar_complete.sqlin any text editor - Select all content (Ctrl+A) and copy it
- Paste into the Supabase SQL Editor
- Click Run (or press Ctrl+Enter)
- You should see a success message at the bottom
This single script does everything in order:
- Drops any existing tables (clean slate)
- Creates all 9 tables with constraints
- Disables Row Level Security on all tables
- Creates all 4 trigger functions and attaches the triggers
- Inserts seed data (users, categories, listings)
After running the script, run this verification query in a new SQL Editor tab:
SELECT 'users' AS tbl, COUNT(*) AS rows FROM users
UNION ALL
SELECT 'students', COUNT(*) FROM students
UNION ALL
SELECT 'faculty', COUNT(*) FROM faculty
UNION ALL
SELECT 'staff', COUNT(*) FROM staff
UNION ALL
SELECT 'admins', COUNT(*) FROM admins
UNION ALL
SELECT 'categories', COUNT(*) FROM categories
UNION ALL
SELECT 'listings', COUNT(*) FROM listings
UNION ALL
SELECT 'transactions',COUNT(*) FROM transactions
UNION ALL
SELECT 'reviews', COUNT(*) FROM reviews;Expected results:
| tbl | rows |
|---|---|
| users | 4 |
| students | 2 |
| faculty | 1 |
| staff | 1 |
| admins | 1 |
| categories | 6 |
| listings | 8 |
| transactions | 0 |
| reviews | 0 |
- In Supabase left sidebar, click Settings (gear icon)
- Click API
- Copy the following two values:
- Project URL — looks like
https://xxxxxxxxxxxxxxxx.supabase.co - anon public key — long JWT string starting with
eyJ...
- Project URL — looks like
Open index_fixed.html in any text editor. Find these two lines near the top of the <script> tag:
const SUPABASE_URL = 'YOUR_SUPABASE_URL_HERE';
const SUPABASE_KEY = 'YOUR_SUPABASE_ANON_KEY_HERE';Replace with your actual values:
const SUPABASE_URL = 'https://xxxxxxxxxxxxxxxx.supabase.co';
const SUPABASE_KEY = 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...';Save the file.
Simply open index_fixed.html in your browser. No server needed. No npm install. No build step. Double-click the file and it opens directly.
Note: Some browsers block external API calls from local files. If listings don't load, use the deployment method below instead.
- Go to netlify.com/drop
- Drag and drop
index_fixed.htmlonto the page - Netlify generates a live URL instantly — no account required
- Share the URL with your instructor
- Go to vercel.com and sign in
- Click Add New Project
- Instead of importing from GitHub, look for the drag-and-drop option
- If using GitHub: create a repo with only
index_fixed.htmlin the root, then:- Framework Preset: Other
- Build Command: (leave empty)
- Output Directory:
./
- Deploy
Follow these steps in order to demonstrate the complete system to your instructor:
1. Open the Schema Page first Navigate to the DB Schema tab in the navbar. Show your instructor the table cards, the relationship map, and the trigger source code. This establishes that the database design is complete and intentional.
2. Show the database in Supabase
Open Supabase Dashboard → Table Editor → click listings. Show the instructor the live rows in the database. Then show users and categories.
3. Register a new user
Click Join Free. Fill in all fields, select Student, enter a degree program. Click Create Account. Then open Supabase → users table — the new row appears instantly. Also check students table — the sub-table row also exists.
4. Post a listing
While logged in, click + List Item. Fill in title, price, condition, category. Submit. Open Supabase → listings table — the new row appears with status active.
5. Buy a listing Log out. Log in as a different user (e.g. Sara). Click any active listing. Enter a meetup point. Click Confirm Purchase. Then:
- Open Supabase →
transactionstable — new row with statuscompleted - Open Supabase →
listingstable — that listing's status changed tosold - This proves the trigger fired automatically
6. Demonstrate trigger protection
While logged in as Sara, try to buy the same listing again. The purchase button is disabled because status is sold. Explain that even if attempted via SQL directly, the trigger trg_prevent_duplicate_purchase would reject it.
7. Leave a review
Navigate to Reviews. Select your completed transaction from the dropdown. Give a star rating and comment. Submit. Open Supabase → reviews table — the new row appears.
8. Show the Admin Dashboard
Log out. Log in as admin@campusbazaar.pk with password admin123. Navigate to Admin in the navbar. Show the statistics cards and switch between the Listings, Users, and Transactions tabs. All data is live from the database.
9. Show Transaction History Navigate to Transactions in the navbar. Show the table with all purchase records, joined with listing and buyer information. Explain this is a multi-table JOIN query running on PostgreSQL.
Run this in Supabase SQL Editor to clear transactions and reset listings:
DELETE FROM reviews;
DELETE FROM transactions;
UPDATE listings SET status = 'active';Run these queries directly in Supabase SQL Editor to prove triggers work independently of the frontend:
INSERT INTO transactions (listing_id, buyer_id, amount, status, meetup_point)
VALUES (1, 2, 800.00, 'completed', 'Library Entrance');
-- Immediately verify listing is now sold
SELECT listing_id, title, status FROM listings WHERE listing_id = 1;
-- Expected: status = 'sold'INSERT INTO transactions (listing_id, buyer_id, amount, status, meetup_point)
VALUES (1, 2, 800.00, 'completed', 'Cafeteria');
-- Expected: ERROR — This listing is already sold!INSERT INTO transactions (listing_id, buyer_id, amount, status, meetup_point)
VALUES (2, 1, 150.00, 'completed', 'Block A');
-- Expected: ERROR — You cannot buy your own listing!-- Note the current last_updated
SELECT listing_id, price, last_updated FROM listings WHERE listing_id = 2;
-- Update the listing
UPDATE listings SET price = 100.00 WHERE listing_id = 2;
-- last_updated should be different now
SELECT listing_id, price, last_updated FROM listings WHERE listing_id = 2;SELECT
l.listing_id,
l.title,
l.price,
l.status,
c.category_name,
u.name AS seller_name,
u.role AS seller_role
FROM
listings l
JOIN categories c ON l.category_id = c.category_id
JOIN users u ON l.seller_id = u.user_id
ORDER BY
l.posted_date DESC;BS Computer Science — BCS-4B FAST-NUCES Faisalabad Roll No: 24F-0622
BS Computer Science — BCS-4B FAST-NUCES Faisalabad Roll No: 24F-0531
| Layer | Technology | Purpose |
|---|---|---|
| Database | PostgreSQL | Core relational database engine |
| Cloud DB Host | Supabase | PostgreSQL hosting + REST API + Dashboard |
| Stored Procedures | PL/pgSQL | Trigger functions for business logic |
| Frontend | React 18 (CDN) | UI component rendering |
| Styling | Pure CSS | Custom design system |
| Fonts | Google Fonts | Playfair Display + DM Sans |
| Deployment | Vercel | Static file hosting |
| Concept | Where Used |
|---|---|
| Primary Keys | Every table — SERIAL auto-increment |
| Foreign Keys | listings → users, transactions → listings, reviews → transactions |
| Shared Primary Key (IS-A) | students, faculty, staff all inherit from users |
| UNIQUE Constraint | email in users, category_name in categories |
| CHECK Constraint | price >= 0, status IN (...), rating BETWEEN 1 AND 5 |
| ON DELETE CASCADE | Sub-tables cascade delete when user is removed |
| ON DELETE SET NULL | listings.category_id becomes NULL if category deleted |
| DEFAULT Values | join_date, posted_date, status all have defaults |
| BEFORE Trigger | Self-purchase prevention, duplicate purchase prevention, timestamp update |
| AFTER Trigger | Auto-mark listing as sold after transaction completes |
| DECLARE Variables | Used in trigger functions to hold intermediate query results |
| RAISE EXCEPTION | Trigger rejection mechanism — cancels the DML operation |
| Multi-table JOIN | Used on Home, Transactions, Admin, Reviews pages |
| Aggregation | COUNT, SUM used on Admin dashboard stats |
CampusBazaar — Built for learning. Powered by PostgreSQL. FAST-NUCES Faisalabad · Database Systems · BCS-4B · 2024