Added vote route in backend
This commit is contained in:
@@ -1,5 +1,6 @@
|
|||||||
import {
|
import {
|
||||||
createPollService,
|
createPollService,
|
||||||
|
createVoteService,
|
||||||
deletePollService,
|
deletePollService,
|
||||||
getAllCreatedPollsService,
|
getAllCreatedPollsService,
|
||||||
getPollDataService,
|
getPollDataService,
|
||||||
@@ -105,3 +106,34 @@ export async function deletePollController(req, res) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
export const createVoteController = async (req, res) => {
|
||||||
|
try {
|
||||||
|
const reqPollId = req.body.pollId;
|
||||||
|
const reqOptionId = req.body.optionId;
|
||||||
|
const reqUserId = req.user._id;
|
||||||
|
|
||||||
|
const vote = await createVoteService(reqPollId, reqUserId, reqOptionId);
|
||||||
|
|
||||||
|
res.json({
|
||||||
|
success : true,
|
||||||
|
message : "Vote created successfully.",
|
||||||
|
data : vote
|
||||||
|
})
|
||||||
|
}
|
||||||
|
catch (err) {
|
||||||
|
console.log(err);
|
||||||
|
if (err.statusCode) {
|
||||||
|
res.status(err.statusCode).json({
|
||||||
|
success: false,
|
||||||
|
message: err.message,
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
res.status(500).json({
|
||||||
|
success: false,
|
||||||
|
message: err.message,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
23
backend/src/models/vote.model.js
Normal file
23
backend/src/models/vote.model.js
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
import mongoose, { Schema } from "mongoose";
|
||||||
|
|
||||||
|
const voteSchema = new Schema({
|
||||||
|
pollId : {
|
||||||
|
type : Schema.Types.ObjectId,
|
||||||
|
ref : "Poll",
|
||||||
|
required : true
|
||||||
|
},
|
||||||
|
userId : {
|
||||||
|
type : Schema.Types.ObjectId,
|
||||||
|
ref : "User",
|
||||||
|
required : true
|
||||||
|
},
|
||||||
|
optionId : {
|
||||||
|
type : Schema.Types.ObjectId,
|
||||||
|
ref : "Option",
|
||||||
|
required : true
|
||||||
|
},
|
||||||
|
}, {timestamps : true});
|
||||||
|
|
||||||
|
|
||||||
|
const VoteModel = mongoose.model("Vote", voteSchema);
|
||||||
|
export default VoteModel;
|
||||||
@@ -1,4 +1,6 @@
|
|||||||
import PollModel from "../models/poll.model.js";
|
import PollModel from "../models/poll.model.js";
|
||||||
|
import mongoose from "mongoose";
|
||||||
|
const {ObjectId} = mongoose.Types;
|
||||||
|
|
||||||
export async function createPollByData(data) {
|
export async function createPollByData(data) {
|
||||||
try {
|
try {
|
||||||
@@ -38,4 +40,21 @@ export async function deletePollById(id) {
|
|||||||
catch(err){
|
catch(err){
|
||||||
throw err;
|
throw err;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function updatePollVoteCount(pollId, optionId) {
|
||||||
|
try {
|
||||||
|
const pollIdObejct = new ObjectId(pollId);
|
||||||
|
const optionIdObject = new ObjectId(optionId);
|
||||||
|
console.log(pollId, optionId, pollIdObejct, optionIdObject);
|
||||||
|
const result = await PollModel.updateOne(
|
||||||
|
{ _id: pollIdObejct, "options._id": optionIdObject },
|
||||||
|
{ $inc: { "options.$.voteCount": 1 } }
|
||||||
|
);
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
catch(err){
|
||||||
|
throw err;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
27
backend/src/repositories/vote.repo.js
Normal file
27
backend/src/repositories/vote.repo.js
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
import VoteModel from "../models/vote.model.js";
|
||||||
|
|
||||||
|
export async function findVoteByPollIdAndUserId(pollId, userId) {
|
||||||
|
try {
|
||||||
|
const vote = VoteModel.findOne({pollId : pollId, userId : userId});
|
||||||
|
return vote;
|
||||||
|
}
|
||||||
|
catch(err){
|
||||||
|
throw err;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
export async function createVote(pollId, userId, optionId) {
|
||||||
|
try{
|
||||||
|
const vote = VoteModel.create({
|
||||||
|
pollId,
|
||||||
|
userId,
|
||||||
|
optionId
|
||||||
|
});
|
||||||
|
|
||||||
|
return vote;
|
||||||
|
}
|
||||||
|
catch(err){
|
||||||
|
throw err;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,8 +1,9 @@
|
|||||||
import express from "express";
|
import express from "express";
|
||||||
import { verifyToken } from "../../middlwares/verifyToken.js";
|
import { verifyToken } from "../../middlwares/verifyToken.js";
|
||||||
import { createPollController, deletePollController, getAllCreatedPollsController, getPollDataController } from "../../controllers/poll.controller.js";
|
import { createPollController, createVoteController, deletePollController, getAllCreatedPollsController, getPollDataController } from "../../controllers/poll.controller.js";
|
||||||
import pollDataSchema from "../../validations/pollDataValidation.js";
|
import pollDataSchema from "../../validations/pollDataValidation.js";
|
||||||
import validator from "../../validations/validator.js";
|
import validator from "../../validations/validator.js";
|
||||||
|
import voteSchema from "../../validations/voteValidation.js";
|
||||||
const pollRouter = express.Router();
|
const pollRouter = express.Router();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -122,4 +123,6 @@ pollRouter.get("/created", verifyToken, getAllCreatedPollsController);
|
|||||||
* */
|
* */
|
||||||
pollRouter.delete("/delete/:pollId", verifyToken, deletePollController);
|
pollRouter.delete("/delete/:pollId", verifyToken, deletePollController);
|
||||||
|
|
||||||
|
pollRouter.post("/vote", validator(voteSchema), verifyToken, createVoteController);
|
||||||
|
|
||||||
export default pollRouter;
|
export default pollRouter;
|
||||||
@@ -1,11 +1,13 @@
|
|||||||
import mongoose from "mongoose";
|
import mongoose from "mongoose";
|
||||||
import { createPollByData, deletePollById, findPollById, findPollsByCreatorId } from "../repositories/poll.repo.js";
|
import { createPollByData, deletePollById, findPollById, findPollsByCreatorId, updatePollVoteCount } from "../repositories/poll.repo.js";
|
||||||
|
import { createVote, findVoteByPollIdAndUserId } from "../repositories/vote.repo.js";
|
||||||
|
|
||||||
export async function createPollService(title, description, options, userId) {
|
export async function createPollService(title, description, options, userId) {
|
||||||
try {
|
try {
|
||||||
const optionsData = options.map(option => ({
|
const optionsData = options.map(option => ({
|
||||||
name: option,
|
name: option,
|
||||||
_id : new mongoose.Types.ObjectId()
|
_id : new mongoose.Types.ObjectId(),
|
||||||
|
voteCount : 0
|
||||||
}));
|
}));
|
||||||
|
|
||||||
const data = {
|
const data = {
|
||||||
@@ -71,6 +73,35 @@ export async function deletePollService(pollId, user) {
|
|||||||
return deletedPoll;
|
return deletedPoll;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
catch(err){
|
||||||
|
throw err;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function createVoteService(pollId, userId, optionId) {
|
||||||
|
try {
|
||||||
|
// vote already voted
|
||||||
|
const vote = await findVoteByPollIdAndUserId(pollId, userId);
|
||||||
|
if (vote) {
|
||||||
|
throw {
|
||||||
|
statusCode: 400,
|
||||||
|
message: "You have already voted on this poll"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
console.log(pollId, userId, optionId);
|
||||||
|
const poll = await findPollById(pollId);
|
||||||
|
if (!poll) {
|
||||||
|
throw {
|
||||||
|
statusCode: 404,
|
||||||
|
message: "Poll not found"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const updatedPollData = await updatePollVoteCount(pollId, optionId);
|
||||||
|
const createdVote = await createVote(pollId, userId, optionId);
|
||||||
|
console.log(updatedPollData)
|
||||||
|
return createVote;
|
||||||
|
|
||||||
|
}
|
||||||
catch(err){
|
catch(err){
|
||||||
throw err;
|
throw err;
|
||||||
}
|
}
|
||||||
|
|||||||
14
backend/src/validations/voteValidation.js
Normal file
14
backend/src/validations/voteValidation.js
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
import { z } from "zod";
|
||||||
|
|
||||||
|
const voteSchema = z.object({
|
||||||
|
pollId: z.string({
|
||||||
|
required_error: "Poll Id is required.",
|
||||||
|
invalid_type_error: "Poll Id should be a string.",
|
||||||
|
}),
|
||||||
|
optionId: z.string({
|
||||||
|
required_error: "Option Id is required.",
|
||||||
|
invalid_type_error: "Option Id should be a string.",
|
||||||
|
}),
|
||||||
|
});
|
||||||
|
|
||||||
|
export default voteSchema;
|
||||||
@@ -5,6 +5,7 @@ import { Chart as ChartJS, BarElement, CategoryScale, LinearScale } from "chart.
|
|||||||
import { useQuery } from "react-query";
|
import { useQuery } from "react-query";
|
||||||
import { useParams } from "react-router-dom";
|
import { useParams } from "react-router-dom";
|
||||||
import getPollData from "../services/getPollData";
|
import getPollData from "../services/getPollData";
|
||||||
|
import ErrorFallback from "../components/Errors/ErrorFallback";
|
||||||
|
|
||||||
ChartJS.register(BarElement, CategoryScale, LinearScale);
|
ChartJS.register(BarElement, CategoryScale, LinearScale);
|
||||||
|
|
||||||
@@ -12,62 +13,57 @@ function VotingPage() {
|
|||||||
|
|
||||||
const { pollId } = useParams();
|
const { pollId } = useParams();
|
||||||
|
|
||||||
const { data: poll, isLoading, isError } = useQuery(["poll", pollId], () => getPollData(pollId), {
|
const { data: poll, isLoading, isError, refetch } = useQuery(["poll", pollId], () => getPollData(pollId), {
|
||||||
cacheTime : 10*100*60, // 10 minutes
|
cacheTime : 10*100*60, // 10 minutes
|
||||||
staleTime : 20*100*60, // 20 minutes
|
staleTime : 20*100*60, // 20 minutes
|
||||||
});
|
});
|
||||||
console.log(poll);
|
|
||||||
|
|
||||||
const pollData = {
|
|
||||||
title: "What's your favorite programming language?",
|
|
||||||
options: [
|
|
||||||
{ id: 1, label: "JavaScript", votes: 20 },
|
|
||||||
{ id: 2, label: "Python", votes: 35 },
|
|
||||||
{ id: 3, label: "Java", votes: 25 },
|
|
||||||
{ id: 4, label: "C++", votes: 15 },
|
|
||||||
],
|
|
||||||
creator: {
|
|
||||||
name: "John Doe",
|
|
||||||
image: "https://via.placeholder.com/100",
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
// Dummy chart data for visualization
|
// Dummy chart data for visualization
|
||||||
const chartData = {
|
const chartData = {
|
||||||
labels: pollData.options.map(option => option.label),
|
labels: poll?.data?.pollData?.options.map(option => option.name),
|
||||||
datasets: [
|
datasets: [
|
||||||
{
|
{
|
||||||
label: "Votes",
|
label: "Votes",
|
||||||
data: pollData.options.map(option => option.votes),
|
data: poll?.data?.pollData?.options.map(option => option.voteCount),
|
||||||
backgroundColor: ["#3B82F6", "#EF4444", "#10B981", "#F59E0B"],
|
backgroundColor: ["#3B82F6", "#EF4444", "#10B981", "#F59E0B"],
|
||||||
borderWidth: 1,
|
borderWidth: 1,
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
};
|
};
|
||||||
|
|
||||||
|
if (isLoading) {
|
||||||
|
return <div className="skeleton h-64 w-full max-w-lg mt-12 mx-auto"></div>;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isError){
|
||||||
|
return <div className="h-64 w-full max-w-lg mt-12 mx-auto">
|
||||||
|
<ErrorFallback onRetry={refetch}/>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="bg-base-200 min-h-screen p-6 text-white flex flex-col items-center">
|
<div className="bg-base-200 min-h-screen p-6 text-white flex flex-col items-center">
|
||||||
{/* Poll Creator Info */}
|
{/* Poll Creator Info */}
|
||||||
<div className="flex items-center justify-center gap-4 mb-6">
|
<div className="flex items-center justify-center gap-4 mb-6">
|
||||||
<img
|
<img
|
||||||
src={pollData.creator.image}
|
src="https://via.placeholder.com/100"
|
||||||
alt={pollData.creator.name}
|
alt={poll?.data?.creatorData?.username}
|
||||||
className="rounded-full h-7 md:h-10 w-7 md:w-10"
|
className="rounded-full h-7 md:h-10 w-7 md:w-10"
|
||||||
/>
|
/>
|
||||||
<h2 className="text-lg md:text-xl font-semibold">{pollData.creator.name}</h2>
|
<h2 className="text-lg md:text-xl font-semibold">{poll?.data?.creatorData?.username || "Unknown"}</h2>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Poll Title */}
|
{/* Poll Title */}
|
||||||
<h1 className="text-xl md:text-3xl font-bold mb-4 text-center">{pollData.title}</h1>
|
<h1 className="text-xl md:text-3xl font-bold mb-4 text-center">{poll?.data?.pollData?.title || "Loading.."}</h1>
|
||||||
|
|
||||||
{/* Voting Options */}
|
{/* Voting Options */}
|
||||||
<div className="grid grid-cols-1 md:grid-cols-2 gap-4 w-full max-w-lg mb-6">
|
<div className="grid grid-cols-1 md:grid-cols-2 gap-4 w-full max-w-lg mb-6">
|
||||||
{pollData.options.map(option => (
|
{poll?.data?.pollData?.options.map(option => (
|
||||||
<div
|
<div
|
||||||
key={option.id}
|
key={option._id}
|
||||||
className="md:p-4 p-2 bg-base-100 rounded-lg shadow-md flex items-center justify-center cursor-pointer hover:bg-base-300 transition"
|
className="md:p-4 p-2 bg-base-100 rounded-lg shadow-md flex items-center justify-center cursor-pointer hover:bg-base-300 transition"
|
||||||
>
|
>
|
||||||
<span className="text-lg">{option.label}</span>
|
<span className="text-lg">{option.name}</span>
|
||||||
</div>
|
</div>
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
Reference in New Issue
Block a user