Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
101 commits
Select commit Hold shift + click to select a range
7348cbf
first commit on frank
Frank-cntrl Jul 11, 2025
112fa4f
Publishing my branch
TabulaRasae Jul 11, 2025
2fb541a
Poll table defined
TabulaRasae Jul 14, 2025
64897c4
added the ballot to separate rankings from poll def
TabulaRasae Jul 14, 2025
66fa230
defined all the tables according to schema
TabulaRasae Jul 14, 2025
63dac2c
db schema is always matched to the code
TabulaRasae Jul 14, 2025
5a6e772
Merge pull request #3 from TabulaRasae/mark
Frank-cntrl Jul 14, 2025
375d573
Publishing my branch
TabulaRasae Jul 11, 2025
bef3e8c
Poll table defined
TabulaRasae Jul 14, 2025
f95629d
added the ballot to separate rankings from poll def
TabulaRasae Jul 14, 2025
edbf8f9
defined all the tables according to schema
TabulaRasae Jul 14, 2025
e7c5f89
db schema is always matched to the code
TabulaRasae Jul 14, 2025
9b02ab0
created basic user routes
Frank-cntrl Jul 14, 2025
9221e10
Merge branch 'main' into frank
AlexNurci Jul 14, 2025
acdc4d4
Merge pull request #4 from TabulaRasae/frank
Frank-cntrl Jul 14, 2025
55e6596
Resolved the conflict
TabulaRasae Jul 14, 2025
b8a77f9
Poll Api routes
TabulaRasae Jul 14, 2025
bbb3860
Merge pull request #5 from TabulaRasae/pollRoutes
AlexNurci Jul 14, 2025
c4c446d
Defined the API routes for the poll options
TabulaRasae Jul 15, 2025
ad8eec3
Merge pull request #9 from TabulaRasae/pollOptions
Frank-cntrl Jul 15, 2025
5f5f7b4
added the eager loading to the polls api routes
TabulaRasae Jul 15, 2025
19748b0
Created seed file
Frank-cntrl Jul 15, 2025
0c697c1
Merge pull request #12 from TabulaRasae/frank
TabulaRasae Jul 15, 2025
87baf5c
added the eager loading to the users api routes
TabulaRasae Jul 15, 2025
1963f65
Added a delete method to Users API
TabulaRasae Jul 15, 2025
387df17
Merge pull request #13 from TabulaRasae/EagerLoading
Frank-cntrl Jul 15, 2025
0b6a108
Added the the image member for user and poll entity
TabulaRasae Jul 16, 2025
31d2f7a
Added the ballot eager loading to User api routes
TabulaRasae Jul 16, 2025
0ec6b13
Added the eager loading to the poll api routes
TabulaRasae Jul 16, 2025
8f4aac3
get API route for All ballots
TabulaRasae Jul 16, 2025
c5063b3
API routes for ballots
TabulaRasae Jul 16, 2025
a162c42
Revorked the post method to store the votes
TabulaRasae Jul 16, 2025
3afe099
api routes hotfix
TabulaRasae Jul 16, 2025
ad8d42d
Post method for the ballot api
TabulaRasae Jul 16, 2025
16199ae
Merge pull request #15 from TabulaRasae/imageUrl
AlexNurci Jul 16, 2025
f88994b
pathc route for polls
Frank-cntrl Jul 16, 2025
588b4b1
Merge pull request #19 from TabulaRasae/frank
AlexNurci Jul 16, 2025
1106af3
Updated patch route
Frank-cntrl Jul 16, 2025
011c707
Merge pull request #20 from TabulaRasae/frank
AlexNurci Jul 16, 2025
1036ffb
Added result related models
TabulaRasae Jul 17, 2025
9d4399f
Schema
TabulaRasae Jul 17, 2025
35b893c
added poll option editing
Frank-cntrl Jul 17, 2025
523b601
Merge pull request #21 from TabulaRasae/resultsSchema
AlexNurci Jul 17, 2025
843e892
fix patch route
Frank-cntrl Jul 17, 2025
14a83f2
Added result related models
TabulaRasae Jul 17, 2025
050f80a
Schema
TabulaRasae Jul 17, 2025
0d12d43
Merge pull request #22 from TabulaRasae/frank
AlexNurci Jul 17, 2025
e134093
Fixed patch route for poll and poll options
Frank-cntrl Jul 17, 2025
13b7e75
Merge pull request #23 from TabulaRasae/frank
AlexNurci Jul 17, 2025
8d3a78c
Made a patch route for users
Frank-cntrl Jul 17, 2025
a689268
Merge pull request #24 from TabulaRasae/frank
AlexNurci Jul 17, 2025
f6b9a68
added the graphic art (very important)
TabulaRasae Jul 17, 2025
4955e68
Merge pull request #25 from TabulaRasae/resultsSchema
Frank-cntrl Jul 17, 2025
fc7723c
added the ballot rankings to the get polls api
TabulaRasae Jul 17, 2025
9d8dfc6
Merge pull request #26 from TabulaRasae/resultsSchema
Frank-cntrl Jul 17, 2025
8487b5e
fixed get route for auth/me
Frank-cntrl Jul 17, 2025
f7ddf26
Merge pull request #27 from TabulaRasae/frank
TabulaRasae Jul 17, 2025
37cf426
Added advanced eager loading to get all polls api route
TabulaRasae Jul 17, 2025
ab78df3
Fixed polls post route
Frank-cntrl Jul 18, 2025
fb5cf79
Merge pull request #28 from TabulaRasae/frank
baryaakov555 Jul 18, 2025
d85beb6
Changed get route for auth/me to return 404 if no user is found inste…
Frank-cntrl Jul 18, 2025
3f8a78b
Merge pull request #29 from TabulaRasae/frank
AlexNurci Jul 18, 2025
b22a65f
Fixed delete route
Frank-cntrl Jul 21, 2025
f9f6978
Merge pull request #30 from TabulaRasae/frank
AlexNurci Jul 21, 2025
8111aca
Better error handling
Frank-cntrl Jul 21, 2025
4676058
Merge pull request #31 from TabulaRasae/frank
AlexNurci Jul 21, 2025
a4b8f51
added the role member to the User entity
TabulaRasae Jul 21, 2025
6b7baa9
added the role to the user token
TabulaRasae Jul 21, 2025
c48696a
added the requireAdmin middleware
TabulaRasae Jul 21, 2025
50f59ce
added the role return to the /me
TabulaRasae Jul 21, 2025
83f49a6
added the admin router
TabulaRasae Jul 21, 2025
fa6583e
resolved the conflicts
TabulaRasae Jul 21, 2025
9aada90
added the comment
TabulaRasae Jul 21, 2025
d8f02c0
fixed a typo
TabulaRasae Jul 21, 2025
90bf94f
Merge pull request #33 from TabulaRasae/adminUsers
Frank-cntrl Jul 21, 2025
9f5b814
cors enabled for localhost
TabulaRasae Jul 21, 2025
8a3a678
Explicitly allowed cross-site cookie sharing
TabulaRasae Jul 21, 2025
e3710a5
hotfix navbar admin visibility
TabulaRasae Jul 22, 2025
2abab68
admin can close any poll
TabulaRasae Jul 22, 2025
3ef7fd2
added the member to user's schema
TabulaRasae Jul 22, 2025
c6d725f
admin route to disable the user's account
TabulaRasae Jul 22, 2025
7fd02ce
Friends and following backedn
Frank-cntrl Jul 22, 2025
0bacb90
Fix minor bug
Frank-cntrl Jul 22, 2025
d45c7e8
fix up
Frank-cntrl Jul 22, 2025
0648f03
commiting merge
TabulaRasae Jul 22, 2025
4588050
Merge pull request #34 from TabulaRasae/frank
TabulaRasae Jul 22, 2025
1b02145
Merge branch 'main' into adminUsers
TabulaRasae Jul 22, 2025
87d2e9a
Merge pull request #36 from TabulaRasae/adminUsers
AlexNurci Jul 22, 2025
2de0fab
allowed admins to disable the accounts of the regular users
TabulaRasae Jul 23, 2025
97ba864
Merge pull request #37 from TabulaRasae/adminUsers
Frank-cntrl Jul 23, 2025
ebe13c5
Creation of data tables for permission to vote and view, also eager l…
Frank-cntrl Jul 23, 2025
720485d
Merge branch 'main' into frank
Frank-cntrl Jul 23, 2025
d989f17
rebase
Frank-cntrl Jul 23, 2025
29973fb
Merge pull request #38 from TabulaRasae/frank
TabulaRasae Jul 23, 2025
fcbe5c5
Hot fix
Frank-cntrl Jul 25, 2025
5edfb23
crossSite cookie sharing
TabulaRasae Jul 24, 2025
33dfc2a
Hot fix
Frank-cntrl Jul 28, 2025
8f3b683
/
Frank-cntrl Jul 28, 2025
415a41f
hotfix
Frank-cntrl Jul 28, 2025
c04240e
admin
Frank-cntrl Jul 28, 2025
b2e75dd
Hot fix
Frank-cntrl Jul 28, 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
24 changes: 1 addition & 23 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,23 +1 @@
# Capstone I 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`

