Feat/controllers - refactor entire backend and new admin interface #75
171
api/controllers/lotteryController.js
Normal file
171
api/controllers/lotteryController.js
Normal file
@@ -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
|
||||||
|
};
|
||||||
296
api/lottery.js
296
api/lottery.js
@@ -1,245 +1,83 @@
|
|||||||
const path = require("path");
|
const path = require("path");
|
||||||
|
const crypto = require("crypto");
|
||||||
|
|
||||||
const Attendee = require(path.join(__dirname, "/schemas/Attendee"));
|
const Attendee = require(path.join(__dirname, "/schemas/Attendee"));
|
||||||
const PreLotteryWine = require(path.join(__dirname, "/schemas/PreLotteryWine"));
|
const PreLotteryWine = require(path.join(__dirname, "/schemas/PreLotteryWine"));
|
||||||
const VirtualWinner = require(path.join(__dirname, "/schemas/VirtualWinner"));
|
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 {
|
const archive = (date, raffles, stolen, wines) => {
|
||||||
constructor(message = "User not found.") {
|
const { blue, red, yellow, green } = raffles;
|
||||||
super(message);
|
const bought = blue + red + yellow + green;
|
||||||
this.name = "UserNotFound";
|
date = date.setHours(0, 0, 0, 0);
|
||||||
this.statusCode = 404;
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO log missing user
|
return Lottery.findOneAndUpdate(
|
||||||
}
|
{ date },
|
||||||
|
{
|
||||||
class WineNotFound extends Error {
|
date,
|
||||||
constructor(message = "Wine not found.") {
|
blue,
|
||||||
super(message);
|
red,
|
||||||
this.name = "WineNotFound";
|
yellow,
|
||||||
this.statusCode = 404;
|
green,
|
||||||
}
|
bought,
|
||||||
|
stolen,
|
||||||
// TODO log missing user
|
wines
|
||||||
}
|
},
|
||||||
|
{ upsert: true }
|
||||||
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();
|
|
||||||
})
|
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
const allWinners = (isAdmin = false) => {
|
const lotteryByDate = date => {
|
||||||
if (!isAdmin) {
|
const startOfDay = new Date(date.setHours(0, 0, 0, 0));
|
||||||
return VirtualWinner.find().then(winners => winners.map(redactWinnerInfoMapper));
|
const endOfDay = new Date(date.setHours(24, 59, 59, 99));
|
||||||
} else {
|
|
||||||
return VirtualWinner.find();
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const winnerById = (id, isAdmin = false) => {
|
const query = [
|
||||||
return VirtualWinner.findOne({ _id: id }).then(winner => {
|
{
|
||||||
if (winner == null) {
|
$match: {
|
||||||
throw new WinnerNotFound();
|
date: {
|
||||||
|
$gte: startOfDay,
|
||||||
|
$lte: endOfDay
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
$lookup: {
|
||||||
|
from: "wines",
|
||||||
|
localField: "wines",
|
||||||
|
foreignField: "_id",
|
||||||
|
as: "wines"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
];
|
||||||
|
|
||||||
if (!isAdmin) {
|
const aggregateLottery = Lottery.aggregate(query);
|
||||||
return redactWinnerInfoMapper(winner);
|
return aggregateLottery.project("-_id -__v").then(lotteries => {
|
||||||
} else {
|
if (lotteries.length == 0) {
|
||||||
return winner;
|
throw new LotteryByDateNotFound(date);
|
||||||
}
|
}
|
||||||
|
return lotteries[0];
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
const deleteWinnerById = id => {
|
const allLotteries = () => {
|
||||||
return VirtualWinner.findOne({ _id: id }).then(winner => {
|
return Lottery.find()
|
||||||
if (winner == null) {
|
.select("-_id -__v")
|
||||||
throw new WinnerNotFound();
|
.populate("wines");
|
||||||
}
|
|
||||||
|
|
||||||
return VirtualWinner.deleteOne({ _id: id }).then(_ => winner);
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
const deleteWinners = () => {
|
|
||||||
return VirtualWinner.deleteMany();
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const drawWinner = async () => {
|
const drawWinner = async () => {
|
||||||
let allContestants = await Attendee.find({ winner: false });
|
let allContestants = await Attendee.find({ winner: false });
|
||||||
|
|
||||||
if (allContestants.length == 0) {
|
if (allContestants.length == 0) {
|
||||||
throw new NoMoreAttendeesToWinError();
|
throw new NoMoreAttendeesToWin();
|
||||||
}
|
}
|
||||||
|
|
||||||
let raffleColors = [];
|
let raffleColors = [];
|
||||||
@@ -278,7 +116,7 @@ const drawWinner = async () => {
|
|||||||
tries++;
|
tries++;
|
||||||
}
|
}
|
||||||
if (contestantsToChooseFrom == undefined) {
|
if (contestantsToChooseFrom == undefined) {
|
||||||
throw new CouldNotFindNewWinnerAfterNTriesError(maxTries);
|
throw new CouldNotFindNewWinnerAfterNTries(maxTries);
|
||||||
}
|
}
|
||||||
|
|
||||||
let attendeeListDemocratic = [];
|
let attendeeListDemocratic = [];
|
||||||
@@ -360,20 +198,8 @@ function shuffle(array) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
allAttendees,
|
drawWinner,
|
||||||
addAttendee,
|
archive,
|
||||||
updateAttendeeById,
|
lotteryByDate,
|
||||||
deleteAttendeeById,
|
allLotteries
|
||||||
deleteAttendees,
|
|
||||||
allWines,
|
|
||||||
addWines,
|
|
||||||
updateWineById,
|
|
||||||
deleteWineById,
|
|
||||||
deleteWines,
|
|
||||||
addWinners,
|
|
||||||
allWinners,
|
|
||||||
winnerById,
|
|
||||||
deleteWinnerById,
|
|
||||||
deleteWinners,
|
|
||||||
drawWinner
|
|
||||||
};
|
};
|
||||||
|
|||||||
Reference in New Issue
Block a user