Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
106 commits
Select commit Hold shift + click to select a range
b9a969a
chore: added model folder for better stucture
tran2106 Jul 12, 2025
49d5bfb
feat: user model addeed adn export to db
tran2106 Jul 13, 2025
82e325b
feat: added auth0Id to support 2 methods of login/Hailia's feedback
tran2106 Jul 13, 2025
8711ff0
fix: changed passwordHash allowNull to true
tran2106 Jul 13, 2025
c84765a
Merge pull request #13 from TPP-Team1/models-user
haicomet Jul 13, 2025
ca01929
feat-define Poll model
Rend1027 Jul 14, 2025
22b77d4
feat-defined Vote model
Rend1027 Jul 14, 2025
4d92ca7
fix-fixed model attributes
Rend1027 Jul 14, 2025
25a36ac
chore-exported poll model from index
Rend1027 Jul 14, 2025
7e534ad
feat-Poll seed data
Rend1027 Jul 14, 2025
8257073
refactor-added console.log
Rend1027 Jul 14, 2025
e568559
fix-User model import
Rend1027 Jul 14, 2025
da88cc7
refactor-cleaned poll data
Rend1027 Jul 14, 2025
32b177c
Merge pull request #14 from TPP-Team1/poll_model
Rend1027 Jul 14, 2025
3972969
feat- defined pollOption Model
Rend1027 Jul 14, 2025
9b44ade
feat- created poll_option data
Rend1027 Jul 14, 2025
432728a
refactor- modified poll data to be more dinamic
Rend1027 Jul 14, 2025
15b7bf8
feat-created poll_option data
Rend1027 Jul 14, 2025
9f55ed8
refactor- made poll_option data more dinamic
Rend1027 Jul 14, 2025
dfac98e
Merge pull request #22 from TPP-Team1/poll_option
Rend1027 Jul 14, 2025
e94a08c
optimized user_model
Rend1027 Jul 14, 2025
2e228de
Merge pull request #24 from TPP-Team1/update_user_model
Rend1027 Jul 14, 2025
9b3c5a9
feat- associations
Rend1027 Jul 15, 2025
e1bcb9c
refactor- change DB name
Rend1027 Jul 15, 2025
2a3b0e8
fixed-bugs with model attributes
Rend1027 Jul 15, 2025
acdfc5c
Merge pull request #25 from TPP-Team1/poll_model
Rend1027 Jul 15, 2025
697ceaa
fixed-Poll FK
Rend1027 Jul 15, 2025
0e4db56
Merge pull request #26 from TPP-Team1/poll_model
Rend1027 Jul 15, 2025
adfb645
feat-votingRank model
Rend1027 Jul 15, 2025
a1ba0b4
defined VotingRank model
Rend1027 Jul 15, 2025
7c4cc31
feat- association
Rend1027 Jul 15, 2025
85544fb
feat- created Vote data
Rend1027 Jul 15, 2025
f252e90
feat- created voteRank data
Rend1027 Jul 15, 2025
bdc266c
fixed- bug in rank data
Rend1027 Jul 15, 2025
fd521c7
add username-based signup endpoint with validation
tran2106 Jul 15, 2025
eca4f0d
feat- added attributes to VotingRank
Rend1027 Jul 15, 2025
b5b23bd
feat- added attr to Vote model
Rend1027 Jul 15, 2025
4e9d505
fixed- association bugs
Rend1027 Jul 15, 2025
ced402c
create- restricted_poll_access model
Rend1027 Jul 15, 2025
c0cbdc9
: feat: email-based sign up and added validations
tran2106 Jul 15, 2025
76f36fa
feat- defined restricted_poll_access model
Rend1027 Jul 15, 2025
0d38a23
chore: credentials and callback url added to .env
haicomet Jul 15, 2025
d6dcc18
feat- defined associations for restricted
Rend1027 Jul 15, 2025
3211772
Merge pull request #33 from TPP-Team1/restricted_poll
Rend1027 Jul 15, 2025
810c2f0
feat(api)-poll router
Rend1027 Jul 15, 2025
6d35049
Merge pull request #34 from TPP-Team1/api-create_poll
Rend1027 Jul 15, 2025
c5d2a02
fix(auth): update signup/login routes for flexible input and model co…
tran2106 Jul 16, 2025
9f32d93
chore: routes/auth created
haicomet Jul 16, 2025
d70e864
feat: defined google strategy
haicomet Jul 16, 2025
02e2f89
feat: GET /auth/google route added
haicomet Jul 16, 2025
86b5520
feat: handle callback route + Issue JWT token and redirect to frontend
haicomet Jul 16, 2025
69ec872
chore: google router mounted
haicomet Jul 16, 2025
26b88a1
Merge pull request #36 from TPP-Team1/config-oauth
tran2106 Jul 16, 2025
1a3feea
refactor: added require jwt + changed const user to let + err->error/…
haicomet Jul 16, 2025
7c6082c
Merge pull request #37 from TPP-Team1/routes-auth
tran2106 Jul 16, 2025
b652f8e
feat-votingRank model
Rend1027 Jul 15, 2025
ba74712
defined VotingRank model
Rend1027 Jul 15, 2025
f25f397
feat- association
Rend1027 Jul 15, 2025
3dd6e84
feat- created Vote data
Rend1027 Jul 15, 2025
a94702b
feat- created voteRank data
Rend1027 Jul 15, 2025
0e7cf4a
fixed- bug in rank data
Rend1027 Jul 15, 2025
ea5cab2
feat- added attributes to VotingRank
Rend1027 Jul 15, 2025
78c546a
feat- added attr to Vote model
Rend1027 Jul 15, 2025
53c26fc
fixed- association bugs
Rend1027 Jul 15, 2025
eb49d4a
create- restricted_poll_access model
Rend1027 Jul 15, 2025
76f7c13
feat- defined restricted_poll_access model
Rend1027 Jul 15, 2025
76a106c
feat- defined associations for restricted
Rend1027 Jul 15, 2025
798b846
feat(api)-poll router
Rend1027 Jul 15, 2025
44fc16f
chore: credentials and callback url added to .env
haicomet Jul 15, 2025
5427bc0
chore: routes/auth created
haicomet Jul 16, 2025
7f02cfb
feat: defined google strategy
haicomet Jul 16, 2025
09602d7
feat: GET /auth/google route added
haicomet Jul 16, 2025
51dffcb
feat: handle callback route + Issue JWT token and redirect to frontend
haicomet Jul 16, 2025
b891697
chore: google router mounted
haicomet Jul 16, 2025
7a7fda2
refactor: added require jwt + changed const user to let + err->error/…
haicomet Jul 16, 2025
64707ac
draft poll creation
Rend1027 Jul 16, 2025
57c8f65
fixed- poll post request
Rend1027 Jul 16, 2025
6742da4
feat- added JWT to poll post request
Rend1027 Jul 16, 2025
13b3cd3
Merge branch 'main' into api-create_poll
Rend1027 Jul 16, 2025
798f41e
fix: resolve Google OAuth startup errors and field name inconsistencies
tran2106 Jul 16, 2025
f8a8c9e
Merge branch 'main' into routes/auth
tran2106 Jul 16, 2025
44ca507
: fix: trying to reosolve conflict
tran2106 Jul 16, 2025
0697da3
fixed- user attribute bugs
Rend1027 Jul 16, 2025
353529c
Merge pull request #39 from TPP-Team1/api-create_poll
Rend1027 Jul 16, 2025
5f93073
fix: standardize auth field names to use 'username' according to new …
tran2106 Jul 16, 2025
f3504a6
fix: update user model to username from userName
tran2106 Jul 16, 2025
7269c10
fix: resolved merge conflict ready to merge
tran2106 Jul 16, 2025
91f0f3d
: fix: log-in/sign-up routes to match username field
tran2106 Jul 16, 2025
3b592b3
Merge pull request #35 from TPP-Team1/routes/sign-up-auth
tran2106 Jul 16, 2025
ecfe8b1
fix: resolve Google OAuth startup errors and field name inconsistencies
tran2106 Jul 16, 2025
4c234e3
fix: resoleved merge con flict
tran2106 Jul 16, 2025
4f7c95d
: fix: resoleved merge conflicts and added .env config
tran2106 Jul 16, 2025
323f7be
refactor- update to deadline attr
Rend1027 Jul 16, 2025
d622b56
fix: added back sign up routes that was lost during merge conflict re…
tran2106 Jul 16, 2025
bff247c
fix: fix route to check both username and emails
tran2106 Jul 16, 2025
cb9df17
Merge pull request #38 from TPP-Team1/routes/auth
tran2106 Jul 16, 2025
49e4bb9
Merge branch 'main' into api-create_poll
Rend1027 Jul 16, 2025
ef4ec87
feat: delete draft poll route made
haicomet Jul 16, 2025
c737a5f
feat: delete is enabled only for poll owner
haicomet Jul 16, 2025
64db6ad
feat: delete is enabled only for drafts
haicomet Jul 16, 2025
4b96e10
feat: deletion confirmation
haicomet Jul 17, 2025
ac76625
Merge pull request #40 from TPP-Team1/delete-draft
tran2106 Jul 17, 2025
8375162
feat- create poll with options
Rend1027 Jul 17, 2025
de2e1ac
feat- added validation
Rend1027 Jul 17, 2025
4c87050
Merge branch 'main' into api-create_poll
Rend1027 Jul 17, 2025
7abe57d
refactor- cleaned up code
Rend1027 Jul 17, 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
2 changes: 2 additions & 0 deletions api/index.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
const express = require("express");
const router = express.Router();
const pollRouter = require("./polls");
const testDbRouter = require("./test-db");

