From edd4f1aa09469f90825e8ea81fe72d91c5ffc9ab Mon Sep 17 00:00:00 2001 From: "Jessada.n" Date: Tue, 25 Feb 2025 21:48:20 +0700 Subject: [PATCH] =?UTF-8?q?=E2=9C=A8=20feat:=20add=20create=20poll=20by=20?= =?UTF-8?q?event=20id=20endpoint?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- backend/src/controllers/event.controller.ts | 1 + backend/src/controllers/poll.controller.ts | 118 ++++++++++++++------ backend/src/interface.ts | 1 + backend/src/middlewares/auth.middleware.ts | 2 + backend/src/routes/event.routes.ts | 5 + backend/src/services/poll.service.ts | 87 ++++++++++++++- 6 files changed, 179 insertions(+), 35 deletions(-) diff --git a/backend/src/controllers/event.controller.ts b/backend/src/controllers/event.controller.ts index 2e720f7..0d3207b 100644 --- a/backend/src/controllers/event.controller.ts +++ b/backend/src/controllers/event.controller.ts @@ -1,6 +1,7 @@ import { PrismaClient } from "@prisma/client"; import { Request, Response } from "express"; import { EventService } from "../services/event.service"; +import { IPoll } from "../interface"; export class EventController { diff --git a/backend/src/controllers/poll.controller.ts b/backend/src/controllers/poll.controller.ts index 899de4b..4fe9bfb 100644 --- a/backend/src/controllers/poll.controller.ts +++ b/backend/src/controllers/poll.controller.ts @@ -1,17 +1,16 @@ - +import { IPoll } from "../interface"; import { PollService } from "../services/poll.service"; import { Request, Response, NextFunction } from "express"; export class PollController { - - constructor(private pollService: PollService) { this.getPoll = this.getPoll.bind(this); this.getPolls = this.getPolls.bind(this); this.myPolls = this.myPolls.bind(this); this.publicPolls = this.publicPolls.bind(this); this.myVotedPolls = this.myVotedPolls.bind(this); + this.createPollByEventId = this.createPollByEventId.bind(this); } /** @@ -22,9 +21,12 @@ export class PollController { * @returns - JSON */ - public async getPolls(req: Request, res: Response, next: NextFunction): Promise { + public async getPolls( + req: Request, + res: Response, + next: NextFunction + ): Promise { try { - const page = Number(req.query.page) || 1; const pageSize = Number(req.query.pageSize) || 10; const search = req.query.search as string; @@ -45,20 +47,18 @@ export class PollController { pageSize: pageSize, totalPages: Math.ceil(polls.totalCount / pageSize) || 1, totalCount: polls.totalCount, - search - } + search, + }, }); - } catch (error) { return res.status(500).json({ success: false, message: "Failed to fetch polls", - error: error + error: error, }); } } - /** * Get all polls user has participated in * @param req - Request @@ -66,16 +66,20 @@ export class PollController { * @param next - NextFunction * @returns - JSON */ - public async myPolls(req: Request, res: Response, next: NextFunction): Promise { + public async myPolls( + req: Request, + res: Response, + next: NextFunction + ): Promise { try { - const user = req.user + const user = req.user; // Check if user exists if (!user) { return res.status(400).json({ success: false, message: "User not found", - error: "User not found" + error: "User not found", }); } @@ -84,14 +88,13 @@ export class PollController { res.status(200).json({ message: "Polls fetched successfully", - data: polls + data: polls, }); - } catch (error) { return res.status(500).json({ success: false, message: "Failed to fetch polls", - error: error + error: error, }); } } @@ -104,16 +107,20 @@ export class PollController { * @returns - JSON */ - public async myVotedPolls(req: Request, res: Response, next: NextFunction): Promise { + public async myVotedPolls( + req: Request, + res: Response, + next: NextFunction + ): Promise { try { - const user = req.user + const user = req.user; // Check if user exists if (!user) { return res.status(400).json({ success: false, message: "User not found", - error: "User not found" + error: "User not found", }); } @@ -122,21 +129,23 @@ export class PollController { res.status(200).json({ message: "Polls fetched successfully", - data: polls + data: polls, }); - } catch (error) { return res.status(500).json({ success: false, message: "Failed to fetch polls", - error: error + error: error, }); } } - public async publicPolls(req: Request, res: Response, next: NextFunction): Promise { + public async publicPolls( + req: Request, + res: Response, + next: NextFunction + ): Promise { try { - const page = Number(req.query.page) || 1; const pageSize = Number(req.query.pageSize) || 10; const search = req.query.search as string; @@ -151,28 +160,31 @@ export class PollController { res.status(200).json({ message: "Polls fetched successfully", - data: { polls } + data: { polls }, }); } catch (error) { return res.status(500).json({ success: false, message: "Failed to fetch polls", - error: error + error: error, }); } } /** - * Get a poll by ID + * Get a poll by ID * @param req - Request * @param res - Response * @param next - NextFunction * @returns - JSON */ - public getPoll = async (req: Request, res: Response, next: NextFunction): Promise => { + public getPoll = async ( + req: Request, + res: Response, + next: NextFunction + ): Promise => { try { - const { pollId } = req.params; const user = req.user; @@ -180,19 +192,23 @@ export class PollController { return res.status(400).json({ success: false, message: "User not found", - error: "User not found" + error: "User not found", }); } // Check if user or guest can vote on poll - const canGetPoll = await this.pollService.userCanVote(pollId, user.id, user.guest); + const canGetPoll = await this.pollService.userCanVote( + pollId, + user.id, + user.guest + ); // If user cannot vote on poll if (!canGetPoll) { return res.status(403).json({ success: false, message: "You cannot vote on this poll", - error: "You cannot vote on this poll" + error: "You cannot vote on this poll", }); } @@ -202,7 +218,7 @@ export class PollController { return res.status(404).json({ success: false, message: "Failed to fetch polls", - error: "Polls not found" + error: "Polls not found", }); } @@ -222,6 +238,40 @@ export class PollController { } catch (error) { next(error); } + }; + + public async createPollByEventId(req: Request, res: Response): Promise { + try { + const eventId = req.params.eventId; + + const user = req.user; + + const { polls } = req.body; + + // Check if user is authenticated + if (!user) { + return res.status(401).json({ + success: false, + message: "Unauthorized", + }); + } + + const createdPolls = await this.pollService.createPollByEventId(polls, eventId , user.id); + + return res.status(200).json({ + success: true, + message: "Event fetched successfully", + data: createdPolls, + }); + + + } catch (error) { + console.error("[ERROR] getEvents:", error); + return res.status(500).json({ + message: "Something went wrong", + error: error || error, + }); + } } // public getPoll = async (req: Request, res: Response, next: NextFunction) => { @@ -276,4 +326,4 @@ export class PollController { // next(error); // } // } -} \ No newline at end of file +} diff --git a/backend/src/interface.ts b/backend/src/interface.ts index 5e970bf..69a5019 100644 --- a/backend/src/interface.ts +++ b/backend/src/interface.ts @@ -52,6 +52,7 @@ export interface IUser { createdAt: Date; updatedAt: Date; deletedAt?: Date | null; + canEdit?: boolean; } diff --git a/backend/src/middlewares/auth.middleware.ts b/backend/src/middlewares/auth.middleware.ts index 48d8918..5d40d21 100644 --- a/backend/src/middlewares/auth.middleware.ts +++ b/backend/src/middlewares/auth.middleware.ts @@ -192,6 +192,8 @@ class AuthMiddleware { /** Extract Token */ private extractToken(req: Request): string | null { const authHeader = req.headers?.authorization; + console.log(authHeader); + return authHeader?.startsWith("Bearer ") ? authHeader.split(" ")[1] : req.cookies?.accessToken || null; diff --git a/backend/src/routes/event.routes.ts b/backend/src/routes/event.routes.ts index 07b2e0a..d58aff3 100644 --- a/backend/src/routes/event.routes.ts +++ b/backend/src/routes/event.routes.ts @@ -10,6 +10,7 @@ import { UserService } from '../services/user.service'; import { AuthService } from '../services/auth.service'; import { CryptoService } from '../services/crypto.service'; import AuthMiddleware from '../middlewares/auth.middleware'; +import { PollService } from '../services/poll.service'; const router = Router(); @@ -18,6 +19,7 @@ const eventService = new EventService( prisma ); +const pollService = new PollService(prisma); const userService = new UserService( prisma @@ -33,12 +35,15 @@ const authMiddleware = new AuthMiddleware( ) const eventController = new EventController(eventService); +const pollController = new PollController(pollService); router.get('/', getAllEventValidator(), authMiddleware.validateMulti, validateRequest, eventController.getEvents); router.get('/:eventId', getEventByIdValidator(), authMiddleware.validateMulti, validateRequest, eventController.getEvent); +router.post('/:eventId/polls/create', getEventByIdValidator(), authMiddleware.validateUserOnly, validateRequest, pollController.createPollByEventId); + router.post('/create', createEventValidator(), authMiddleware.validateUserOnly, validateRequest, eventController.createEvent); export { diff --git a/backend/src/services/poll.service.ts b/backend/src/services/poll.service.ts index aa508fe..044d713 100644 --- a/backend/src/services/poll.service.ts +++ b/backend/src/services/poll.service.ts @@ -321,4 +321,89 @@ export class PollService { : undefined, }; } -} + + public async createPollByEventId( + polls: IPoll[], + eventId: string, + userId: string + ): Promise { + try { + return await this.prisma.$transaction(async (prisma) => { + // Step 1: Create Polls + const createdPolls = await prisma.poll.createMany({ + data: polls.map((poll) => ({ + eventId: eventId, + userId: userId, + question: poll.question, + description: poll.description, + isPublic: eventId == null ? false : poll.isPublic, + canEdit: poll.canEdit, + isVoteEnd: false, // Default vote end status + banner: poll.banner, + publishedAt: poll.publishedAt ? new Date(poll.publishedAt) : null, + startVoteAt: new Date(poll.startVoteAt), + endVoteAt: new Date(poll.endVoteAt), + })), + }); + + // Step 2: Fetch created polls to get their IDs + const createdPollsData = await prisma.poll.findMany({ + where: { eventId: eventId, userId: userId }, + }); + + // Step 3: Add options and vote restrictions using Promise.all + await Promise.all( + polls.map(async (poll) => { + const relatedPoll = createdPollsData.find( + (p) => p.question === poll.question + ); + + if (!relatedPoll) { + throw new Error(`Poll not found for question: ${poll.question}`); + } + + // Create options + const createdOptions = await prisma.option.createMany({ + data: (poll.options || []).map((option) => ({ + pollId: relatedPoll.id, + text: option.text, + banner: option.banner, + description: option.description, + })), + }); + + // Step 4: Add vote restrictions + await Promise.all( + (poll.voteRestrict || []).map(async (restriction) => { + const option = await prisma.option.findFirst({ + where: { + pollId: relatedPoll.id, + text: restriction.option?.text, // Ensure option text matches + }, + }); + + if (option) { + await prisma.voteRestriction.create({ + data: { + pollId: relatedPoll.id, + optionId: option.id, + userId: restriction?.userId || null, + guestId: restriction?.guestId || null, + }, + }); + } + }) + ); + }) + ); + + return createdPolls; + }); + } catch (error) { + console.error("Error creating polls:", error); + throw new Error( + `Failed to create polls for event ${eventId}: ${error}` + ); + } + } +} \ No newline at end of file