From b52cdfc06157f1edb87899d4877433079da27188 Mon Sep 17 00:00:00 2001 From: Harry Wu Date: Tue, 5 May 2026 02:04:54 +1200 Subject: [PATCH 1/3] prompt generation of backend.... post and get giving error 404 but it should be good once its fixed. --- client/src/pages/Sponsors.tsx | 31 +++++++++++++---------------- server/package-lock.json | 32 +++++++++++++++--------------- server/package.json | 2 +- server/src/index.ts | 2 ++ server/src/model/sponsors.ts | 12 +++++++++++ server/src/routes/sponsorRoutes.ts | 28 ++++++++++++++++++++++++++ 6 files changed, 73 insertions(+), 34 deletions(-) create mode 100644 server/src/model/sponsors.ts create mode 100644 server/src/routes/sponsorRoutes.ts diff --git a/client/src/pages/Sponsors.tsx b/client/src/pages/Sponsors.tsx index 63d481c..acaa788 100644 --- a/client/src/pages/Sponsors.tsx +++ b/client/src/pages/Sponsors.tsx @@ -1,32 +1,25 @@ import { useEffect, useState } from "react"; import SponsorCard from "../components/SponsorCard"; -import sponsorsJsonData from "../placeholders/sponsors.json"; -// 1. Define the shape of a single sponsor interface Sponsor { name: string; deal: string; address: string; + category: "cbd" | "newmarket" | "other"; code?: string; } -// 2. Define the shape of the whole JSON file -interface SponsorsData { - cbd_sponsors: Sponsor[]; - newmarket_sponsors: Sponsor[]; - other_sponsors: Sponsor[]; -} - const Sponsors = () => { - const [sponsors, setSponsors] = useState(null); + const [sponsors, setSponsors] = useState([]); useEffect(() => { - Promise.resolve(sponsorsJsonData as SponsorsData) + fetch("http://localhost:3000/api/sponsors") + .then((res) => res.json()) .then((data) => setSponsors(data)) .catch((err) => console.error("Error loading sponsors:", err)); }, []); - if (!sponsors) { + if (sponsors.length === 0) { return (
Loading Sponsors... @@ -42,6 +35,10 @@ const Sponsors = () => { padding: "2rem", } as const; + const cbd = sponsors.filter((s) => s.category === "cbd"); + const newmarket = sponsors.filter((s) => s.category === "newmarket"); + const other = sponsors.filter((s) => s.category === "other"); + return (
{/* HERO SECTION */} @@ -71,12 +68,12 @@ const Sponsors = () => {

CBD Sponsors

- {sponsors.cbd_sponsors.map((s, index) => ( + {cbd.map((s, index) => ( ))}
@@ -86,7 +83,7 @@ const Sponsors = () => {

Newmarket Sponsors

- {sponsors.newmarket_sponsors.map((s, index) => ( + {newmarket.map((s, index) => ( {

Other Sponsors

- {sponsors.other_sponsors.map((s, index) => ( + {other.map((s, index) => ( =18.0.0" @@ -2806,13 +2806,13 @@ } }, "node_modules/mongodb": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/mongodb/-/mongodb-7.0.0.tgz", - "integrity": "sha512-vG/A5cQrvGGvZm2mTnCSz1LUcbOPl83hfB6bxULKQ8oFZauyox/2xbZOoGNl+64m8VBrETkdGCDBdOsCr3F3jg==", + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/mongodb/-/mongodb-7.2.0.tgz", + "integrity": "sha512-F/2+BMZtLVhY30ioZp0dAmZ+IRZMBqI+nrv6t5+9/1AIwCa8sMRC3jBf81lpxMhnZgqq8CoUD503Z1oZWq1/sw==", "license": "Apache-2.0", "dependencies": { "@mongodb-js/saslprep": "^1.3.0", - "bson": "^7.0.0", + "bson": "^7.2.0", "mongodb-connection-string-url": "^7.0.0" }, "engines": { @@ -2865,13 +2865,13 @@ } }, "node_modules/mongoose": { - "version": "9.2.4", - "resolved": "https://registry.npmjs.org/mongoose/-/mongoose-9.2.4.tgz", - "integrity": "sha512-XNh+jiztVMddDFDCv8TWxVxi/rGx+0FfsK3Ftj6hcYzEmhTcos2uC144OJRmUFPHSu3hJr6Pgip++Ab2+Da35Q==", + "version": "9.6.1", + "resolved": "https://registry.npmjs.org/mongoose/-/mongoose-9.6.1.tgz", + "integrity": "sha512-3T8/b0plM3ZJPW3WjlzVMIGJEYYTjgDPQ05Qzru3xu3/wOPSFKWYxdwUF2dl8h3NG5dVkzIuOkZdLacnlLf/sA==", "license": "MIT", "dependencies": { - "kareem": "3.2.0", - "mongodb": "~7.0", + "kareem": "3.3.0", + "mongodb": "~7.2", "mpath": "0.9.0", "mquery": "6.0.0", "ms": "2.1.3", diff --git a/server/package.json b/server/package.json index c7764c9..7d4b874 100644 --- a/server/package.json +++ b/server/package.json @@ -20,7 +20,7 @@ "dotenv": "^17.3.1", "express": "^5.2.1", "express-session": "^1.19.0", - "mongoose": "^9.2.4", + "mongoose": "^9.6.1", "multer": "^2.1.1", "passport": "^0.7.0", "passport-google-oauth20": "^2.0.0" diff --git a/server/src/index.ts b/server/src/index.ts index d0f704f..bfd79c4 100644 --- a/server/src/index.ts +++ b/server/src/index.ts @@ -7,6 +7,7 @@ import { Strategy as GoogleStrategy } from "passport-google-oauth20"; import authRoutes from "./routes/authRoutes"; import imageRoutes from "./routes/imageRoutes"; import executivesRoutes from "./routes/executivesRoutes"; +import sponsorRoutes from "./routes/sponsorRoutes"; // app config dotenv.config({ quiet: true }); @@ -47,6 +48,7 @@ app.use(express.json()); app.use("/api/auth", authRoutes); app.use("/api/images", imageRoutes); app.use("/api/executives", executivesRoutes); +app.use("/api/sponsors", sponsorRoutes); // Connect to MongoDB and start the server mongoose diff --git a/server/src/model/sponsors.ts b/server/src/model/sponsors.ts new file mode 100644 index 0000000..6c54bb3 --- /dev/null +++ b/server/src/model/sponsors.ts @@ -0,0 +1,12 @@ +import mongoose from "mongoose"; + +const sponsorSchema = new mongoose.Schema( + { + name: { type: String, required: true }, + tier: { type: String, enum: ["gold", "silver", "bronze"], required: true }, + logoUrl: { type: String, required: true }, + }, + { timestamps: true } +); + +export const Sponsor = mongoose.model("Sponsor", sponsorSchema); diff --git a/server/src/routes/sponsorRoutes.ts b/server/src/routes/sponsorRoutes.ts new file mode 100644 index 0000000..403ffc1 --- /dev/null +++ b/server/src/routes/sponsorRoutes.ts @@ -0,0 +1,28 @@ +import express from "express"; +import { Sponsor } from "../model/sponsors"; + +const router = express.Router(); + +router.post("/", async (req, res) => { + const sponsor = await Sponsor.create(req.body); + res.json(sponsor); +}); + +router.get("/", async (req, res) => { + const sponsors = await Sponsor.find(); + res.json(sponsors); +}); + +router.put("/:id", async (req, res) => { + const updated = await Sponsor.findByIdAndUpdate(req.params.id, req.body, { + new: true, + }); + res.json(updated); +}); + +router.delete("/:id", async (req, res) => { + await Sponsor.findByIdAndDelete(req.params.id); + res.json({ message: "Deleted" }); +}); + +export default router; From 56087c82af6ae5088a53f5602be2df604a6d565f Mon Sep 17 00:00:00 2001 From: Harry Wu Date: Wed, 6 May 2026 23:56:20 +1200 Subject: [PATCH 2/3] added sponsor api file. --- client/src/api/sponsorsApi.ts | 36 +++++++++++++++++++++++++++++++++++ client/src/pages/Sponsors.tsx | 10 ++++++---- 2 files changed, 42 insertions(+), 4 deletions(-) create mode 100644 client/src/api/sponsorsApi.ts diff --git a/client/src/api/sponsorsApi.ts b/client/src/api/sponsorsApi.ts new file mode 100644 index 0000000..ab6f753 --- /dev/null +++ b/client/src/api/sponsorsApi.ts @@ -0,0 +1,36 @@ +import api from "./index"; + +export function getSponsors() { + const res = api.get("/sponsors"); + return res.then((response) => response.data); +} + +export function createSponsor(sponsor: { + name: string; + deal: string; + address: string; + category: "cbd" | "newmarket" | "other"; + code?: string; +}) { + const res = api.post("/sponsors", sponsor); + return res.then((response) => response.data); +} + +export function updateSponsor( + id: string, + sponsor: Partial<{ + name: string; + deal: string; + address: string; + category: "cbd" | "newmarket" | "other"; + code?: string; + }> +) { + const res = api.put(`/sponsors/${id}`, sponsor); + return res.then((response) => response.data); +} + +export function deleteSponsor(id: string) { + const res = api.delete(`/sponsors/${id}`); + return res.then((response) => response.data); +} diff --git a/client/src/pages/Sponsors.tsx b/client/src/pages/Sponsors.tsx index 31adf3c..4e3caa8 100644 --- a/client/src/pages/Sponsors.tsx +++ b/client/src/pages/Sponsors.tsx @@ -1,5 +1,6 @@ import { useEffect, useState } from "react"; import SponsorCard from "../components/SponsorCard"; +import { getSponsors } from "../api/sponsorsApi"; interface Sponsor { name: string; @@ -11,15 +12,16 @@ interface Sponsor { const Sponsors = () => { const [sponsors, setSponsors] = useState([]); + const [loading, setLoading] = useState(true); useEffect(() => { - fetch("http://localhost:3000/api/sponsors") - .then((res) => res.json()) + getSponsors() .then((data) => setSponsors(data)) - .catch((err) => console.error("Error loading sponsors:", err)); + .catch((err) => console.error("Error loading sponsors:", err)) + .finally(() => setLoading(false)); }, []); - if (sponsors.length === 0) { + if (loading) { return (
Loading Sponsors... From 748f31794beb0cc990a4bfaad7411aa8bfd4c188 Mon Sep 17 00:00:00 2001 From: Harry Wu Date: Sun, 10 May 2026 19:53:06 +1200 Subject: [PATCH 3/3] implement sponsor seeding and fix schema mismatch with MongoDB --- server/src/model/sponsors.ts | 10 +- server/src/scripts/seedSponsors.ts | 294 +++++++++++++++++++++++++++++ 2 files changed, 302 insertions(+), 2 deletions(-) create mode 100644 server/src/scripts/seedSponsors.ts diff --git a/server/src/model/sponsors.ts b/server/src/model/sponsors.ts index 6c54bb3..8f8be5d 100644 --- a/server/src/model/sponsors.ts +++ b/server/src/model/sponsors.ts @@ -3,8 +3,14 @@ import mongoose from "mongoose"; const sponsorSchema = new mongoose.Schema( { name: { type: String, required: true }, - tier: { type: String, enum: ["gold", "silver", "bronze"], required: true }, - logoUrl: { type: String, required: true }, + deal: { type: String, required: true }, + address: { type: String, required: true }, + category: { + type: String, + enum: ["cbd", "newmarket", "other"], + required: true, + }, + code: { type: String }, // optional (only some sponsors have it) }, { timestamps: true } ); diff --git a/server/src/scripts/seedSponsors.ts b/server/src/scripts/seedSponsors.ts new file mode 100644 index 0000000..561e78d --- /dev/null +++ b/server/src/scripts/seedSponsors.ts @@ -0,0 +1,294 @@ +import mongoose from "mongoose"; +import dotenv from "dotenv"; +import { Sponsor } from "../model/sponsors"; + +dotenv.config(); + +// Mongo connection +const mongoUrl = `mongodb+srv://${process.env.MONGODB_USER}:${process.env.MONGODB_PASSWORD}@kac-prod.cf1fyh5.mongodb.net/`; + +const sponsors = [ + // CBD + ...[ + { + name: "KOMPASS COFFEE", + deal: "15% OFF", + address: "2 Kitchener St, Auckland CBD", + }, + { + name: "ESCAPE MASTERS", + deal: "$2 OFF STUDENT PRICE", + address: "Level 11/300 Queen St, Auckland CBD", + }, + { + name: "FACE WASH BEAR", + deal: "15% OFF", + address: "430 Queen St, Auckland CBD", + }, + { + name: "VAGABOND", + deal: "10% OFF", + address: "40 Courthouse Ln, Auckland CBD", + }, + { + name: "YOSHIZAWA", + deal: "10% OFF WEEKDAYS", + address: "9C Victoria St East, Auckland CBD", + }, + { + name: "YUAN TASTE", + deal: "10% OFF", + address: "19/239 Queen St, Auckland CBD", + }, + { name: "CHAHALO", deal: "10% OFF", address: "55B High St, Auckland CBD" }, + { + name: "SIZE GIFT SHOP", + deal: "SPEND $20, RECEIVE 10% OFF", + address: "2K/239 Queen St, Auckland CBD", + }, + { + name: "STARRY TATTOO & PIERCING", + deal: "20% OFF", + address: "2 Lorne St, Auckland CBD", + }, + { + name: "TEA TALK", + deal: "10% OFF", + address: "G20/239 Queen St, Auckland CBD", + }, + { name: "THE SHELF", deal: "10% OFF", address: "50 High St, Auckland CBD" }, + { + name: "GAMEON ARCADE", + deal: "STUDENT VIP", + address: "291-297 Queen St, Auckland CBD", + }, + { + name: "WAFFLERS & MORE", + deal: "10% OFF or FREE ICE CREAM TOP UP", + address: "12 O'Connell St, Auckland CBD", + }, + { + name: "THE PROPER PIZZA COMPANY", + deal: "Various deals", + address: "12 O'Connell St, Auckland CBD", + }, + { + name: "SEOUL NIGHT", + deal: "10% OFF", + address: "16/20 Fort St, Auckland CBD", + }, + { + name: "PATCH CAFE", + deal: "10% OFF", + address: "36 Lorne St, Auckland CBD", + }, + { name: "THE DON", deal: "12% OFF", address: "47 High St, Auckland CBD" }, + { + name: "OH MY CROFFLE", + deal: "10% OFF", + address: "6 Chancery St, Auckland CBD", + }, + { + name: "PASTKATSU", + deal: "Free drink (worth $5)", + address: "20 Lorne St, Auckland CBD", + }, + { + name: "CHUBBY BOY", + deal: "10% OFF", + address: "42c High St, Auckland CBD", + }, + { + name: "MEESO", + deal: "10% OFF", + address: "22 Kitchener St, Auckland CBD", + }, + { + name: "BANNSANG", + deal: "$2 OFF full meals", + address: "47 High St, Auckland CBD", + }, + { + name: "BBQ CHICKEN", + deal: "10% OFF", + address: "2/290 Queen St, Auckland CBD", + }, + { name: "SSAMJANG", deal: "10% OFF", address: "26 Lorne St, Auckland CBD" }, + { + name: "SAL'S PIZZA", + deal: "10% OFF", + address: "265 Queen St, Auckland CBD", + }, + { + name: "SUMTHIN DUMPLIN", + deal: "10% OFF", + address: "18/26 Wellesley St, Auckland CBD", + }, + { + name: "TSUJIRI", + deal: "FREE UPSIZE / TOPPING", + address: "10-14 Lorne St, Auckland CBD", + }, + { + name: "AFTERLIFE BILLIARDS", + deal: "20% OFF", + address: "520 Queen St, Auckland CBD", + }, + { name: "OBAR", deal: "10% OFF", address: "15 Chancery St, Auckland CBD" }, + { + name: "DOSIROCK", + deal: "$3 OFF", + address: "30 Chancery St, Auckland CBD", + }, + { + name: "IPPONDO (CBD)", + deal: "Membership discounts", + address: "229 Queen St, Auckland CBD", + }, + { + name: "NENE CHICKEN", + deal: "Combo deals", + address: "246 Queen St, Auckland CBD", + }, + { + name: "YOGO BAY", + deal: "$5 OFF", + address: "21 Shortland St, Auckland CBD", + }, + { + name: "MERMAID CAFE", + deal: "10% OFF", + address: "60 High St, Auckland CBD", + }, + { + name: "THE COSMETIC STORE", + deal: "5% OFF", + address: "1/10 Victoria St E, Auckland CBD", + }, + { + name: "FIFI'S GELATO", + deal: "5% OFF", + address: "22 High St, Auckland CBD", + }, + { + name: "KUMI DESSERTS", + deal: "10% OFF", + address: "1 Courthouse Ln, Auckland CBD", + }, + { + name: "GREAT ESCAPE", + deal: "15% OFF", + address: "1/28 Lorne St, Auckland CBD", + }, + { name: "NAHM", deal: "5% OFF", address: "39 Elliot St, Auckland CBD" }, + { + name: "POCHA", + deal: "10% OFF", + address: "50 Kitchener St, Auckland CBD", + }, + { + name: "ANYTIME FITNESS", + deal: "Discounted membership", + address: "14 Lorne St, Auckland CBD", + }, + { + name: "HAUS OF GELATO", + deal: "15% OFF", + address: "12/269-287 Queen St, Auckland CBD", + }, + { name: "WUCHA", deal: "10% OFF", address: "22B Lorne St, Auckland CBD" }, + { + name: "MOKKI", + deal: "10% OFF", + address: "L2 Commercial Bay, Auckland CBD", + }, + ].map((s) => ({ ...s, category: "cbd" })), + + // NEWMARKET + ...[ + { name: "PANDA", deal: "10% OFF", address: "11A Nuffield St, Newmarket" }, + { + name: "WILD CHILD TATTOO", + deal: "15% OFF", + address: "18 Remuera Rd, Newmarket", + }, + { name: "FANKERY", deal: "10% OFF", address: "2B York St, Newmarket" }, + { name: "SUGAR BAE", deal: "20% OFF", address: "6 Broadway, Newmarket" }, + { + name: "SIP'N CHILL", + deal: "10% OFF", + address: "432 Khyber Pass Rd, Newmarket", + }, + { + name: "40° FOOT MASSAGE", + deal: "15% OFF", + address: "222 Broadway, Newmarket", + }, + { + name: "THE COSMETIC STORE", + deal: "5% OFF", + address: "290B Broadway, Newmarket", + }, + { name: "KKANG", deal: "5% OFF", address: "238 Broadway, Newmarket" }, + { + name: "PARIS NAILS & BEAUTY", + deal: "12% OFF", + address: "8 Remuera Rd, Newmarket", + }, + { + name: "JOHN BARLEYCORN'S TAPHOUSE", + deal: "15% OFF", + address: "309 Broadway, Newmarket", + }, + ].map((s) => ({ ...s, category: "newmarket" })), + + // OTHER + ...[ + { name: "GAO ASIAN FUSION", deal: "10% OFF", address: "Albany" }, + { + name: "CHEEKY CHIC", + deal: "15% OFF", + address: "Hillcrest", + code: "HAPPY_KAC_15%_OFF", + }, + { name: "TEASER", deal: "10% OFF", address: "Albany" }, + { name: "TWO GUYS CHICKEN", deal: "10% OFF", address: "Royal Oak" }, + { name: "MAI MATCHA", deal: "5% OFF", address: "Online", code: "KAC2026" }, + { name: "AMAI HOUSE", deal: "10% OFF", address: "Pop up stalls" }, + { name: "CROCHET N FLOWERS", deal: "10% OFF", address: "Epsom" }, + { name: "RAIDED NZ", deal: "$30 OFF", address: "Online" }, + { name: "RABTU'S FLORIST", deal: "10% OFF", address: "Online" }, + { name: "NORTHERN ROCKS", deal: "10% OFF", address: "Wairau Valley" }, + { name: "BELLA BAKES", deal: "8% OFF", address: "Westgate" }, + { name: "T-MARK", deal: "10% OFF", address: "Online" }, + { + name: "THE TAKASHI JAPANESE KITCHEN", + deal: "10% OFF", + address: "Royal Oak", + }, + ].map((s) => ({ ...s, category: "other" })), +]; + +async function seed() { + try { + console.log("Starting seed..."); + + await mongoose.connect(mongoUrl); + console.log("Connected to MongoDB"); + + console.log("Deleting old sponsors..."); + await Sponsor.deleteMany(); + + console.log("Inserting sponsors..."); + await Sponsor.insertMany(sponsors); + + console.log("Sponsors seeded successfully"); + + process.exit(); + } catch (err) { + console.error("Seeding failed:", err); + process.exit(1); + } +} + +seed();