From 87257fd5b2d4e3331208dcd0c6e60f3b147e42e6 Mon Sep 17 00:00:00 2001 From: KevinMidboe Date: Tue, 26 Jan 2021 22:56:08 +0100 Subject: [PATCH] Moved everything not lottery out. Moved all logic related to lotteryAttendees, lotteryWinners and prelotteryWines into each their repository and controller. Now only holds draw, archive and get archived lotteries. --- api/controllers/lotteryController.js | 171 ++++++++++++++++ api/lottery.js | 296 ++++++--------------------- 2 files changed, 232 insertions(+), 235 deletions(-) create mode 100644 api/controllers/lotteryController.js diff --git a/api/controllers/lotteryController.js b/api/controllers/lotteryController.js new file mode 100644 index 0000000..a107415 --- /dev/null +++ b/api/controllers/lotteryController.js @@ -0,0 +1,171 @@ +const path = require("path"); +const lotteryRepository = require(path.join(__dirname, "../lottery")); + +const drawWinner = (req, res) => { + return lotteryRepository + .drawWinner() + .then(({ winner, color, winners }) => { + var io = req.app.get("socketio"); + io.emit("winner", { + color: color, + name: winner.name, + winner_count: winners.length + 1 + }); + + return { winner, color, winners }; + }) + .then(({ winner, color, winners }) => + res.send({ + color: color, + winner: winner, + success: true + }) + ) + .catch(error => { + const { statusCode, message } = error; + + return res.status(statusCode || 500).send({ + message: message || "Unexpected error occured while drawing winner.", + success: false + }); + }); +}; + +const archiveLottery = (req, res) => { + const { lottery } = req.body; + if (lottery == undefined || !lottery instanceof Object) { + return res.status(400).send({ + message: "Missing lottery object.", + success: false + }); + } + + let { stolen, date, raffles, wines } = lottery; + stolen = stolen !== undefined ? stolen : 0; // default = 0 + date = date !== undefined ? date : new Date(); // default + + const validDateFormat = new RegExp("d{4}-d{2}-d{2}"); + if (!validDateFormat.test(date) || isNaN(date)) { + return res.status(400).send({ + message: "Date must be defined as 'yyyy-mm-dd'.", + success: false + }); + } else { + date = Date.parse(date, "yyyy-MM-dd"); + } + + return verifyLotteryPayload(raffles, stolen, wines) + .then(_ => lottery.archive(date, raffles, stolen, wines)) + .catch(error => { + const { statusCode, message } = error; + + return res.status(statusCode || 500).send({ + message: message || "Unexpected error occured while submitting lottery.", + success: false + }); + }); +}; + +const lotteryByDate = (req, res) => { + const { epoch } = req.params; + + if (!/^\d+$/.test(epoch)) { + return res.status(400).send({ + message: "Last parameter must be epoch (in seconds).", + success: false + }); + } + const date = new Date(Number(epoch) * 1000); + + return lotteryRepository + .lotteryByDate(date) + .then(lottery => + res.send({ + lottery, + message: `Lottery for date: ${dateToDateString(date)}/${epoch}.`, + success: true + }) + ) + .catch(error => { + const { statusCode, message } = error; + + return res.status(statusCode || 500).send({ + message: message || "Unexpected error occured while fetching lottery by date.", + success: false + }); + }); +}; + +const allLotteries = (req, res) => { + const isAdmin = req.isAuthenticated() || true; + + return lotteryRepository + .allLotteries(isAdmin) + .then(lotteries => + res.send({ + lotteries, + message: "All lotteries.", + success: true + }) + ) + .catch(error => { + const { statusCode, message } = error; + + return res.status(statusCode || 500).send({ + message: message || "Unexpected error occured while fetching all lotteries.", + success: false + }); + }); +}; + +function verifyLotteryPayload(raffles, stolen, wines) { + return new Promise((resolve, reject) => { + if (raffles == undefined || !raffles instanceof Array) { + reject({ + message: "Raffles must be array.", + status: 400 + }); + } + + const requiredColors = [raffles["red"], raffles["blue"], raffles["green"], raffles["yellow"]]; + const correctColorsTypes = requiredColors.filter(color => typeof color === "number"); + if (requiredColors.length !== correctColorsTypes.length) { + reject({ + message: + "Incorrect or missing raffle colors, required type Number for keys: 'blue', 'red', 'green' & 'yellow'.", + status: 400 + }); + } + + if (stolen == undefined || (isNaN(stolen) && stolen >= 0)) { + reject({ + message: "Number of stolen raffles must be positive integer or 0.", + status: 400 + }); + } + + if (wines == undefined || !wines instanceof Array) { + reject({ + message: "Wines must be array.", + status: 400 + }); + } + + resolve(); + }); +} + +function dateToDateString(date) { + const ye = new Intl.DateTimeFormat("en", { year: "numeric" }).format(date); + const mo = new Intl.DateTimeFormat("en", { month: "2-digit" }).format(date); + const da = new Intl.DateTimeFormat("en", { day: "2-digit" }).format(date); + + return `${ye}-${mo}-${da}`; +} + +module.exports = { + drawWinner, + archiveLottery, + lotteryByDate, + allLotteries +}; diff --git a/api/lottery.js b/api/lottery.js index dee8b96..aec1c4a 100644 --- a/api/lottery.js +++ b/api/lottery.js @@ -1,245 +1,83 @@ const path = require("path"); +const crypto = require("crypto"); const Attendee = require(path.join(__dirname, "/schemas/Attendee")); const PreLotteryWine = require(path.join(__dirname, "/schemas/PreLotteryWine")); const VirtualWinner = require(path.join(__dirname, "/schemas/VirtualWinner")); +const Lottery = require(path.join(__dirname, "/schemas/Purchase")); -const crypto = require("crypto"); +const Message = require(path.join(__dirname, "/message")); +const { + WinnerNotFound, + NoMoreAttendeesToWin, + CouldNotFindNewWinnerAfterNTries, + LotteryByDateNotFound +} = require(path.join(__dirname, "/vinlottisErrors")); -class UserNotFound extends Error { - constructor(message = "User not found.") { - super(message); - this.name = "UserNotFound"; - this.statusCode = 404; - } +const archive = (date, raffles, stolen, wines) => { + const { blue, red, yellow, green } = raffles; + const bought = blue + red + yellow + green; + date = date.setHours(0, 0, 0, 0); - // TODO log missing user -} - -class WineNotFound extends Error { - constructor(message = "Wine not found.") { - super(message); - this.name = "WineNotFound"; - this.statusCode = 404; - } - - // TODO log missing user -} - -class WinnerNotFound extends Error { - constructor(message = "Winner not found.") { - super(message); - this.name = "WinnerNotFound"; - this.statusCode = 404; - } - - // TODO log missing user -} - -class NoMoreAttendeesToWinError extends Error { - constructor(message = "No more attendees left to drawn from.") { - super(message); - this.name = "NoMoreAttendeesToWinError"; - this.statusCode = 404; - } -} - -class CouldNotFindNewWinnerAfterNTriesError extends Error { - constructor(tries) { - let message = `Could not a new winner after ${tries} tries.`; - super(message); - this.name = "CouldNotFindNewWinnerAfterNTriesError"; - this.statusCode = 404; - } -} - -const redactAttendeeInfoMapper = attendee => { - return { - name: attendee.name, - raffles: attendee.red + attendee.blue + attendee.yellow + attendee.green, - red: attendee.red, - blue: attendee.blue, - green: attendee.green, - yellow: attendee.yellow - }; -}; - -const redactWinnerInfoMapper = winner => { - return { - name: winner.name, - color: winner.color - }; -}; - -const allAttendees = (isAdmin = false) => { - if (!isAdmin) { - return Attendee.find().then(attendees => attendees.map(redactAttendeeInfoMapper)); - } else { - return Attendee.find(); - } -}; - -const addAttendee = attendee => { - const { name, red, blue, green, yellow, phoneNumber } = attendee; - - let newAttendee = new Attendee({ - name, - red, - blue, - green, - yellow, - phoneNumber, - winner: false - }); - - return newAttendee.save(); -}; - -const updateAttendeeById = (id, updateModel) => { - return Attendee.findOne({ _id: id }).then(attendee => { - if (attendee == null) { - throw new UserNotFound(); - } - - const updatedAttendee = { - name: updateModel.name != null ? updateModel.name : attendee.name, - green: updateModel.green != null ? updateModel.green : attendee.green, - red: updateModel.red != null ? updateModel.red : attendee.red, - blue: updateModel.blue != null ? updateModel.blue : attendee.blue, - yellow: updateModel.yellow != null ? updateModel.yellow : attendee.yellow, - phoneNumber: updateModel.phoneNumber != null ? updateModel.phoneNumber : attendee.phoneNumber, - winner: updateModel.winner != null ? updateModel.winner : attendee.winner - }; - - return Attendee.updateOne({ _id: id }, updatedAttendee).then(_ => updatedAttendee); - }); -}; - -const deleteAttendeeById = id => { - return Attendee.findOne({ _id: id }).then(attendee => { - if (attendee == null) { - throw new UserNotFound(); - } - - return Attendee.deleteOne({ _id: id }).then(_ => attendee); - }); -}; - -const deleteAttendees = () => { - return Attendee.deleteMany(); -}; - -const allWines = () => { - return PreLotteryWine.find(); -}; - -const addWines = wines => { - const prelotteryWines = wines.map(wine => { - let newPrelotteryWine = new PreLotteryWine({ - name: wine.name, - vivinoLink: wine.vivinoLink, - rating: wine.rating, - image: wine.image, - price: wine.price, - country: wine.country, - id: wine.id - }); - - return newPrelotteryWine.save(); - }); - - return Promise.all(prelotteryWines); -}; - -const updateWineById = (id, updateModel) => { - return PreLotteryWine.findOne({ _id: id }).then(wine => { - if (wine == null) { - throw new WineNotFound(); - } - - const updatedWine = { - name: updateModel.name != null ? updateModel.name : wine.name, - vivinoLink: updateModel.vivinoLink != null ? updateModel.vivinoLink : wine.vivinoLink, - rating: updateModel.rating != null ? updateModel.rating : wine.rating, - image: updateModel.image != null ? updateModel.image : wine.image, - price: updateModel.price != null ? updateModel.price : wine.price, - country: updateModel.country != null ? updateModel.country : wine.country, - id: updateModel.id != null ? updateModel.id : wine.id - }; - - return PreLotteryWine.updateOne({ _id: id }, updatedWine).then(_ => updatedWine); - }); -}; - -const deleteWineById = id => { - return PreLotteryWine.findOne({ _id: id }).then(wine => { - if (wine == null) { - throw new WineNotFound(); - } - - return PreLotteryWine.deleteOne({ _id: id }).then(_ => wine); - }); -}; - -const deleteWines = () => { - return PreLotteryWine.deleteMany(); -}; - -const addWinners = winners => { - return Promise.all( - winners.map(winner => { - let newWinnerElement = new VirtualWinner({ - name: winner.name, - color: winner.color, - timestamp_drawn: new Date().getTime() - }); - - return newWinnerElement.save(); - }) + return Lottery.findOneAndUpdate( + { date }, + { + date, + blue, + red, + yellow, + green, + bought, + stolen, + wines + }, + { upsert: true } ); }; -const allWinners = (isAdmin = false) => { - if (!isAdmin) { - return VirtualWinner.find().then(winners => winners.map(redactWinnerInfoMapper)); - } else { - return VirtualWinner.find(); - } -}; +const lotteryByDate = date => { + const startOfDay = new Date(date.setHours(0, 0, 0, 0)); + const endOfDay = new Date(date.setHours(24, 59, 59, 99)); -const winnerById = (id, isAdmin = false) => { - return VirtualWinner.findOne({ _id: id }).then(winner => { - if (winner == null) { - throw new WinnerNotFound(); + const query = [ + { + $match: { + date: { + $gte: startOfDay, + $lte: endOfDay + } + } + }, + { + $lookup: { + from: "wines", + localField: "wines", + foreignField: "_id", + as: "wines" + } } + ]; - if (!isAdmin) { - return redactWinnerInfoMapper(winner); - } else { - return winner; + const aggregateLottery = Lottery.aggregate(query); + return aggregateLottery.project("-_id -__v").then(lotteries => { + if (lotteries.length == 0) { + throw new LotteryByDateNotFound(date); } + return lotteries[0]; }); }; -const deleteWinnerById = id => { - return VirtualWinner.findOne({ _id: id }).then(winner => { - if (winner == null) { - throw new WinnerNotFound(); - } - - return VirtualWinner.deleteOne({ _id: id }).then(_ => winner); - }); -}; - -const deleteWinners = () => { - return VirtualWinner.deleteMany(); +const allLotteries = () => { + return Lottery.find() + .select("-_id -__v") + .populate("wines"); }; const drawWinner = async () => { let allContestants = await Attendee.find({ winner: false }); if (allContestants.length == 0) { - throw new NoMoreAttendeesToWinError(); + throw new NoMoreAttendeesToWin(); } let raffleColors = []; @@ -278,7 +116,7 @@ const drawWinner = async () => { tries++; } if (contestantsToChooseFrom == undefined) { - throw new CouldNotFindNewWinnerAfterNTriesError(maxTries); + throw new CouldNotFindNewWinnerAfterNTries(maxTries); } let attendeeListDemocratic = []; @@ -360,20 +198,8 @@ function shuffle(array) { } module.exports = { - allAttendees, - addAttendee, - updateAttendeeById, - deleteAttendeeById, - deleteAttendees, - allWines, - addWines, - updateWineById, - deleteWineById, - deleteWines, - addWinners, - allWinners, - winnerById, - deleteWinnerById, - deleteWinners, - drawWinner + drawWinner, + archive, + lotteryByDate, + allLotteries };