router.use("/test-db", testDbRouter);
router.use("/polls", pollRouter)

module.exports = router;
Empty file added api/poll.js
Empty file.
73 changes: 73 additions & 0 deletions api/polls.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
const express = require("express");
const router = express.Router();
const { Poll, PollOption } = require("../database");
const { authenticateJWT } = require("../auth")


// Create polls
router.post("/", authenticateJWT, async (req, res) => {
const userId = req.user.id
const { title, description, deadline, status, options = [] } = req.body;

if (status === "published" && options.length < 2) {
res.status(400).json({
error: " 2 options are requires to publish a poll"
})
};

try {
const newPoll = await Poll.create({
title,
description,
deadline,
status,
userId,
});
//[opttion1, option2, option3]
if (options.length > 0) {
const formattedOptions = options.map(text => ({
optionText: text,
pollId: newPoll.id
}));

await PollOption.bulkCreate(formattedOptions)
res.status(201).json({
message: "Poll and options created",
poll: newPoll
});
}
} catch (error) {
res.status(500).json({
error: "Failed to create poll",
message: "Check that API fields and data are correct"
})
}
});



//delete draft poll
router.delete("/:id", authenticateJWT, async (req, res) => {
try {
const pollId = req.params.id;
const userId = req.user.id;

const poll = await Poll.findByPk(pollId);

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

if (poll.userId !== userId) { res.status(401).json({ error: "Unauthorized action: You do not own this poll" }) };

if (poll.status !== "draft") { res.status(401).json({ error: "Unauthorized action: Only draft polls can be deleted" }) };

await poll.destroy();

res.json({ message: "Draft poll deleted successfully" });

}
catch (error) {
res.status(500).json({ error: "Failed to delete draft poll" });
}
});

