Jugoton

Online vinyl shop, built on PHP, MySQL and Oracle Cloud

A working online vinyl shop with full CRUD, where the database itself guards stock, orders and addresses. Deployed live on Oracle Cloud.

Status
University project
Role
Full-stack developer, team of two (with Sajra Agić); cloud deployment done on my own
Timeline
Spring 2026, SSST (CS270, CS240 and CS260), deployed June 2026
Platforms
Responsive web app
  • 10tables
  • 30+business rules
  • 2triggers, plus a view, procedure and function
  • 4indexes for search and reports

The full demo, then every screen

Adding a record as an admin. Choosing the artist triggers an iTunes Search API lookup that fills in the release date and iTunes ID, and the cover art is fetched on save. Then the record page with its 30-second preview and reviews. Slow parts are sped up (the label shows the speed).
Jugoton home page with the latest records
Home page: the latest records, and the record-shaped menu button.
A record page with the iTunes player and track list
Record page with a 30-second preview through the iTunes embed.
Genre filter page
Genres page. A record can belong to several genres.
Search results for an artist
Search by album title or artist name.
The side cart with a record and the total
The cart opens on the side, with quantity and total.
Review form and customer reviews
Reviews: rating required, comment optional, one per user and record.
Admin dashboard with best sellers, top customers and latest orders
Admin dashboard: best sellers, top customers, latest orders and inventory.
Top-selling records report
Sales report, read from a database view.
Form for adding a new record
Adding a record. Details are filled in from the iTunes Search API.

Side A

A1The ideaWhat it is and why

Vinyl is popular again, so we built Jugoton, a record shop named after and styled like the old Yugoslav record label: dark, with orange accents. The project had to satisfy two courses, so it is a complete web app and a properly designed database at the same time. We started from written business rules, built the ER model and normalised it before writing application code.

Built for two courses at once, Web Environments (CS270) and Database Systems (CS240), in a team of two, then deployed to Oracle Cloud for a third, Cloud Computing (CS260). Customers browse, search, filter by genre, preview tracks, review records and check out; admins add, edit and delete records and artists and read sales reports.

A2What I built7 parts
  • Responsive front end in plain HTML, CSS and JavaScript, no frameworks, with a record-shaped menu button.
  • Catalogue with new arrivals, search by title or artist, genre filtering with paging, and record pages with an iTunes track preview.
  • Registration and login with hashed passwords and sessions, a profile with account balance, and separate customer and admin areas.
  • Side cart, checkout that lowers balance and stock in one transaction, and reviews (one per user and record, updated in a single query).
  • Admin panel with full CRUD for records and artists, inventory, and reports: best sellers, most valuable customers, latest orders.
  • New records are filled in automatically from the iTunes Search API (release date, preview ID, genre); cover images live in Oracle Cloud Object Storage.
  • MySQL database: 10 tables, 30+ business rules, 4 indexes, a sales analytics view, an order procedure, a customer tier function and 2 triggers.

Stack: PHP, HTML, CSS, JavaScript, MySQL/MariaDB, phpMyAdmin, iTunes Search API, Oracle Cloud Object Storage, GitHub; deployment: Oracle Cloud Infrastructure, Ubuntu 24, Nginx, PHP 8.4-FPM, Oracle Autonomous Database, OCI8

A3How it works2 diagrams

Checkout, step by step

Cartrecords and quantitiesCheckout formshipping addresssp_place_orderOne transaction1 Check the record exists2 Check there is enough stock3 Create the order4 Add items, lower stockCommitorder placedRollbacknothing changesTrigger on Order_Itemsblocks any quantity above stock
Placing an order is all or nothing. The database stops overselling even if the application misses it.

Entity relationship diagram

Entity relationship diagram
The final schema: ten tables, including the Vinyl_Genres link table for the many-to-many relationship.
A4Technical highlight: letting the database enforce the rulesA closer look

