From 5e018f071d645c912b7d16764f12bf21c819bd68 Mon Sep 17 00:00:00 2001 From: KevinMidboe Date: Tue, 26 Jan 2021 22:38:27 +0100 Subject: [PATCH] Prize distribution for selecting wines. Moved the same functionality out from lottery.js and simplified a bit. Now the backend also sends with wines to pick from. When hitting the controller we check that the user is the next user in line. --- .../prizeDistributionController.js | 90 +++++++++++++++ api/prizeDistribution.js | 109 ++++++++++++++++++ 2 files changed, 199 insertions(+) create mode 100644 api/controllers/prizeDistributionController.js create mode 100644 api/prizeDistribution.js diff --git a/api/controllers/prizeDistributionController.js b/api/controllers/prizeDistributionController.js new file mode 100644 index 0000000..b0c176a --- /dev/null +++ b/api/controllers/prizeDistributionController.js @@ -0,0 +1,90 @@ +const path = require("path"); + +const prizeDistribution = require(path.join(__dirname, "../prizeDistribution")); +const winner = require(path.join(__dirname, "../winner")); +const message = require(path.join(__dirname, "../message")); + +const start = async (req, res) => { + const allWinners = await winners.allWinners(); + if (allWinners.length === 0) { + return res.status(503).send({ + message: "No winners left.", + success: false + }); + } + + const laterWinners = allWinners.slice(1); + + return prizeDistribution + .notifyNextWinner() + .then(_ => message.sendInitialMessageToWinners(laterWinners)) + .then(_ => + res.send({ + message: `Send link to first winner and notified everyone else.`, + success: true + }) + ) + .catch(error => { + const { statusCode, message } = error; + + return res.status(statusCode || 500).send({ + message: message || "Unexpected error occured while starting prize distribution.", + success: false + }); + }); +}; + +const getPrizesForWinnerById = (req, res) => { + const { id } = req.params; + + return prizeDistribution + .verifyWinnerNextInLine(id) + .then(_ => lottery.allWines()) + .then(wines => + res.send({ + wines: wines, + message: "Wines to select from", + success: true + }) + ) + .catch(error => { + const { statusCode, message } = error; + + return res.status(statusCode || 500).send({ + message: message || "Unexpected error occured while fetching prizes.", + success: false + }); + }); +}; + +const submitPrizeForWinnerById = async (req, res) => { + const { id } = req.params; + const { wine } = req.body; + + const winner = await prizeDistribution.verifyWinnerNextInLine(id); + const prelotteryWine = await lottery.wineById(wine.id); + + return prizeDistribution + .claimPrize(winner, prelotteryWine) + .then(_ => prizeDistribution.notifyNextWinner()) + .then(_ => + res.send({ + message: `${winner.name} successfully claimed prize: ${wine.name}`, + success: true + }) + ) + .catch(error => { + const { statusCode, message } = error; + + return res.status(statusCode || 500).send({ + message: message || "Unexpected error occured while claiming prize.", + success: false + }); + }); +}; + +module.exports = { + start, + getPrizesForWinnerById, + submitPrizeForWinnerById +}; diff --git a/api/prizeDistribution.js b/api/prizeDistribution.js new file mode 100644 index 0000000..e530574 --- /dev/null +++ b/api/prizeDistribution.js @@ -0,0 +1,109 @@ +const path = require("path"); + +const Wine = require(path.join(__dirname, "/schemas/Wine")); +const PreLotteryWine = require(path.join(__dirname, "/schemas/PreLotteryWine")); +const VirtualWinner = require(path.join(__dirname, "/schemas/VirtualWinner")); + +const message = require(path.join(__dirname, "/message")); +const highscoreRepository = require(path.join(__dirname, "/winner")); +const wineRepository = require(path.join(__dirname, "/wine")); +const lottery = require(path.join(__dirname, "/lottery")); + +const { WinnerNotFound, WineSelectionWinnerNotNextInLine, WinnersTimelimitExpired } = require(path.join( + __dirname, + "/vinlottisErrors" +)); + +const verifyWinnerNextInLine = async id => { + let foundWinner = await VirtualWinner.findOne({ id: id }); + + if (!foundWinner) { + throw new WinnerNotFound(); + } else if (foundWinner.timestamp_limit < new Date().getTime()) { + throw new WinnersTimelimitExpired(); + } + + let allWinners = await VirtualWinner.find().sort({ timestamp_drawn: 1 }); + if ( + allWinners[0].id != foundWinner.id || + foundWinner.timestamp_limit == undefined || + foundWinner.timestamp_sent == undefined + ) { + throw new WineSelectionWinnerNotNextInLine(); + } + + return Promise.resolve(foundWinner); +}; + +const claimPrize = (winner, wine) => { + return wineRepository + .addWine(wine) + .then(_ => lottery.deleteWineById(wine.id)) // prelotteryWine.deleteById + .then(_ => highscoreRepository.addWinnerWithWine(winner, wine)) // wines.js : addWine + .then(_ => lottery.addWinnerWithWine(winner, wine)) + .then(_ => message.sendWineConfirmation(winner, wine)); +}; + +const notifyNextWinner = async () => { + let nextWinner = undefined; + + const winnersLeft = await VirtualWinner.find().sort({ timestamp_drawn: 1 }); + const winesLeft = await PreLotteryWine.find(); + + if (winnersLeft.length > 1) { + console.log("multiple winners left, choose next in line"); + nextWinner = winnersLeft[0]; // multiple winners left, choose next in line + } else if (winnersLeft.length == 1 && winesLeft.length > 1) { + console.log("one winner left, but multiple wines"); + nextWinner = winnersLeft[0]; // one winner left, but multiple wines + } else if (winnersLeft.length == 1 && winesLeft.length == 1) { + console.log("one winner and one wine left, choose for user"); + nextWinner = winnersLeft[0]; // one winner and one wine left, choose for user + wine = winesLeft[0]; + return claimPrize(nextWinner, wine); + } + + if (nextWinner) { + return message.sendPrizeSelectionLink(nextWinner).then(_ => startTimeout(nextWinner.id)); + } else { + console.info("All winners notified. Could start cleanup here."); + return Promise.resolve({ + message: "All winners notified." + }); + } +}; + +// these need to be register somewhere to cancel if something +// goes wrong and we want to start prize distribution again +function startTimeout(id) { + const minute = 60000; + const minutesForTimeout = 10; + + console.log(`Starting timeout for user ${id}.`); + console.log(`Timeout duration: ${minutesForTimeout * minute}`); + setTimeout(async () => { + let virtualWinner = await VirtualWinner.findOne({ id: id }); + if (!virtualWinner) { + console.log(`Timeout done for user ${id}, but user has already sent data.`); + return; + } + console.log(`Timeout done for user ${id}, sending update to user.`); + + message.sendWineSelectMessageTooLate(virtualWinner); + + virtualWinner.timestamp_drawn = new Date().getTime(); + virtualWinner.timestamp_limit = null; + virtualWinner.timestamp_sent = null; + await virtualWinner.save(); + + findAndNotifyNextWinner(); + }, minutesForTimeout * minute); + + return Promise.resolve(); +} + +module.exports = { + verifyWinnerNextInLine, + claimPrize, + notifyNextWinner +};