From e8ea17b8cfe6384d5a20a18d346db48e47aabcdb Mon Sep 17 00:00:00 2001 From: liv Date: Thu, 2 May 2024 16:28:17 +1000 Subject: [PATCH 1/4] Added typing to requests --- backend/routes/routes.ts | 98 ++++++++++++++++++++-------------------- backend/types.ts | 69 ++++++++++++++++++++++++++++ 2 files changed, 118 insertions(+), 49 deletions(-) create mode 100644 backend/types.ts diff --git a/backend/routes/routes.ts b/backend/routes/routes.ts index d704797..98d859f 100644 --- a/backend/routes/routes.ts +++ b/backend/routes/routes.ts @@ -1,8 +1,18 @@ // Imports import { Router, Request, Response } from 'express'; // Import Request and Response types import * as db from '../db/index'; -import { v4 as uuidv4, validate } from 'uuid'; -const { exec } = require('child_process'); +import { + Comment as IComment, + CommentBodyParams, + CommentRouteParams, + CourseBodyParams, + CourseQueryParams, + CourseRouteParams, + ExamBodyParams, + ExamRouteParams, + QuestionBodyParams, + QuestionRouteParams, +} from '../types'; // Export Routers export const router = Router(); @@ -20,16 +30,18 @@ export const router = Router(); */ // Edits a question -router.put('/questions/:questionId/edit', async (req: Request, res: Response) => { +router.put('/questions/:questionId/edit', async (req: Request, res: Response) => { const questionId = req.params.questionId const { questionText, questionType, questionPNG } = req.body; if (!questionText && !questionType && !questionPNG) { res.status(400).json('No changes made!'); return; } - let args = []; - let query = `UPDATE questions SET ` + + const args = []; let count = 1; + let query = `UPDATE questions SET `; + if (questionText) { query += `"questionText" = $${count}::text, ` args.push(questionText); @@ -46,6 +58,8 @@ router.put('/questions/:questionId/edit', async (req: Request, res: Response) => count++; } query += `"updated_at" = NOW() WHERE "questionId" = $${count}::int` + args.push(questionId); + const r = await db.query(query, args); if (r.rowCount === 0) { res.status(401).json('Question not found!'); @@ -55,13 +69,13 @@ router.put('/questions/:questionId/edit', async (req: Request, res: Response) => }); // Edits a comment -router.put('/comments/:commentId/edit', async (req: Request, res: Response) => { +router.put('/comments/:commentId/edit', async (req: Request, res: Response) => { const commentId = req.params.commentId; if (!req.body.commentText && !req.body.commentPNG) { res.status(400).json('No changes made!'); return; } - const count = await editComment(+commentId, req.body.commentText, req.body.commentPNG); + const count = await editComment(commentId, req.body.commentText, req.body.commentPNG); if (count.rowCount === 0) { res.status(401).json('Question not found!'); return; @@ -78,9 +92,9 @@ router.put('/comments/:commentId/edit', async (req: Request, res: Response) => { */ // Deletes a comment -router.patch('/comments/:commentId/delete', async (req: Request, res: Response) => { +router.patch('/comments/:commentId/delete', async (req: Request, res: Response) => { const commentId = req.params.commentId; - const count = await editComment(+commentId, '', ''); + const count = await editComment(commentId, '', ''); if (count.rowCount === 0) { res.status(401).json('Question not found!'); return; @@ -89,7 +103,7 @@ router.patch('/comments/:commentId/delete', async (req: Request, res: Response) }); // Sets a comment as correct -router.patch('/comments/:commentId/correct', async (req: Request, res: Response) => { +router.patch('/comments/:commentId/correct', async (req: Request, res: Response) => { const commentId = req.params.commentId; const count = await db.query(` UPDATE comments @@ -104,7 +118,7 @@ router.patch('/comments/:commentId/correct', async (req: Request, res: Response) }); // Endorses a comment -router.patch('/comments/:commentId/endorse', async (req: Request, res: Response) => { +router.patch('/comments/:commentId/endorse', async (req: Request, res: Response) => { const commentId = req.params.commentId; const count = await db.query(` UPDATE comments @@ -119,7 +133,7 @@ router.patch('/comments/:commentId/endorse', async (req: Request, res: Response) }); // Downvotes a comment -router.patch('/comments/:commentId/downvote', async (req: Request, res: Response) => { +router.patch('/comments/:commentId/downvote', async (req: Request, res: Response) => { const commentId = req.params.commentId; const count = await db.query(` UPDATE comments @@ -134,7 +148,7 @@ router.patch('/comments/:commentId/downvote', async (req: Request, res: Response }); // Upvotes a comment -router.patch('/comments/:commentId/upvote', async (req: Request, res: Response) => { +router.patch('/comments/:commentId/upvote', async (req: Request, res: Response) => { const commentId = req.params.commentId; const count = await db.query(` UPDATE comments @@ -157,7 +171,7 @@ router.patch('/comments/:commentId/upvote', async (req: Request, res: Response) */ // Adds a new comment to the database -router.post('/comments', async (req: Request, res: Response) => { +router.post('/comments', async (req: Request, res: Response) => { const { questionId, parentCommentId, commentText, commentPNG, isCorrect, isEndorsed, upvotes, downvotes } = req.body; // Check key if (!questionId) { @@ -190,7 +204,7 @@ router.post('/comments', async (req: Request, res: Response) => { }); // Adds a new question to the database -router.post('/questions', async (req: Request, res: Response) => { +router.post('/questions', async (req: Request, res: Response) => { const { examId, questionText, questionType, questionPNG } = req.body; // Check key if (!examId) { @@ -210,7 +224,7 @@ router.post('/questions', async (req: Request, res: Response) => { }); // Adds a new exam to the databasecustomersscustomerss -router.post('/exams', async (req: Request, res: Response) => { +router.post('/exams', async (req: Request, res: Response) => { const { examYear, examSemester, examType, courseCode } = req.body; // Check key if (!courseCode) { @@ -230,7 +244,7 @@ router.post('/exams', async (req: Request, res: Response) => { }); // Adds a new Course to the database -router.post('/courses', async (req: Request, res: Response) => { +router.post('/courses', async (req: Request, res: Response) => { const { courseCode, courseName, courseDescription } = req.body; const r = await db.query(`SELECT courseCode FROM courses WHERE courseCode = $1`, [courseCode]); if (r.rowCount !== 0) { @@ -250,10 +264,10 @@ router.post('/courses', async (req: Request, res: Response) => { * * See outputs and params in HANDSHAKE.md * - */ + */ // Gets comment by comment id -router.get('/comments/:commentId', async (req: Request, res: Response) => { +router.get('/comments/:commentId', async (req: Request, res: Response) => { const commentId = req.params.commentId; const comment = await db.query(` SELECT "commentId", "questionId", "parentCommentId", "commentText", "commentPNG", "isCorrect", "isEndorsed", "upvotes", "downvotes", "created_at", "updated_at" @@ -264,7 +278,7 @@ router.get('/comments/:commentId', async (req: Request, res: Response) => { }); // Gets all comments by question id -router.get('/questions/:questionId/comments', async (req: Request, res: Response) => { +router.get('/questions/:questionId/comments', async (req: Request, res: Response) => { const questionId = req.params.questionId; const question = await db.query(` SELECT "commentId", "parentCommentId", "commentText", "commentPNG", "isCorrect", "isEndorsed", "upvotes", "downvotes", "created_at", "updated_at" @@ -275,7 +289,7 @@ router.get('/questions/:questionId/comments', async (req: Request, res: Response }); // Gets question information by question id -router.get('/questions/:questionId', async (req: Request, res: Response) => { +router.get('/questions/:questionId', async (req: Request, res: Response) => { const questionId = req.params.questionId; const question = await db.query(` SELECT "questionId", "questionText", "questionType", "questionPNG" @@ -286,7 +300,7 @@ router.get('/questions/:questionId', async (req: Request, res: Response) => { }); // Exam questions by exam ID -router.get('/exams/:examId/questions', async (req: Request, res: Response) => { +router.get('/exams/:examId/questions', async (req: Request, res: Response) => { const examId = req.params.examId; const exam = await db.query(` SELECT "questionId", "questionText", "questionType", "questionPNG" @@ -297,7 +311,7 @@ router.get('/exams/:examId/questions', async (req: Request, res: Response) => { }); // Exam by ID -router.get('/exams/:examId', async (req: Request, res: Response) => { +router.get('/exams/:examId', async (req: Request, res: Response) => { const examId = req.params.examId; const exams = await db.query(` SELECT "examId", "examYear", "examSemester", "examType" @@ -308,7 +322,7 @@ router.get('/exams/:examId', async (req: Request, res: Response) => { }); // A course's exams by code -router.get('/courses/:courseCode/exams', async (req: Request, res: Response) => { +router.get('/courses/:courseCode/exams', async (req: Request, res: Response) => { const courseCode = req.params.courseCode; const course = await db.query(` SELECT "examId", "examYear", "examSemester", "examType" @@ -319,7 +333,7 @@ router.get('/courses/:courseCode/exams', async (req: Request, res: Response) => }); // A Courses information by code -router.get('/courses/:courseCode', async (req: Request, res: Response) => { +router.get('/courses/:courseCode', async (req: Request, res: Response) => { const courseCode = req.params.courseCode; const course = await db.query(` SELECT "courseCode", "courseName", "courseDescription" @@ -330,15 +344,15 @@ router.get('/courses/:courseCode', async (req: Request, res: Response) => { }); // All courses -router.get('/courses', async (req: Request, res: Response) => { - const offet = req.query.offset ?? 0; +router.get('/courses', async (req: Request, res: Response) => { + const offset = req.query.offset ?? 0; const limit = req.query.limit ?? 100; const courses = await db.query(` SELECT "courseCode", "courseName", "courseDescription" FROM courses LIMIT $1 OFFSET $2 - `, [limit, offet]); + `, [limit, offset]); res.status(200).json(courses.rows); }); @@ -420,31 +434,15 @@ router.get('/sketch', async (req: Request, res: Response) => { `); res.status(200).json(`THIS SHIT SKETCH ASF AND WAS LIV'S IDEA!!!`); }); -// Interfaces - -// Used in nest helper function -interface CommentObject { - commentId: number; - questionId: number; - parentCommentId: number | null; - commenttext: string; - commentPNG: string | null; - iscorrect: boolean; - isendorsed: boolean; - upvotes: number; - downvotes: number; - created_at: string; - updated_at: string; - children?: CommentObject[]; -} // Helper functions // function to edit / delete a comment -async function editComment(commentId: number, commentText: string, commentPNG: string) { - let args = []; +async function editComment(commentId: number, commentText?: string | null, commentPNG?: string | null) { + const args = []; let query = `UPDATE questions SET ` let count = 1; + if (commentText) { query += `"commentText" = $${count}::text, ` args.push(commentText); @@ -456,12 +454,14 @@ async function editComment(commentId: number, commentText: string, commentPNG: s count++; } query += `"updated_at" = NOW() WHERE "commentId" = $${count}::int` + args.push(commentId) + return await db.query(query, args); } // function to nest comments into their parent comments export function nest(jsonData: any[]) { - const dataDict: { [id: number]: CommentObject } = {}; + const dataDict: { [id: number]: IComment } = {}; jsonData.forEach(item => dataDict[item.commentId] = item); jsonData.forEach(item => { @@ -480,7 +480,7 @@ export function nest(jsonData: any[]) { // function to return one comment with its children export function single_nest(jsonData: any[], commentId: number) { - const dataDict: { [id: number]: CommentObject } = {}; + const dataDict: { [id: number]: IComment } = {}; jsonData.forEach(item => dataDict[item.commentId] = item); jsonData.forEach(item => { diff --git a/backend/types.ts b/backend/types.ts new file mode 100644 index 0000000..3d8415a --- /dev/null +++ b/backend/types.ts @@ -0,0 +1,69 @@ +export type Course = { + courseCode: string + courseName: string + courseDescription: string +} + +export type Exam = { + examId: number + examYear: number + examSemester: number + examType: string +} + +export type Question = { + questionId: number + questionText: string + questionType: string + questionPNG: string +} + +export type Comment = { + commentId: number + parentCommentId: number | null + commentText: string + commentPNG: string | null + isCorrect: boolean + isEndorsed: boolean + upvotes: number + downvotes: number + created_at: string + updated_at: string + children?: Comment[] +} + + +export type CommentBodyParams = Partial> & { + questionId?: number +} + +export type CommentRouteParams = { + commentId: number +} + +export type QuestionBodyParams = Partial> & { + examId?: number +} + +export type QuestionRouteParams = { + questionId: number +} + +export type ExamBodyParams = Partial> & { + courseCode?: string +} + +export type ExamRouteParams = { + examId: number +} + +export type CourseBodyParams = Course + +export type CourseRouteParams = { + courseCode: string +} + +export type CourseQueryParams = { + offset?: number + limit?: number +} \ No newline at end of file From c06d0f083c8a12490b38a6c1d2c975e456159fda Mon Sep 17 00:00:00 2001 From: liv Date: Thu, 2 May 2024 16:46:06 +1000 Subject: [PATCH 2/4] Cleanup Co-authored-by: Jackson Trenerry <105094182+jtrenerry@users.noreply.github.com> --- backend/routes/routes.ts | 193 +++++++++++++++++++++++++++------------ 1 file changed, 134 insertions(+), 59 deletions(-) diff --git a/backend/routes/routes.ts b/backend/routes/routes.ts index 98d859f..0b826d6 100644 --- a/backend/routes/routes.ts +++ b/backend/routes/routes.ts @@ -31,8 +31,9 @@ export const router = Router(); // Edits a question router.put('/questions/:questionId/edit', async (req: Request, res: Response) => { - const questionId = req.params.questionId + const { questionId } = req.params; const { questionText, questionType, questionPNG } = req.body; + if (!questionText && !questionType && !questionPNG) { res.status(400).json('No changes made!'); return; @@ -47,39 +48,46 @@ router.put('/questions/:questionId/edit', async (req: Request, res: Response) => { - const commentId = req.params.commentId; + const { commentId } = req.params; + if (!req.body.commentText && !req.body.commentPNG) { res.status(400).json('No changes made!'); return; } - const count = await editComment(commentId, req.body.commentText, req.body.commentPNG); - if (count.rowCount === 0) { + + const { rowCount } = await editComment(commentId, req.body.commentText, req.body.commentPNG); + if (rowCount === 0) { res.status(401).json('Question not found!'); return; } + res.status(200).json('Comment Edited!'); }); @@ -93,72 +101,82 @@ router.put('/comments/:commentId/edit', async (req: Request, res: Response) => { - const commentId = req.params.commentId; - const count = await editComment(commentId, '', ''); - if (count.rowCount === 0) { + const { commentId } = req.params; + + const { rowCount } = await editComment(commentId, '', ''); + if (rowCount === 0) { res.status(401).json('Question not found!'); return; } + res.status(200).json('Comment Deleted!'); }); // Sets a comment as correct router.patch('/comments/:commentId/correct', async (req: Request, res: Response) => { - const commentId = req.params.commentId; - const count = await db.query(` + const { commentId } = req.params; + + const { rowCount } = await db.query(` UPDATE comments SET "isCorrect" = true WHERE "commentId" = $1 `, [commentId]); - if (count.rowCount === 0) { + if (rowCount === 0) { res.status(400).json('Comment not found!'); return; } + res.status(200).json('Corrected!'); }); // Endorses a comment router.patch('/comments/:commentId/endorse', async (req: Request, res: Response) => { - const commentId = req.params.commentId; - const count = await db.query(` + const { commentId } = req.params; + + const { rowCount } = await db.query(` UPDATE comments SET "isEndorsed" = true WHERE "commentId" = $1 `, [commentId]); - if (count.rowCount === 0) { + if (rowCount === 0) { res.status(400).json('Comment not found!'); return; } + res.status(200).json('Endorsed!'); }); // Downvotes a comment router.patch('/comments/:commentId/downvote', async (req: Request, res: Response) => { - const commentId = req.params.commentId; - const count = await db.query(` + const { commentId } = req.params; + + const { rowCount } = await db.query(` UPDATE comments SET "downvotes" = "downvotes" + 1 WHERE "commentId" = $1 `, [commentId]); - if (count.rowCount === 0) { + if (rowCount === 0) { res.status(400).json('Comment not found!'); return; } + res.status(200).json('Downvoted!'); }); // Upvotes a comment router.patch('/comments/:commentId/upvote', async (req: Request, res: Response) => { - const commentId = req.params.commentId; - const count = await db.query(` + const { commentId } = req.params; + + const { rowCount } = await db.query(` UPDATE comments SET "upvotes" = "upvotes" + 1 WHERE "commentId" = $1 `, [commentId]); - if (count.rowCount === 0) { + if (rowCount === 0) { res.status(400).json('Comment not found!'); return; } + res.status(200).json('Upvoted!'); }); @@ -172,89 +190,128 @@ router.patch('/comments/:commentId/upvote', async (req: Request, res: Response) => { - const { questionId, parentCommentId, commentText, commentPNG, isCorrect, isEndorsed, upvotes, downvotes } = req.body; + const { + questionId, + parentCommentId, + commentText, + commentPNG, + isCorrect, + isEndorsed, + upvotes, + downvotes, + } = req.body; + // Check key if (!questionId) { res.status(400).json('Missing questionId!'); return; } - const r = await db.query(`SELECT "questionId" FROM questions WHERE "questionId" = $1`, [questionId]); - if (r.rowCount === 0) { + + const { rowCount } = await db.query(`SELECT "questionId" FROM questions WHERE "questionId" = $1`, [questionId]); + if (rowCount === 0) { res.status(401).json('Question not found!'); return; } + // Check parent id if (parentCommentId) { - const r = await db.query(`SELECT "commentId", "questionId" FROM comments WHERE "commentId" = $1`, [parentCommentId]); - if (r.rowCount === 0) { + const { rowCount, rows } = await db.query(`SELECT "commentId", "questionId" FROM comments WHERE "commentId" = $1`, [parentCommentId]); + if (rowCount === 0) { res.status(402).json('Parent comment not found!'); return; } - const a = r.rows[0]; + const a = rows[0]; if ((a as any).questionId !== questionId) { res.status(403).json('Parent comment is not from the same question!'); return; } } + await db.query(` INSERT INTO comments ("questionId", "parentCommentId", "commentText", "commentPNG", "isCorrect", "isEndorsed", "upvotes", "downvotes") VALUES ($1, $2, $3, $4, $5, $6, $7, $8) `, [questionId, parentCommentId, commentText, commentPNG, isCorrect, isEndorsed, upvotes, downvotes]); + res.status(201).json('Comment Added!'); }); // Adds a new question to the database router.post('/questions', async (req: Request, res: Response) => { - const { examId, questionText, questionType, questionPNG } = req.body; + const { + examId, + questionText, + questionType, + questionPNG, + } = req.body; + // Check key if (!examId) { res.status(400).json('Missing examId!'); return; } - const r = await db.query(`SELECT "examId" exams WHERE "examId" = $1`, [examId]); - if (r.rowCount === 0) { + + const { rowCount } = await db.query(`SELECT "examId" exams WHERE "examId" = $1`, [examId]); + if (rowCount === 0) { res.status(401).json('Exam not found!'); return; } + await db.query(` INSERT INTO questions ("examId", "questionText", "questionType", "questionPNG") VALUES ($1, $2, $3, $4) `, [examId, questionText, questionType, questionPNG]); + res.status(201).json('Question Added!'); }); // Adds a new exam to the databasecustomersscustomerss router.post('/exams', async (req: Request, res: Response) => { - const { examYear, examSemester, examType, courseCode } = req.body; + const { + examYear, + examSemester, + examType, + courseCode, + } = req.body; + // Check key if (!courseCode) { res.status(400).json('Missing courseCode!'); return; } - const r = await db.query(`SELECT "courseCode" courses WHERE "courseCode" = $1`, [courseCode]); - if (r.rowCount === 0) { + + const { rowCount } = await db.query(`SELECT "courseCode" courses WHERE "courseCode" = $1`, [courseCode]); + if (rowCount === 0) { res.status(401).json('Exam not found!'); return; } + await db.query(` INSERT INTO exams ("examYear", "examSemester", "examType", "courseCode") VALUES ($1, $2, $3, $4) `, [examYear, examSemester, examType, courseCode]); + res.status(201).json('Exam Added!'); }); // Adds a new Course to the database router.post('/courses', async (req: Request, res: Response) => { - const { courseCode, courseName, courseDescription } = req.body; - const r = await db.query(`SELECT courseCode FROM courses WHERE courseCode = $1`, [courseCode]); - if (r.rowCount !== 0) { + const { + courseCode, + courseName, + courseDescription, + } = req.body; + + const { rowCount } = await db.query(`SELECT courseCode FROM courses WHERE courseCode = $1`, [courseCode]); + if (rowCount !== 0) { res.status(400).json('Course already exists!'); return; } + await db.query(` INSERT INTO courses ("courseCode", "courseName", "courseDescription") VALUES ($1, $2, $3) `, [courseCode, courseName, courseDescription]); + res.status(201).json('Course Added!'); }); @@ -268,92 +325,108 @@ router.post('/courses', async (req: Request, res: Re // Gets comment by comment id router.get('/comments/:commentId', async (req: Request, res: Response) => { - const commentId = req.params.commentId; - const comment = await db.query(` + const { commentId } = req.params; + + const { rows } = await db.query(` SELECT "commentId", "questionId", "parentCommentId", "commentText", "commentPNG", "isCorrect", "isEndorsed", "upvotes", "downvotes", "created_at", "updated_at" FROM comments WHERE comments."commentId" = $1 `, [commentId]); - res.status(200).json(comment.rows[0]); + + res.status(200).json(rows[0]); }); // Gets all comments by question id router.get('/questions/:questionId/comments', async (req: Request, res: Response) => { - const questionId = req.params.questionId; - const question = await db.query(` + const { questionId } = req.params; + + const { rows } = await db.query(` SELECT "commentId", "parentCommentId", "commentText", "commentPNG", "isCorrect", "isEndorsed", "upvotes", "downvotes", "created_at", "updated_at" FROM comments WHERE comments."questionId" = $1 `, [questionId]); - res.status(200).json(nest(question.rows)); + + res.status(200).json(nest(rows)); }); // Gets question information by question id router.get('/questions/:questionId', async (req: Request, res: Response) => { - const questionId = req.params.questionId; - const question = await db.query(` + const { questionId } = req.params; + + const { rows } = await db.query(` SELECT "questionId", "questionText", "questionType", "questionPNG" FROM questions WHERE questions."questionId" = $1 `, [questionId]); - res.status(200).json(question.rows[0]); + + res.status(200).json(rows[0]); }); // Exam questions by exam ID router.get('/exams/:examId/questions', async (req: Request, res: Response) => { - const examId = req.params.examId; - const exam = await db.query(` + const { examId } = req.params; + + const { rows } = await db.query(` SELECT "questionId", "questionText", "questionType", "questionPNG" FROM questions WHERE questions."examId" = $1 `, [examId]); - res.status(200).json(exam.rows); + + res.status(200).json(rows); }); // Exam by ID router.get('/exams/:examId', async (req: Request, res: Response) => { - const examId = req.params.examId; - const exams = await db.query(` + const { examId } = req.params; + + const { rows } = await db.query(` SELECT "examId", "examYear", "examSemester", "examType" FROM exams WHERE exams."examId" = $1 `, [examId]); - res.status(200).json(exams.rows[0]); + + res.status(200).json(rows[0]); }); // A course's exams by code router.get('/courses/:courseCode/exams', async (req: Request, res: Response) => { - const courseCode = req.params.courseCode; - const course = await db.query(` + const { courseCode } = req.params; + + const { rows } = await db.query(` SELECT "examId", "examYear", "examSemester", "examType" FROM exams WHERE exams."courseCode" = $1 `, [courseCode]); - res.status(200).json(course.rows); + + res.status(200).json(rows); }); // A Courses information by code router.get('/courses/:courseCode', async (req: Request, res: Response) => { - const courseCode = req.params.courseCode; - const course = await db.query(` + const { courseCode } = req.params; + + const { rows } = await db.query(` SELECT "courseCode", "courseName", "courseDescription" FROM courses WHERE courses."courseCode" = $1 `, [courseCode]); - res.status(200).json(course.rows[0]); + + res.status(200).json(rows[0]); }); // All courses router.get('/courses', async (req: Request, res: Response) => { const offset = req.query.offset ?? 0; const limit = req.query.limit ?? 100; - const courses = await db.query(` + + const { rows } = await db.query(` SELECT "courseCode", "courseName", "courseDescription" FROM courses LIMIT $1 OFFSET $2 `, [limit, offset]); - res.status(200).json(courses.rows); + + res.status(200).json(rows); }); // Health Check @@ -448,11 +521,13 @@ async function editComment(commentId: number, commentText?: string | null, comme args.push(commentText); count++; } + if (commentPNG) { query += `"commentPNG" = $${count}::text, ` args.push(commentPNG); count++; } + query += `"updated_at" = NOW() WHERE "commentId" = $${count}::int` args.push(commentId) From c8b252b7311afd4b2982a8f8e4ee6c18b67d826c Mon Sep 17 00:00:00 2001 From: liv Date: Thu, 2 May 2024 17:00:15 +1000 Subject: [PATCH 3/4] Added typing to db queries --- backend/db/index.ts | 14 +++++++------- backend/routes/routes.ts | 41 +++++++++++++++++++++------------------- backend/types.ts | 1 + 3 files changed, 30 insertions(+), 26 deletions(-) diff --git a/backend/db/index.ts b/backend/db/index.ts index 1210ac5..9c66af7 100644 --- a/backend/db/index.ts +++ b/backend/db/index.ts @@ -1,5 +1,5 @@ // Imports -import { Pool } from 'pg' +import { Pool, QueryResultRow } from 'pg'; const pool = new Pool({ user: process.env.DB_USER, @@ -54,14 +54,14 @@ export const setupTables = () => { pool.query(query); } -export const query1 = (text: any) => { - return pool.query(text); +export function query1(text: any) { + return pool.query(text); } -export const query = (text: any, params: any) => { - return pool.query(text, params); +export function query(text: any, params: any) { + return pool.query(text, params); } -export const query3 = (text: any, params: any, callback: any) => { - return pool.query(text, params, callback); +export function query3(text: any, params: any, callback: any) { + return pool.query(text, params, callback); } diff --git a/backend/routes/routes.ts b/backend/routes/routes.ts index 0b826d6..45d69a7 100644 --- a/backend/routes/routes.ts +++ b/backend/routes/routes.ts @@ -5,11 +5,14 @@ import { Comment as IComment, CommentBodyParams, CommentRouteParams, + Course, CourseBodyParams, CourseQueryParams, CourseRouteParams, + Exam, ExamBodyParams, ExamRouteParams, + Question, QuestionBodyParams, QuestionRouteParams, } from '../types'; @@ -215,13 +218,13 @@ router.post('/comments', async (req: Request, res: // Check parent id if (parentCommentId) { - const { rowCount, rows } = await db.query(`SELECT "commentId", "questionId" FROM comments WHERE "commentId" = $1`, [parentCommentId]); + const { rowCount, rows } = await db.query>(`SELECT "commentId", "questionId" FROM comments WHERE "commentId" = $1`, [parentCommentId]); if (rowCount === 0) { res.status(402).json('Parent comment not found!'); return; } - const a = rows[0]; - if ((a as any).questionId !== questionId) { + const parentComment = rows[0]; + if (parentComment.questionId !== questionId) { res.status(403).json('Parent comment is not from the same question!'); return; } @@ -327,7 +330,7 @@ router.post('/courses', async (req: Request, res: Re router.get('/comments/:commentId', async (req: Request, res: Response) => { const { commentId } = req.params; - const { rows } = await db.query(` + const { rows } = await db.query(` SELECT "commentId", "questionId", "parentCommentId", "commentText", "commentPNG", "isCorrect", "isEndorsed", "upvotes", "downvotes", "created_at", "updated_at" FROM comments WHERE comments."commentId" = $1 @@ -340,7 +343,7 @@ router.get('/comments/:commentId', async (req: Request, res: router.get('/questions/:questionId/comments', async (req: Request, res: Response) => { const { questionId } = req.params; - const { rows } = await db.query(` + const { rows } = await db.query(` SELECT "commentId", "parentCommentId", "commentText", "commentPNG", "isCorrect", "isEndorsed", "upvotes", "downvotes", "created_at", "updated_at" FROM comments WHERE comments."questionId" = $1 @@ -353,7 +356,7 @@ router.get('/questions/:questionId/comments', async (req: Request, res: Response) => { const { questionId } = req.params; - const { rows } = await db.query(` + const { rows } = await db.query(` SELECT "questionId", "questionText", "questionType", "questionPNG" FROM questions WHERE questions."questionId" = $1 @@ -366,7 +369,7 @@ router.get('/questions/:questionId', async (req: Request, r router.get('/exams/:examId/questions', async (req: Request, res: Response) => { const { examId } = req.params; - const { rows } = await db.query(` + const { rows } = await db.query(` SELECT "questionId", "questionText", "questionType", "questionPNG" FROM questions WHERE questions."examId" = $1 @@ -379,7 +382,7 @@ router.get('/exams/:examId/questions', async (req: Request, res router.get('/exams/:examId', async (req: Request, res: Response) => { const { examId } = req.params; - const { rows } = await db.query(` + const { rows } = await db.query(` SELECT "examId", "examYear", "examSemester", "examType" FROM exams WHERE exams."examId" = $1 @@ -392,7 +395,7 @@ router.get('/exams/:examId', async (req: Request, res: Response router.get('/courses/:courseCode/exams', async (req: Request, res: Response) => { const { courseCode } = req.params; - const { rows } = await db.query(` + const { rows } = await db.query(` SELECT "examId", "examYear", "examSemester", "examType" FROM exams WHERE exams."courseCode" = $1 @@ -405,7 +408,7 @@ router.get('/courses/:courseCode/exams', async (req: Request, router.get('/courses/:courseCode', async (req: Request, res: Response) => { const { courseCode } = req.params; - const { rows } = await db.query(` + const { rows } = await db.query(` SELECT "courseCode", "courseName", "courseDescription" FROM courses WHERE courses."courseCode" = $1 @@ -419,7 +422,7 @@ router.get('/courses', async (req: Request, re const offset = req.query.offset ?? 0; const limit = req.query.limit ?? 100; - const { rows } = await db.query(` + const { rows } = await db.query(` SELECT "courseCode", "courseName", "courseDescription" FROM courses LIMIT $1 @@ -535,11 +538,11 @@ async function editComment(commentId: number, commentText?: string | null, comme } // function to nest comments into their parent comments -export function nest(jsonData: any[]) { +export function nest(commentRows: IComment[]) { const dataDict: { [id: number]: IComment } = {}; - jsonData.forEach(item => dataDict[item.commentId] = item); + commentRows.forEach(item => dataDict[item.commentId] = item); - jsonData.forEach(item => { + commentRows.forEach(item => { if (item.parentCommentId !== null) { const parent = dataDict[item.parentCommentId]; if (!parent.children) { @@ -549,16 +552,16 @@ export function nest(jsonData: any[]) { } }); - const resultJsonData = jsonData.filter(item => item.parentCommentId === null); + const resultJsonData = commentRows.filter(item => item.parentCommentId === null); return resultJsonData; } // function to return one comment with its children -export function single_nest(jsonData: any[], commentId: number) { +export function single_nest(commentRows: IComment[], commentId: number) { const dataDict: { [id: number]: IComment } = {}; - jsonData.forEach(item => dataDict[item.commentId] = item); + commentRows.forEach(item => dataDict[item.commentId] = item); - jsonData.forEach(item => { + commentRows.forEach(item => { if (item.parentCommentId !== null) { const parent = dataDict[item.parentCommentId]; if (!parent.children) { @@ -568,6 +571,6 @@ export function single_nest(jsonData: any[], commentId: number) { } }); - const resultJsonData = jsonData.filter(item => item.commentId === commentId); + const resultJsonData = commentRows.filter(item => item.commentId === commentId); return resultJsonData; } diff --git a/backend/types.ts b/backend/types.ts index 3d8415a..8524922 100644 --- a/backend/types.ts +++ b/backend/types.ts @@ -30,6 +30,7 @@ export type Comment = { created_at: string updated_at: string children?: Comment[] + questionId?: number } From 3cb14bbfd856a2e3423254c27d210b9d2dad9ea5 Mon Sep 17 00:00:00 2001 From: liv Date: Thu, 2 May 2024 17:07:11 +1000 Subject: [PATCH 4/4] Moved backend files into src directory --- backend/Dockerfile | 4 +--- backend/__tests__/backend.test.js | 2 +- backend/package.json | 2 +- backend/{ => src}/db/index.ts | 0 backend/{ => src}/index.ts | 5 ++--- backend/{ => src}/routes/index.ts | 0 backend/{ => src}/routes/routes.ts | 2 +- backend/{ => src}/types.ts | 0 8 files changed, 6 insertions(+), 9 deletions(-) rename backend/{ => src}/db/index.ts (100%) rename backend/{ => src}/index.ts (83%) rename backend/{ => src}/routes/index.ts (100%) rename backend/{ => src}/routes/routes.ts (99%) rename backend/{ => src}/types.ts (100%) diff --git a/backend/Dockerfile b/backend/Dockerfile index df7126a..1ec3f9d 100644 --- a/backend/Dockerfile +++ b/backend/Dockerfile @@ -6,8 +6,6 @@ COPY ./package.json /app COPY ./package-lock.json /app RUN npm ci -COPY ./index.ts . -COPY ./db ./db -COPY ./routes ./routes +COPY ./src ./src CMD ["npm", "run", "start"] diff --git a/backend/__tests__/backend.test.js b/backend/__tests__/backend.test.js index 42b9e15..d4a11de 100644 --- a/backend/__tests__/backend.test.js +++ b/backend/__tests__/backend.test.js @@ -1,4 +1,4 @@ -const { single_nest, nest } = require("../routes/routes"); +const { single_nest, nest } = require("../src/routes/routes"); // All tests for the nest function describe("nest function", () => { diff --git a/backend/package.json b/backend/package.json index 385b662..219808a 100644 --- a/backend/package.json +++ b/backend/package.json @@ -4,7 +4,7 @@ "description": "", "main": "src/index.ts", "scripts": { - "start": "ts-node index.ts", + "start": "ts-node src/index.ts", "test": "jest" }, "author": "", diff --git a/backend/db/index.ts b/backend/src/db/index.ts similarity index 100% rename from backend/db/index.ts rename to backend/src/db/index.ts diff --git a/backend/index.ts b/backend/src/index.ts similarity index 83% rename from backend/index.ts rename to backend/src/index.ts index e838a54..e17dad0 100644 --- a/backend/index.ts +++ b/backend/src/index.ts @@ -2,11 +2,10 @@ * Imports */ import express from "express"; -import { Pool } from 'pg'; import cors from 'cors'; -import { routes } from './routes/index'; -import * as db from './db/index'; +import { routes } from './routes'; +import * as db from './db'; const PORT: number = process.env.PORT ? parseInt(process.env.PORT) : 8080; diff --git a/backend/routes/index.ts b/backend/src/routes/index.ts similarity index 100% rename from backend/routes/index.ts rename to backend/src/routes/index.ts diff --git a/backend/routes/routes.ts b/backend/src/routes/routes.ts similarity index 99% rename from backend/routes/routes.ts rename to backend/src/routes/routes.ts index 45d69a7..aab3af7 100644 --- a/backend/routes/routes.ts +++ b/backend/src/routes/routes.ts @@ -1,6 +1,6 @@ // Imports import { Router, Request, Response } from 'express'; // Import Request and Response types -import * as db from '../db/index'; +import * as db from '../db'; import { Comment as IComment, CommentBodyParams, diff --git a/backend/types.ts b/backend/src/types.ts similarity index 100% rename from backend/types.ts rename to backend/src/types.ts