Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
38 commits
Select commit Hold shift + click to select a range
cf9b560
feat: user model created
haicomet Jul 30, 2025
ccfbeef
Merge pull request #2 from TTP-Capstone-II/user-model
AlexNurci Jul 30, 2025
1bcd237
add socket functionality, fix cookies
fterdal Jul 31, 2025
b88556b
clean up
fterdal Jul 31, 2025
e76a799
update package lock
AlexNurci Jul 31, 2025
8e273f6
pull from upstream
AlexNurci Jul 31, 2025
d9b9e98
Merge branch 'main' of https://github.com/TTP-Capstone-II/Capstone-1-…
AlexNurci Jul 31, 2025
6bcc44b
chore: model files added (thread,like,reply)
haicomet Aug 2, 2025
489077b
feat: thread model created
haicomet Aug 2, 2025
475a937
feat: reply model created
haicomet Aug 2, 2025
6cd75ef
feat: like model created
haicomet Aug 2, 2025
40a6c0e
feat: model associations defined
haicomet Aug 2, 2025
d20cdc2
chore: forum file added to api for routes
haicomet Aug 2, 2025
b5b89ad
fix: missing pkg installed, route to models corrected, db name changed
haicomet Aug 2, 2025
882d7a7
Merge branch 'main' into models
haicomet Aug 2, 2025
cd53a42
Merge pull request #12 from TTP-Capstone-II/models
AlexNurci Aug 3, 2025
6802390
Merge pull request #13 from TTP-Capstone-II/setup
AlexNurci Aug 3, 2025
6dddb16
feat: added thread has many likes association
haicomet Aug 4, 2025
b28aa98
Merge branch 'main' of https://github.com/TTP-Capstone-II/Capstone-II…
haicomet Aug 4, 2025
e28b661
feat: models + associations updated, get route made, seed created
haicomet Aug 5, 2025
898be59
Merge pull request #14 from TTP-Capstone-II/models
AlexNurci Aug 5, 2025
4db6f40
feat: get forum route added
haicomet Aug 5, 2025
e812e64
Merge pull request #15 from TTP-Capstone-II/routes
AlexNurci Aug 5, 2025
c332a39
Created new POST post route in forum.js and DELETE post in post.js
DMoreno-87 Aug 5, 2025
f76213c
Merge branch 'main' into POST_post_and_DELETE_post
DMoreno-87 Aug 5, 2025
51255c4
Merge pull request #16 from TTP-Capstone-II/POST_post_and_DELETE_post
DMoreno-87 Aug 5, 2025
11c334e
Added GET route for each post
AlexNurci Aug 5, 2025
26c52de
Added GET endpoint for getting all replies from a forum post
PKhant78 Aug 5, 2025
d91e6d3
Fixed spacing
PKhant78 Aug 5, 2025
a814c8d
Put the endpoint for getting all replies from a post in post.js from …
PKhant78 Aug 6, 2025
3ea5465
Merge pull request #18 from TTP-Capstone-II/17-create-endpoint-for-ge…
haicomet Aug 6, 2025
9df8652
fix: login error
haicomet Aug 6, 2025
9714e65
Merge pull request #20 from TTP-Capstone-II/hailia
AlexNurci Aug 6, 2025
7fb558c
refactor: added 2 users to seed + more posts and replies
haicomet Aug 6, 2025
46982d4
Created models and seeded sample data for postlikes and replylikes. S…
DMoreno-87 Aug 6, 2025
c118729
Fixed routes for postlikes and replylikes
DMoreno-87 Aug 6, 2025
f2a1dda
Merge pull request #21 from TTP-Capstone-II/hailia
AlexNurci Aug 6, 2025
dcbaaac
Merge branch 'main' of https://github.com/TTP-Capstone-II/Capstone-II…
DMoreno-87 Aug 6, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 0 additions & 3 deletions .env.example

This file was deleted.

4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
# Capstone I Backend
# Capstone II Backend

## Getting Started

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

Expand Down
68 changes: 68 additions & 0 deletions api/forum.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
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}`);
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.' });
}
});

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.' });
}
});

//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");
}
});

// 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;
8 changes: 8 additions & 0 deletions api/index.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,15 @@
const express = require("express");
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;
64 changes: 64 additions & 0 deletions api/post.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
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");
}
});

// 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;
70 changes: 70 additions & 0 deletions api/postlikes.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
const express = require("express");
const router = express.Router();
const { Postlikes, Post, User } = require("../database");

// Check if the user has liked a specific post
router.get("/:postId/likes/:userId", async (req, res) => {
try {
const { postId, userId } = req.params;

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 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." });
}
});

// Like a post
router.post("/:postId/like/:userId", async (req, res) => {
const { postId, userId } = req.params;

try {
const existing = await Postlikes.findOne({ where: { userId, postId } });
if (existing) {
return res.status(400).json({ error: "You already liked this post" });
}

await Postlikes.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 { postId, userId } = req.params;

try {
const deleted = await Postlikes.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;
70 changes: 70 additions & 0 deletions api/replylikes.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
const express = require("express");
const router = express.Router();
const { Replylikes, Reply } = require("../database");

// Check if the user has liked a specific reply
router.get("/:replyId/likes/:userId", async (req, res) => {
try {
const { replyId, userId } = req.params;

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 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." });
}
});

// Like a reply
router.post("/:replyId/like/:userId", async (req, res) => {
const { replyId, userId } = req.params;

try {
const existing = await Replylikes.findOne({ where: { userId, replyId } });
if (existing) {
return res.status(400).json({ error: "You already liked this reply" });
}

await Replylikes.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 { replyId, userId } = req.params;

try {
const deleted = await Replylikes.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;
7 changes: 5 additions & 2 deletions app.js
Original file line number Diff line number Diff line change
Expand Up @@ -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";

Expand Down Expand Up @@ -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);
}
Expand Down
28 changes: 10 additions & 18 deletions auth/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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",
Expand Down Expand Up @@ -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",
Expand Down Expand Up @@ -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",
Expand Down
2 changes: 1 addition & 1 deletion database/db.js
Original file line number Diff line number Diff line change
Expand Up @@ -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}`,
Expand Down
Loading