From 01c0f792484f8f52606eae0e58abe528acef3086 Mon Sep 17 00:00:00 2001 From: Leo Goetz Date: Thu, 22 Jan 2026 09:10:15 +0100 Subject: feat: completed course, added some types and changed output to dist folder --- backend/src/db.js | 72 ----------------------------- backend/src/db.ts | 67 +++++++++++++++++++++++++++ backend/src/routes/api.js | 12 ----- backend/src/routes/api.ts | 10 +++++ backend/src/routes/events.js | 97 --------------------------------------- backend/src/routes/events.ts | 105 +++++++++++++++++++++++++++++++++++++++++++ backend/src/routes/users.js | 65 --------------------------- backend/src/routes/users.ts | 67 +++++++++++++++++++++++++++ backend/src/server.js | 40 ----------------- backend/src/server.ts | 37 +++++++++++++++ backend/src/types.ts | 24 ++++++++++ 11 files changed, 310 insertions(+), 286 deletions(-) delete mode 100644 backend/src/db.js create mode 100644 backend/src/db.ts delete mode 100644 backend/src/routes/api.js create mode 100644 backend/src/routes/api.ts delete mode 100644 backend/src/routes/events.js create mode 100644 backend/src/routes/events.ts delete mode 100644 backend/src/routes/users.js create mode 100644 backend/src/routes/users.ts delete mode 100644 backend/src/server.js create mode 100644 backend/src/server.ts create mode 100644 backend/src/types.ts (limited to 'backend/src') diff --git a/backend/src/db.js b/backend/src/db.js deleted file mode 100644 index f20cac1..0000000 --- a/backend/src/db.js +++ /dev/null @@ -1,72 +0,0 @@ -import Database from 'better-sqlite3'; -import EVENTS from './data/events.json' with {type: 'json'}; -import USERS from './data/users.json' with {type: 'json'}; -import RSVPS from './data/rsvps.json' with {type: 'json'}; - - -const db = new Database('src/sqlite.db', { verbose: console.log }); - -console.log(`Initializing database: ${db.name} `); - - -db.pragma('foreign_keys = ON'); - - -db.exec(` - CREATE TABLE IF NOT EXISTS users ( - id INTEGER PRIMARY KEY AUTOINCREMENT, - username TEXT NOT NULL UNIQUE, - name TEXT NOT NULL, - email TEXT - ); - `); -db.exec(` - CREATE TABLE IF NOT EXISTS events ( - id INTEGER PRIMARY KEY, - title TEXT NOT NULL, - description TEXT, - image_url TEXT, - date DATETIME DEFAULT CURRENT_TIMESTAMP NOT NULL, - host_id INTEGER REFERENCES users NOT NULL - ); - CREATE INDEX IF NOT EXISTS eventhosts ON events(host_id); - `); - - -const upsertUser = db.prepare(` - INSERT INTO users VALUES (@id, @username, @name, @email) - ON CONFLICT(id) DO NOTHING - `) - -USERS.map((user) => upsertUser.run(user)); - -const upsertEvent = db.prepare(` - INSERT INTO events VALUES (@id, @title, @description, @image_url, @date, @host_id) - ON CONFLICT(id) DO NOTHING - `) - -EVENTS.map((event) => { - upsertEvent.run(event); -}); - - -db.exec(` - CREATE TABLE IF NOT EXISTS rsvps ( - event_id INTEGER REFERENCES events NOT NULL, - name TEXT NOT NULL, - email TEXT NOT NULL, - UNIQUE(event_id, email) ON CONFLICT REPLACE - ); - CREATE INDEX IF NOT EXISTS rsvpevents ON rsvps(event_id); -`); - -const upsertRSVP = db.prepare(` - INSERT INTO rsvps VALUES (@event_id, @name, @email) -`); -RSVPS.map((rsvp) => { - upsertRSVP.run(rsvp); -}); - - - -export default db; \ No newline at end of file diff --git a/backend/src/db.ts b/backend/src/db.ts new file mode 100644 index 0000000..2b124f1 --- /dev/null +++ b/backend/src/db.ts @@ -0,0 +1,67 @@ +import DatabaseConstructor, { type Database } from "better-sqlite3"; +import EVENTS from "./data/events.json" with { type: "json" }; +import USERS from "./data/users.json" with { type: "json" }; +import RSVPS from "./data/rsvps.json" with { type: "json" }; + +const db: Database = new DatabaseConstructor("src/sqlite.db", { + verbose: console.log, +}); + +console.log(`Initializing database: ${db.name} `); + +db.pragma("foreign_keys = ON"); + +db.exec(` + CREATE TABLE IF NOT EXISTS users ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + username TEXT NOT NULL UNIQUE, + name TEXT NOT NULL, + email TEXT + ); + `); +db.exec(` + CREATE TABLE IF NOT EXISTS events ( + id INTEGER PRIMARY KEY, + title TEXT NOT NULL, + description TEXT, + image_url TEXT, + date DATETIME DEFAULT CURRENT_TIMESTAMP NOT NULL, + host_id INTEGER REFERENCES users NOT NULL + ); + CREATE INDEX IF NOT EXISTS eventhosts ON events(host_id); + `); + +const upsertUser = db.prepare(` + INSERT INTO users VALUES (@id, @username, @name, @email) + ON CONFLICT(id) DO NOTHING + `); + +USERS.map((user) => upsertUser.run(user)); + +const upsertEvent = db.prepare(` + INSERT INTO events VALUES (@id, @title, @description, @image_url, @date, @host_id) + ON CONFLICT(id) DO NOTHING + `); + +EVENTS.map((event) => { + upsertEvent.run(event); +}); + +db.exec(` + CREATE TABLE IF NOT EXISTS rsvps ( + event_id INTEGER REFERENCES events NOT NULL, + name TEXT NOT NULL, + email TEXT NOT NULL, + UNIQUE(event_id, email) ON CONFLICT REPLACE + ); + CREATE INDEX IF NOT EXISTS rsvpevents ON rsvps(event_id); +`); + +const upsertRSVP = db.prepare(` + INSERT INTO rsvps VALUES (@event_id, @name, @email) +`); +RSVPS.map((rsvp) => { + upsertRSVP.run(rsvp); +}); + +export default db; diff --git a/backend/src/routes/api.js b/backend/src/routes/api.js deleted file mode 100644 index 2eb07d1..0000000 --- a/backend/src/routes/api.js +++ /dev/null @@ -1,12 +0,0 @@ -import { Router } from 'express'; -import usersRouter from './users.js'; -import eventsRouter from './events.js'; - - -const router = Router(); - -router.use('/users', usersRouter); -router.use('/events', eventsRouter); - - -export default router; diff --git a/backend/src/routes/api.ts b/backend/src/routes/api.ts new file mode 100644 index 0000000..92eae46 --- /dev/null +++ b/backend/src/routes/api.ts @@ -0,0 +1,10 @@ +import { Router } from "express"; +import usersRouter from "./users.js"; +import eventsRouter from "./events.js"; + +const router = Router(); + +router.use("/users", usersRouter); +router.use("/events", eventsRouter); + +export default router; diff --git a/backend/src/routes/events.js b/backend/src/routes/events.js deleted file mode 100644 index b95c747..0000000 --- a/backend/src/routes/events.js +++ /dev/null @@ -1,97 +0,0 @@ -import db from '../db.js'; -import { Router } from 'express'; -import { getUser } from './users.js'; - -const router = Router(); - -const joinHost = (event) => { - const host = getUser(event.host_id); - return { ...event, host }; -} - -const joinRSVPs = (event) => { - const { id } = event; - const getRSVPs = db.prepare('SELECT * FROM rsvps WHERE event_id = @id'); - const rsvps = getRSVPs.all({ id }); - return { ...event, rsvps }; -} - -const getEvent = (eventId) => { - const byId = db.prepare('SELECT * FROM events WHERE id = @eventId'); - const event = byId.get({ eventId }); - return joinHost(event); -} - -router.get('/', (_req, res) => { - const listEvents = db.prepare(`SELECT * FROM events`); - const events = listEvents.all(); - res.json(events.map(joinHost).map(joinRSVPs)); -}); - -const insertEvent = db.prepare(`INSERT INTO events VALUES (@id, @title, @description, @image_url, @date, @host_id)`); - -router.post('/new', (req, res) => { - const data = req.body; - const { lastInsertRowid: id } = insertEvent.run(data); - const event = getEvent(id); - res.status(201).json(event); -}); - -router.get('/:id', (req, res) => { - const id = parseInt(req.params.id); - const event = getEvent(id); - if (!event) { - return res.status(404).json({ error: 'Event not found' }); - } - res.json(event); -}); - -router.patch('/:id', (req, res) => { - const eventId = parseInt(req.params.id); - const patch = req.body; - - const updateCol = db.prepare(` - UPDATE events SET @col = @val WHERE id = @eventId - `); - const updateEvent = db.transaction((patch) => { - for (const [col, val] of Object.entries(patch)) { - updateCol.run({ col, val, eventId }); - } - }); - - updateEvent(Object.entries(patch)); - const updated = getEvent(eventId); - res.json(updated); -}); - -router.delete('/:id', (req, res) => { - const deleteEvent = db.prepare(`DELETE FROM events WHERE id = @eventId`); - const eventId = parseInt(req.params.id); - const event = getEvent(eventId); - if (!event) { - return res.status(404).json({ error: 'Event not found' }); - } - deleteEvent.run({ eventId }); - res.json(event); -}); - -router.post('/:id/rsvp', (req, res) => { - const eventId = parseInt(req.params.id); - const { name, email } = req.body; - - const getRSVP = db.prepare(`SELECT * FROM rsvps WHERE (event_id = ${eventId} AND email = '${email}')`); - const insertRSVP = db.prepare(`INSERT INTO rsvps VALUES (@eventId, @name, @email)`); - - let [rsvp] = getRSVP.all({ eventId, email }); - if (rsvp) { - // This email has already RSVPed - res.status(200).json({ rsvp }); - } else { - // New RSVP - insertRSVP.run({ name, email, eventId }); - rsvp = getRSVP.run({ eventId, email }); - res.status(201).json({ rsvp }); - } -}); - -export default router; diff --git a/backend/src/routes/events.ts b/backend/src/routes/events.ts new file mode 100644 index 0000000..df65eb5 --- /dev/null +++ b/backend/src/routes/events.ts @@ -0,0 +1,105 @@ +import db from "../db.js"; +import { Router } from "express"; +import { getUser } from "./users.js"; +import { type Event, type Id, type Rsvps, type User } from "../types.js"; + +const router = Router(); + +const joinHost = (event: Event) => { + const host = getUser(event.host_id) as User; + return { ...event, host }; +}; + +const joinRSVPs = (event: Event) => { + const { id } = event; + const getRSVPs = db.prepare("SELECT * FROM rsvps WHERE event_id = @id"); + const rsvps = getRSVPs.all({ id }) as Rsvps[]; + return { ...event, rsvps }; +}; + +const getEvent = (eventId: Id) => { + const byId = db.prepare("SELECT * FROM events WHERE id = @eventId"); + const event = byId.get({ eventId }) as Event; + return joinHost(event); +}; + +router.get("/", (_req, res) => { + const listEvents = db.prepare(`SELECT * FROM events`); + const events = listEvents.all() as Event[]; + res.json(events.map(joinHost).map(joinRSVPs)); +}); + +const insertEvent = db.prepare( + `INSERT INTO events VALUES (@id, @title, @description, @image_url, @date, @host_id)`, +); + +router.post("/new", (req, res) => { + const data = req.body; + const { lastInsertRowid } = insertEvent.run(data); + const id = lastInsertRowid as number; + const event = getEvent(id); + res.status(201).json(event); +}); + +router.get("/:id", (req, res) => { + const id = parseInt(req.params.id); + const event = getEvent(id); + if (!event) { + return res.status(404).json({ error: "Event not found" }); + } + res.json(event); +}); + +router.patch("/:id", (req, res) => { + const eventId = parseInt(req.params.id); + const patch = req.body; + + const updateCol = db.prepare(` + UPDATE events SET @col = @val WHERE id = @eventId + `); + const updateEvent = db.transaction((patch) => { + for (const [col, val] of Object.entries(patch)) { + updateCol.run({ col, val, eventId }); + } + }); + + updateEvent(Object.entries(patch)); + const updated = getEvent(eventId); + res.json(updated); +}); + +router.delete("/:id", (req, res) => { + const deleteEvent = db.prepare(`DELETE FROM events WHERE id = @eventId`); + const eventId = parseInt(req.params.id); + const event = getEvent(eventId); + if (!event) { + return res.status(404).json({ error: "Event not found" }); + } + deleteEvent.run({ eventId }); + res.json(event); +}); + +router.post("/:id/rsvp", (req, res) => { + const eventId = parseInt(req.params.id); + const { name, email } = req.body; + + const getRSVP = db.prepare( + `SELECT * FROM rsvps WHERE (event_id = ${eventId} AND email = '${email}')`, + ); + const insertRSVP = db.prepare( + `INSERT INTO rsvps VALUES (@eventId, @name, @email)`, + ); + + let [rsvp] = getRSVP.all({ eventId, email }); + if (rsvp) { + // This email has already RSVPed + res.status(200).json({ rsvp }); + } else { + // New RSVP + insertRSVP.run({ name, email, eventId }); + rsvp = getRSVP.run({ eventId, email }); + res.status(201).json({ rsvp }); + } +}); + +export default router; diff --git a/backend/src/routes/users.js b/backend/src/routes/users.js deleted file mode 100644 index 6d874e9..0000000 --- a/backend/src/routes/users.js +++ /dev/null @@ -1,65 +0,0 @@ -import { Router } from 'express'; -import db from '../db.js'; - -const router = Router(); - -export const getUser = (userId) => { - const byId = db.prepare('SELECT * FROM users WHERE id = @userId'); - return byId.get({ userId }); -} - -router.get('/', (_req, res) => { - const listUsers = db.prepare(`SELECT * FROM users`) - const users = listUsers.all(); - res.json(users); -}); - -router.post('/new', (req, res) => { - const data = req.body; - const cols = Object.keys(data).join(' , '); - const vals = Object.values(data).join(' , '); - const insertUser = db.prepare(`INSERT INTO users(@cols) VALUES (@vals)`); - const { lastInsertRowid: id } = insertUser.run({ cols, vals }); - const user = getUser(id); - res.json(user); -}); - -router.get('/:id', (req, res) => { - const id = req.params.id; - const user = getUser(id); - if (!user) { - res.status(404).json({ error: 'User not found' }); - } - res.json(user); -}); - -router.patch('/:id', (req, res) => { - const userId = req.params.id; - const patch = req.body; - - const updateCol = db.prepare(` - UPDATE users SET @col = @val WHERE id = @userId - `); - const updateUser = db.transaction((patch) => { - for (const [col, val] of Object.entries(patch)) { - updateCol.run(col, val, userId); - }; - }); - - updateUser(Object.entries(patch)); - const updated = getUser(userId); - res.json(updated); -}); - -router.delete('/:id', (req, res) => { - const deleteUser = db.prepare(`DELETE FROM users WHERE id = @userId`) - const userId = parseInt(req.params.id); - const user = getUser(userId); - if (!user) { - res.status(404).json({ error: 'User not found' }); - } - deleteUser.run({ userId }); - res.json(user); -}); - -export default router; diff --git a/backend/src/routes/users.ts b/backend/src/routes/users.ts new file mode 100644 index 0000000..98ec361 --- /dev/null +++ b/backend/src/routes/users.ts @@ -0,0 +1,67 @@ +import { Router } from "express"; +import db from "../db.js"; +import type { User } from "../types.js"; + +const router = Router(); + +export const getUser = (userId: User["id"]) => { + const byId = db.prepare("SELECT * FROM users WHERE id = @userId"); + return byId.get({ userId }); +}; + +router.get("/", (_req, res) => { + const listUsers = db.prepare(`SELECT * FROM users`); + const users = listUsers.all(); + res.json(users); +}); + +router.post("/new", (req, res) => { + const data = req.body; + const cols = Object.keys(data).join(" , "); + const vals = Object.values(data).join(" , "); + const insertUser = db.prepare(`INSERT INTO users(@cols) VALUES (@vals)`); + const { lastInsertRowid } = insertUser.run({ cols, vals }); + const id = lastInsertRowid as number; + const user = getUser(id); + res.json(user); +}); + +router.get("/:id", (req, res) => { + const id = Number(req.params.id); + const user = getUser(id); + if (!user) { + res.status(404).json({ error: "User not found" }); + } + res.json(user); +}); + +router.patch("/:id", (req, res) => { + const userId = Number(req.params.id); + const patch = req.body; + + const updateCol = db.prepare(` + UPDATE users SET @col = @val WHERE id = @userId + `); + const updateUser = db.transaction((patch) => { + for (const [col, val] of Object.entries(patch)) { + updateCol.run(col, val, userId); + } + }); + + updateUser(Object.entries(patch)); + const updated = getUser(userId); + res.json(updated); +}); + +router.delete("/:id", (req, res) => { + const deleteUser = db.prepare(`DELETE FROM users WHERE id = @userId`); + const userId = parseInt(req.params.id); + const user = getUser(userId); + if (!user) { + res.status(404).json({ error: "User not found" }); + } + deleteUser.run({ userId }); + res.json(user); +}); + +export default router; diff --git a/backend/src/server.js b/backend/src/server.js deleted file mode 100644 index 8b579b3..0000000 --- a/backend/src/server.js +++ /dev/null @@ -1,40 +0,0 @@ -import express from 'express'; -import cookieParser from 'cookie-parser'; -import logger from 'morgan'; -import cors from 'cors'; -import helmet from 'helmet'; - -import apiRouter from './routes/api.js'; - - -const PORT = process.env.PORT || 3000; -const NODE_ENV = process.env.NODE_ENV || 'development'; - -const app = express(); - - -app.use(logger('dev')); -app.use(express.json()); -app.use(express.urlencoded({ extended: false })); -app.use(cookieParser()); -app.use(helmet()); -app.use(cors({ - origin: 'http://localhost:5173' -})); - -app.use('/api', apiRouter); - -app.use((_req, res) => { - res.status(404).json({ - error: 'Not Found', - endpoints: [ - '/api/events', - '/api/users' - ] - }); -}); - -app.listen(PORT, () => { - console.log(`\nServer listening at http://localhost:${PORT}`); - console.log(`Server running in ${NODE_ENV} mode\n`); -}); \ No newline at end of file diff --git a/backend/src/server.ts b/backend/src/server.ts new file mode 100644 index 0000000..8582347 --- /dev/null +++ b/backend/src/server.ts @@ -0,0 +1,37 @@ +import express from "express"; +import cookieParser from "cookie-parser"; +import logger from "morgan"; +import cors from "cors"; +import helmet from "helmet"; + +import apiRouter from "./routes/api.js"; + +const PORT = process.env.PORT || 3000; +const NODE_ENV = process.env.NODE_ENV || "development"; + +const app = express(); + +app.use(logger("dev")); +app.use(express.json()); +app.use(express.urlencoded({ extended: false })); +app.use(cookieParser()); +app.use(helmet()); +app.use( + cors({ + origin: "http://localhost:5173", + }), +); + +app.use("/api", apiRouter); + +app.use((_req, res) => { + res.status(404).json({ + error: "Not Found", + endpoints: ["/api/events", "/api/users"], + }); +}); + +app.listen(PORT, () => { + console.log(`\nServer listening at http://localhost:${PORT}`); + console.log(`Server running in ${NODE_ENV} mode\n`); +}); diff --git a/backend/src/types.ts b/backend/src/types.ts new file mode 100644 index 0000000..5c8112c --- /dev/null +++ b/backend/src/types.ts @@ -0,0 +1,24 @@ +export type Id = number; + +export interface Event { + id: Id; + title: string; + description?: string; + date: Date; + host_id: number; + image_url?: string; + host: User; + rsvps: Rsvps[]; +} + +export interface User { + id: number; + name: string; + email: string; +} + +export interface Rsvps { + id: number; + name: string; + email: string; +} -- cgit v1.3