Project Documentation

Author: Dignesh Solanki  ·  Student Number: 110173120  ·  Course: COMP3340-91-R-2026S — World Wide Web Info System Dev

Overview

BrewLeaf is a browsable catalogue of coffee and tea with per-product size/grind options, a cart that works for guests and logged-in customers, checkout with order history, product reviews, a full admin back office, a public help wiki, and a live backend status monitor.

It's built with plain PHP and no framework or build step -- every .php file is requested directly by the web server, talking to MySQL through raw mysqli. That deploys to any standard shared PHP host with nothing to compile.

Tech Stack

Server: PHP 8, procedural style, no Composer packages. Data: MySQL/MariaDB via mysqli, prepared statements throughout. Client: plain JavaScript, one small file per behaviour, no third-party JS libraries anywhere on the site. Styling: hand-written CSS on custom-property tokens, with four swappable theme files.

Seeded Accounts

UsernamePasswordRole
adminAdmin123!admin
jsmithAdmin123!customer

Architecture

Every request loads the same small chain: config/db.php (database connection), includes/auth.php (session + role helpers), includes/functions.php (shared helpers like h() and money()), the shared includes/header.php, the page's own logic and markup, then includes/footer.php. There's no router -- the URL is the file path.

Folders: config/ (db connection + base URL), includes/ (shared header/footer/auth), admin/ (6 back-office pages), help/ (5-article public + admin wiki), assets/css and assets/js (one file per component/behaviour), and sql/schema.sql (full schema + seed data).

Database

9 InnoDB tables. selected_options on cart_items/order_items stores a JSON array rather than a foreign key, since one product can have multiple option groups (Size and Grind) selected on a single line.

TableRole
usersCustomers and admins in one table; role splits them, status lets an admin disable an account instead of deleting it.
products20 seeded rows (10 coffee, 10 tea); rating_avg/rating_count are cached aggregates; is_active is a soft-hide flag.
product_optionsOne row per variant value (e.g. Size / 500g) with its own price modifier.
reviewsStar rating (1-5) + comment, tied to one product and one user.
cart_itemsLines for a logged-in user (user_id) or a guest (session_id).
ordersHeader row per checkout: pending → processing → shipped → delivered / cancelled.
order_itemsSnapshots product name/price/options at purchase time, so past orders stay accurate if the product changes later.
site_settingsKey/value store; today holds one key, active_theme.
service_statusRows polled and rewritten by monitor.php on every visit.

Roles & Access

The site separates the customer experience from the admin one, enforced server-side, not just by hiding buttons in the UI. Customers/guests see Home, Shop, Coffee, Tea, About, Help, Contact, a live cart badge, and an account menu. Admins see a different nav instead -- Storefront, Dashboard, Products, Orders, Customers -- with no Cart link, no cart badge, and no Add-to-Cart controls anywhere.

cart.php, checkout.php, cart_add.php, and cart_update.php all check is_admin() (reading the session role set at login) before doing anything, and reject admins regardless of what the client sends -- verified directly by sending a raw request to cart_add.php bypassing the UI entirely, which still gets rejected.

Public Pages

PageAuthWhat it does
index.phpopenHome page: hero, roast video, category tabs, catalogue chart.
products.php / product.phpopenCatalogue with search/filter/sort, and a product page with option pills + live price + reviews.
login.php / register.phpopenBcrypt password hashing, validation, auto-login after registration.
profile.phplogin requiredEdit account details + full order history.
monitor.phpopenRuns 6 live checks (DB ping, table existence, session) every request.

Cart & Checkout

Quantity changes and removals save in the background over AJAX (cart_update.php), with a spinner and disabled buttons while the request is in flight, an error banner if it fails (reverting a quantity edit to its last saved value rather than showing a stale number), and a confirmation prompt before removing a line. Checkout wraps the whole conversion in one database transaction -- insert order, insert every line item, clear the cart, commit -- and rolls all of it back if anything fails. Card fields are collected but never validated or stored, by design (a school-project demo, no real payment is processed).

Admin Panel

Dashboard

KPI cards, a 14-day revenue chart (zero-filled so every day shows, not just days with a sale), and a live service-status snapshot.

Products

Search, category/status filters, pagination, add/edit/delete, and inline management of each product's option rows.

Orders

Update any order's status from a whitelisted set; customers see the change instantly.

Customers

List every account, disable/enable (can't disable your own).

Charts

Both charts on the site -- the home page's "Catalogue at a Glance" and the admin dashboard's revenue chart -- are plain HTML and CSS, no charting library and no CDN. PHP works out each bar's height as a percentage of the highest value and renders it as a styled <div>; the browser does the rest.

Theming

Four CSS files share the same variable names; whichever theme file loads last wins, so switching themes (White, Regular, Autumn, Winter) is a single database write from admin/theme.php, applied to every visitor immediately.

SEO

Every page sets a title and description before including the shared header. sitemap.php generates an XML sitemap of every static page plus every active product, live from the database.

Security Model

MechanismStatus
Password storage bcrypt
SQL injection defense prepared statements
XSS defense h() escaping everywhere
Role enforcement server-side
CSRF protection none found
Session handling no regeneration

Known Gaps

No CSRF tokens on any state-changing form. No session_regenerate_id() call on login (a session-fixation gap).

Running Locally

mysql -u root -e "CREATE DATABASE brewleaf CHARACTER SET utf8mb4;", then mysql -u root brewleaf < sql/schema.sql, then php -S localhost:8000. config/db.php targets localhost / root / no password / database brewleaf by default.