Skip to content

Commit

Permalink
merged with dev
Browse files Browse the repository at this point in the history
  • Loading branch information
stevem-zhou committed Jan 10, 2025
2 parents a2172ce + 85ab1ed commit 962313f
Show file tree
Hide file tree
Showing 11 changed files with 290 additions and 43 deletions.
2 changes: 1 addition & 1 deletion server/db/schema/articles.sql
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
CREATE TABLE IF NOT EXISTS articles (
id INTEGER PRIMARY KEY NOT NULL GENERATED ALWAYS AS IDENTITY ( INCREMENT 1 START 1 MINVALUE 1 MAXVALUE 2147483647 CACHE 1 ),
id INTEGER PRIMARY KEY NOT NULL GENERATED BY DEFAULT DENTITY ( INCREMENT 1 START 1 MINVALUE 1 MAXVALUE 2147483647 CACHE 1 ),
s3_url VARCHAR(256) NOT NULL,
description TEXT NOT NULL,
media_url VARCHAR(256) NOT NULL
Expand Down
2 changes: 1 addition & 1 deletion server/db/schema/class_enrollments.sql
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
CREATE TABLE IF NOT EXISTS class_enrollments (
id INTEGER NOT NULL GENERATED ALWAYS AS IDENTITY ( INCREMENT 1 START 1 MINVALUE 1 MAXVALUE 2147483647 CACHE 1 ),
id INTEGER NOT NULL GENERATED BY DEFAULT AS IDENTITY ( INCREMENT 1 START 1 MINVALUE 1 MAXVALUE 2147483647 CACHE 1 ),
student_id INTEGER NOT NULL,
class_id INTEGER NOT NULL,
attendance DATE NOT NULL,
Expand Down
2 changes: 1 addition & 1 deletion server/db/schema/class_videos.sql
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
DROP TABLE IF EXISTS class_videos CASCADE;

CREATE TABLE class_videos (
id INTEGER PRIMARY KEY NOT NULL GENERATED ALWAYS AS IDENTITY ( INCREMENT 1 START 1 MINVALUE 1 MAXVALUE 2147483647 CACHE 1 ),
id INTEGER PRIMARY KEY NOT NULL GENERATED BY DEFAULT AS IDENTITY ( INCREMENT 1 START 1 MINVALUE 1 MAXVALUE 2147483647 CACHE 1 ),
title VARCHAR(256) NOT NULL,
s3_url VARCHAR(256) NOT NULL,
description TEXT NOT NULL,
Expand Down
2 changes: 1 addition & 1 deletion server/db/schema/classes.sql
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ CREATE TYPE LEVEL AS ENUM ("beginner", "intermediate", "advanced")

CREATE TABLE IF NOT EXISTS public.classes
(
id integer NOT NULL GENERATED ALWAYS AS IDENTITY ( INCREMENT 1 START 1 MINVALUE 1 MAXVALUE 2147483647 CACHE 1 ),
id integer NOT NULL GENERATED BY DEFAULT AS IDENTITY ( INCREMENT 1 START 1 MINVALUE 1 MAXVALUE 2147483647 CACHE 1 ),
title VARCHAR(256) NOT NULL,
description TEXT,
location VARCHAR(256) NOT NULL,
Expand Down
2 changes: 1 addition & 1 deletion server/db/schema/event_enrollments.sql
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
CREATE TABLE IF NOT EXISTS event_enrollments (
id INTEGER PRIMARY KEY NOT NULL GENERATED ALWAYS AS IDENTITY ( INCREMENT 1 START 1 MINVALUE 1 MAXVALUE 2147483647 CACHE 1 ),,
id INTEGER PRIMARY KEY NOT NULL GENERATED BY DEFAULT AS IDENTITY ( INCREMENT 1 START 1 MINVALUE 1 MAXVALUE 2147483647 CACHE 1 ),,
student_id INTEGER NOT NULL,
event_id INTEGER NOT NULL,
attendance BOOLEAN NOT NULL,
Expand Down
2 changes: 1 addition & 1 deletion server/db/schema/events.sql
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ DROP TYPE IF EXISTS LEVEL;
CREATE TYPE LEVEL AS ENUM ('beginner', 'intermediate', 'advanced');

CREATE TABLE Events (
id INTEGER PRIMARY KEY NOT NULL GENERATED ALWAYS AS IDENTITY ( INCREMENT 1 START 1 MINVALUE 1 MAXVALUE 2147483647 CACHE 1 ),,
id INTEGER PRIMARY KEY NOT NULL GENERATED BY DEFAULT AS IDENTITY ( INCREMENT 1 START 1 MINVALUE 1 MAXVALUE 2147483647 CACHE 1 ),,
location VARCHAR(256) NOT NULL,
title VARCHAR(256) NOT NULL,
description TEXT,
Expand Down
2 changes: 1 addition & 1 deletion server/db/schema/users.sql
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ CREATE TYPE USER_ROLE AS ENUM('student', 'teacher', 'admin');

CREATE TABLE IF NOT EXISTS public.users
(
id integer NOT NULL GENERATED ALWAYS AS IDENTITY ( INCREMENT 1 START 1 MINVALUE 1 MAXVALUE 2147483647 CACHE 1 ),
id integer NOT NULL GENERATED BY DEFAULT AS IDENTITY ( INCREMENT 1 START 1 MINVALUE 1 MAXVALUE 2147483647 CACHE 1 ),
email character varying(256) COLLATE pg_catalog."default" NOT NULL,
firebase_uid character varying(128) COLLATE pg_catalog."default" NOT NULL,
role character varying(16) COLLATE pg_catalog."default" NOT NULL DEFAULT 'user'::character varying,
Expand Down
148 changes: 148 additions & 0 deletions server/routes/articles.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,148 @@
import express from "express";

import { keysToCamel } from "../common/utils";
import { db } from "../db/db-pgp";

const articlesRouter = express.Router();
articlesRouter.use(express.json());

interface Article {
id: number;
s3_url: string;
description: string;
media_url: string;
}

interface ArticleRequest {
s3_url?: string;
description?: string;
media_url?: string;
}

// GET /articles/:id
articlesRouter.get("/:id", async (req, res) => {
try {
// Select rows where the id matches the id in the request parameters
const rows = await db.query("SELECT * FROM articles WHERE id = $1", [
req.params.id,
]);
// If no rows are returned, send a 404 response
if (rows.length === 0) {
return res.status(404).json({ error: "Article not found" });
}
// Convert the snake_case keys to camelCase and send the response
res.status(200).json(keysToCamel(rows[0] as Article));
} catch (error) {
res.status(500).json({ error: error.message });
}
});

// GET /articles
articlesRouter.get("/", async (req, res) => {
try {
// Select all rows from the articles table
const rows = await db.query("SELECT * FROM articles");
// Convert the snake_case keys to camelCase and send the response
res.status(200).json(keysToCamel(rows) as Article[]);
} catch (error) {
res.status(500).json({ error: error.message });
}
});

// POST /articles
articlesRouter.post("/", async (req, res) => {
try {
// Destructure the request body
const { s3_url, description, media_url } = req.body as ArticleRequest;
// Since its required in the schema send an error
if (!s3_url || !description || !media_url) {
return res.status(400).json({ error: "Missing required parameters" });
}
// Insert the new article into the database
// Returning * will return the newly inserted row in the response
const rows = await db.query(
"INSERT INTO articles (s3_url, description, media_url) VALUES ($1, $2, $3) RETURNING *",
[s3_url, description, media_url]
);
// Convert the snake_case keys to camelCase and send the response with status 201 (Created)
res.status(201).json(keysToCamel(rows[0] as Article));
} catch (error) {
res.status(500).json({ error: error.message });
}
});

// PUT /articles/:id
articlesRouter.put("/:id", async (req, res) => {
try {
// Destructure the request body
const { id } = req.params;
const { s3_url, description, media_url } = req.body as ArticleRequest;

const to_update = []; // Parameters that needed to be updated
const values = []; // Values that need to be assosciated with the specific parameter
let paramCount = 1; // the id of the value matching the parameter

if (s3_url) {
to_update.push(`s3_url = $${paramCount}`);
values.push(s3_url);
paramCount++;
}
if (description) {
to_update.push(`description = $${paramCount}`);
values.push(description);
paramCount++;
}
if (media_url) {
to_update.push(`media_url = $${paramCount}`);
values.push(media_url);
paramCount++;
}

values.push(id);

const query = `
UPDATE articles
SET ${to_update.join(", ")}
WHERE id = $${paramCount}
RETURNING *
`;

// Update the article with the matching id
const rows = await db.query(query, values);

// If no rows are returned, send a 404 response
if (rows.length === 0) {
// Could not find the article with the given id
return res.status(404).json({ error: "Article not found" });
}
// Convert the snake_case keys to camelCase and send the response
res.status(200).json(keysToCamel(rows[0]) as Article);
} catch (error) {
// Send a 500 response with the error message
res.status(500).json({ error: error.message });
}
});

// DELETE /articles/:id
articlesRouter.delete("/:id", async (req, res) => {
try {
// Delete the article with the matching id
const rows = await db.query(
"DELETE FROM articles WHERE id = $1 RETURNING *",
[req.params.id]
);
// If no rows are returned, send a 404 response
if (rows.length === 0) {
// Could not find the article with the given id
return res.status(404).json({ error: "Article not found" });
}
// Convert the snake_case keys to camelCase and send the response
res.status(200).json(keysToCamel(rows[0] as Article));
} catch (error) {
// Send a 500 response with the error message
res.status(500).json({ error: error.message });
}
});

// Export the router made to handle these new routes
export default articlesRouter;
128 changes: 128 additions & 0 deletions server/routes/event_enrollments.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,128 @@
import express from "express";

import { keysToCamel } from "../common/utils";
import { db } from "../db/db-pgp";

const eventEnrollmentRouter = express.Router();
eventEnrollmentRouter.use(express.json());

interface EventEnrollment {
id: number;
student_id: number;
event_id: number;
attendance: boolean;
}

interface EventEnrollmentRequest {
student_id?: number;
event_id?: number;
attendance?: boolean;
}

// GET /
eventEnrollmentRouter.get("/", async (req, res) => {
try {
const events = await db.query("SELECT * FROM event_enrollments");
res.status(200).json(keysToCamel(events) as EventEnrollment[]);
} catch (error) {
res.status(500).json({ error: error.message });
}
});

// GET /:id
eventEnrollmentRouter.get("/:id", async (req, res) => {
try {
const events = await db.query(
"SELECT * FROM event_enrollments WHERE id = $1",
[req.params.id]
);
// If no rows are returned, send a 404 response
if (events.length === 0) {
return res.status(404).json({ error: "Event not found" });
}
// Convert the snake_case keys to camelCase and send the response
res.status(200).json(keysToCamel(events[0] as EventEnrollment));
} catch (error) {
res.status(500).json({ error: error.message });
}
});

// POST /event-enrollments
eventEnrollmentRouter.post("/", async (req, res) => {
try {
// Destructure the request body
const { student_id, event_id } = req.body as EventEnrollmentRequest;
// mathing sql schema
if (!student_id || !event_id) {
return res.status(400).json({ error: "Missing required parameters" });
}

// Insert the new article into the database
// Returning * will return the newly inserted row in the response

// By default the attendance will be false as mentioned in the table
const rows = await db.query(
"INSERT INTO event_enrollments (student_id, event_id, attendance) VALUES ($1, $2, false) RETURNING *",
[student_id, event_id]
);
// Convert the snake_case keys to camelCase and send the response with status 201 (Created)
res.status(201).json(keysToCamel(rows[0] as EventEnrollment));
} catch (error) {
res.status(500).json({ error: error.message });
}
});

// PUT /:id
eventEnrollmentRouter.put("/:id", async (req, res) => {
try {
// Destructure the request body
const { id } = req.params;
const { student_id, event_id, attendance } =
req.body as EventEnrollmentRequest;

const to_update = [];
const values = [];
let paramCount = 1;

if (student_id) {
to_update.push(`student_id = $${paramCount}`);
values.push(student_id);
paramCount++;
}
if (event_id) {
to_update.push(`event_id = $${paramCount}`);
values.push(event_id);
paramCount++;
}
if (attendance) {
to_update.push(`attendance = $${paramCount}`);
values.push(attendance);
paramCount++;
}

values.push(id);

const query = `
UPDATE event_enrollments
SET ${to_update.join(", ")}
WHERE id = $${paramCount}
RETURNING *
`;

// Update the article with the matching id
const events = await db.query(query, values);

// If no rows are returned, send a 404 response
if (events.length === 0) {
// Could not find the event-enrollment with the given id
return res.status(404).json({ error: "Event not found" });
}
// Convert the snake_case keys to camelCase and send the response
res.status(200).json(keysToCamel(events[0]) as EventEnrollment);
} catch (error) {
// Send a 500 response with the error message
res.status(500).json({ error: error.message });
}
});

export default eventEnrollmentRouter;
34 changes: 0 additions & 34 deletions server/routes/sample.ts

This file was deleted.

9 changes: 7 additions & 2 deletions server/src/app.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,9 @@ import dotenv from "dotenv";
import express from "express";
import schedule from "node-schedule"; // TODO: Keep only if scheduling cronjobs

import { sampleRouter } from "../routes/sample"; // TODO: delete sample router
import articlesRouter from "../routes/articles";
import eventEnrollmentRouter from "../routes/event_enrollments";
// import { sampleRouter } from "../routes/sample"; // TODO: delete sample router
import { usersRouter } from "../routes/users";
import { teachersRouter } from "../routes/teachers"
import { verifyToken } from "./middleware";
Expand Down Expand Up @@ -37,9 +39,12 @@ if (process.env.NODE_ENV === "production") {
app.use(verifyToken);
}

app.use("/", sampleRouter); // TODO: delete sample endpoint
// app.use("/", sampleRouter); // TODO: delete sample endpoint
app.use("/users", usersRouter);
app.use("/teachers", teachersRouter)
// connecting made router with the app
app.use("/articles", articlesRouter);
app.use("/event-enrollments", eventEnrollmentRouter);

app.listen(SERVER_PORT, () => {
console.info(`Server listening on ${SERVER_PORT}`);
Expand Down

0 comments on commit 962313f

Please sign in to comment.