module.exports = router;
15 changes: 15 additions & 0 deletions app.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,14 @@ const morgan = require("morgan");
const path = require("path");
const jwt = require("jsonwebtoken");
const cookieParser = require("cookie-parser");
const passport = require("passport");
const session = require("express-session");
const app = express();
const apiRouter = require("./api");
const { router: authRouter } = require("./auth");
const { db } = require("./database");
const cors = require("cors");
const oAuthRouter = require("./auth/routes")

const PORT = process.env.PORT || 8080;
const FRONTEND_URL = process.env.FRONTEND_URL || "http://localhost:3000";
Expand All @@ -26,10 +29,22 @@ app.use(
// cookie parser middleware
app.use(cookieParser());

// Session middleware for passport
app.use(session({
secret: process.env.JWT_SECRET || 'your-secret-key',
resave: false,
saveUninitialized: false
}));

// Initialize passport
app.use(passport.initialize());
app.use(passport.session());

app.use(morgan("dev")); // logging middleware
app.use(express.static(path.join(__dirname, "public"))); // serve static files from public folder
app.use("/api", apiRouter); // mount api router
app.use("/auth", authRouter); // mount auth router
app.use("/auth", oAuthRouter); //mount oAuth router

// error handling middleware
app.use((err, req, res, next) => {
Expand Down
182 changes: 179 additions & 3 deletions auth/index.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
const express = require("express");
const jwt = require("jsonwebtoken");
const { User } = require("../database");
const { Op } = require("sequelize");

const router = express.Router();

Expand Down Expand Up @@ -167,9 +168,15 @@ router.post("/login", async (req, res) => {
return;
}

// Find user
const user = await User.findOne({ where: { username } });
user.checkPassword(password);
// Find user by username or email
const user = await User.findOne({
where: {
[Op.or]: [
{ username: username },
{ email: username }
]
}
});
if (!user) {
return res.status(401).send({ error: "Invalid credentials" });
}
Expand Down Expand Up @@ -230,4 +237,173 @@ router.get("/me", (req, res) => {
});
});

// Helper function to check if input looks like email
const looksLikeEmail = (input) => {
return input.includes("@");
};

// Signup with username and password
router.post("/signup/username", async (req, res) => {
try {
const { username, password, firstName, lastName } = req.body;

if (!username || !password) {
return res
.status(400)
.send({ error: "Username and password are required" });
}

if (password.length < 6) {
return res
.status(400)
.send({ error: "Password must be at least 6 characters long" });
}

// Check if input looks like email
if (looksLikeEmail(username)) {
return res
.status(400)
.send({
error:
"Username cannot be an email address. Please use a regular username.",
});
}

// Check if username already exists (check both username and email fields to prevent duplicates)
const existingUser = await User.findOne({
where: {
[Op.or]: [{ username }, { email: username }],
},
});

if (existingUser) {
return res.status(409).send({ error: "Username already exists" });
}

// Create new user
const passwordHash = User.hashPassword(password);
const userData = {
username,
passwordHash,
firstName: firstName || null,
lastName: lastName || null,
};

const user = await User.create(userData);

// Generate JWT token
const token = jwt.sign(
{
id: user.id,
username: user.username,
email: user.email,
},
JWT_SECRET,
{ expiresIn: "24h" }
);

res.cookie("token", token, {
httpOnly: true,
secure: process.env.NODE_ENV === "production",
sameSite: "strict",
maxAge: 24 * 60 * 60 * 1000, // 24 hours
});

res.send({
message: "User created successfully with username",
user: {
id: user.id,
username: user.username,
firstName: user.firstName,
lastName: user.lastName,
},
});
} catch (error) {
console.error("Username signup error:", error);
res.status(500).send({ error: "Internal server error" });
}
});

// Helper function to validate email format
const isValidEmail = (email) => {
const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
return emailRegex.test(email);
};

// Signup with email and password
router.post("/signup/email", async (req, res) => {
try {
const { email, password, firstName, lastName } = req.body;

if (!email || !password) {
return res.status(400).send({ error: "Email and password are required" });
}

if (password.length < 6) {
return res.status(400).send({ error: "Password must be at least 6 characters long" });
}

// Validate email format
if (!isValidEmail(email)) {
return res.status(400).send({ error: "Please provide a valid email address" });
}

// Check if email already exists (check both email and username fields to prevent duplicates)
const existingUser = await User.findOne({
where: {
[Op.or]: [
{ email: email },
{ username: email }
]
}
});

if (existingUser) {
return res.status(409).send({ error: "Email already exists" });
}

// Create new user
const passwordHash = User.hashPassword(password);
const userData = {
email: email,
passwordHash,
firstName: firstName || null,
lastName: lastName || null
};

const user = await User.create(userData);

// Generate JWT token
const token = jwt.sign(
{
id: user.id,
username: user.username,
email: user.email,
},
JWT_SECRET,
{ expiresIn: "24h" }
);

res.cookie("token", token, {
httpOnly: true,
secure: process.env.NODE_ENV === "production",
sameSite: "strict",
maxAge: 24 * 60 * 60 * 1000, // 24 hours
});

res.send({
message: "User created successfully with email",
user: {
id: user.id,
email: user.email,
firstName: user.firstName,
lastName: user.lastName,
},
});
} catch (error) {
console.error("Email signup error:", error);
res.status(500).send({ error: "Internal server error" });
}
});

module.exports = { router, authenticateJWT };
Loading