diff --git a/Backend/src/app.js b/Backend/src/app.js index 93c5ee5..f065f62 100644 --- a/Backend/src/app.js +++ b/Backend/src/app.js @@ -45,4 +45,8 @@ app.use("/api/v1/users/games", gameRouter); import tournamentRouter from "./routes/tournament.routes.js"; app.use("/api/v1/tournament",tournamentRouter); +//ADMIN ROUTE +import adminRouter from "./routes/admin.routes.js"; +app.use("/api/v1/admin",adminRouter); + export default app; \ No newline at end of file diff --git a/Backend/src/controllers/admin.controller.js b/Backend/src/controllers/admin.controller.js new file mode 100644 index 0000000..0efeb71 --- /dev/null +++ b/Backend/src/controllers/admin.controller.js @@ -0,0 +1,314 @@ +import { DateTime } from "luxon"; +import asyncHandler from "../utils/asyncHandler.js"; +import ApiError from "../utils/ApiError.js"; +import ApiResponse from "../utils/ApiResponse.js"; +import { Result } from "../models/result.models.js"; +import { Tournament } from "../models/tournament.models.js"; +import { REGION, GAME_ID } from "../constants.js"; +import { check24HourFormat, checkDateFormat } from "../helpers/checkDateTime.js"; +import { IDP } from "../models/idp.models.js"; + +const now = new Date(); + +const createTournament = asyncHandler(async (req, res) => { + const { + name, + matchDate, + matchTime, + registrationEndDate, + registrationEndTime, + totalSlots, + prizePool, + type, + game, + entryFee, + description, + instructions, + } = req.body; + if (!check24HourFormat(matchTime) || !check24HourFormat(registrationEndTime)) { + throw new ApiError(400, "Invalid Time Format. Please Use hh:mm Format."); + } + 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 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: matchTiming.toFormat("dd-MM-yyyy"), + matchTime: matchTiming.toFormat("HH:mm"), + registrationEndDate: registrationTiming.toFormat("dd-MM-yyyy"), + registrationEndTime: registrationTiming.toFormat("HH:mm"), + totalSlots, + prizePool, + type, + name, + matchDate, + matchTime, + registrationEndDate, + registrationEndTime, + totalSlots, + prizePool, + game, + entryFee, + description, + instructions, + isActive: true, + //isOngoing: true, + }); + + return res + .status(201) + .json( new ApiResponse(201, tournament, "Tournament Created Successfully!!")); + } catch (error) { + throw new ApiError(500, error.message || "Internal Server Error"); + } +}); + +const addIDP = asyncHandler(async (req, res) => { + const { tournamentName, roomId, roomPassword } = req.body; + + if (!tournamentName || !roomId || !roomPassword) { + throw new ApiError(400, "All Fields are Required."); + } + + const tournament = await Tournament.findOne({ name: tournamentName }); + if (!tournament) { + throw new ApiError(404, "Tournament Not Found."); + } + + if(tournament.idp){ + throw new ApiError(400, "IDP Already Exists for this Tournament."); + } + + try { + const idp = await IDP.create({ + tournamentId: tournament._id, + tournamentName, + roomId, + roomPassword, + }); + + const updatedTournament = await Tournament.findOneAndUpdate( + { _id: tournament._id }, + { idp: idp._id }, + { new: true } + ); + + return res + .status(201) + .json(new ApiResponse(201, updatedTournament, "IDP Added Successfully!!")); + } catch (error) { + throw new ApiError(500, error.message || "Internal Server Error"); + } +}); + +const editIDP =asyncHandler(async (req,res) =>{ + const { tournamentName, roomId, roomPassword } = req.body; + + if (!tournamentName || !roomId || !roomPassword){ + throw new ApiError(400, "All Fields are Required."); + } + + const tournament = await Tournament.findOne({ name: tournamentName }); + if (!tournament) { + throw new ApiError(404, "Tournament Not Found."); + } + + const idp = await IDP.findOne({ tournamentId: tournament._id }); + if (!idp) { + throw new ApiError(404, "IDP Not Found, create one first!"); + } + + + try { + idp.roomId = roomId; + idp.roomPassword = roomPassword; + + await idp.save(); + + return res + .status(200) + .json(new ApiResponse(200, idp, "IDP Updated Successfully!!")); + + } catch (error) { + throw new ApiError(500, error.message || "Internal Server Error"); + } +}); + +const editTournament = asyncHandler(async (req, res) =>{ + const { + oldName, + name, + matchDate, + matchTime, + registrationEndDate, + registrationEndTime, + totalSlots, + prizePool, + type, + game, + entryFee, + description, + instructions + } = req.body; + + if (!check24HourFormat(matchTime) || !check24HourFormat(registrationEndTime)) { + throw new ApiError(400, "Invalid Time Format. Please Use hh:mm Format."); + } + 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: oldName }); + if (!existingTournament) { + throw new ApiError(404, "Tournament not found."); + } + + try { + const matchTiming = DateTime.fromISO(`${matchDate}T${matchTime}`, { zone: REGION }); + const registrationTiming = DateTime.fromISO(`${registrationEndDate}T${registrationEndTime}`, { zone: REGION }); + const tournament = await Tournament.findOneAndUpdate( + { name: oldName }, + { + $set:{ + name, + 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, + name, + matchDate, + matchTime, + registrationEndDate, + registrationEndTime, + totalSlots, + prizePool, + game, + entryFee, + description, + instructions, + } + }, + { new: true } + ); + return res + .status(200) + .json(new ApiResponse(200, tournament, "Tournament Updated Successfully!!")); + } catch (error) { + throw new ApiError(500, error.message || "Internal Server Error"); + + } +}); + +const deleteTournament = 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."); + } + try { + await Tournament.deleteOne({ name: tournamentName }); + return res + .status(200) + .json(new ApiResponse(200, {}, "Tournament Deleted Successfully!!")); + } catch (error) { + throw new ApiError(500, error.message || "Internal Server Error"); + } +}); + +const postTournamentResult = asyncHandler(async (req, res) => { + const { tournamentName,leaderboard} = req.body; + + if (!tournamentName || !leaderboard) { + throw new ApiError(400, "All Fields are Required."); + } + const tournament = await Tournament.findOne({ name: tournamentName }); + + if (!tournament) { + throw new ApiError(404, "Tournament Not Found."); + } + + const result = await Result.findOne({ tournament: tournament._id }); + + if (result) { + throw new ApiError(400, "Result Already Exists go to the edit route."); + } + + try { + const result = await Result.create({ + tournament: tournament._id, + leaderboard, + }); + + return res + .status(201) + .json(new ApiResponse(201, result, "Tournament Result Posted Successfully")); + } catch (error) { + throw new ApiError(500, error.message || "Internal Server Error"); + } +}); + +const editTournamentResult = asyncHandler(async (req, res) => { + const { tournamentName,leaderboard} = req.body; + + if (!tournamentName || !leaderboard) { + throw new ApiError(400, "All Fields are Required."); + } + const tournament = await Tournament.findOne({ name: tournamentName }); + + if (!tournament) { + throw new ApiError(404, "Tournament Not Found."); + } + + const result = await Result.findOne({ tournament: tournament._id }); + + if (!result) { + throw new ApiError(400, "Result Not Found."); + } + + try { + result.leaderboard = leaderboard; + + await result.save(); + + return res + .status(200) + .json(new ApiResponse(200, result, "Tournament Result Updated Successfully")); + } catch (error) { + throw new ApiError(500, error.message || "Internal Server Error"); + } +}); + +export +{ + addIDP, + editIDP, + createTournament, + editTournament, + deleteTournament, + postTournamentResult, + editTournamentResult +} \ No newline at end of file diff --git a/Backend/src/controllers/tournament.controller.js b/Backend/src/controllers/tournament.controller.js index ebcaa82..68213e6 100644 --- a/Backend/src/controllers/tournament.controller.js +++ b/Backend/src/controllers/tournament.controller.js @@ -8,71 +8,11 @@ import { Tournament } from "../models/tournament.models.js"; import { REGION, GAME_ID } from "../constants.js"; import { check24HourFormat, checkDateFormat } from "../helpers/checkDateTime.js"; -const now = new Date(); - -// admin controlled routes -const createTournament = asyncHandler(async (req, res) => { - const { - name, - matchDate, - matchTime, - registrationEndDate, - registrationEndTime, - totalSlots, - prizePool, - type, - game, - entryFee, - description, - instructions - } = req.body; - if (!check24HourFormat(matchTime) || !check24HourFormat(registrationEndTime)) { - throw new ApiError(400, "Invalid Time Format. Please Use hh:mm Format."); - } - 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 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: matchTiming.toFormat("dd-MM-yyyy"), - matchTime: matchTiming.toFormat("HH:mm"), - registrationEndDate: registrationTiming.toFormat("dd-MM-yyyy"), - registrationEndTime: registrationTiming.toFormat("HH:mm"), - totalSlots, - prizePool, - type, - game, - entryFee, - description, - instructions, - }); - return res - .status(201) - .json( new ApiResponse(201, tournament, "Tournament Created Successfully!!")); - } catch (error) { - throw new ApiError(500, error.message || "Internal Server Error"); - } -}); - -// user accessed routes const getTournaments = asyncHandler(async (req, res) => { try { const tournaments = await Tournament.find({ isActive: true, - isOngoing: true, + //isOngoing: true, }).sort({ createdAt:-1, }).select( @@ -142,7 +82,6 @@ const registerUser = asyncHandler(async (req, res) => { }); export { - createTournament, getTournaments, getTournamentInfo, registerUser, diff --git a/Backend/src/models/idp.models.js b/Backend/src/models/idp.models.js new file mode 100644 index 0000000..1d44085 --- /dev/null +++ b/Backend/src/models/idp.models.js @@ -0,0 +1,23 @@ +import mongoose from 'mongoose'; + +const idpSchema = new mongoose.Schema({ + tournamentId: { + type: mongoose.Schema.Types.ObjectId, + ref: 'Tournament', + required: true + }, + tournamentName: { + type: String, + required: true + }, + roomId: { + type: String, + required: true + }, + roomPassword: { + type: String, + required: true + } +}); + +export const IDP = mongoose.model('IDP', idpSchema); \ No newline at end of file diff --git a/Backend/src/models/result.models.js b/Backend/src/models/result.models.js index 0b9a30b..f8cd9f5 100644 --- a/Backend/src/models/result.models.js +++ b/Backend/src/models/result.models.js @@ -1,5 +1,7 @@ import mongoose , { Schema } from "mongoose"; import {User} from "./user.models.js"; +import {Tournament} from "./tournament.models.js"; +import ApiError from "../utils/ApiError.js"; const resultSchema = new mongoose.Schema( { @@ -13,14 +15,16 @@ const resultSchema = new mongoose.Schema( player: { type: mongoose.Schema.Types.ObjectId, ref: "User", - required: true, + required: false, }, position: { type: Number, required: true, + unique: true, }, username: { type: String, + unique: true, }, }, ], @@ -30,23 +34,50 @@ const resultSchema = new mongoose.Schema( } ); -resultSchema.pre("validate", async function (next) { +resultSchema.pre("save", async function (next) { try { + if (!this.leaderboard || this.leaderboard.length === 0) { + return next(); + } + for (let entry of this.leaderboard) { if (entry.username && !entry.player) { const user = await User.findOne({ username: entry.username }); + if (user) { - entry.player = user._id; - delete entry.username; + entry.player = user._id; // Assign player's ObjectId + //entry.username = undefined; // Remove the username field safely } else { throw new Error(`User with username "${entry.username}" not found.`); } } } + next(); } catch (error) { next(error); } }); +resultSchema.pre("save",async function(next){ + try { + const tournament = await Tournament.findById(this.tournament); + + if(!tournament){ + throw new ApiError(404,"Tournament not found"); + } + + for (let entry of this.leaderboard) { + const isRegistered = tournament.registeredPlayers.includes(entry.player); + + if(!isRegistered){ + throw new ApiError(400,`Player with username ${entry.username} is not registered in the tournament`); + } + } + next(); + } catch (error) { + next(error); + } +}) + export const Result = mongoose.model("Result", resultSchema); \ No newline at end of file diff --git a/Backend/src/models/tournament.models.js b/Backend/src/models/tournament.models.js index bb46248..350ccec 100644 --- a/Backend/src/models/tournament.models.js +++ b/Backend/src/models/tournament.models.js @@ -83,7 +83,10 @@ const tournamentScehma = new Schema({ type: Boolean, default: false, }, - // idp: {}, + idp:{ + type: Schema.Types.ObjectId, + ref: "IDP", + } // refundForm: {}, }, { timestamps: true, diff --git a/Backend/src/routes/admin.routes.js b/Backend/src/routes/admin.routes.js new file mode 100644 index 0000000..3b4c28b --- /dev/null +++ b/Backend/src/routes/admin.routes.js @@ -0,0 +1,25 @@ +import { Router } from 'express'; +import verifyToken from '../middlewares/auth.middleware.js'; +import isAdmin from '../middlewares/isAdmin.middleware.js'; +import +{ + addIDP, + editIDP, + createTournament, + editTournament, + deleteTournament, + postTournamentResult, + editTournamentResult +} from "../controllers/admin.controller.js"; + +const router = Router(); + +router.route("/createTournament").post(verifyToken, isAdmin, createTournament); +router.route("/addIDP").post(verifyToken, isAdmin, addIDP); +router.route("/editIDP").patch(verifyToken, isAdmin, editIDP); +router.route("/editTournament").put(verifyToken, isAdmin, editTournament); +router.route("/deleteTournament").delete(verifyToken, isAdmin, deleteTournament); +router.route("/postResult").post(verifyToken, isAdmin, postTournamentResult); +router.route("/editResult").put(verifyToken, isAdmin, editTournamentResult); + +export default router; \ No newline at end of file diff --git a/Backend/src/routes/tournament.routes.js b/Backend/src/routes/tournament.routes.js index 63255f5..5554b20 100644 --- a/Backend/src/routes/tournament.routes.js +++ b/Backend/src/routes/tournament.routes.js @@ -1,23 +1,13 @@ import { Router } from "express"; import verifyToken from "../middlewares/auth.middleware.js"; -import isAdmin from "../middlewares/isAdmin.middleware.js"; import { - createTournament, getTournaments, getTournamentInfo, registerUser, } from "../controllers/tournament.controller.js"; -import { postResult } from "../controllers/result.controller.js"; - const router = Router(); -//admin controlled routes -router.route("/createTournament").post(isAdmin, createTournament); - -// router.route("/postResult").post(postResult); -// router.route("/getResults").get(verifyToken,getResults); - //user accessed routes router.route("/getTournaments").get(verifyToken, getTournaments); router.route("/getTournamentInfo").get(verifyToken, getTournamentInfo);