After that, you can get started with these commands

```
npm install # 📦 To install the packages
npm run seed # 🌱 To seed the database
npm run start-dev # 🚀 To start the server in development mode
```

This project runs in the Node.js runtime environment. We're not using Webpack here, but we are using a tool called nodemon, which re-runs our app whenever we save a file. You should see a message in the terminal telling you that the server is running on port 8080.

When an error occurs on the backend (Express), you'll see a message in the terminal. When an error occurs on the frontend (React), you'll see that error in the browser.

## Deployment

This project has a vercel.json file, which will make it easier to deploy this project to Vercel. Check the video listed in the cohort repository for a walkthrough of how to connect your deployed Express server to Neon Postgres.
# FriendPollster Backend
184 changes: 184 additions & 0 deletions api/admins.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,184 @@
const express = require('express');
const { authenticateJWT, requireAdmin } = require('../auth');
const router = express.Router();
const { User, Poll, Ballot, PollOption } = require("../database");

router.get(
'/users',
authenticateJWT,
requireAdmin,
async (req, res) => {
try {
const users = await User.findAll({ include: [Poll, Ballot] });
res.status(200).send(users);
} catch (error) {
console.error("Error fetching users: ", error);
res.status(500).send("Error fetching users");
}
}
);

router.get(
'/polls',
authenticateJWT,
requireAdmin,
async (req, res) => {
try {
const polls = await Poll.findAll({
include: [
{
model: PollOption,
as: "PollOptions"
},
{
model: User,
as: "creator",
attributes: ["id", "username", "imageUrl"]
},
{
model: Ballot,
required: false
}
],
order: [["createdAt", "DESC"]]
});
res.status(200).json(polls);
} catch (error) {
console.error("Error fetching polls for admin:", error);
res.status(500).json({ error: "Failed to fetch polls" });
}
}
);

