From 209b03f35df2e355bdd43356857dbbfa42a73511 Mon Sep 17 00:00:00 2001 From: Rykkel <220993G@mymail.nyp.edu.sg> Date: Wed, 31 Jul 2024 18:59:33 +0800 Subject: [PATCH] Added comment model, comment post function --- client/src/components/CommentsModule.tsx | 76 ++++++++++++++++++++++++ client/src/pages/PostPage.tsx | 5 ++ server/models/Comment.js | 38 ++++++++++++ server/routes/post.js | 29 ++++++++- 4 files changed, 146 insertions(+), 2 deletions(-) create mode 100644 client/src/components/CommentsModule.tsx create mode 100644 server/models/Comment.js diff --git a/client/src/components/CommentsModule.tsx b/client/src/components/CommentsModule.tsx new file mode 100644 index 0000000..9277ba1 --- /dev/null +++ b/client/src/components/CommentsModule.tsx @@ -0,0 +1,76 @@ +import { Formik, Form } from "formik"; +import React, { useEffect, useState } from "react"; +import config from '../config'; +import * as Yup from "yup"; +import NextUIFormikTextarea from "../components/NextUIFormikTextarea"; +import instance from '../security/http'; +import { useNavigate, useParams } from "react-router-dom"; +import { retrieveUserInformation } from "../security/users"; + + +const validationSchema = Yup.object({ + content: Yup.string() + .trim() + .min(3, "Content must be at least 3 characters") + .max(500, "Content must be at most 500 characters") +}); + +export default function CommentsModule() { + const navigate = useNavigate(); + const [userId, setUserId] = useState(null); + const { id } = useParams(); // Retrieve 'id' from the route + + const initialValues = { + content: "", + }; + + const postId = id; + + useEffect(() => { + const getUserInformation = async () => { + try { + const user = await retrieveUserInformation(); // Get the user ID + setUserId(user.id); // Set the user ID in the state + } catch (error) { + console.error(error); + } + }; + getUserInformation(); + }, []); + + const submitComment = async (values: any) => { + const response = await instance.post(config.serverAddress + `/post/${postId}/comments`, + { ...values, userId, postId } + ); + console.log("Comment created succesfully", response.data); + }; + + return ( +
+
+
+
+ + {({ isValid, dirty }) => ( +
+ + + + )} +
+
+
+
+
+ ); +} \ No newline at end of file diff --git a/client/src/pages/PostPage.tsx b/client/src/pages/PostPage.tsx index 256852e..aba9cc3 100644 --- a/client/src/pages/PostPage.tsx +++ b/client/src/pages/PostPage.tsx @@ -25,6 +25,7 @@ import { ArrowUTurnLeftIcon, } from "../icons"; import { retrieveUserInformationById } from "../security/usersbyid"; +import CommentsModule from "../components/CommentsModule"; interface Post { title: string; @@ -200,6 +201,10 @@ const PostPage: React.FC = () => {
+
+ +
+ {(onClose) => ( diff --git a/server/models/Comment.js b/server/models/Comment.js new file mode 100644 index 0000000..7410dd8 --- /dev/null +++ b/server/models/Comment.js @@ -0,0 +1,38 @@ +module.exports = (sequelize, DataTypes) => { + const Comment = sequelize.define("Comment", { + id: { + type: DataTypes.UUID, + defaultValue: DataTypes.UUIDV4, + primaryKey: true, + allowNull: false, + }, + content: { + type: DataTypes.TEXT, + allowNull: false, + }, + parentId: { + type: DataTypes.UUID, + allowNull: true, + }, + }, { + tableName: 'comments', + timestamps: true, + }); + + Comment.associate = (models) => { + Comment.belongsTo(models.User, { + foreignKey: 'userId', + onDelete: 'CASCADE', + }); + Comment.belongsTo(models.Post, { + foreignKey: 'postId', + onDelete: 'CASCADE', + }); + Comment.hasMany(models.Comment, { + foreignKey: 'parentId', + as: 'replies', + }); + }; + + return Comment; +} \ No newline at end of file diff --git a/server/routes/post.js b/server/routes/post.js index 9d7d692..5834b9e 100644 --- a/server/routes/post.js +++ b/server/routes/post.js @@ -1,6 +1,6 @@ const express = require('express'); const router = express.Router(); -const { Post, User } = require('../models'); +const { Post, Comment } = require('../models'); const { Op, where } = require("sequelize"); const yup = require("yup"); const multer = require("multer"); @@ -28,7 +28,7 @@ router.post("/", async (req, res) => { data = await validationSchema.validate(data, // validate() method is used to validate data against the schema and returns the valid data and any applied transformations { abortEarly: false }); // abortEarly: false means the validation won’t stop when the first error is detected // Process valid data - + // Check for profanity if (filter.isProfane(data.title)) { return res.status(400).json({ field: 'title', error: 'Profane content detected in title' }); @@ -45,6 +45,31 @@ router.post("/", async (req, res) => { } }); +router.post("/:id/comments", async (req, res) => { + let data = req.body; + + // Validate request body + let validationSchema = yup.object({ + content: yup.string().trim().min(3).max(500).required(), + userId: yup.string().required(), + postId: yup.string().required() + }); + + try { + console.log("Validating schema..."); + data = await validationSchema.validate(data, { abortEarly: false }); + + console.log("Creating comment..."); + let result = await Comment.create(data); + + res.json(result); + console.log("Success!"); + } catch (err) { + console.log("Error caught! Info: " + err); + res.status(400).json({ errors: err.errors }); + } +}); + // // sequelize method findAll is used to generate a standard SELECT query which will retrieve all entries from the table // router.get("/", async (req, res) => {