From cf9b56035aa69cbbc866ac86296f9266015e4c4e Mon Sep 17 00:00:00 2001 From: hailia sommerville Date: Wed, 30 Jul 2025 15:46:49 -0400 Subject: [PATCH 01/23] feat: user model created --- database/models/user.js | 41 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 41 insertions(+) create mode 100644 database/models/user.js diff --git a/database/models/user.js b/database/models/user.js new file mode 100644 index 0000000..9730516 --- /dev/null +++ b/database/models/user.js @@ -0,0 +1,41 @@ +const {DataTypes} = require('sequelize'); +const db = require('../db'); +const bcrypt = require('bcrypt'); + +const User = db.define('user', { + username: { + type: DataTypes.STRING, + allowNull: false, + unique: true, + }, + password: { + type: DataTypes.STRING, + allowNull: false, + validate: { + len: [6, 100], + }, + }, + auth0Id: { + type: DataTypes.STRING, + allowNull: true, + unique: true, + }, + email: { + type: DataTypes.STRING, + unique: true, + validate: { + isEmail: true, + notEmpty: true, + }, + }, + isAdmin: { + type: DataTypes.BOOLEAN, + allowNull: false, + defaultValue: false, + }, + isDisable: { + type: DataTypes.BOOLEAN, + allowNull: false, + defaultValue: false, + }, + }); \ No newline at end of file From 1bcd2377a5acb49708756c57855183d82e4dd302 Mon Sep 17 00:00:00 2001 From: Finn Terdal Date: Wed, 30 Jul 2025 23:24:42 -0400 Subject: [PATCH 02/23] add socket functionality, fix cookies --- README.md | 2 +- app.js | 7 +- auth/index.js | 28 ++--- database/db.js | 2 +- package-lock.json | 276 ++++++++++++++++++++++++++++++++++++++++++++- package.json | 5 +- public/favicon.ico | Bin 2496 -> 0 bytes public/index.html | 9 +- public/style.css | 2 +- socket-server.js | 35 ++++++ vercel.json | 3 +- 11 files changed, 337 insertions(+), 32 deletions(-) delete mode 100644 public/favicon.ico create mode 100644 socket-server.js diff --git a/README.md b/README.md index b8ac36b..f308c14 100644 --- a/README.md +++ b/README.md @@ -4,7 +4,7 @@ This project uses Express.js to serve up an API server, and Sequelize to connect to a PostgreSQL database. It uses JWTs for authentication with username and password. -You will also need to create the database: by default it is called `capstone-1`, but you are welcome to rename it in `database/db.js` +You will also need to create the database: by default it is called `capstone-2`, but you are welcome to rename it in `database/db.js` After that, you can get started with these commands diff --git a/app.js b/app.js index 5857036..2574d33 100644 --- a/app.js +++ b/app.js @@ -9,7 +9,7 @@ const apiRouter = require("./api"); const { router: authRouter } = require("./auth"); const { db } = require("./database"); const cors = require("cors"); - +const initSocketServer = require("./socket-server"); const PORT = process.env.PORT || 8080; const FRONTEND_URL = process.env.FRONTEND_URL || "http://localhost:3000"; @@ -41,9 +41,12 @@ const runApp = async () => { try { await db.sync(); console.log("✅ Connected to the database"); - app.listen(PORT, () => { + const server = app.listen(PORT, () => { console.log(`🚀 Server is running on port ${PORT}`); }); + + initSocketServer(server); + console.log("🧦 Socket server initialized"); } catch (err) { console.error("❌ Unable to connect to the database:", err); } diff --git a/auth/index.js b/auth/index.js index 07968c5..bd2b05b 100644 --- a/auth/index.js +++ b/auth/index.js @@ -6,6 +6,13 @@ const router = express.Router(); const JWT_SECRET = process.env.JWT_SECRET || "your-secret-key"; +const cookieSettings = { + httpOnly: true, + secure: process.env.NODE_ENV === "production", + sameSite: process.env.NODE_ENV === "production" ? "none" : "strict", + maxAge: 24 * 60 * 60 * 1000, // 24 hours +}; + // Middleware to authenticate JWT tokens const authenticateJWT = (req, res, next) => { const token = req.cookies.token; @@ -79,12 +86,7 @@ router.post("/auth0", async (req, res) => { { expiresIn: "24h" } ); - res.cookie("token", token, { - httpOnly: true, - secure: process.env.NODE_ENV === "production", - sameSite: "strict", - maxAge: 24 * 60 * 60 * 1000, // 24 hours - }); + res.cookie("token", token, cookieSettings); res.send({ message: "Auth0 authentication successful", @@ -140,12 +142,7 @@ router.post("/signup", async (req, res) => { { expiresIn: "24h" } ); - res.cookie("token", token, { - httpOnly: true, - secure: true, - sameSite: "strict", - maxAge: 24 * 60 * 60 * 1000, // 24 hours - }); + res.cookie("token", token, cookieSettings); res.send({ message: "User created successfully", @@ -191,12 +188,7 @@ router.post("/login", async (req, res) => { { expiresIn: "24h" } ); - res.cookie("token", token, { - httpOnly: true, - secure: process.env.NODE_ENV === "production", - sameSite: "strict", - maxAge: 24 * 60 * 60 * 1000, // 24 hours - }); + res.cookie("token", token, cookieSettings); res.send({ message: "Login successful", diff --git a/database/db.js b/database/db.js index b251a9d..3b580f1 100644 --- a/database/db.js +++ b/database/db.js @@ -3,7 +3,7 @@ const { Sequelize } = require("sequelize"); const pg = require("pg"); // Feel free to rename the database to whatever you want! -const dbName = "capstone-1"; +const dbName = "capstone-2"; const db = new Sequelize( process.env.DATABASE_URL || `postgres://localhost:5432/${dbName}`, diff --git a/package-lock.json b/package-lock.json index af0cf82..2f758cd 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,11 +1,11 @@ { - "name": "capstone-i-backend", + "name": "capstone-2-backend", "version": "1.0.0", "lockfileVersion": 3, "requires": true, "packages": { "": { - "name": "capstone-i-backend", + "name": "capstone-2-backend", "version": "1.0.0", "license": "ISC", "dependencies": { @@ -17,7 +17,8 @@ "jsonwebtoken": "^9.0.2", "morgan": "^1.10.0", "pg": "^8.16.2", - "sequelize": "^6.37.7" + "sequelize": "^6.37.7", + "socket.io": "^4.8.1" }, "devDependencies": { "nodemon": "^3.1.10" @@ -26,6 +27,21 @@ "win-node-env": "^0.6.1" } }, + "node_modules/@socket.io/component-emitter": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/@socket.io/component-emitter/-/component-emitter-3.1.2.tgz", + "integrity": "sha512-9BCxFwvbGg/RsZK9tjXd8s4UcwR0MWeFQ1XEKIQVVvAGJyINdrqKMcTRyLoK8Rse1GjzLV9cwjWV1olXRWEXVA==", + "license": "MIT" + }, + "node_modules/@types/cors": { + "version": "2.8.19", + "resolved": "https://registry.npmjs.org/@types/cors/-/cors-2.8.19.tgz", + "integrity": "sha512-mFNylyeyqN93lfe/9CSxOGREz8cpzAhH+E93xJ4xWQf62V8sQ/24reV2nyzUWM6H6Xji+GGHpkbLe7pVoUEskg==", + "license": "MIT", + "dependencies": { + "@types/node": "*" + } + }, "node_modules/@types/debug": { "version": "4.1.12", "resolved": "https://registry.npmjs.org/@types/debug/-/debug-4.1.12.tgz", @@ -90,6 +106,15 @@ "dev": true, "license": "MIT" }, + "node_modules/base64id": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/base64id/-/base64id-2.0.0.tgz", + "integrity": "sha512-lGe34o6EHj9y3Kts9R4ZYs/Gr+6N7MCaMlIFA3F1R2O5/m7K06AxfSeO5530PEERE6/WyEg3lsuyw4GHlPZHog==", + "license": "MIT", + "engines": { + "node": "^4.5.0 || >= 5.9" + } + }, "node_modules/basic-auth": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/basic-auth/-/basic-auth-2.0.1.tgz", @@ -408,6 +433,95 @@ "node": ">= 0.8" } }, + "node_modules/engine.io": { + "version": "6.6.4", + "resolved": "https://registry.npmjs.org/engine.io/-/engine.io-6.6.4.tgz", + "integrity": "sha512-ZCkIjSYNDyGn0R6ewHDtXgns/Zre/NT6Agvq1/WobF7JXgFff4SeDroKiCO3fNJreU9YG429Sc81o4w5ok/W5g==", + "license": "MIT", + "dependencies": { + "@types/cors": "^2.8.12", + "@types/node": ">=10.0.0", + "accepts": "~1.3.4", + "base64id": "2.0.0", + "cookie": "~0.7.2", + "cors": "~2.8.5", + "debug": "~4.3.1", + "engine.io-parser": "~5.2.1", + "ws": "~8.17.1" + }, + "engines": { + "node": ">=10.2.0" + } + }, + "node_modules/engine.io-parser": { + "version": "5.2.3", + "resolved": "https://registry.npmjs.org/engine.io-parser/-/engine.io-parser-5.2.3.tgz", + "integrity": "sha512-HqD3yTBfnBxIrbnM1DoD6Pcq8NECnh8d4As1Qgh0z5Gg3jRRIqijury0CL3ghu/edArpUYiYqQiDUQBIs4np3Q==", + "license": "MIT", + "engines": { + "node": ">=10.0.0" + } + }, + "node_modules/engine.io/node_modules/accepts": { + "version": "1.3.8", + "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz", + "integrity": "sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==", + "license": "MIT", + "dependencies": { + "mime-types": "~2.1.34", + "negotiator": "0.6.3" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/engine.io/node_modules/debug": { + "version": "4.3.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.7.tgz", + "integrity": "sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ==", + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/engine.io/node_modules/mime-db": { + "version": "1.52.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", + "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/engine.io/node_modules/mime-types": { + "version": "2.1.35", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", + "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", + "license": "MIT", + "dependencies": { + "mime-db": "1.52.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/engine.io/node_modules/negotiator": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz", + "integrity": "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, "node_modules/es-define-property": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz", @@ -1608,6 +1722,141 @@ "node": ">=10" } }, + "node_modules/socket.io": { + "version": "4.8.1", + "resolved": "https://registry.npmjs.org/socket.io/-/socket.io-4.8.1.tgz", + "integrity": "sha512-oZ7iUCxph8WYRHHcjBEc9unw3adt5CmSNlppj/5Q4k2RIrhl8Z5yY2Xr4j9zj0+wzVZ0bxmYoGSzKJnRl6A4yg==", + "license": "MIT", + "dependencies": { + "accepts": "~1.3.4", + "base64id": "~2.0.0", + "cors": "~2.8.5", + "debug": "~4.3.2", + "engine.io": "~6.6.0", + "socket.io-adapter": "~2.5.2", + "socket.io-parser": "~4.2.4" + }, + "engines": { + "node": ">=10.2.0" + } + }, + "node_modules/socket.io-adapter": { + "version": "2.5.5", + "resolved": "https://registry.npmjs.org/socket.io-adapter/-/socket.io-adapter-2.5.5.tgz", + "integrity": "sha512-eLDQas5dzPgOWCk9GuuJC2lBqItuhKI4uxGgo9aIV7MYbk2h9Q6uULEh8WBzThoI7l+qU9Ast9fVUmkqPP9wYg==", + "license": "MIT", + "dependencies": { + "debug": "~4.3.4", + "ws": "~8.17.1" + } + }, + "node_modules/socket.io-adapter/node_modules/debug": { + "version": "4.3.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.7.tgz", + "integrity": "sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ==", + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/socket.io-parser": { + "version": "4.2.4", + "resolved": "https://registry.npmjs.org/socket.io-parser/-/socket.io-parser-4.2.4.tgz", + "integrity": "sha512-/GbIKmo8ioc+NIWIhwdecY0ge+qVBSMdgxGygevmdHj24bsfgtCmcUUcQ5ZzcylGFHsN3k4HB4Cgkl96KVnuew==", + "license": "MIT", + "dependencies": { + "@socket.io/component-emitter": "~3.1.0", + "debug": "~4.3.1" + }, + "engines": { + "node": ">=10.0.0" + } + }, + "node_modules/socket.io-parser/node_modules/debug": { + "version": "4.3.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.7.tgz", + "integrity": "sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ==", + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/socket.io/node_modules/accepts": { + "version": "1.3.8", + "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz", + "integrity": "sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==", + "license": "MIT", + "dependencies": { + "mime-types": "~2.1.34", + "negotiator": "0.6.3" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/socket.io/node_modules/debug": { + "version": "4.3.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.7.tgz", + "integrity": "sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ==", + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/socket.io/node_modules/mime-db": { + "version": "1.52.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", + "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/socket.io/node_modules/mime-types": { + "version": "2.1.35", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", + "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", + "license": "MIT", + "dependencies": { + "mime-db": "1.52.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/socket.io/node_modules/negotiator": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz", + "integrity": "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, "node_modules/split2": { "version": "4.2.0", "resolved": "https://registry.npmjs.org/split2/-/split2-4.2.0.tgz", @@ -1773,6 +2022,27 @@ "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", "license": "ISC" }, + "node_modules/ws": { + "version": "8.17.1", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.17.1.tgz", + "integrity": "sha512-6XQFvXTkbfUOZOKKILFG1PDK2NDQs4azKQl26T0YS5CxqWLgXajbPZ+h4gZekJyRqFU8pvnbAbbs/3TgRPy+GQ==", + "license": "MIT", + "engines": { + "node": ">=10.0.0" + }, + "peerDependencies": { + "bufferutil": "^4.0.1", + "utf-8-validate": ">=5.0.2" + }, + "peerDependenciesMeta": { + "bufferutil": { + "optional": true + }, + "utf-8-validate": { + "optional": true + } + } + }, "node_modules/xtend": { "version": "4.0.2", "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz", diff --git a/package.json b/package.json index 7e0a0af..05c4e0c 100644 --- a/package.json +++ b/package.json @@ -1,5 +1,5 @@ { - "name": "capstone-1-backend", + "name": "capstone-2-backend", "version": "1.0.0", "main": "index.js", "scripts": { @@ -21,7 +21,8 @@ "jsonwebtoken": "^9.0.2", "morgan": "^1.10.0", "pg": "^8.16.2", - "sequelize": "^6.37.7" + "sequelize": "^6.37.7", + "socket.io": "^4.8.1" }, "devDependencies": { "nodemon": "^3.1.10" diff --git a/public/favicon.ico b/public/favicon.ico deleted file mode 100644 index f63c4b5f94b55cda05f301980f330ffc22a29b05..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 2496 zcmY*bc|6nqAOCE~RqkUYT2pQtkK=Pe)apk-;dYl^?bix&)4(ye!c(tP;9Ktb_nbe003Zzg*gVt_E3)T zZDW5)k&#Pm$Bjp0(Ew1HD!Adn!%2FX^m@c^*S4#i9WK&S=)Ot}Mq{w)BI z4$7&sMY0p|-VPQ%SS+Bz#(V$|NDSa&BM^H7pj`k*jg5e#AelcH2U7aY0RuoH0pR}4 zabkPUGG`m7=9lN9f&MDez<=0K8rNUUDLd65JkNIgLFSGm01y=C7zoJ9fw0va2`3yv z9I#gUUc>;n$7!OcH#{OBh*Je1BlOuYz&peP8WG?hNYal$!G1FI*_abXz@R@VA$};B z1J(v=Obqsh>cBPN8ZZL^C=`keKJBBA!-2N5<^2!Fc_!NpY`jW zAq1blodQX}$6^nN;A9Y*a1F#CZFVV=6V*Q!Oz>uR=I9$}B7ZXfFZR0+62WQyKbQG6 z>CY&8R09Dd;?J`g2<&tl;ROI*v;_uz!hxS|NAP!fFS$AE>_bms^;2HTR5Oq6yC@+> z3s5<2mICIc8`-29qG4boRY{)p0^HCM+2qo|Qm6ZGOR=h=Z%T%th9{I0Fjcw5*3<-89niqUQ*D|+M7(p5LT+4Q=`PaJ-glSW5Bzjp19O@sLCnRYd)GQDM9#wCAMn)W_q3KDZG{t{4=``}lI> zUMx$v{1jZTb9KPwXv~pHdej}cibXZiKAAV*TBiM8RL@PL35CL5J%a>i)4F*~36Jy9 z3zUQ87%A$VudCQwRy=3>x>ym_=MX~v=SAqV_wi`bL9AA2b1DAL<_txz{iS|F+wt&9EfacvJ;AkLRP z$)M>{>k-l+YmXq^gs73Z7Bxq%e!{cnqG@uFnln==DYiLlgo*zmZ%ilF%r^R6WrS+K z1DBVJ2_yeyUbAG@ILApJH`SnJUVE6It#D**Dc|+qeNjk>Gjf5++h`fB^Yz1~+9^6u z%|FGq4|BgT`@ZmTmZg-8algd%T3c44X&!m+7?pZ%T>n!|%P(Y3qc;L4M%(k_w|pDtoyRO@tCHu##<>}9_GgXt5cC9RhbjN#=skABqlYsFAT z`1SWQ?7-XzbB&j3TGwodXdnPGJ*@t<*ep(k^;Q1HVE$Q5@^_n)_LMJE5TiA+N@%S1 z;$eI;3njh3cl-76G~GT_%mBqA>m4m2$wA&T!=J3m=sJ5g91WyaWd+kJ=%2#stV2C{?F*73CN?)LgH^4A_I8|5h^W2!ku#6J0 zi?7t)V`|=?7yo(g*+6$R_hNHTfmyDzaP_agZ+L+A*uC(S70s{o4IyV~~;e<8GHapNLB1u}7|t zU-V9$chIiyL`&KC4q2RD&ftISsik=#fjQBBeeX?3Ay*P~a%U0CO*0H`mSEA}xz`() zDS9YnYMgrlYu;5@@_2I1wqwI7?}$e52;3`Ab9X`;n?$jsM~YqTpDIz?7#k56Sw}rQ zZuY)SH0gCwyb$T4_CN(DsqFkt`8bo8`@O_@NoU6>hs9OPY{e;^nyX8j^#t)SCgb}- zZh6q_p=)m3OSH1-a0Ra!(vKIA8@`coyc4u4pSKdPPFZxiA)6KhMz24oLu2g4_~RNE z55bVj8AFrlo4Tw4V?I+sWpHfRhW}NO&#viKrVZAKv2Cr)j7i6+oq3yzl9irr?IZH5 z9WGGt0+)hNtKJtY&1n1u+2@Zh_lCVGt4yv!*S8-wew10^jDsvAx)XUb3W{03v|16XQ155I`+6E_>X1EPvfMz!&VEESI1f}z3uGHksDQP h$?bWKOU=>R0(rT>WbXIL7IOYeEKIC16-Mr{{{c*WGVK5W diff --git a/public/index.html b/public/index.html index 6c972d9..77ae02c 100644 --- a/public/index.html +++ b/public/index.html @@ -3,13 +3,16 @@ - - Capstone I Backend + + Capstone II Backend -

Welcome to the Capstone I Backend!

+

Welcome to the Capstone II Backend!

To get started, open your terminal and install the packages:

npm install
diff --git a/public/style.css b/public/style.css index b4a0453..e10fa09 100644 --- a/public/style.css +++ b/public/style.css @@ -1,5 +1,5 @@ body { - background-color: #8fcb9b; + background-color: #bcd4de; font-family: Arial, sans-serif; } diff --git a/socket-server.js b/socket-server.js new file mode 100644 index 0000000..d887933 --- /dev/null +++ b/socket-server.js @@ -0,0 +1,35 @@ +const { Server } = require("socket.io"); + +let io; + +const FRONTEND_URL = process.env.FRONTEND_URL || "http://localhost:3000"; +const corsOptions = + process.env.NODE_ENV === "production" + ? { + origin: FRONTEND_URL, + credentials: true, + } + : { + cors: "*", + }; + +const initSocketServer = (server) => { + try { + io = new Server(server, corsOptions); + + io.on("connection", (socket) => { + console.log(`🔗 User ${socket.id} connected to sockets`); + + socket.on("disconnect", () => { + console.log(`🔗 User ${socket.id} disconnected from sockets`); + }); + + // Define event handlers here... + }); + } catch (error) { + console.error("❌ Error initializing socket server:"); + console.error(error); + } +}; + +module.exports = initSocketServer; diff --git a/vercel.json b/vercel.json index 1b0a308..8be8eb5 100644 --- a/vercel.json +++ b/vercel.json @@ -9,7 +9,8 @@ "routes": [ { "src": "/(.*)", - "dest": "app.js" + "dest": "app.js", + "methods": ["GET", "POST", "PUT", "DELETE", "PATCH", "OPTIONS"] } ] } From b88556b1deb9078fdf7c2d77aef886aa73178952 Mon Sep 17 00:00:00 2001 From: Finn Terdal Date: Wed, 30 Jul 2025 23:48:31 -0400 Subject: [PATCH 03/23] clean up --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index f308c14..3a553bd 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -# Capstone I Backend +# Capstone II Backend ## Getting Started From e76a799f983e1582934dda1c801d9e26b3af9569 Mon Sep 17 00:00:00 2001 From: Alex Nurci Date: Thu, 31 Jul 2025 11:15:27 -0400 Subject: [PATCH 04/23] update package lock --- package-lock.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/package-lock.json b/package-lock.json index af0cf82..1b30289 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,11 +1,11 @@ { - "name": "capstone-i-backend", + "name": "capstone-1-backend", "version": "1.0.0", "lockfileVersion": 3, "requires": true, "packages": { "": { - "name": "capstone-i-backend", + "name": "capstone-1-backend", "version": "1.0.0", "license": "ISC", "dependencies": { From 6bcc44bdae2943fbb5374a0cbfcdf05ab588c193 Mon Sep 17 00:00:00 2001 From: hailia sommerville Date: Sat, 2 Aug 2025 11:10:22 -0400 Subject: [PATCH 05/23] chore: model files added (thread,like,reply) --- database/models/like.js | 0 database/models/reply.js | 0 database/models/thread.js | 0 3 files changed, 0 insertions(+), 0 deletions(-) create mode 100644 database/models/like.js create mode 100644 database/models/reply.js create mode 100644 database/models/thread.js diff --git a/database/models/like.js b/database/models/like.js new file mode 100644 index 0000000..e69de29 diff --git a/database/models/reply.js b/database/models/reply.js new file mode 100644 index 0000000..e69de29 diff --git a/database/models/thread.js b/database/models/thread.js new file mode 100644 index 0000000..e69de29 From 489077b324281cb9e191fb385db5766bcf350d37 Mon Sep 17 00:00:00 2001 From: hailia sommerville Date: Sat, 2 Aug 2025 11:14:47 -0400 Subject: [PATCH 06/23] feat: thread model created --- database/models/thread.js | 27 +++++++++++++++++++++++++++ 1 file changed, 27 insertions(+) diff --git a/database/models/thread.js b/database/models/thread.js index e69de29..0dd4eb0 100644 --- a/database/models/thread.js +++ b/database/models/thread.js @@ -0,0 +1,27 @@ +const { DataTypes } = require('sequelize'); +const db = require('../db'); + +const thread = db.define('thread', { + title: { + type: DataTypes.STRING, + allowNull: false, + }, + content: { + type: DataTypes.TEXT, + allowNull: false, + }, + userId: { + type: DataTypes.INTEGER, + allowNull: false, + }, + createdAt: { + type: DataTypes.DATE, + defaultValue: DataTypes.NOW, + }, + updatedAt: { + type: DataTypes.DATE, + defaultValue: DataTypes.NOW, + }, +}); + +module.exports = thread; \ No newline at end of file From 475a937bcf8d9eca0532573b2e4632ba3cf18950 Mon Sep 17 00:00:00 2001 From: hailia sommerville Date: Sat, 2 Aug 2025 11:17:31 -0400 Subject: [PATCH 07/23] feat: reply model created --- database/models/reply.js | 27 +++++++++++++++++++++++++++ database/models/thread.js | 4 ++-- database/models/user.js | 4 +++- 3 files changed, 32 insertions(+), 3 deletions(-) diff --git a/database/models/reply.js b/database/models/reply.js index e69de29..6951fb5 100644 --- a/database/models/reply.js +++ b/database/models/reply.js @@ -0,0 +1,27 @@ +const { DataTypes } = require('sequelize'); +const db = require('../db'); + +const Reply = db.define('reply', { + content: { + type: DataTypes.TEXT, + allowNull: false, + }, + threadId: { + type: DataTypes.INTEGER, + allowNull: false, + }, + userId: { + type: DataTypes.INTEGER, + allowNull: false, + }, + createdAt: { + type: DataTypes.DATE, + defaultValue: DataTypes.NOW, + }, + updatedAt: { + type: DataTypes.DATE, + defaultValue: DataTypes.NOW, + }, +}); + +module.exports = Reply; \ No newline at end of file diff --git a/database/models/thread.js b/database/models/thread.js index 0dd4eb0..2785372 100644 --- a/database/models/thread.js +++ b/database/models/thread.js @@ -1,7 +1,7 @@ const { DataTypes } = require('sequelize'); const db = require('../db'); -const thread = db.define('thread', { +const Thread = db.define('thread', { title: { type: DataTypes.STRING, allowNull: false, @@ -24,4 +24,4 @@ const thread = db.define('thread', { }, }); -module.exports = thread; \ No newline at end of file +module.exports = Thread; \ No newline at end of file diff --git a/database/models/user.js b/database/models/user.js index 9730516..8a99f46 100644 --- a/database/models/user.js +++ b/database/models/user.js @@ -38,4 +38,6 @@ const User = db.define('user', { allowNull: false, defaultValue: false, }, - }); \ No newline at end of file + }); + + module.exports = User; \ No newline at end of file From 6cd75ef52ad7a899ea272c2bb39739ab52a1f02d Mon Sep 17 00:00:00 2001 From: hailia sommerville Date: Sat, 2 Aug 2025 11:18:41 -0400 Subject: [PATCH 08/23] feat: like model created --- database/models/like.js | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/database/models/like.js b/database/models/like.js index e69de29..851e5d7 100644 --- a/database/models/like.js +++ b/database/models/like.js @@ -0,0 +1,15 @@ +const { DataTypes } = require('sequelize'); +const db = require('../db'); + +const Like = db.define('like', { + userId: { + type: DataTypes.INTEGER, + allowNull: false, + }, + threadId: { + type: DataTypes.INTEGER, + allowNull: false, + }, +}); + +module.exports = Like; \ No newline at end of file From 40a6c0ef80228df93718af63864126a3b8b94c7a Mon Sep 17 00:00:00 2001 From: hailia sommerville Date: Sat, 2 Aug 2025 11:30:44 -0400 Subject: [PATCH 09/23] feat: model associations defined --- database/index.js | 27 +++++++++++++++++++++++++++ 1 file changed, 27 insertions(+) diff --git a/database/index.js b/database/index.js index e498df6..5fcbe19 100644 --- a/database/index.js +++ b/database/index.js @@ -1,7 +1,34 @@ const db = require("./db"); const User = require("./user"); +const Thread = require("./thread"); +const Reply = require("./reply"); +const Like = require("./like"); + +// Associations +User.hasMany(Thread, { foreignKey: "userId" }); // A user can have many threads + +Thread.belongsTo(User, { foreignKey: "userId" }); // A thread belongs to a user + +Thread.hasMany(Reply, { foreignKey: "threadId" }); // A thread can have many replies + +Reply.belongsTo(Thread, { foreignKey: "threadId" }); // A reply belongs to a thread + +User.belongsToMany(Thread, { + through: Like, + foreignKey: "userId", + otherKey: "threadId", +}); // A user can like many threads + +Thread.belongsToMany(User, { + through: Like, + foreignKey: "threadId", + otherKey: "userId", +}); // A thread can be liked by many users module.exports = { db, User, + Thread, + Reply, + Like, }; From d20cdc2e211421d34a965f889f0128d921754bd2 Mon Sep 17 00:00:00 2001 From: hailia sommerville Date: Sat, 2 Aug 2025 12:23:44 -0400 Subject: [PATCH 10/23] chore: forum file added to api for routes --- api/forum.js | 0 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 api/forum.js diff --git a/api/forum.js b/api/forum.js new file mode 100644 index 0000000..e69de29 From b5b89ad30953c1708c8da26bc602c46099dfdc64 Mon Sep 17 00:00:00 2001 From: hailia sommerville Date: Sat, 2 Aug 2025 12:40:53 -0400 Subject: [PATCH 11/23] fix: missing pkg installed, route to models corrected, db name changed --- database/db.js | 2 +- database/index.js | 8 ++++---- package-lock.json | 4 ++-- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/database/db.js b/database/db.js index b251a9d..3b580f1 100644 --- a/database/db.js +++ b/database/db.js @@ -3,7 +3,7 @@ const { Sequelize } = require("sequelize"); const pg = require("pg"); // Feel free to rename the database to whatever you want! -const dbName = "capstone-1"; +const dbName = "capstone-2"; const db = new Sequelize( process.env.DATABASE_URL || `postgres://localhost:5432/${dbName}`, diff --git a/database/index.js b/database/index.js index 5fcbe19..bc434ce 100644 --- a/database/index.js +++ b/database/index.js @@ -1,8 +1,8 @@ const db = require("./db"); -const User = require("./user"); -const Thread = require("./thread"); -const Reply = require("./reply"); -const Like = require("./like"); +const User = require("./models/user"); +const Thread = require("./models/thread"); +const Reply = require("./models/reply"); +const Like = require("./models/like"); // Associations User.hasMany(Thread, { foreignKey: "userId" }); // A user can have many threads diff --git a/package-lock.json b/package-lock.json index af0cf82..1b30289 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,11 +1,11 @@ { - "name": "capstone-i-backend", + "name": "capstone-1-backend", "version": "1.0.0", "lockfileVersion": 3, "requires": true, "packages": { "": { - "name": "capstone-i-backend", + "name": "capstone-1-backend", "version": "1.0.0", "license": "ISC", "dependencies": { From 6dddb16944fd972bbf9e6678979b4180f88c4327 Mon Sep 17 00:00:00 2001 From: hailia sommerville Date: Mon, 4 Aug 2025 11:46:55 -0400 Subject: [PATCH 12/23] feat: added thread has many likes association --- database/index.js | 2 ++ 1 file changed, 2 insertions(+) diff --git a/database/index.js b/database/index.js index bc434ce..fd8df61 100644 --- a/database/index.js +++ b/database/index.js @@ -11,6 +11,8 @@ Thread.belongsTo(User, { foreignKey: "userId" }); // A thread belongs to a user Thread.hasMany(Reply, { foreignKey: "threadId" }); // A thread can have many replies +Thread.hasMany(Like, { foreignKey: "threadId" }); // A thread can have many likes + Reply.belongsTo(Thread, { foreignKey: "threadId" }); // A reply belongs to a thread User.belongsToMany(Thread, { From e28b661466949b75e6e701fb135a1acd9c48698b Mon Sep 17 00:00:00 2001 From: hailia sommerville Date: Mon, 4 Aug 2025 21:04:15 -0400 Subject: [PATCH 13/23] feat: models + associations updated, get route made, seed created --- api/forum.js | 21 ++++++++++ api/index.js | 2 + database/index.js | 35 +++++++--------- database/models/forum.js | 19 +++++++++ database/models/like.js | 15 ------- database/models/{thread.js => post.js} | 8 +++- database/models/reply.js | 6 ++- database/models/user.js | 1 + database/seed.js | 58 ++++++++++++++++++++++++-- 9 files changed, 124 insertions(+), 41 deletions(-) create mode 100644 database/models/forum.js delete mode 100644 database/models/like.js rename database/models/{thread.js => post.js} (78%) diff --git a/api/forum.js b/api/forum.js index e69de29..26ea47c 100644 --- a/api/forum.js +++ b/api/forum.js @@ -0,0 +1,21 @@ +const express = require('express'); +const router = express.Router(); +const {Forum, Post, Reply, User} = require('../database') + +router.get('/:forumId/posts', async (req, res) => { + const { forumId } = req.params; + console.log(`Fetching posts for forum ID: ${forumId}`); + try { + const posts = await Post.findAll({ + where: { forumId }, + include: [{ model: User, attributes: ['username'] }], + order: [['createdAt', 'DESC']], + }); + res.json(posts); + } catch (err) { + console.error('Error fetching posts:', err); + res.status(500).json({ error: 'Failed to fetch posts for this forum.' }); + } + }); + + module.exports = router; \ No newline at end of file diff --git a/api/index.js b/api/index.js index f08162e..808c420 100644 --- a/api/index.js +++ b/api/index.js @@ -1,7 +1,9 @@ const express = require("express"); const router = express.Router(); const testDbRouter = require("./test-db"); +const forumRouter = require("./forum"); router.use("/test-db", testDbRouter); +router.use("/forum", forumRouter); module.exports = router; diff --git a/database/index.js b/database/index.js index fd8df61..1cd087e 100644 --- a/database/index.js +++ b/database/index.js @@ -1,36 +1,33 @@ const db = require("./db"); const User = require("./models/user"); -const Thread = require("./models/thread"); +const Post = require("./models/post"); const Reply = require("./models/reply"); -const Like = require("./models/like"); +const Forum = require("./models/forum"); + // Associations -User.hasMany(Thread, { foreignKey: "userId" }); // A user can have many threads +Forum.hasMany(Post, { foreignKey: "forumId" }); // A forum can have many posts + +Post.belongsTo(Forum, { foreignKey: "forumId" }); // A post belongs to a forum + +Post.belongsTo(User, { foreignKey: "userId" }); // A post belongs to a user + +Post.hasMany(Reply, { foreignKey: "postId" }); // A post can have many replies -Thread.belongsTo(User, { foreignKey: "userId" }); // A thread belongs to a user +Reply.belongsTo(Post, { foreignKey: "postId" }); // A reply belongs to a post -Thread.hasMany(Reply, { foreignKey: "threadId" }); // A thread can have many replies +Reply.belongsTo(User, { foreignKey: "userId" }); // A reply belongs to a user -Thread.hasMany(Like, { foreignKey: "threadId" }); // A thread can have many likes +Reply.belongsTo(Reply, { foreignKey: "parentId", as: "parentReply" }); // A reply can have a parent reply -Reply.belongsTo(Thread, { foreignKey: "threadId" }); // A reply belongs to a thread +Reply.hasMany(Reply, { foreignKey: "parentId", as: "childReplies" }); // A reply can have many child replies -User.belongsToMany(Thread, { - through: Like, - foreignKey: "userId", - otherKey: "threadId", -}); // A user can like many threads -Thread.belongsToMany(User, { - through: Like, - foreignKey: "threadId", - otherKey: "userId", -}); // A thread can be liked by many users module.exports = { db, User, - Thread, + Post, + Forum, Reply, - Like, }; diff --git a/database/models/forum.js b/database/models/forum.js new file mode 100644 index 0000000..7fffa43 --- /dev/null +++ b/database/models/forum.js @@ -0,0 +1,19 @@ +const { DataTypes } = require('sequelize'); +const db = require('../db'); + +const Forum = db.define('forum', { + name: { + type: DataTypes.STRING, + allowNull: false, + }, + createdAt: { + type: DataTypes.DATE, + defaultValue: DataTypes.NOW, + }, + updatedAt: { + type: DataTypes.DATE, + defaultValue: DataTypes.NOW, + }, +}); + +module.exports = Forum; \ No newline at end of file diff --git a/database/models/like.js b/database/models/like.js deleted file mode 100644 index 851e5d7..0000000 --- a/database/models/like.js +++ /dev/null @@ -1,15 +0,0 @@ -const { DataTypes } = require('sequelize'); -const db = require('../db'); - -const Like = db.define('like', { - userId: { - type: DataTypes.INTEGER, - allowNull: false, - }, - threadId: { - type: DataTypes.INTEGER, - allowNull: false, - }, -}); - -module.exports = Like; \ No newline at end of file diff --git a/database/models/thread.js b/database/models/post.js similarity index 78% rename from database/models/thread.js rename to database/models/post.js index 2785372..a7ce59c 100644 --- a/database/models/thread.js +++ b/database/models/post.js @@ -1,7 +1,7 @@ const { DataTypes } = require('sequelize'); const db = require('../db'); -const Thread = db.define('thread', { +const Post = db.define('post', { title: { type: DataTypes.STRING, allowNull: false, @@ -10,6 +10,10 @@ const Thread = db.define('thread', { type: DataTypes.TEXT, allowNull: false, }, + likes: { + type: DataTypes.INTEGER, + defaultValue: 0, + }, userId: { type: DataTypes.INTEGER, allowNull: false, @@ -24,4 +28,4 @@ const Thread = db.define('thread', { }, }); -module.exports = Thread; \ No newline at end of file +module.exports = Post; \ No newline at end of file diff --git a/database/models/reply.js b/database/models/reply.js index 6951fb5..2a3ef8d 100644 --- a/database/models/reply.js +++ b/database/models/reply.js @@ -6,7 +6,7 @@ const Reply = db.define('reply', { type: DataTypes.TEXT, allowNull: false, }, - threadId: { + postId: { type: DataTypes.INTEGER, allowNull: false, }, @@ -14,6 +14,10 @@ const Reply = db.define('reply', { type: DataTypes.INTEGER, allowNull: false, }, + likes: { + type: DataTypes.INTEGER, + defaultValue: 0, + }, createdAt: { type: DataTypes.DATE, defaultValue: DataTypes.NOW, diff --git a/database/models/user.js b/database/models/user.js index 8a99f46..a78f1c1 100644 --- a/database/models/user.js +++ b/database/models/user.js @@ -38,6 +38,7 @@ const User = db.define('user', { allowNull: false, defaultValue: false, }, + }); module.exports = User; \ No newline at end of file diff --git a/database/seed.js b/database/seed.js index e58b595..4163df8 100644 --- a/database/seed.js +++ b/database/seed.js @@ -1,18 +1,68 @@ const db = require("./db"); const { User } = require("./index"); +const { Post } = require("./index"); +const { Reply } = require("./index"); +const { Forum } = require("./index"); const seed = async () => { try { db.logging = false; await db.sync({ force: true }); // Drop and recreate tables + // Users const users = await User.bulkCreate([ - { username: "admin", passwordHash: User.hashPassword("admin123") }, - { username: "user1", passwordHash: User.hashPassword("user111") }, - { username: "user2", passwordHash: User.hashPassword("user222") }, + { username: 'hailia', password: 'test1234', email: 'hailia@example.com' }, + { username: 'alex', password: 'test5678', email: 'alex@example.com' }, + ], { returning: true }); + + const [hailia, alex] = users; + + // Forums + const forums = await Forum.bulkCreate([ + { name: 'Projectile Motion' }, + { name: 'Friction' }, + ], { returning: true }); + + const [projectileForum, frictionForum] = forums; + + // Posts + const posts = await Post.bulkCreate([ + { + title: 'How to calculate max height?', + content: 'I’m confused about which formula to use when given initial velocity and angle.', + forumId: projectileForum.id, + userId: hailia.id, + }, + { + title: 'Static vs kinetic friction', + content: 'When does static friction switch to kinetic friction? Is it instantaneous?', + forumId: frictionForum.id, + userId: alex.id, + }, + ], { returning: true }); + + const [post1, post2] = posts; + + // Replies + const replies = await Reply.bulkCreate([ + { + content: 'Use v² = v₀² - 2g(y - y₀), or break velocity into vertical component!', + postId: post1.id, + userId: alex.id, + }, + { + content: 'Ah, so I should use v₀y = v₀ * sin(θ)?', + postId: post1.id, + userId: hailia.id, + parentId: 1, // be cautious with hardcoding IDs if running multiple times + }, + { + content: 'Yes — and kinetic friction kicks in once the object is moving.', + postId: post2.id, + userId: hailia.id, + }, ]); - console.log(`👤 Created ${users.length} users`); // Create more seed data here once you've created your models // Seed files are a great way to test your database schema! From 4db6f4050cf276989683232fbd2b4f8356a61756 Mon Sep 17 00:00:00 2001 From: hailia sommerville Date: Tue, 5 Aug 2025 14:32:14 -0400 Subject: [PATCH 14/23] feat: get forum route added --- api/forum.js | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/api/forum.js b/api/forum.js index 26ea47c..fa993dd 100644 --- a/api/forum.js +++ b/api/forum.js @@ -2,6 +2,8 @@ const express = require('express'); const router = express.Router(); const {Forum, Post, Reply, User} = require('../database') +// Get posts for a specific forum + router.get('/:forumId/posts', async (req, res) => { const { forumId } = req.params; console.log(`Fetching posts for forum ID: ${forumId}`); @@ -17,5 +19,17 @@ router.get('/:forumId/posts', async (req, res) => { res.status(500).json({ error: 'Failed to fetch posts for this forum.' }); } }); + + // Get all forums + + router.get('/', async (req, res) => { + try { + const forums = await Forum.findAll(); + res.json(forums); + } catch (err) { + console.error('Error fetching forums:', err); + res.status(500).json({ error: 'Failed to fetch forums.' }); + } + }); module.exports = router; \ No newline at end of file From c332a39eb4dc611ad8b0fc1a8295e4171137587c Mon Sep 17 00:00:00 2001 From: Darrell Moreno Date: Tue, 5 Aug 2025 15:10:30 -0400 Subject: [PATCH 15/23] Created new POST post route in forum.js and DELETE post in post.js --- api/forum.js | 22 +++++++++++++++++++++- api/index.js | 2 ++ api/post.js | 47 +++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 70 insertions(+), 1 deletion(-) create mode 100644 api/post.js diff --git a/api/forum.js b/api/forum.js index 26ea47c..29d5a32 100644 --- a/api/forum.js +++ b/api/forum.js @@ -17,5 +17,25 @@ router.get('/:forumId/posts', async (req, res) => { res.status(500).json({ error: 'Failed to fetch posts for this forum.' }); } }); - + +//Create a new post in a forum +router.post('/:forumId/post/', async(req, res) => { + const { forumId } = req.params; + try { + const { title, content, userId, likes = 0 } = req.body; + const newPost = await Post.create({ + title, + content, + likes, + userId, + forumId: forumId, + }); + + res.status(201).json(newPost); + } catch (error) { + console.error(error); + res.status(500).send("Error from the post new post route"); + } +}); + module.exports = router; \ No newline at end of file diff --git a/api/index.js b/api/index.js index 808c420..9568736 100644 --- a/api/index.js +++ b/api/index.js @@ -2,8 +2,10 @@ const express = require("express"); const router = express.Router(); const testDbRouter = require("./test-db"); const forumRouter = require("./forum"); +const postRouter = require("./post"); router.use("/test-db", testDbRouter); router.use("/forum", forumRouter); +router.use("/post", postRouter); module.exports = router; diff --git a/api/post.js b/api/post.js new file mode 100644 index 0000000..1eb1f4e --- /dev/null +++ b/api/post.js @@ -0,0 +1,47 @@ +const express = require('express'); +const router = express.Router(); +const {Forum, Post, Reply, User} = require('../database'); + +//Get all posts +router.get("/", async (req, res) => { + try { + const posts = await Post.findAll(); + res.status(200).json(posts); + } catch (error) { + console.error(error); + res.status(500).send("Error from the get all posts route"); + } +}); + +//Get a specific post +router.get("/:id", async (req, res) => { + try { + const postID = Number(req.params.id); + console.log(postID); + const post = await Post.findByPk(postID); + if (!post) { + return res.status(404).send("Post not found"); + } + res.status(200).json(post); + } catch (error) { + console.error(error); + res.status(500).send("Error from the get single post route"); + } +}); + +router.delete("/:postId", async (req, res) => { + try{ + const post = await Post.findByPk(req.params.postId); + if (!post) { + return res.status(404).send("Post not found"); + } + + await post.destroy(); + res.status(200).send("Post deleted successfully"); + } catch (error) { + console.error(error); + res.status(500).send("Error from the delete existing post route"); + } +}); + +module.exports = router; \ No newline at end of file From 11c334e814d53ced3b4cb566752877adc47a97bc Mon Sep 17 00:00:00 2001 From: Alex Nurci Date: Tue, 5 Aug 2025 15:28:46 -0400 Subject: [PATCH 16/23] Added GET route for each post --- api/forum.js | 17 ++++++++++++++++- database/seed.js | 6 ++++++ 2 files changed, 22 insertions(+), 1 deletion(-) diff --git a/api/forum.js b/api/forum.js index 88078e6..bfbca94 100644 --- a/api/forum.js +++ b/api/forum.js @@ -15,7 +15,22 @@ router.get('/:forumId/posts', async (req, res) => { }); res.json(posts); } catch (err) { - console.error('Error fetching posts:', err); + console.error('Error fetching posts: ', err); + res.status(500).json({ error: 'Failed to fetch posts for this forum.' }); + } + }); + + router.get('/:forumId/posts/:postId', async (req, res) => { + const { postId, forumId } = req.params; + console.log(`Fetching post for post ID: ${postId}`); + try { + const post = await Post.findOne({ + where: { forumId, id: postId }, + include: [{ model: User, attributes: ['username']}], + }); + res.json(post); + } catch (err) { + console.error('Error fetching post by id: ', err); res.status(500).json({ error: 'Failed to fetch posts for this forum.' }); } }); diff --git a/database/seed.js b/database/seed.js index 4163df8..8133548 100644 --- a/database/seed.js +++ b/database/seed.js @@ -33,6 +33,12 @@ const seed = async () => { forumId: projectileForum.id, userId: hailia.id, }, + { + title: 'How to calculate max width?', + content: 'Idk bro physics is hard', + forumId: projectileForum.id, + userId: hailia.id, + }, { title: 'Static vs kinetic friction', content: 'When does static friction switch to kinetic friction? Is it instantaneous?', From 26c52de17e9834ceedd18686d140863415e7a219 Mon Sep 17 00:00:00 2001 From: PKhant78 Date: Tue, 5 Aug 2025 19:47:02 -0400 Subject: [PATCH 17/23] Added GET endpoint for getting all replies from a forum post --- api/forum.js | 20 +++++++++++++++++++- 1 file changed, 19 insertions(+), 1 deletion(-) diff --git a/api/forum.js b/api/forum.js index bfbca94..63d9788 100644 --- a/api/forum.js +++ b/api/forum.js @@ -56,7 +56,6 @@ router.post('/:forumId/post/', async(req, res) => { }); // Get all forums - router.get('/', async (req, res) => { try { const forums = await Forum.findAll(); @@ -66,5 +65,24 @@ router.post('/:forumId/post/', async(req, res) => { res.status(500).json({ error: 'Failed to fetch forums.' }); } }); + + +// Get all replies from a forum post +router.get('/:forumId/posts/:postId/replies', async (req, res) => { + const { forumId } = req.params; + const { postId } = req.params; + + try { + const replies = await Reply.findAll({ + where: { forumId: forumId, postId: postId }, + include: [{ model: User, attributes: ['username'] }], + order: [['createdAt', 'DESC']], + }); + res.json(replies); + } catch (err) { + console.error('Error fetching replies:', err); + res.status(500).json({ error: 'Failed to fetch replies for this forum.' }); + } +}); module.exports = router; \ No newline at end of file From d91e6d3d50953fdcaeae892d934ecc6573b751df Mon Sep 17 00:00:00 2001 From: PKhant78 Date: Tue, 5 Aug 2025 19:51:19 -0400 Subject: [PATCH 18/23] Fixed spacing --- api/forum.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/api/forum.js b/api/forum.js index 63d9788..0ef428d 100644 --- a/api/forum.js +++ b/api/forum.js @@ -85,4 +85,4 @@ router.get('/:forumId/posts/:postId/replies', async (req, res) => { } }); - module.exports = router; \ No newline at end of file +module.exports = router; \ No newline at end of file From a814c8d444dc2cdeab4f408461037fceb02ddbae Mon Sep 17 00:00:00 2001 From: PKhant78 Date: Tue, 5 Aug 2025 20:22:20 -0400 Subject: [PATCH 19/23] Put the endpoint for getting all replies from a post in post.js from forum.js --- .env.example | 3 --- api/forum.js | 24 ++---------------------- api/post.js | 17 +++++++++++++++++ 3 files changed, 19 insertions(+), 25 deletions(-) delete mode 100644 .env.example diff --git a/.env.example b/.env.example deleted file mode 100644 index 848ad61..0000000 --- a/.env.example +++ /dev/null @@ -1,3 +0,0 @@ -DATABASE_URL=postgres://hostname:5432/database-name -JWT_SECRET=super-secret-jwt-key -FRONTEND_URL=http://localhost:3000 diff --git a/api/forum.js b/api/forum.js index 0ef428d..5d33f6b 100644 --- a/api/forum.js +++ b/api/forum.js @@ -3,7 +3,6 @@ const router = express.Router(); const {Forum, Post, Reply, User} = require('../database') // Get posts for a specific forum - router.get('/:forumId/posts', async (req, res) => { const { forumId } = req.params; console.log(`Fetching posts for forum ID: ${forumId}`); @@ -55,8 +54,8 @@ router.post('/:forumId/post/', async(req, res) => { } }); - // Get all forums - router.get('/', async (req, res) => { +// Get all forums +router.get('/', async (req, res) => { try { const forums = await Forum.findAll(); res.json(forums); @@ -65,24 +64,5 @@ router.post('/:forumId/post/', async(req, res) => { res.status(500).json({ error: 'Failed to fetch forums.' }); } }); - - -// Get all replies from a forum post -router.get('/:forumId/posts/:postId/replies', async (req, res) => { - const { forumId } = req.params; - const { postId } = req.params; - - try { - const replies = await Reply.findAll({ - where: { forumId: forumId, postId: postId }, - include: [{ model: User, attributes: ['username'] }], - order: [['createdAt', 'DESC']], - }); - res.json(replies); - } catch (err) { - console.error('Error fetching replies:', err); - res.status(500).json({ error: 'Failed to fetch replies for this forum.' }); - } -}); module.exports = router; \ No newline at end of file diff --git a/api/post.js b/api/post.js index 1eb1f4e..90b5ff5 100644 --- a/api/post.js +++ b/api/post.js @@ -44,4 +44,21 @@ router.delete("/:postId", async (req, res) => { } }); +// Get all replies from a post +router.get('/:postId/replies', async (req, res) => { + const { postId } = req.params; + + try { + const replies = await Reply.findAll({ + where: { postId }, + include: [{ model: User, attributes: ['username'] }], + order: [['createdAt', 'DESC']], + }); + res.json(replies); + } catch (err) { + console.error('Error fetching replies:', err); + res.status(500).json({ error: 'Failed to fetch replies for this post.' }); + } +}); + module.exports = router; \ No newline at end of file From 9df8652526dfa04666636ea78a622ab84633093e Mon Sep 17 00:00:00 2001 From: hailia sommerville Date: Wed, 6 Aug 2025 11:16:17 -0400 Subject: [PATCH 20/23] fix: login error --- database/models/user.js | 75 ++++++++++++++++++++++------------------- database/seed.js | 4 +-- 2 files changed, 43 insertions(+), 36 deletions(-) diff --git a/database/models/user.js b/database/models/user.js index a78f1c1..b65001a 100644 --- a/database/models/user.js +++ b/database/models/user.js @@ -1,44 +1,51 @@ -const {DataTypes} = require('sequelize'); -const db = require('../db'); -const bcrypt = require('bcrypt'); +const { DataTypes } = require("sequelize"); +const db = require("../db"); +const bcrypt = require("bcrypt"); -const User = db.define('user', { +const User = db.define("user", { username: { type: DataTypes.STRING, allowNull: false, unique: true, }, - password: { + passwordHash: { type: DataTypes.STRING, - allowNull: false, + allowNull: false, + }, + auth0Id: { + type: DataTypes.STRING, + allowNull: true, + unique: true, + }, + email: { + type: DataTypes.STRING, + unique: true, validate: { - len: [6, 100], + isEmail: true, + notEmpty: true, }, - }, - auth0Id: { - type: DataTypes.STRING, - allowNull: true, - unique: true, - }, - email: { - type: DataTypes.STRING, - unique: true, - validate: { - isEmail: true, - notEmpty: true, - }, - }, - isAdmin: { - type: DataTypes.BOOLEAN, - allowNull: false, - defaultValue: false, - }, - isDisable: { - type: DataTypes.BOOLEAN, - allowNull: false, - defaultValue: false, - }, - - }); + }, + isAdmin: { + type: DataTypes.BOOLEAN, + allowNull: false, + defaultValue: false, + }, + isDisable: { + type: DataTypes.BOOLEAN, + allowNull: false, + defaultValue: false, + }, +}); +// Instance method to check password +User.prototype.checkPassword = function (password) { + if (!this.passwordHash) { + return false; // Auth0 users don't have passwords + } + return bcrypt.compareSync(password, this.passwordHash); +}; - module.exports = User; \ No newline at end of file +// Class method to hash password +User.hashPassword = function (password) { + return bcrypt.hashSync(password, 10); +}; +module.exports = User; diff --git a/database/seed.js b/database/seed.js index 8133548..e6268aa 100644 --- a/database/seed.js +++ b/database/seed.js @@ -11,8 +11,8 @@ const seed = async () => { // Users const users = await User.bulkCreate([ - { username: 'hailia', password: 'test1234', email: 'hailia@example.com' }, - { username: 'alex', password: 'test5678', email: 'alex@example.com' }, + { username: 'hailia', passwordHash: User.hashPassword('hai123'), email: 'hailia@example.com' }, + { username: 'alex', passwordHash: User.hashPassword('alex123'), email: 'alex@example.com' }, ], { returning: true }); const [hailia, alex] = users; From 7fb558c3d146a1a10df58d23769cd5ae815b1298 Mon Sep 17 00:00:00 2001 From: hailia sommerville Date: Wed, 6 Aug 2025 11:59:59 -0400 Subject: [PATCH 21/23] refactor: added 2 users to seed + more posts and replies --- database/seed.js | 194 ++++++++++++++++++++++++++++++++--------------- 1 file changed, 134 insertions(+), 60 deletions(-) diff --git a/database/seed.js b/database/seed.js index e6268aa..507e648 100644 --- a/database/seed.js +++ b/database/seed.js @@ -9,66 +9,140 @@ const seed = async () => { db.logging = false; await db.sync({ force: true }); // Drop and recreate tables - // Users - const users = await User.bulkCreate([ - { username: 'hailia', passwordHash: User.hashPassword('hai123'), email: 'hailia@example.com' }, - { username: 'alex', passwordHash: User.hashPassword('alex123'), email: 'alex@example.com' }, - ], { returning: true }); - - const [hailia, alex] = users; - - // Forums - const forums = await Forum.bulkCreate([ - { name: 'Projectile Motion' }, - { name: 'Friction' }, - ], { returning: true }); - - const [projectileForum, frictionForum] = forums; - - // Posts - const posts = await Post.bulkCreate([ - { - title: 'How to calculate max height?', - content: 'I’m confused about which formula to use when given initial velocity and angle.', - forumId: projectileForum.id, - userId: hailia.id, - }, - { - title: 'How to calculate max width?', - content: 'Idk bro physics is hard', - forumId: projectileForum.id, - userId: hailia.id, - }, - { - title: 'Static vs kinetic friction', - content: 'When does static friction switch to kinetic friction? Is it instantaneous?', - forumId: frictionForum.id, - userId: alex.id, - }, - ], { returning: true }); - - const [post1, post2] = posts; - - // Replies - const replies = await Reply.bulkCreate([ - { - content: 'Use v² = v₀² - 2g(y - y₀), or break velocity into vertical component!', - postId: post1.id, - userId: alex.id, - }, - { - content: 'Ah, so I should use v₀y = v₀ * sin(θ)?', - postId: post1.id, - userId: hailia.id, - parentId: 1, // be cautious with hardcoding IDs if running multiple times - }, - { - content: 'Yes — and kinetic friction kicks in once the object is moving.', - postId: post2.id, - userId: hailia.id, - }, - ]); - + // USERS + const users = await User.bulkCreate([ + { username: "hailia", passwordHash: User.hashPassword("hai123"), email: "hailia@example.com" }, + { username: "alex", passwordHash: User.hashPassword("alex123"), email: "alex@example.com" }, + { username: "phone", passwordHash: User.hashPassword("phone123"), email: "phone@example.com" }, + { username: "darrel", passwordHash: User.hashPassword("darrel123"), email: "darrel@example.com" }, + ], { returning: true }); + + const [hailia, alex, phone, darrel] = users; + + // FORUMS + const forums = await Forum.bulkCreate([ + { name: "Projectile Motion" }, + { name: "Friction" }, + { name: "Free Fall" }, + { name: "Torque" }, + { name: "Inertia" }, + ], { returning: true }); + + const [projectileForum, frictionForum, freeFallForum, torqueForum, inertiaForum] = forums; + + // POSTS + const posts = await Post.bulkCreate([ + { + title: "How to calculate max height?", + content: "I’m confused about which formula to use when given initial velocity and angle.", + forumId: projectileForum.id, + userId: hailia.id, + }, + { + title: "How to calculate max width?", + content: "Idk bro physics is hard", + forumId: projectileForum.id, + userId: hailia.id, + }, + { + title: "Static vs kinetic friction", + content: "When does static friction switch to kinetic friction? Is it instantaneous?", + forumId: frictionForum.id, + userId: alex.id, + }, + { + title: "Does mass affect fall speed?", + content: "If I drop a feather and a rock, why don’t they fall the same?", + forumId: freeFallForum.id, + userId: phone.id, + }, + { + title: "What exactly is torque?", + content: "I don’t understand torque — is it just force but twisty?", + forumId: torqueForum.id, + userId: darrel.id, + }, + { + title: "How does inertia work?", + content: "Why do objects in motion stay in motion? Magic?", + forumId: inertiaForum.id, + userId: alex.id, + }, + ], { returning: true }); + + const [post1, post2, post3, post4, post5, post6] = posts; + + // REPLIES + const replies = await Reply.bulkCreate([ + // Projectile Motion + { + content: "Use v² = v₀² - 2g(y - y₀), or break velocity into vertical component!", + postId: post1.id, + userId: alex.id, + }, + { + content: "Ah, so I should use v₀y = v₀ * sin(θ)?", + postId: post1.id, + userId: hailia.id, + parentId: 1, + }, + { + content: "Yes, that’s it! Then solve for y using kinematic equations.", + postId: post1.id, + userId: phone.id, + parentId: 2, + }, + + // Friction + { + content: "Yes — and kinetic friction kicks in once the object is moving.", + postId: post3.id, + userId: hailia.id, + }, + { + content: "Static friction can vary but maxes out. Once exceeded, motion begins.", + postId: post3.id, + userId: darrel.id, + }, + + // Free Fall + { + content: "Air resistance slows the feather down. In a vacuum, they'd fall together.", + postId: post4.id, + userId: alex.id, + }, + { + content: "Try looking up the Galileo drop test!", + postId: post4.id, + userId: hailia.id, + parentId: 6, + }, + + // Torque + { + content: "Torque = force × distance from pivot point. Think of a wrench.", + postId: post5.id, + userId: phone.id, + }, + { + content: "So the longer the lever, the more torque?", + postId: post5.id, + userId: darrel.id, + parentId: 8, + }, + + // Inertia + { + content: "It's Newton’s 1st Law — no magic, just physics!", + postId: post6.id, + userId: hailia.id, + }, + { + content: "Inertia resists changes in motion. Mass affects it.", + postId: post6.id, + userId: alex.id, + }, + ]); // Create more seed data here once you've created your models // Seed files are a great way to test your database schema! From 46982d42c595b18822484aad72978d3123bf9771 Mon Sep 17 00:00:00 2001 From: Darrell Moreno Date: Wed, 6 Aug 2025 13:00:46 -0400 Subject: [PATCH 22/23] Created models and seeded sample data for postlikes and replylikes. Started routes for postlikes and replylikes, but need reviewing, fixing, and testing --- api/index.js | 4 ++ api/postlikes.js | 77 +++++++++++++++++++++++++++++++++++ api/replylikes.js | 75 ++++++++++++++++++++++++++++++++++ database/index.js | 33 ++++++++++++++- database/models/postlikes.js | 22 ++++++++++ database/models/replylikes.js | 22 ++++++++++ database/seed.js | 25 ++++++++++++ 7 files changed, 256 insertions(+), 2 deletions(-) create mode 100644 api/postlikes.js create mode 100644 api/replylikes.js create mode 100644 database/models/postlikes.js create mode 100644 database/models/replylikes.js diff --git a/api/index.js b/api/index.js index 9568736..7e8785d 100644 --- a/api/index.js +++ b/api/index.js @@ -3,9 +3,13 @@ const router = express.Router(); const testDbRouter = require("./test-db"); const forumRouter = require("./forum"); const postRouter = require("./post"); +const postLikesRouter = require("./postlikes"); +const replyLikesRouter = require("./replylikes"); router.use("/test-db", testDbRouter); router.use("/forum", forumRouter); router.use("/post", postRouter); +router.use("/postlikes", postLikesRouter); +router.use("/replylikes", replyLikesRouter); module.exports = router; diff --git a/api/postlikes.js b/api/postlikes.js new file mode 100644 index 0000000..1c43e5d --- /dev/null +++ b/api/postlikes.js @@ -0,0 +1,77 @@ +const express = require("express"); +const router = express.Router(); +const { PostLike, Post, User } = require("../database"); + +// Check if the user has liked a specific post +router.get('/:postId/likes/:userId', async (req, res) => { + try { + const postId = req.params.postId; + + const userId = req.params.userId; + + if (!userId) { + return res.status(401).json({ error: 'User not authenticated.' }); + } + + if (!postId || isNaN(postId)) { + return res.status(400).json({ error: 'Invalid post ID.' }); + } + + const like = await PostLike.findOne({ where: { postId, userId } }); + + return res.status(200).json({ liked: !!like }); + + } catch (error) { + console.error('Error checking post like:', error); + return res.status(500).json({ error: 'Internal server error.' }); + } +}); + + + +// Like a post +router.post("/:postId/like/:userId", async (req, res) => { + const userId = req.params.userId; + const postId = Number(req.params.postId); + + try { + const existing = await PostLike.findOne({ where: { userId, postId } }); + if (existing) { + return res.status(400).json({ error: "You already liked this post" }); + } + + await PostLike.create({ userId, postId }); + + // Increment post.likes count + await Post.increment("likes", { where: { id: postId } }); + + res.status(201).json({ message: "Post liked" }); + } catch (error) { + console.error(error); + res.status(500).json({ error: "Failed to like post" }); + } +}); + +// Unlike a post +router.delete("/:postId/unlike/:userId", async (req, res) => { + const userId = req.params.userId; + const postId = Number(req.params.postId); + + try { + const deleted = await PostLike.destroy({ where: { userId, postId } }); + + if (!deleted) { + return res.status(404).json({ error: "Like not found" }); + } + + // Decrement post.likes count + await Post.decrement("likes", { where: { id: postId } }); + + res.status(200).json({ message: "Post unliked" }); + } catch (error) { + console.error(error); + res.status(500).json({ error: "Failed to unlike post" }); + } +}); + +module.exports = router; diff --git a/api/replylikes.js b/api/replylikes.js new file mode 100644 index 0000000..204e62c --- /dev/null +++ b/api/replylikes.js @@ -0,0 +1,75 @@ +const express = require("express"); +const router = express.Router(); +const { ReplyLike, Reply } = require("../database"); + +// Check if the user has liked a specific reply +router.get('/:replyId/likes/:userId', async (req, res) => { + try { + const { replyId } = req.params.replyId; + const { userId } = req.params.userId; + + if (!userId) { + return res.status(401).json({ error: 'User not authenticated.' }); + } + + if (!replyId || isNaN(replyId)) { + return res.status(400).json({ error: 'Invalid reply ID.' }); + } + + const like = await ReplyLike.findOne({ where: { replyId, userId } }); + + return res.status(200).json({ liked: !!like }); + + } catch (error) { + console.error('Error checking reply like:', error); + return res.status(500).json({ error: 'Internal server error.' }); + } +}); + + +// Like a reply +router.post("/:replyId/like/:userId", async (req, res) => { + const userId = req.params.userId; + const replyId = Number(req.params.replyId); + + try { + const existing = await ReplyLike.findOne({ where: { userId, replyId } }); + if (existing) { + return res.status(400).json({ error: "You already liked this reply" }); + } + + await ReplyLike.create({ userId, replyId }); + + // Optional: increment reply.likes + await Reply.increment("likes", { where: { id: replyId } }); + + res.status(201).json({ message: "Reply liked" }); + } catch (error) { + console.error(error); + res.status(500).json({ error: "Failed to like reply" }); + } +}); + +// Unlike a reply +router.delete("/:replyId/unlike/:userId", async (req, res) => { + const userId = req.params.userId; + const replyId = Number(req.params.replyId); + + try { + const deleted = await ReplyLike.destroy({ where: { userId, replyId } }); + + if (!deleted) { + return res.status(404).json({ error: "Like not found" }); + } + + // Optional: decrement reply.likes + await Reply.decrement("likes", { where: { id: replyId } }); + + res.status(200).json({ message: "Reply unliked" }); + } catch (error) { + console.error(error); + res.status(500).json({ error: "Failed to unlike reply" }); + } +}); + +module.exports = router; diff --git a/database/index.js b/database/index.js index 1cd087e..621e317 100644 --- a/database/index.js +++ b/database/index.js @@ -3,7 +3,8 @@ const User = require("./models/user"); const Post = require("./models/post"); const Reply = require("./models/reply"); const Forum = require("./models/forum"); - +const Postlikes = require("./models/postlikes"); +const Replylikes = require("./models/replylikes"); // Associations Forum.hasMany(Post, { foreignKey: "forumId" }); // A forum can have many posts @@ -22,7 +23,33 @@ Reply.belongsTo(Reply, { foreignKey: "parentId", as: "parentReply" }); // A repl Reply.hasMany(Reply, { foreignKey: "parentId", as: "childReplies" }); // A reply can have many child replies - +// Post <-> User likes +User.belongsToMany(Post, { + through: Postlikes, + foreignKey: "userId", + otherKey: "postId", + as: "likedPosts", +}); +Post.belongsToMany(User, { + through: Postlikes, + foreignKey: "postId", + otherKey: "userId", + as: "likedByUsers", +}); + +// Reply <-> User likes +User.belongsToMany(Reply, { + through: Replylikes, + foreignKey: "userId", + otherKey: "replyId", + as: "likedReplies", +}); +Reply.belongsToMany(User, { + through: Replylikes, + foreignKey: "replyId", + otherKey: "userId", + as: "likedByUsers", +}); module.exports = { db, @@ -30,4 +57,6 @@ module.exports = { Post, Forum, Reply, + Postlikes, + Replylikes, }; diff --git a/database/models/postlikes.js b/database/models/postlikes.js new file mode 100644 index 0000000..a9ccd9f --- /dev/null +++ b/database/models/postlikes.js @@ -0,0 +1,22 @@ +const { DataTypes } = require('sequelize'); +const db = require('../db'); + +const Postlikes = db.define("Postlikes", { + userId: { + type: DataTypes.INTEGER, + allowNull: false, + primaryKey: true, + }, + postId: { + type: DataTypes.INTEGER, + allowNull: false, + primaryKey: true, + }, +}, + { + timestamps: false, + tableName: "postlikes", + freezeTableName: true, +}); + +module.exports = Postlikes; \ No newline at end of file diff --git a/database/models/replylikes.js b/database/models/replylikes.js new file mode 100644 index 0000000..85aac96 --- /dev/null +++ b/database/models/replylikes.js @@ -0,0 +1,22 @@ +const { DataTypes } = require("sequelize"); +const db = require("../db"); + +const Replylikes = db.define("Replylikes", { + userId: { + type: DataTypes.INTEGER, + allowNull: false, + primaryKey: true, + }, + replyId: { + type: DataTypes.INTEGER, + allowNull: false, + primaryKey: true, + }, +}, + { + timestamps: false, + tableName: "replylikes", + freezeTableName: true, +}); + +module.exports = Replylikes; diff --git a/database/seed.js b/database/seed.js index 8133548..babcfeb 100644 --- a/database/seed.js +++ b/database/seed.js @@ -3,6 +3,8 @@ const { User } = require("./index"); const { Post } = require("./index"); const { Reply } = require("./index"); const { Forum } = require("./index"); +const { Postlikes } = require("./index"); +const { Replylikes } = require("./index"); const seed = async () => { try { @@ -69,6 +71,29 @@ const seed = async () => { }, ]); + //Postlikes + const postlikes = await Postlikes.bulkCreate([ + { + postId: post1.id, + userId: alex.id, + }, + { + postId: post2.id, + userId: hailia.id, + }, + ]); + + //Replylikes + const replylikes = await Replylikes.bulkCreate([ + { + replyId: 1, + userId: alex.id, + }, + { + replyId: 2, + userId: hailia.id, + } + ]); // Create more seed data here once you've created your models // Seed files are a great way to test your database schema! From c118729fe398a54ce52012da72fdfbaac6c875ad Mon Sep 17 00:00:00 2001 From: Darrell Moreno Date: Wed, 6 Aug 2025 13:19:22 -0400 Subject: [PATCH 23/23] Fixed routes for postlikes and replylikes --- api/postlikes.js | 33 +++++++++++++-------------------- api/replylikes.js | 31 +++++++++++++------------------ 2 files changed, 26 insertions(+), 38 deletions(-) diff --git a/api/postlikes.js b/api/postlikes.js index 1c43e5d..4059146 100644 --- a/api/postlikes.js +++ b/api/postlikes.js @@ -1,46 +1,40 @@ const express = require("express"); const router = express.Router(); -const { PostLike, Post, User } = require("../database"); +const { Postlikes, Post, User } = require("../database"); // Check if the user has liked a specific post -router.get('/:postId/likes/:userId', async (req, res) => { +router.get("/:postId/likes/:userId", async (req, res) => { try { - const postId = req.params.postId; - - const userId = req.params.userId; + const { postId, userId } = req.params; if (!userId) { - return res.status(401).json({ error: 'User not authenticated.' }); + return res.status(401).json({ error: "User not authenticated." }); } if (!postId || isNaN(postId)) { - return res.status(400).json({ error: 'Invalid post ID.' }); + return res.status(400).json({ error: "Invalid post ID." }); } - const like = await PostLike.findOne({ where: { postId, userId } }); + const like = await Postlikes.findOne({ where: { postId, userId } }); return res.status(200).json({ liked: !!like }); - } catch (error) { - console.error('Error checking post like:', error); - return res.status(500).json({ error: 'Internal server error.' }); + console.error("Error checking post like:", error); + return res.status(500).json({ error: "Internal server error." }); } }); - - // Like a post router.post("/:postId/like/:userId", async (req, res) => { - const userId = req.params.userId; - const postId = Number(req.params.postId); + const { postId, userId } = req.params; try { - const existing = await PostLike.findOne({ where: { userId, postId } }); + const existing = await Postlikes.findOne({ where: { userId, postId } }); if (existing) { return res.status(400).json({ error: "You already liked this post" }); } - await PostLike.create({ userId, postId }); + await Postlikes.create({ userId, postId }); // Increment post.likes count await Post.increment("likes", { where: { id: postId } }); @@ -54,11 +48,10 @@ router.post("/:postId/like/:userId", async (req, res) => { // Unlike a post router.delete("/:postId/unlike/:userId", async (req, res) => { - const userId = req.params.userId; - const postId = Number(req.params.postId); + const { postId, userId } = req.params; try { - const deleted = await PostLike.destroy({ where: { userId, postId } }); + const deleted = await Postlikes.destroy({ where: { userId, postId } }); if (!deleted) { return res.status(404).json({ error: "Like not found" }); diff --git a/api/replylikes.js b/api/replylikes.js index 204e62c..964b7af 100644 --- a/api/replylikes.js +++ b/api/replylikes.js @@ -1,44 +1,40 @@ const express = require("express"); const router = express.Router(); -const { ReplyLike, Reply } = require("../database"); +const { Replylikes, Reply } = require("../database"); // Check if the user has liked a specific reply -router.get('/:replyId/likes/:userId', async (req, res) => { +router.get("/:replyId/likes/:userId", async (req, res) => { try { - const { replyId } = req.params.replyId; - const { userId } = req.params.userId; + const { replyId, userId } = req.params; if (!userId) { - return res.status(401).json({ error: 'User not authenticated.' }); + return res.status(401).json({ error: "User not authenticated." }); } if (!replyId || isNaN(replyId)) { - return res.status(400).json({ error: 'Invalid reply ID.' }); + return res.status(400).json({ error: "Invalid reply ID." }); } - const like = await ReplyLike.findOne({ where: { replyId, userId } }); + const like = await Replylikes.findOne({ where: { replyId, userId } }); return res.status(200).json({ liked: !!like }); - } catch (error) { - console.error('Error checking reply like:', error); - return res.status(500).json({ error: 'Internal server error.' }); + console.error("Error checking reply like:", error); + return res.status(500).json({ error: "Internal server error." }); } }); - // Like a reply router.post("/:replyId/like/:userId", async (req, res) => { - const userId = req.params.userId; - const replyId = Number(req.params.replyId); + const { replyId, userId } = req.params; try { - const existing = await ReplyLike.findOne({ where: { userId, replyId } }); + const existing = await Replylikes.findOne({ where: { userId, replyId } }); if (existing) { return res.status(400).json({ error: "You already liked this reply" }); } - await ReplyLike.create({ userId, replyId }); + await Replylikes.create({ userId, replyId }); // Optional: increment reply.likes await Reply.increment("likes", { where: { id: replyId } }); @@ -52,11 +48,10 @@ router.post("/:replyId/like/:userId", async (req, res) => { // Unlike a reply router.delete("/:replyId/unlike/:userId", async (req, res) => { - const userId = req.params.userId; - const replyId = Number(req.params.replyId); + const { replyId, userId } = req.params; try { - const deleted = await ReplyLike.destroy({ where: { userId, replyId } }); + const deleted = await Replylikes.destroy({ where: { userId, replyId } }); if (!deleted) { return res.status(404).json({ error: "Like not found" });