router.patch(
"/polls/:id/disable",
authenticateJWT,
requireAdmin,
async (req, res) => {
try {
const poll = await Poll.findByPk(req.params.id, {
include: [
{
model: User,
as: "creator",
attributes: ["id", "username"]
}
]
});

if (!poll) {
return res.status(404).json({ error: "Poll not found" });
}

poll.isActive = !poll.isActive;

if (!poll.isActive && !poll.endAt) {
poll.endAt = new Date();
}

await poll.save();

res.json({
id: poll.id,
isActive: poll.isActive,
title: poll.title,
creator: poll.creator?.username,
message: `Poll ${poll.isActive ? 'enabled' : 'disabled'} successfully`
});
} catch (err) {
console.error("Error toggling poll status:", err);
res.status(500).json({ error: "Failed to update poll status" });
}
}
);

router.patch(
"/polls/:id/close",
authenticateJWT,
requireAdmin,
async (req, res) => {
try {
const poll = await Poll.findByPk(req.params.id, {
include: [PollOption, Ballot],
});
if (!poll) return res.status(404).json({ error: "Poll not found" });
if (poll.status === "closed")
return res.status(400).json({ error: "Poll already closed" });

poll.status = "closed";
poll.isActive = false;
if (!poll.endAt) poll.endAt = new Date();
await poll.save();

res.json(poll);
} catch (err) {
console.error("Error closing poll:", err);
res.status(500).json({ error: "Failed to close poll" });
}
}
);

router.delete(
"/polls/:id",
authenticateJWT,
requireAdmin,
async (req, res) => {
try {
const poll = await Poll.findByPk(req.params.id, {
include: [
{
model: User,
as: "creator",
attributes: ["id", "username"]
}
]
});

if (!poll) {
return res.status(404).json({ error: "Poll not found" });
}

const pollInfo = {
id: poll.id,
title: poll.title,
creator: poll.creator?.username
};

await poll.destroy();

res.json({
message: "Poll deleted successfully",
deletedPoll: pollInfo
});
} catch (err) {
console.error("Error deleting poll:", err);
res.status(500).json({ error: "Failed to delete poll" });
}
}
);

