From 1b7334920c6cdc9878c7dae2304ae80009b4a915 Mon Sep 17 00:00:00 2001 From: anmol420 Date: Thu, 2 Jan 2025 09:28:56 +0530 Subject: [PATCH 1/2] Added Changes --- Backend/project_todo.tldr | 123 +++++++++++------- Backend/src/constants.js | 6 + .../src/controllers/tournament.controller.js | 9 +- Backend/src/middlewares/isAdmin.middleware.js | 27 ++++ Backend/src/models/user.models.js | 7 +- Backend/src/routes/tournament.routes.js | 19 ++- 6 files changed, 131 insertions(+), 60 deletions(-) create mode 100644 Backend/src/middlewares/isAdmin.middleware.js diff --git a/Backend/project_todo.tldr b/Backend/project_todo.tldr index ea968bc..bc12c96 100644 --- a/Backend/project_todo.tldr +++ b/Backend/project_todo.tldr @@ -854,25 +854,82 @@ "index": "aP9ZL", "typeName": "shape" }, + { + "x": -204.05286853787877, + "y": 390.42841513372946, + "rotation": 0, + "isLocked": false, + "opacity": 1, + "meta": {}, + "id": "shape:mlzG293ZNC4ErhSzu7vil", + "type": "geo", + "props": { + "w": 48.91670317923786, + "h": 34.46120763042444, + "geo": "check-box", + "color": "light-green", + "labelColor": "black", + "fill": "none", + "dash": "draw", + "size": "m", + "font": "draw", + "text": "", + "align": "middle", + "verticalAlign": "middle", + "growY": 0, + "url": "", + "scale": 1 + }, + "parentId": "page:page", + "index": "aQ6o5", + "typeName": "shape" + }, + { + "x": -193.79413182702604, + "y": 572.7542001635088, + "rotation": 0, + "isLocked": false, + "opacity": 1, + "meta": {}, + "id": "shape:R681YunC-u92M4irC_Vhp", + "type": "geo", + "props": { + "w": 53.57974009365725, + "h": 33.06228801777078, + "geo": "check-box", + "color": "light-green", + "labelColor": "black", + "fill": "none", + "dash": "draw", + "size": "m", + "font": "draw", + "text": "", + "align": "middle", + "verticalAlign": "middle", + "growY": 0, + "url": "", + "scale": 1 + }, + "parentId": "page:page", + "index": "aR5f6", + "typeName": "shape" + }, { "id": "pointer:pointer", "typeName": "pointer", - "x": -142.2900188476215, - "y": 724.0479477552308, - "lastActivityTimestamp": 1735535246081, + "x": 466.42058734924035, + "y": 382.5432546427344, + "lastActivityTimestamp": 1735632564544, "meta": {} }, { "followingUserId": null, "opacityForNextShape": 1, - "stylesForNextShape": { - "tldraw:geo": "check-box", - "tldraw:color": "light-green" - }, + "stylesForNextShape": {}, "brush": null, "scribbles": [], "cursor": { - "type": "default", + "type": "move", "rotation": 0 }, "isFocusMode": false, @@ -882,8 +939,8 @@ "screenBounds": { "x": 0, "y": 0, - "w": 962, - "h": 566 + "w": 1022, + "h": 605.3333740234375 }, "insets": [ false, @@ -914,7 +971,7 @@ "editingShapeId": null, "croppingShapeId": null, "selectedShapeIds": [ - "shape:R681YunC-u92M4irC_Vhp" + "shape:V_PREryF1OCKukEyq1YhG" ], "hoveredShapeId": null, "erasingShapeIds": [], @@ -926,21 +983,21 @@ "typeName": "instance_page_state" }, { - "x": 270.9906232117464, - "y": -15.028843178119544, - "z": 0.7148373580259958, + "x": 289.19234468921684, + "y": -8.284293861588765, + "z": 0.7517077023851779, "meta": {}, "id": "camera:page:page", "typeName": "camera" }, { - "x": -204.05286853787877, - "y": 390.42841513372946, + "x": 454.00432916536636, + "y": 359.3879675876655, "rotation": 0, "isLocked": false, "opacity": 1, "meta": {}, - "id": "shape:mlzG293ZNC4ErhSzu7vil", + "id": "shape:V_PREryF1OCKukEyq1YhG", "type": "geo", "props": { "w": 48.91670317923786, @@ -960,37 +1017,7 @@ "scale": 1 }, "parentId": "page:page", - "index": "aQ6o5", - "typeName": "shape" - }, - { - "x": -193.79413182702604, - "y": 572.7542001635088, - "rotation": 0, - "isLocked": false, - "opacity": 1, - "meta": {}, - "id": "shape:R681YunC-u92M4irC_Vhp", - "type": "geo", - "props": { - "w": 53.57974009365725, - "h": 33.06228801777078, - "geo": "check-box", - "color": "light-green", - "labelColor": "black", - "fill": "none", - "dash": "draw", - "size": "m", - "font": "draw", - "text": "", - "align": "middle", - "verticalAlign": "middle", - "growY": 0, - "url": "", - "scale": 1 - }, - "parentId": "page:page", - "index": "aR5f6", + "index": "aS3pc", "typeName": "shape" } ] diff --git a/Backend/src/constants.js b/Backend/src/constants.js index b40d84a..5dcadaf 100644 --- a/Backend/src/constants.js +++ b/Backend/src/constants.js @@ -26,10 +26,16 @@ const TOURNAMENT_TYPES = Object.freeze([ "Squad", ]); +const USER_ROLES = Object.freeze([ + "USER", + "ADMIN", +]); + export { DB_NAME, COOKIE_OPTIONS, USER_BADGES, GAMES, TOURNAMENT_TYPES, + USER_ROLES, }; \ No newline at end of file diff --git a/Backend/src/controllers/tournament.controller.js b/Backend/src/controllers/tournament.controller.js index 01d3d9c..914c436 100644 --- a/Backend/src/controllers/tournament.controller.js +++ b/Backend/src/controllers/tournament.controller.js @@ -3,9 +3,9 @@ import ApiError from "../utils/ApiError.js"; import ApiResponse from "../utils/ApiResponse.js"; import { Tournament } from "../models/tournament.models.js"; -const createTournaments = asyncHandler(async(req,res)=>{ - - const {name, +const createTournaments = asyncHandler(async (req, res) => { + const { + name, matchDate, matchTime, registrationEndDate, @@ -15,7 +15,8 @@ const createTournaments = asyncHandler(async(req,res)=>{ game, entryFee, description, - instructions,} = req.body; + instructions + } = req.body; const now = new Date(); if(new Date(registrationEndDate) <= now){ diff --git a/Backend/src/middlewares/isAdmin.middleware.js b/Backend/src/middlewares/isAdmin.middleware.js new file mode 100644 index 0000000..c4b804f --- /dev/null +++ b/Backend/src/middlewares/isAdmin.middleware.js @@ -0,0 +1,27 @@ +import ApiError from "../utils/ApiError.js"; +import jwt from "jsonwebtoken"; +import asyncHandler from "../utils/asyncHandler.js"; +import { User } from "../models/user.models.js"; +import { USER_ROLES } from "../constants.js"; + +const isAdmin = asyncHandler(async (req, res, next) => { + const token = req.cookies?.token || req.header("Authorization")?.replace("Bearer ", ""); + if (!token) { + throw new ApiError(401, "Unauthorized Access."); + } + const decoded = jwt.verify(token, process.env.JWT_SECRET); + const user = await User.findById(decoded.id); + if (!user) { + throw new ApiError(401, "Unauthorized Access."); + } + if (user.role !== USER_ROLES[1]) { + throw new ApiError(403, "Forbidden Access."); + } + try { + next(); + } catch (error) { + throw new ApiError(500, error.message || "Internal Server Error"); + } +}); + +export default isAdmin; \ No newline at end of file diff --git a/Backend/src/models/user.models.js b/Backend/src/models/user.models.js index 566a762..1b15f0e 100644 --- a/Backend/src/models/user.models.js +++ b/Backend/src/models/user.models.js @@ -1,6 +1,6 @@ import mongoose, { Schema } from "mongoose"; import bcrypt from "bcryptjs"; -import { USER_BADGES } from "../constants.js"; +import { USER_BADGES, USER_ROLES } from "../constants.js"; const userSchema = new Schema({ email: { @@ -44,6 +44,11 @@ const userSchema = new Schema({ canChangePassword: { type: Boolean, default: false, + }, + role: { + type: String, + enum: USER_ROLES, + default: USER_ROLES[0], } }, { timestamps: true, diff --git a/Backend/src/routes/tournament.routes.js b/Backend/src/routes/tournament.routes.js index f6cfc2d..5eabab1 100644 --- a/Backend/src/routes/tournament.routes.js +++ b/Backend/src/routes/tournament.routes.js @@ -1,16 +1,21 @@ import { Router } from "express"; import verifyToken from "../middlewares/auth.middleware.js"; -import { createTournaments , getTournaments , registerPlayer , tournamentInfo} from "../controllers/tournament.controller.js"; +import isAdmin from "../middlewares/isAdmin.middleware.js"; +import { + createTournaments, + getTournaments, + registerPlayer, + tournamentInfo +} from "../controllers/tournament.controller.js"; const router = Router(); //admin controlled routes -router.route("/createTournament").post(createTournaments) +router.route("/createTournament").post(isAdmin, createTournaments) //user accessed routes -router.route("/getAllTournaments").get(verifyToken,getTournaments) -router.route("/getTournamentInfo").get(verifyToken,tournamentInfo) -router.route("/registerTournament").post(verifyToken,registerPlayer) +router.route("/getAllTournaments").get(verifyToken, getTournaments); +router.route("/getTournamentInfo").get(verifyToken, tournamentInfo); +router.route("/registerTournament").post(verifyToken, registerPlayer); - -export default router \ No newline at end of file +export default router; \ No newline at end of file From 322f758a31b2ee823c1a4eff82425105de6f5f80 Mon Sep 17 00:00:00 2001 From: anmol420 Date: Fri, 3 Jan 2025 14:40:10 +0530 Subject: [PATCH 2/2] Added Nominal Tournament Changes --- Backend/package.json | 1 + Backend/src/constants.js | 12 + .../src/controllers/tournament.controller.js | 217 +++++++----------- Backend/src/helpers/checkDateTime.js | 22 ++ Backend/src/models/tournament.models.js | 21 +- Backend/src/models/user.models.js | 8 +- Backend/src/routes/tournament.routes.js | 26 ++- 7 files changed, 153 insertions(+), 154 deletions(-) create mode 100644 Backend/src/helpers/checkDateTime.js diff --git a/Backend/package.json b/Backend/package.json index 6ba008f..0146d48 100644 --- a/Backend/package.json +++ b/Backend/package.json @@ -32,6 +32,7 @@ "express": "^4.19.2", "ioredis": "^5.4.2", "jsonwebtoken": "^9.0.2", + "luxon": "^3.5.0", "mongoose": "^8.4.4", "node-cron": "^3.0.3", "nodemailer": "^6.9.16", diff --git a/Backend/src/constants.js b/Backend/src/constants.js index 5dcadaf..e935f49 100644 --- a/Backend/src/constants.js +++ b/Backend/src/constants.js @@ -31,6 +31,16 @@ const USER_ROLES = Object.freeze([ "ADMIN", ]); +const REGION = `Asia/Kolkata`; + +const GAME_ID = Object.freeze({ + "Battlegrounds Mobile India": "bgmiId", + "Call of Duty Mobile": "codmId", + "Valorant": "valorantId", + "Free Fire": "freefireId", + "Asphalt 9": "asphaltId", +}); + export { DB_NAME, COOKIE_OPTIONS, @@ -38,4 +48,6 @@ export { GAMES, TOURNAMENT_TYPES, USER_ROLES, + REGION, + GAME_ID, }; \ No newline at end of file diff --git a/Backend/src/controllers/tournament.controller.js b/Backend/src/controllers/tournament.controller.js index fa9b917..ebcaa82 100644 --- a/Backend/src/controllers/tournament.controller.js +++ b/Backend/src/controllers/tournament.controller.js @@ -1,16 +1,23 @@ +import { DateTime } from "luxon"; + import asyncHandler from "../utils/asyncHandler.js"; import ApiError from "../utils/ApiError.js"; import ApiResponse from "../utils/ApiResponse.js"; +import { Game } from "../models/gameId.models.js"; import { Tournament } from "../models/tournament.models.js"; -import { Result } from "../models/result.models.js"; -import mongoose from "mongoose"; +import { REGION, GAME_ID } from "../constants.js"; +import { check24HourFormat, checkDateFormat } from "../helpers/checkDateTime.js"; + +const now = new Date(); -const createTournaments = asyncHandler(async (req, res) => { +// admin controlled routes +const createTournament = asyncHandler(async (req, res) => { const { name, matchDate, matchTime, registrationEndDate, + registrationEndTime, totalSlots, prizePool, type, @@ -19,28 +26,31 @@ const createTournaments = asyncHandler(async (req, res) => { description, instructions } = req.body; - - const now = new Date(); - if(new Date(registrationEndDate) <= now){ - throw new ApiError(400,"Registration end date must be in future!") + if (!check24HourFormat(matchTime) || !check24HourFormat(registrationEndTime)) { + throw new ApiError(400, "Invalid Time Format. Please Use hh:mm Format."); } - - if (new Date(matchDate) <= new Date(registrationEndDate)) { - throw new ApiError(400,"Match must be after registration ends!") + if (!checkDateFormat(matchDate) || !checkDateFormat(registrationEndDate)) { + throw new ApiError(400, "Invalid Date Format. Please Use yyyy-MM-dd Format."); + } + if(new Date(`${registrationEndDate}T${registrationEndTime}`) <= now){ + throw new ApiError(400, "Registration End Date Must Be Greater Than Current Date and Time."); + } + if (new Date(`${matchDate}T${matchTime}`) <= new Date(`${registrationEndDate}T${registrationEndTime}`)) { + throw new ApiError(400, "Match Date Must Be Greater Than Registration End Date and Time."); } - const existingTournament = await Tournament.findOne({ name }); - if (existingTournament) { - throw new ApiError(400, "Tournament already exists with this name"); + throw new ApiError(400, "Tournament With This Name Already Exists."); } - try { + const matchTiming = DateTime.fromISO(`${matchDate}T${matchTime}`, { zone: REGION }); + const registrationTiming = DateTime.fromISO(`${registrationEndDate}T${registrationEndTime}`, { zone: REGION }); const tournament = await Tournament.create({ name, - matchDate, - matchTime, - registrationEndDate, + matchDate: matchTiming.toFormat("dd-MM-yyyy"), + matchTime: matchTiming.toFormat("HH:mm"), + registrationEndDate: registrationTiming.toFormat("dd-MM-yyyy"), + registrationEndTime: registrationTiming.toFormat("HH:mm"), totalSlots, prizePool, type, @@ -48,163 +58,92 @@ const createTournaments = asyncHandler(async (req, res) => { entryFee, description, instructions, - }) - + }); return res .status(201) - .json( new ApiResponse(201,tournament,"Tournament Created Successfully!!")) - + .json( new ApiResponse(201, tournament, "Tournament Created Successfully!!")); } catch (error) { - throw new ApiError(500, error.message || "Internal Server Error") + throw new ApiError(500, error.message || "Internal Server Error"); } -}) - -const getTournaments = asyncHandler(async(_,res)=>{ +}); +// user accessed routes +const getTournaments = asyncHandler(async (req, res) => { try { - const tournaments = await Tournament.find({ isActive:true }) - .sort({createdAt:-1}) - .select("name matchDate matchTime registrationEndDate totalSlots filledSlots prizePool type game entryFee rating description instructions") + const tournaments = await Tournament.find({ + isActive: true, + isOngoing: true, + }).sort({ + createdAt:-1, + }).select( + "name matchDate matchTime registrationEndDate registrationEndTime totalSlots filledSlots prizePool type game entryFee rating description instructions" + ); return res .status(200) - .json(new ApiResponse(200,tournaments,"Tournaments fetched successfully!!")) + .json(new ApiResponse(200, tournaments, "Tournaments Fetched Successfully!!")); } catch (error) { - throw new ApiError(500, error.message || "Internal Server Error") + throw new ApiError(500, error.message || "Internal Server Error"); } }) -const tournamentInfo = asyncHandler(async(req,res)=>{ +const getTournamentInfo = asyncHandler(async (req,res) => { const { tournamentName } = req.body; - + if (!tournamentName) { + throw new ApiError(400, "Tournament Name is Required."); + } const tournament = await Tournament.findOne({ name: tournamentName }); if (!tournament) { throw new ApiError(404, "Tournament not found"); } - - return res. - status(200). - json(new ApiResponse(200, tournament, "Tournament fetched successfully!!")); - + try { + return res + .status(200) + .json(new ApiResponse(200, tournament, "Tournament fetched successfully!!")); + } catch (error) { + throw new ApiError(500, error.message || "Internal Server Error"); + } }) -const registerPlayer = asyncHandler(async (req, res) => { +const registerUser = asyncHandler(async (req, res) => { const { tournamentName } = req.body; const user = req.user; - const tournament = await Tournament.findOne({ name: tournamentName }); if (!tournament) { - throw new ApiError(404, "Tournament not found"); + throw new ApiError(404, "Tournament Not Found."); } - if (!tournament.isActive) { - throw new ApiError(400, "Tournament is not active"); + throw new ApiError(400, "Tournament Is Not Active."); } - - if (tournament.registeredPlayers.includes(user._id)) { - throw new ApiError(400, "You are already registered for this tournament"); - } - if (tournament.filledSlots >= tournament.totalSlots) { - throw new ApiError(400, "Tournament is full"); + throw new ApiError(400, "Tournament Is Full."); + } + const tournamentGame = GAME_ID[tournament.game]; + const userHasGameId = await Game.findOne({ owner: user._id }); + if (!userHasGameId || !userHasGameId[tournamentGame]) { + throw new ApiError(400, "Please Add Game Id First."); + } + + const userRegistered = await Tournament.findOne({ registeredPlayers: user._id }); + if (userRegistered) { + throw new ApiError(400, "User Already Registered For This Tournament."); } - try { + user.registeredTournaments.push(tournament._id); + await user.save(); tournament.registeredPlayers.push(user._id); tournament.filledSlots += 1; await tournament.save(); - - return res.status(200).json(new ApiResponse(200, "Registered Successfully!!")); - } catch (error) { - throw new ApiError(500, error.message || "Internal Server Error"); - } -}); - -const postResult = asyncHandler(async (req, res) => { - const { tournamentName, leaderboard } = req.body; - - const tournament = await Tournament.findOne({ name: tournamentName }); - if (!tournament) { - throw new ApiError(404, "Tournament not found"); - } - - if (!tournament.isActive) { - throw new ApiError(400, "Tournament is not active"); - } - - if(!leaderboard || leaderboard.length === 0){ - throw new ApiError(400, "Leaderboard is required"); - } - - try { - await Result.create({ - tournament: tournament._id, - leaderboard, - }); - return res - .status(201) - .json(new ApiResponse(201, "Result Posted Successfully!!")); + .status(200) + .json(new ApiResponse(200, null,"Registered Successfully!!")); } catch (error) { throw new ApiError(500, error.message || "Internal Server Error"); } }); -//for admin -const getResults = asyncHandler(async (req, res) => { - const { tournamentName } = req.body; - - const tournament = await Tournament.findOne({ name: tournamentName }); - - if (!tournament) { - throw new ApiError(404, "Tournament not found"); - } - - const results = await Result.find({ tournament: tournament._id }) - - if (!results) { - throw new ApiError(404, "Results not found"); - } - - return res - .status(200) - .json(new ApiResponse(200, results, "Results fetched successfully!!")); -}); - -//for user -const getIndividualResult = asyncHandler(async (req, res) => { - const { tournamentName } = req.body; - const user = req.user; - - if(!tournamentName){ - throw new ApiError(400, "Tournament name is required"); - } - - const tournament = await Tournament.findOne({ name: tournamentName }); - - if (!tournament) { - throw new ApiError(404, "Tournament not found"); - } - - const position = await Result.findOne( - { tournament: tournament._id, "leaderboard.player": user._id }, - { "leaderboard.$": 1 } - ).select("-tournament -_id -createdAt -updatedAt -__v"); - - if (!position) { - throw new ApiError(404, "Result not found"); - } - - return res - .status(200) - .json(new ApiResponse(200, position, "Result fetched successfully!!")); -}); - -export { - createTournaments, - getTournaments, - registerPlayer, - tournamentInfo, - postResult, - getResults, - getIndividualResult, -} \ No newline at end of file +export { + createTournament, + getTournaments, + getTournamentInfo, + registerUser, +}; \ No newline at end of file diff --git a/Backend/src/helpers/checkDateTime.js b/Backend/src/helpers/checkDateTime.js new file mode 100644 index 0000000..daba599 --- /dev/null +++ b/Backend/src/helpers/checkDateTime.js @@ -0,0 +1,22 @@ +import { DateTime } from "luxon"; + +const check24HourFormat = (time) => { + const parsedTime = DateTime.fromFormat(time, "HH:mm"); + if (!parsedTime.isValid) { + return false; + } + return true; +}; + +const checkDateFormat = (date) => { + const parsedDate = DateTime.fromFormat(date, "yyyy-MM-dd"); + if (!parsedDate.isValid) { + return false; + } + return true; +} + +export { + check24HourFormat, + checkDateFormat, +}; \ No newline at end of file diff --git a/Backend/src/models/tournament.models.js b/Backend/src/models/tournament.models.js index 539c544..bb46248 100644 --- a/Backend/src/models/tournament.models.js +++ b/Backend/src/models/tournament.models.js @@ -8,15 +8,19 @@ const tournamentScehma = new Schema({ unique: true, }, matchDate: { - type: Date, + type: String, required: true, }, matchTime: { - type: Date, + type: String, required: true, }, registrationEndDate: { - type: Date, + type: String, + required: true, + }, + registrationEndTime: { + type: String, required: true, }, totalSlots: { @@ -64,10 +68,21 @@ const tournamentScehma = new Schema({ ref: "User", } ], + // for finished registration isActive: { type: Boolean, default: true, }, + // for ongoing tournaments (registration completed) + isOngoing: { + type: Boolean, + default: false, + }, + // for finished tournaments + isCompleted: { + type: Boolean, + default: false, + }, // idp: {}, // refundForm: {}, }, { diff --git a/Backend/src/models/user.models.js b/Backend/src/models/user.models.js index 1b15f0e..35fbb4f 100644 --- a/Backend/src/models/user.models.js +++ b/Backend/src/models/user.models.js @@ -49,7 +49,13 @@ const userSchema = new Schema({ type: String, enum: USER_ROLES, default: USER_ROLES[0], - } + }, + registeredTournaments: [ + { + type: Schema.Types.ObjectId, + ref: "Tournament", + } + ], }, { timestamps: true, }); diff --git a/Backend/src/routes/tournament.routes.js b/Backend/src/routes/tournament.routes.js index 41da68d..63255f5 100644 --- a/Backend/src/routes/tournament.routes.js +++ b/Backend/src/routes/tournament.routes.js @@ -2,24 +2,28 @@ import { Router } from "express"; import verifyToken from "../middlewares/auth.middleware.js"; import isAdmin from "../middlewares/isAdmin.middleware.js"; import { - createTournaments, + createTournament, getTournaments, - registerPlayer, - tournamentInfo - , postResult ,getResults ,getIndividualResult} from "../controllers/tournament.controller.js"; + getTournamentInfo, + registerUser, +} from "../controllers/tournament.controller.js"; + +import { postResult } from "../controllers/result.controller.js"; const router = Router(); //admin controlled routes -router.route("/createTournament").post(createTournaments) -router.route("/postResult").post(postResult) -router.route("/getResults").get(verifyToken,getResults) +router.route("/createTournament").post(isAdmin, createTournament); + +// router.route("/postResult").post(postResult); +// router.route("/getResults").get(verifyToken,getResults); //user accessed routes -router.route("/getAllTournaments").get(verifyToken,getTournaments) -router.route("/getTournamentInfo").get(verifyToken,tournamentInfo) -router.route("/getIndividualResult").get(verifyToken,getIndividualResult) -router.route("/registerTournament").post(verifyToken,registerPlayer) +router.route("/getTournaments").get(verifyToken, getTournaments); +router.route("/getTournamentInfo").get(verifyToken, getTournamentInfo); +router.route("/registerTournament").post(verifyToken, registerUser); + +// router.route("/getIndividualResult").get(verifyToken,getIndividualResult); export default router; \ No newline at end of file