Some rules are too important to leave to application code. A trigger stops any order item that asks for more copies than are in stock, and a second trigger keeps exactly one default address per user. Placing an order runs through a stored procedure as a single transaction: it checks the record and the stock, creates the order, adds the item and lowers the stock, and rolls everything back if one step fails. The next step we planned is moving every query to prepared statements.

tr_verify_vinyl_stock.sql
CREATE TRIGGER tr_verify_vinyl_stock
BEFORE INSERT ON Order_Items FOR EACH ROW
BEGIN
  DECLARE current_stock INT;
  SELECT stock INTO current_stock FROM Vinyls WHERE vinyl_id = NEW.vinyl_id;
  IF current_stock < NEW.quantity THEN
    SIGNAL SQLSTATE '45000'
      SET MESSAGE_TEXT = 'Transaction aborted: Insufficient vinyl inventory available.';
  END IF;
END

From the project report. The trigger runs before every order item is saved.

Side B

B1Deploying it to Oracle Cloud (CS260 Cloud Computing)Ubuntu, Nginx, PHP-FPM, MySQL

For the Cloud Computing course (CS260), I took the same app live on Oracle Cloud Infrastructure on my own in June 2026. It runs on an Ubuntu 24 virtual machine as a classic three-tier setup: Nginx serves the static files, PHP 8.4-FPM runs the PHP, and MySQL stores the data. A copy of the database also lives in an Oracle Autonomous Database. The cloud server was a course deployment and may be taken down, so the video on this page shows the full flow.

Oracle Cloud (OCI)Ubuntu 24 virtual machineBrowservisitorNginxserves CSS,images, HTMLPHP 8.4-FPMruns the PHPscriptsMySQLvinyl_shoplocalhost onlyPort 80 open, port 3306 closed to the internetAutonomous DBOracle copy ofthe data:80OCI8socket
Three tiers on one Oracle Cloud VM, with an Oracle Autonomous Database holding a copy of the data.

What I did

  1. I connected to the VM over SSH, updated the system, and cloned the repository from GitHub into the Nginx web root, owned by the www-data user.
  2. I wrote an Nginx server block that passes PHP files to PHP-FPM through a Unix socket.
  3. I installed MySQL, created the vinyl_shop database and a dedicated user, and imported the project's SQL export.
  4. I opened port 80 in the OCI security list and the VM firewall, and kept the MySQL port closed, so only the VM itself can reach the database.
  5. I exported the data with mysqldump and rewrote the script by hand for Oracle (NUMBER instead of INT UNSIGNED, sequences instead of AUTO_INCREMENT, TO_DATE for dates), then loaded it into an Oracle Autonomous Database through SQL Developer Web.
  6. I built the PHP OCI8 extension against the Oracle Instant Client, so PHP can connect to the Autonomous Database for syncing and reports.

What broke, and how I found it

  • Every page returned a 500 error. The Nginx error log showed PHP logging in to MySQL as a user that only existed only on our own machines, and index.php had a second hard-coded connection of its own. I fixed db.php, then found every other reference with grep.
  • Some pages said tables like Artists didn't exist. MySQL on Windows ignores case in table names, MySQL on Linux doesn't. A sed replace that was too broad emptied several files. I restored them with git checkout and redid the fix with word-boundary patterns.
  • The OCI8 build couldn't find oci.h, and PHP-FPM couldn't load libclntsh.so. I pointed PECL at the Instant Client folder and made LD_LIBRARY_PATH available to the www-data user.

What a real production setup would change

One VM works for a course, not for real customers. Production would move the database to a managed service, put a load balancer in front of several app servers, serve everything over HTTPS, keep credentials in OCI Vault instead of in the code, and deploy through a CI/CD pipeline instead of by hand over SSH. The biggest lesson was to read the logs: none of the real errors showed up in the browser.

Oracle SQL Developer Web listing the Jugoton tables in the Autonomous Database
All project tables imported into the Oracle Autonomous Database.
Nginx error log showing PHP failing to log in to MySQL
The first clue: the Nginx error log behind the 500 errors.