router.patch(
"/users/:id/disable",
authenticateJWT,
requireAdmin,
async (req, res) => {
try {
const target = await User.findByPk(req.params.id);
if (!target) return res.status(404).json({ error: "User not found" });

if (target.role === "admin") {
return res.status(403).json({ error: "Cannot disable an admin account" });
}

target.disabled = !target.disabled;
await target.save();

res.json({ id: target.id, disabled: target.disabled });
} catch (err) {
console.error("Error disabling user:", err);
res.status(500).json({ error: "Failed to modify user" });
}
}
);

module.exports = router;
168 changes: 168 additions & 0 deletions api/ballots.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,168 @@
const express = require("express");
const router = express.Router();
const {
Poll,
User,
PollOption,
Ballot,
BallotRanking,
db,
} = require("../database");
const { requireAuth } = require("../auth");

// _______
// | |
// | ✔ |
// |_______|
// ||
// \/
// ______________________
// | |
// | _____ |
// | | | |
// | | VOTE| |
// | |_____| |
// | |
// |______________________|
// \__________________/
// || ||
// || ||

router.get("/", async (req, res) => {
try {
const ballots = await Ballot.findAll({
include: [{ model: BallotRanking }],
});
console.log(`Found ${ballots.length} ballots`);
res.status(200).json(ballots);
} catch (error) {
console.error("Error fetching ballots:", error);
res.status(500).json({
error: "Failed to fetch ballots",
message: "Check your database connection",
});
}
});

router.get("/:id", async (req, res) => {
try {
const ballot = await Ballot.findByPk(req.params.id, {
include: [{ model: BallotRanking }],
});
if (!ballot) {
return res.status(404).json({ error: "Ballot not found" });
}
res.status(200).json(ballot);
} catch (error) {
console.error(`Error fetching ballot`, error);
res.status(500).json({
error: "Failed to fetch ballot",
message: "Check your database connection",
});
}
});

router.post("/", async (req, res) => {
const { userId = null, pollId, rankings } = req.body;

if (!pollId) return res.status(400).json({ error: "pollId is required" });

if (!Array.isArray(rankings) || rankings.length === 0)
return res
.status(400)
.json({ error: "rankings must be a non-empty array" });

for (const r of rankings) {
if (typeof r !== "object" || r.rank == null || r.pollOptionId == null) {
return res.status(400).json({
error: "Each ranking must be an object { pollOptionId, rank }",
});
}
}

let tx;
try {
tx = await db.transaction();

const poll = await Poll.findByPk(pollId);
if (!poll) {
await tx.rollback();
return res.status(404).json({ error: "Poll not found" });
}

if (!poll.isActive) {
await tx.rollback();
return res.status(403).json({
error: "This poll has been disabled by an administrator"
});
}

if (poll.endAt && new Date() > new Date(poll.endAt)) {
await tx.rollback();
return res.status(400).json({ error: "Poll has ended" });
}

if (poll.status !== "published") {
await tx.rollback();
return res.status(400).json({ error: "Cannot vote on unpublished polls" });
}

if (!poll.allowAnonymous && !userId) {
await tx.rollback();
return res.status(401).json({
error: "Please log in to vote on this poll"
});
}

if (userId && !poll.allowAnonymous) {
const existingBallot = await Ballot.findOne({
where: {
user_id: userId,
poll_id: pollId
}
});

if (existingBallot) {
await tx.rollback();
return res.status(400).json({
error: "You have already voted on this poll"
});
}
}

const ballot = await Ballot.create(
{
user_id: userId,
poll_id: pollId,
},
{ transaction: tx }
);

const rankingRows = rankings.map(({ pollOptionId, rank }) => ({
ballot_id: ballot.id,
option_id: pollOptionId,
rank,
}));
await BallotRanking.bulkCreate(rankingRows, { transaction: tx });

await tx.commit();

const result = await Ballot.findByPk(ballot.id, {
include: [{ model: BallotRanking, order: [["rank", "ASC"]] }],
});

return res.status(201).json({
message: "Vote recorded successfully",
ballot: result
});
} catch (err) {
if (tx) await tx.rollback();
console.error("Failed to create ballot:", err);
return res.status(500).json({
error: "Failed to submit ballot",
message: err.message,
});
}
});

module.exports = router;
Loading