Feat/controllers - refactor entire backend and new admin interface #75

Merged
KevinMidboe merged 117 commits from feat/controllers into master 2021-02-19 00:19:52 +00:00
2 changed files with 232 additions and 235 deletions
Showing only changes of commit 87257fd5b2 - Show all commits

View 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
};

View File

@@ -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
}; };