Alle foreslåtte viner
-Ingen har foreslått noe enda!
-diff --git a/api/attendee.js b/api/attendee.js
new file mode 100644
index 0000000..e26cbf8
--- /dev/null
+++ b/api/attendee.js
@@ -0,0 +1,81 @@
+const path = require("path");
+
+const Attendee = require(path.join(__dirname, "/schemas/Attendee"));
+const { UserNotFound } = require(path.join(__dirname, "/vinlottisErrors"));
+
+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 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().then(_ => newAttendee);
+};
+
+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();
+};
+
+module.exports = {
+ allAttendees,
+ addAttendee,
+ updateAttendeeById,
+ deleteAttendeeById,
+ deleteAttendees
+};
diff --git a/api/chatHistory.js b/api/controllers/chatController.js
similarity index 64%
rename from api/chatHistory.js
rename to api/controllers/chatController.js
index 9578176..b680cb7 100644
--- a/api/chatHistory.js
+++ b/api/controllers/chatController.js
@@ -1,5 +1,6 @@
const path = require("path");
-const { history, clearHistory } = require(path.join(__dirname + "/../api/redis"));
+const { history, clearHistory } = require(path.join(__dirname + "/../redis"));
+console.log("loading chat");
const getAllHistory = (req, res) => {
let { page, limit } = req.query;
@@ -8,19 +9,23 @@ const getAllHistory = (req, res) => {
return history(page, limit)
.then(messages => res.json(messages))
- .catch(error => res.status(500).json({
- message: error.message,
- success: false
- }));
+ .catch(error =>
+ res.status(500).json({
+ message: error.message,
+ success: false
+ })
+ );
};
const deleteHistory = (req, res) => {
return clearHistory()
.then(message => res.json(message))
- .catch(error => res.status(500).json({
- message: error.message,
- success: false
- }));
+ .catch(error =>
+ res.status(500).json({
+ message: error.message,
+ success: false
+ })
+ );
};
module.exports = {
diff --git a/api/controllers/historyController.js b/api/controllers/historyController.js
new file mode 100644
index 0000000..777ed72
--- /dev/null
+++ b/api/controllers/historyController.js
@@ -0,0 +1,261 @@
+const path = require("path");
+const historyRepository = require(path.join(__dirname, "../history"));
+
+const sortOptions = ["desc", "asc"];
+const includeWinesOptions = ["true", "false"];
+
+const all = (req, res) => {
+ const { sort, includeWines } = req.query;
+
+ if (sort !== undefined && !sortOptions.includes(sort)) {
+ return res.status(400).send({
+ message: `Sort option must be: '${sortOptions.join(", ")}'`,
+ success: false
+ });
+ }
+
+ if (includeWines !== undefined && !includeWinesOptions.includes(includeWines)) {
+ return res.status(400).send({
+ message: `includeWines option must be: '${includeWinesOptions.join(", ")}'`,
+ success: false
+ });
+ }
+
+ return historyRepository
+ .all(includeWines == "true")
+ .then(winners =>
+ res.send({
+ winners: sort !== "asc" ? winners : winners.reverse(),
+ success: true
+ })
+ )
+ .catch(error => {
+ const { statusCode, message } = error;
+
+ return res.status(statusCode || 500).send({
+ success: false,
+ message: message || "Unable to fetch winners."
+ });
+ });
+};
+
+const byDate = (req, res) => {
+ let { date } = req.params;
+
+ const regexDate = new RegExp("^\\d{4}-\\d{2}-\\d{2}$");
+ if (!isNaN(date)) {
+ date = new Date(new Date(parseInt(date * 1000)).setHours(0, 0, 0, 0));
+ } else if (regexDate.test(date)) {
+ date = new Date(date);
+ } else if (date !== undefined) {
+ return res.status(400).send({
+ message: "Invalid date parameter, allowed epoch seconds or YYYY-MM-DD.",
+ success: false
+ });
+ }
+
+ return historyRepository
+ .byDate(date)
+ .then(winners =>
+ res.send({
+ date: date,
+ winners: winners,
+ success: true
+ })
+ )
+ .catch(error => {
+ const { statusCode, message } = error;
+
+ return res.status(statusCode || 500).send({
+ success: false,
+ message: message || "Unable to fetch winner by date."
+ });
+ });
+};
+
+const groupByDate = (req, res) => {
+ const { sort, includeWines } = req.query;
+
+ if (sort !== undefined && !sortOptions.includes(sort)) {
+ return res.status(400).send({
+ message: `Sort option must be: '${sortOptions.join(", ")}'`,
+ success: false
+ });
+ }
+
+ if (includeWines !== undefined && !includeWinesOptions.includes(includeWines)) {
+ return res.status(400).send({
+ message: `includeWines option must be: '${includeWinesOptions.join(", ")}'`,
+ success: false
+ });
+ }
+
+ return historyRepository
+ .groupByDate(includeWines == "true", sort)
+ .then(lotteries =>
+ res.send({
+ lotteries: lotteries,
+ success: true
+ })
+ )
+ .catch(error => {
+ const { statusCode, message } = error;
+
+ return res.status(statusCode || 500).send({
+ success: false,
+ message: message || "Unable to fetch winner by date."
+ });
+ });
+};
+
+const latest = (req, res) => {
+ return historyRepository
+ .latest()
+ .then(winners =>
+ res.send({
+ ...winners,
+ success: true
+ })
+ )
+ .catch(error => {
+ const { statusCode, message } = error;
+
+ return res.status(statusCode || 500).send({
+ success: false,
+ message: message || "Unable to fetch winner by date."
+ });
+ });
+};
+
+const byName = (req, res) => {
+ const { name } = req.params;
+ const { sort } = req.query;
+
+ if (sort !== undefined && !sortOptions.includes(sort)) {
+ return res.status(400).send({
+ message: `Sort option must be: '${sortOptions.join(", ")}'`,
+ success: false
+ });
+ }
+
+ return historyRepository
+ .byName(name, sort)
+ .then(winner =>
+ res.send({
+ winner: winner,
+ success: true
+ })
+ )
+ .catch(error => {
+ const { statusCode, message } = error;
+
+ return res.status(statusCode || 500).send({
+ success: false,
+ message: message || "Unable to fetch winner by name."
+ });
+ });
+};
+
+const search = (req, res) => {
+ const { name, sort } = req.query;
+
+ if (sort !== undefined && !sortOptions.includes(sort)) {
+ return res.status(400).send({
+ message: `Sort option must be: '${sortOptions.join(", ")}'`,
+ success: false
+ });
+ }
+
+ return historyRepository
+ .search(name, sort)
+ .then(winners =>
+ res.send({
+ winners: winners || [],
+ success: true
+ })
+ )
+ .catch(error => {
+ const { statusCode, message } = error;
+
+ return res.status(statusCode || 500).send({
+ success: false,
+ message: message || "Unable to fetch winner by name."
+ });
+ });
+};
+
+const groupByColor = (req, res) => {
+ const { includeWines } = req.query;
+
+ if (includeWines !== undefined && !includeWinesOptions.includes(includeWines)) {
+ return res.status(400).send({
+ message: `includeWines option must be: '${includeWinesOptions.join(", ")}'`,
+ success: false
+ });
+ }
+
+ return historyRepository
+ .groupByColor(includeWines == "true")
+ .then(colors =>
+ res.send({
+ colors: colors,
+ success: true
+ })
+ )
+ .catch(error => {
+ const { statusCode, message } = error;
+
+ return res.status(statusCode || 500).send({
+ success: false,
+ message: message || "Unable to fetch winners by color."
+ });
+ });
+};
+
+const orderByWins = (req, res) => {
+ let { includeWines, limit } = req.query;
+
+ if (includeWines !== undefined && !includeWinesOptions.includes(includeWines)) {
+ return res.status(400).send({
+ message: `includeWines option must be: '${includeWinesOptions.join(", ")}'`,
+ success: false
+ });
+ }
+
+ if (limit && isNaN(limit)) {
+ return res.status(400).send({
+ message: "If limit query parameter is provided it must be a number",
+ success: false
+ });
+ } else if (!!!isNaN(limit)) {
+ limit = Number(limit);
+ }
+
+ return historyRepository
+ .orderByWins(includeWines == "true", limit)
+ .then(winners =>
+ res.send({
+ winners: winners,
+ success: true
+ })
+ )
+ .catch(error => {
+ const { statusCode, message } = error;
+
+ return res.status(statusCode || 500).send({
+ success: false,
+ message: message || "Unable to fetch winners by color."
+ });
+ });
+};
+
+module.exports = {
+ all,
+ byDate,
+ groupByDate,
+ latest,
+ byName,
+ search,
+ groupByColor,
+ orderByWins
+};
diff --git a/api/controllers/lotteryAttendeeController.js b/api/controllers/lotteryAttendeeController.js
new file mode 100644
index 0000000..911aa16
--- /dev/null
+++ b/api/controllers/lotteryAttendeeController.js
@@ -0,0 +1,135 @@
+const path = require("path");
+const attendeeRepository = require(path.join(__dirname, "../attendee"));
+
+const allAttendees = (req, res) => {
+ const isAdmin = req.isAuthenticated();
+
+ return attendeeRepository
+ .allAttendees(isAdmin)
+ .then(attendees =>
+ res.send({
+ attendees: attendees,
+ success: true
+ })
+ )
+ .catch(error => {
+ const { statusCode, message } = error;
+
+ return res.status(statusCode || 500).send({
+ success: false,
+ message: message || "Unable to fetch lottery attendees."
+ });
+ });
+};
+
+const addAttendee = (req, res) => {
+ const { attendee } = req.body;
+
+ const requiredColors = [attendee["red"], attendee["blue"], attendee["green"], attendee["yellow"]];
+ const correctColorsTypes = requiredColors.filter(color => typeof color === "number");
+ if (requiredColors.length !== correctColorsTypes.length) {
+ return res.status(400).send({
+ message: "Incorrect or missing color, required type Number for keys: 'blue', 'red', 'green' & 'yellow'.",
+ success: false
+ });
+ }
+
+ if (typeof attendee["name"] !== "string" || typeof attendee["phoneNumber"] !== "number") {
+ return res.status(400).send({
+ message: "Incorrect or missing attendee keys 'name' or 'phoneNumber'.",
+ success: false
+ });
+ }
+
+ return attendeeRepository
+ .addAttendee(attendee)
+ .then(savedAttendee => {
+ var io = req.app.get("socketio");
+ io.emit("new_attendee", {});
+ return savedAttendee;
+ })
+ .then(savedAttendee =>
+ res.send({
+ attendee: savedAttendee,
+ message: `Successfully added attendee ${attendee.name} to lottery.`,
+ success: true
+ })
+ );
+};
+
+const updateAttendeeById = (req, res) => {
+ const { id } = req.params;
+ const { attendee } = req.body;
+
+ return attendeeRepository
+ .updateAttendeeById(id, attendee)
+ .then(updatedAttendee => {
+ var io = req.app.get("socketio");
+ io.emit("refresh_data", {});
+ return updatedAttendee;
+ })
+ .then(attendee =>
+ res.send({
+ attendee,
+ message: `Updated attendee: ${attendee.name}`,
+ success: true
+ })
+ )
+ .catch(error => {
+ const { statusCode, message } = error;
+
+ return res.status(statusCode || 500).send({
+ message: message || "Unexpected error occured while deleteing attendee by id.",
+ success: false
+ });
+ });
+};
+
+const deleteAttendeeById = (req, res) => {
+ const { id } = req.params;
+
+ return attendeeRepository
+ .deleteAttendeeById(id)
+ .then(removedAttendee => {
+ var io = req.app.get("socketio");
+ io.emit("refresh_data", {});
+ return removedAttendee;
+ })
+ .then(attendee =>
+ res.send({
+ message: `Removed attendee: ${attendee.name}`,
+ success: true
+ })
+ )
+ .catch(error => {
+ const { statusCode, message } = error;
+
+ return res.status(statusCode || 500).send({
+ message: message || "Unexpected error occured while deleteing attendee by id.",
+ success: false
+ });
+ });
+};
+
+const deleteAttendees = (req, res) => {
+ return attendeeRepository
+ .deleteAttendees()
+ .then(removedAttendee => {
+ var io = req.app.get("socketio");
+ io.emit("refresh_data", {});
+ })
+ .then(_ =>
+ res.send({
+ message: "Removed all attendees",
+ success: true
+ })
+ );
+};
+
+module.exports = {
+ allAttendees,
+ addAttendee,
+ updateAttendeeById,
+ deleteAttendeeById,
+ deleteAttendees
+};
diff --git a/api/controllers/lotteryController.js b/api/controllers/lotteryController.js
new file mode 100644
index 0000000..ab446cc
--- /dev/null
+++ b/api/controllers/lotteryController.js
@@ -0,0 +1,192 @@
+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
+
+ const validDateFormat = new RegExp("d{4}-d{2}-d{2}");
+ if (date != undefined && (!validDateFormat.test(date) || isNaN(date))) {
+ return res.status(400).send({
+ message: "Date must be defined as 'yyyy-mm-dd'.",
+ success: false
+ });
+ } else if (date != undefined) {
+ date = Date.parse(date, "yyyy-MM-dd");
+ } else {
+ date = new Date();
+ }
+
+ return verifyLotteryPayload(raffles, stolen, wines)
+ .then(_ => lotteryRepository.archive(date, raffles, stolen, wines))
+ .then(_ =>
+ res.send({
+ message: "Successfully archive lottery",
+ success: true
+ })
+ )
+ .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 sortOptions = ["desc", "asc"];
+const allLotteries = (req, res) => {
+ let { includeWinners, year, sort } = req.query;
+
+ if (sort !== undefined && !sortOptions.includes(sort)) {
+ return res.status(400).send({
+ message: `Sort option must be: '${sortOptions.join(", ")}'`,
+ success: false
+ });
+ } else if (sort === undefined) {
+ sort = "asc";
+ }
+
+ let allLotteriesFunction = lotteryRepository.allLotteries;
+ if (includeWinners === "true") {
+ allLotteriesFunction = lotteryRepository.allLotteriesIncludingWinners;
+ }
+
+ return allLotteriesFunction(sort, year)
+ .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/controllers/lotteryWineController.js b/api/controllers/lotteryWineController.js
new file mode 100644
index 0000000..38685ed
--- /dev/null
+++ b/api/controllers/lotteryWineController.js
@@ -0,0 +1,207 @@
+const path = require("path");
+const prelotteryWineRepository = require(path.join(__dirname, "../prelotteryWine"));
+
+const allWines = (req, res) => {
+ return prelotteryWineRepository
+ .allWines()
+ .then(wines =>
+ res.send({
+ wines: wines,
+ success: true
+ })
+ )
+ .catch(error => {
+ const { statusCode, message } = error;
+
+ return res.status(statusCode || 500).send({
+ success: false,
+ message: message || "Unable to fetch lottery wines."
+ });
+ });
+};
+
+const addWines = (req, res) => {
+ let { wines } = req.body;
+
+ if (!(wines instanceof Array)) {
+ return res.status(400).send({
+ message: "Wines must be array.",
+ success: false
+ });
+ }
+
+ const validateAllWines = wines =>
+ wines.map(wine => {
+ const requiredAttributes = ["name", "vivinoLink", "image", "id", "price"];
+
+ return Promise.all(
+ requiredAttributes.map(attr => {
+ if (typeof wine[attr] === "undefined" || wine[attr] == "") {
+ return Promise.reject({
+ message: `Incorrect or missing attribute: ${attr}.`,
+ statusCode: 400,
+ success: false
+ });
+ }
+ return Promise.resolve();
+ })
+ ).then(_ => Promise.resolve(wine));
+ });
+
+ return Promise.all(validateAllWines(wines))
+ .then(wines => prelotteryWineRepository.addWines(wines))
+ .then(savedWines => {
+ var io = req.app.get("socketio");
+ io.emit("new_wine", {});
+ return true;
+ })
+ .then(success =>
+ res.send({
+ message: `Successfully added wines to lottery.`,
+ success: success
+ })
+ )
+ .catch(error => {
+ const { statusCode, message } = error;
+
+ return res.status(statusCode || 500).send({
+ message: message || "Unexpected error occured adding wines.",
+ success: false
+ });
+ });
+};
+
+const wineById = (req, res) => {
+ const { id } = req.params;
+
+ return prelotteryWineRepository
+ .wineById(id)
+ .then(wine =>
+ res.send({
+ wine,
+ success: true
+ })
+ )
+ .catch(error => {
+ const { statusCode, message } = error;
+
+ return res.status(statusCode || 500).send({
+ message: message || "Unexpected error occured while fetching wine by id.",
+ success: false
+ });
+ });
+};
+
+const updateWineById = (req, res) => {
+ const { id } = req.params;
+ const { wine } = req.body;
+
+ if (id == null || id == "undefined") {
+ return res.status(400).send({
+ message: "Unable to update without id.",
+ success: false
+ });
+ }
+
+ return prelotteryWineRepository
+ .updateWineById(id, wine)
+ .then(updatedWine => {
+ var io = req.app.get("socketio");
+ io.emit("refresh_data", {});
+ return updatedWine;
+ })
+ .then(wine =>
+ res.send({
+ wine,
+ message: `Updated wine: ${wine.name}`,
+ success: true
+ })
+ )
+ .catch(error => {
+ const { statusCode, message } = error;
+
+ return res.status(statusCode || 500).send({
+ message: message || "Unexpected error occured while deleteing wine by id.",
+ success: false
+ });
+ });
+};
+
+const deleteWineById = (req, res) => {
+ const { id } = req.params;
+
+ return prelotteryWineRepository
+ .deleteWineById(id)
+ .then(removedWine => {
+ var io = req.app.get("socketio");
+ io.emit("refresh_data", {});
+ return removedWine;
+ })
+ .then(wine =>
+ res.send({
+ message: `Removed wine: ${wine.name}`,
+ success: true
+ })
+ )
+ .catch(error => {
+ const { statusCode, message } = error;
+
+ return res.status(statusCode || 500).send({
+ message: message || "Unexpected error occured while deleteing wine by id.",
+ success: false
+ });
+ });
+};
+
+const deleteWines = (req, res) => {
+ return prelotteryWineRepository
+ .deleteWines()
+ .then(_ => {
+ var io = req.app.get("socketio");
+ io.emit("refresh_data", {});
+ })
+ .then(_ =>
+ res.send({
+ message: "Removed all wines.",
+ success: true
+ })
+ )
+ .catch(error => {
+ const { statusCode, message } = error;
+
+ return res.status(statusCode || 500).send({
+ message: message || "Unexpected error occured while deleting wines",
+ success: false
+ });
+ });
+};
+
+const wineSchema = (req, res) => {
+ return prelotteryWineRepository
+ .wineSchema()
+ .then(schema =>
+ res.send({
+ schema: schema,
+ message: `Wine schema template.`,
+ success: true
+ })
+ )
+ .catch(error => {
+ const { statusCode, message } = error;
+
+ return res.status(statusCode || 500).send({
+ success: false,
+ message: message || "Unable to fetch wine schema template."
+ });
+ });
+};
+
+module.exports = {
+ allWines,
+ addWines,
+ wineById,
+ updateWineById,
+ deleteWineById,
+ deleteWines,
+ wineSchema
+};
diff --git a/api/controllers/lotteryWinnerController.js b/api/controllers/lotteryWinnerController.js
new file mode 100644
index 0000000..1923d2e
--- /dev/null
+++ b/api/controllers/lotteryWinnerController.js
@@ -0,0 +1,195 @@
+const path = require("path");
+const winnerRepository = require(path.join(__dirname, "../winner"));
+const { WinnerNotFound } = require(path.join(__dirname, "../vinlottisErrors"));
+const prizeDistributionRepository = require(path.join(__dirname, "../prizeDistribution"));
+
+// should not be used, is done through POST /lottery/prize-distribution/prize/:id - claimPrize.
+const addWinners = (req, res) => {
+ const { winners } = req.body;
+
+ if (!(winners instanceof Array)) {
+ return res.status(400).send({
+ message: "Winners must be array.",
+ success: false
+ });
+ }
+
+ const requiredAttributes = ["name", "color", "wine"];
+ const validColors = ["red", "blue", "green", "yellow"];
+ const validateAllWinners = winners =>
+ winners.map(winner => {
+ return Promise.all(
+ requiredAttributes.map(attr => {
+ if (typeof winner[attr] === "undefined") {
+ return Promise.reject({
+ message: `Incorrect or missing attribute: ${attr}.`,
+ statusCode: 400
+ });
+ }
+
+ if (!validColors.includes(winner.color)) {
+ return Promise.reject({
+ message: `Missing or incorrect color value, must have one of values: ${validColors.join(", ")}.`,
+ statusCode: 400
+ });
+ }
+
+ return Promise.resolve();
+ })
+ ).then(_ => Promise.resolve(winner));
+ });
+
+ return Promise.all(validateAllWinners(winners))
+ .then(winners =>
+ winners.map(winner => {
+ return prizeDistributionRepository.claimPrize(winner, winner.wine);
+ })
+ )
+ .then(winners =>
+ res.send({
+ winners: winners,
+ message: `Successfully added winners to lottery.`,
+ success: true
+ })
+ )
+ .catch(error => {
+ const { statusCode, message } = error;
+
+ return res.status(statusCode || 500).send({
+ message: message || "Unexpected error occured adding winners.",
+ success: false
+ });
+ });
+};
+
+const allWinners = (req, res) => {
+ const isAdmin = req.isAuthenticated();
+
+ return winnerRepository
+ .allWinners(isAdmin)
+ .then(winners =>
+ res.send({
+ winners: winners,
+ success: true
+ })
+ )
+ .catch(error => {
+ const { statusCode, message } = error;
+
+ return res.status(statusCode || 500).send({
+ success: false,
+ message: message || "Unable to fetch lottery winners."
+ });
+ });
+};
+
+const winnerById = (req, res) => {
+ const { id } = req.params;
+ const isAdmin = req.isAuthenticated();
+
+ return winnerRepository
+ .winnerById(id, isAdmin)
+ .then(winner =>
+ res.send({
+ winner,
+ success: true
+ })
+ )
+ .catch(error => {
+ const { statusCode, message } = error;
+
+ return res.status(statusCode || 500).send({
+ message: message || "Unexpected error occured, unable to fetch winner by id.",
+ success: false
+ });
+ });
+};
+
+const updateWinnerById = (req, res) => {
+ const { id } = req.params;
+ const { winner } = req.body;
+
+ if (id == null || id == "undefined") {
+ return res.status(400).send({
+ message: "Unable to update without id.",
+ success: false
+ });
+ }
+
+ return winnerRepository
+ .updateWinnerById(id, winner)
+ .then(winner =>
+ res.send({
+ winner,
+ message: `Updated winner: ${winner.name}`,
+ success: true
+ })
+ )
+ .catch(error => {
+ const { statusCode, message } = error;
+
+ return res.status(statusCode || 500).send({
+ message: message || "Unexpected error occured while updating winner by id.",
+ success: false
+ });
+ });
+};
+
+const deleteWinnerById = (req, res) => {
+ const isAdmin = req.isAuthenticated();
+ const { id } = req.params;
+
+ return winnerRepository
+ .deleteWinnerById(id, isAdmin)
+ .then(removedWinner => {
+ var io = req.app.get("socketio");
+ io.emit("refresh_data", {});
+ return removedWinner;
+ })
+ .then(winner =>
+ res.send({
+ message: `Removed winner: ${winner.name}`,
+ success: true
+ })
+ )
+ .catch(error => {
+ const { statusCode, message } = error;
+
+ return res.status(statusCode || 500).send({
+ message: message || "Unexpected error occured while deleteing wine by id.",
+ success: false
+ });
+ });
+};
+
+const deleteWinners = (req, res) => {
+ return winnerRepository
+ .deleteWinners()
+ .then(_ => {
+ var io = req.app.get("socketio");
+ io.emit("refresh_data", {});
+ })
+ .then(_ =>
+ res.send({
+ message: "Removed all winners.",
+ success: true
+ })
+ )
+ .catch(error => {
+ const { statusCode, message } = error;
+
+ return res.status(statusCode || 500).send({
+ message: message || "Unexpected error occured while deleting wines",
+ success: false
+ });
+ });
+};
+
+module.exports = {
+ addWinners,
+ allWinners,
+ winnerById,
+ updateWinnerById,
+ deleteWinnerById,
+ deleteWinners
+};
diff --git a/api/controllers/messageController.js b/api/controllers/messageController.js
new file mode 100644
index 0000000..94dffe9
--- /dev/null
+++ b/api/controllers/messageController.js
@@ -0,0 +1,30 @@
+const path = require("path");
+const messageRepository = require(path.join(__dirname, "../message"));
+const winnerRepository = require(path.join(__dirname, "../winner"));
+
+const notifyWinnerById = (req, res) => {
+ const { id } = req.params;
+ const isAdmin = req.isAuthenticated();
+
+ return winnerRepository
+ .winnerById(id, isAdmin)
+ .then(winner => messageRepository.sendPrizeSelectionLink(winner))
+ .then(messageResponse =>
+ res.send({
+ messageResponse,
+ success: true
+ })
+ )
+ .catch(error => {
+ const { statusCode, message } = error;
+
+ return res.status(statusCode || 500).send({
+ message: message || "Unexpected error occured while sending message to winner by id.",
+ success: false
+ });
+ });
+};
+
+module.exports = {
+ notifyWinnerById
+};
diff --git a/api/controllers/prizeDistributionController.js b/api/controllers/prizeDistributionController.js
new file mode 100644
index 0000000..7e9b840
--- /dev/null
+++ b/api/controllers/prizeDistributionController.js
@@ -0,0 +1,104 @@
+const path = require("path");
+
+const prizeDistribution = require(path.join(__dirname, "../prizeDistribution"));
+const prelotteryWineRepository = require(path.join(__dirname, "../prelotteryWine"));
+const winnerRepository = require(path.join(__dirname, "../winner"));
+const message = require(path.join(__dirname, "../message"));
+
+const start = async (req, res) => {
+ const allWinners = await winnerRepository.allWinners(true);
+ if (allWinners.length === 0) {
+ return res.status(503).send({
+ message: "No winners found to distribute prizes to.",
+ 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(winner => {
+ return prelotteryWineRepository.allWinesWithoutWinner().then(wines => [wines, winner]);
+ })
+ .then(([wines, winner]) =>
+ res.send({
+ wines: wines,
+ winner: winner,
+ 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;
+
+ let prelotteryWine, winner;
+ try {
+ prelotteryWine = await prelotteryWineRepository.wineById(wine._id);
+ winner = await winnerRepository.winnerById(id, true);
+ } catch (error) {
+ const { statusCode, message } = error;
+
+ return res.status(statusCode || 500).send({
+ message: message || "Unexpected error occured while claiming prize.",
+ success: false
+ });
+ }
+
+ return prizeDistribution
+ .claimPrize(prelotteryWine, winner)
+ .then(_ => prizeDistribution.notifyNextWinner())
+ .then(_ =>
+ res.send({
+ message: `${winner.name} successfully claimed prize: ${prelotteryWine.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/controllers/requestController.js b/api/controllers/requestController.js
new file mode 100644
index 0000000..7144674
--- /dev/null
+++ b/api/controllers/requestController.js
@@ -0,0 +1,104 @@
+const path = require("path");
+const requestRepository = require(path.join(__dirname, "../request"));
+
+function addRequest(req, res) {
+ const { wine } = req.body;
+
+ return verifyWineValues(wine)
+ .then(_ => requestRepository.addNew(wine))
+ .then(wine =>
+ res.json({
+ message: "Successfully added new request",
+ wine: wine,
+ success: true
+ })
+ )
+ .catch(error => {
+ const { message, statusCode } = error;
+
+ return res.status(statusCode || 500).send({
+ success: false,
+ message: message || "Unable to add requested wine."
+ });
+ });
+}
+
+function allRequests(req, res) {
+ return requestRepository
+ .getAll()
+ .then(wines =>
+ res.json({
+ wines: wines,
+ success: true
+ })
+ )
+ .catch(error => {
+ const { message, statusCode } = error;
+ return res.status(statusCode || 500).json({
+ success: false,
+ message: message || "Unable to fetch all requested wines."
+ });
+ });
+}
+
+function deleteRequest(req, res) {
+ const { id } = req.params;
+
+ return requestRepository
+ .deleteById(id)
+ .then(_ =>
+ res.json({
+ message: `Slettet vin med id: ${id}`,
+ success: true
+ })
+ )
+ .catch(error => {
+ const { statusCode, message } = error;
+
+ return res.status(statusCode || 500).send({
+ success: false,
+ message: message || "Unable to delete requested wine."
+ });
+ });
+}
+
+function verifyWineValues(wine) {
+ return new Promise((resolve, reject) => {
+ if (wine == undefined) {
+ reject({
+ message: "No wine object found in request body.",
+ status: 400
+ });
+ }
+
+ if (wine.id == null) {
+ reject({
+ message: "Wine object missing value id.",
+ status: 400
+ });
+ } else if (wine.name == null) {
+ reject({
+ message: "Wine object missing value name.",
+ status: 400
+ });
+ } else if (wine.vivinoLink == null) {
+ reject({
+ message: "Wine object missing value vivinoLink.",
+ status: 400
+ });
+ } else if (wine.image == null) {
+ reject({
+ message: "Wine object missing value image.",
+ status: 400
+ });
+ }
+
+ resolve();
+ });
+}
+
+module.exports = {
+ addRequest,
+ allRequests,
+ deleteRequest
+};
diff --git a/api/controllers/userController.js b/api/controllers/userController.js
new file mode 100644
index 0000000..b12b2f5
--- /dev/null
+++ b/api/controllers/userController.js
@@ -0,0 +1,55 @@
+const path = require("path");
+const userRepository = require(path.join(__dirname, "../user"));
+
+function register(req, res, next) {
+ const { username, password } = req.body;
+
+ return userRepository
+ .register(username, password)
+ .then(user => userRepository.login(req, user))
+ .then(_ =>
+ res.send({
+ messsage: `Bruker registrert. Velkommen ${username}`,
+ success: true
+ })
+ )
+ .catch(error => {
+ const { statusCode, message } = error;
+
+ return res.status(statusCode || 500).send({
+ message: message || "Unable to sign in with given username and passowrd",
+ success: false
+ });
+ });
+}
+
+const login = (req, res, next) => {
+ return userRepository
+ .authenticate(req)
+ .then(user => userRepository.login(req, user))
+ .then(user => {
+ res.send({
+ message: `Velkommen ${user.username}`,
+ success: true
+ });
+ })
+ .catch(error => {
+ const { statusCode, message } = error;
+
+ return res.status(statusCode || 500).send({
+ message: message || "Unable to sign in with given username and passowrd",
+ success: false
+ });
+ });
+};
+
+const logout = (req, res) => {
+ req.logout();
+ res.redirect("/");
+};
+
+module.exports = {
+ register,
+ login,
+ logout
+};
diff --git a/api/controllers/vinmonopoletController.js b/api/controllers/vinmonopoletController.js
new file mode 100644
index 0000000..b68ec51
--- /dev/null
+++ b/api/controllers/vinmonopoletController.js
@@ -0,0 +1,85 @@
+const path = require("path");
+const vinmonopoletRepository = require(path.join(__dirname, "../vinmonopolet"));
+
+function searchWines(req, res) {
+ const { name, page } = req.query;
+
+ return vinmonopoletRepository.searchWinesByName(name, page).then(wines =>
+ res.json({
+ wines: wines,
+ count: wines.length,
+ page: page,
+ success: true
+ })
+ );
+}
+
+function wineByEAN(req, res) {
+ const { ean } = req.params;
+
+ return vinmonopoletRepository.searchByEAN(ean).then(wines =>
+ res.json({
+ wines: wines,
+ success: true
+ })
+ );
+}
+
+function wineById(req, res) {
+ const { id } = req.params;
+
+ return vinmonopoletRepository.wineById(id).then(wines =>
+ res.json({
+ wine: wines[0],
+ success: true
+ })
+ );
+}
+
+function allStores(req, res) {
+ return vinmonopoletRepository
+ .allStores()
+ .then(stores =>
+ res.send({
+ stores,
+ success: true
+ })
+ )
+ .catch(error => {
+ const { statusCode, message } = error;
+
+ return res.status(statusCode || 500).send({
+ message: message || "Unexpected error occured while fetch all vinmonopolet stores.",
+ success: false
+ });
+ });
+}
+
+function searchStores(req, res) {
+ const { name } = req.query;
+
+ return vinmonopoletRepository
+ .searchStoresByName(name)
+ .then(stores =>
+ res.send({
+ stores,
+ success: true
+ })
+ )
+ .catch(error => {
+ const { statusCode, message } = error;
+
+ return res.status(statusCode || 500).send({
+ message: message || "Unexpected error occured while fetch all vinmonopolet stores.",
+ success: false
+ });
+ });
+}
+
+module.exports = {
+ searchWines,
+ wineByEAN,
+ wineById,
+ allStores,
+ searchStores
+};
diff --git a/api/controllers/wineController.js b/api/controllers/wineController.js
new file mode 100644
index 0000000..677bee6
--- /dev/null
+++ b/api/controllers/wineController.js
@@ -0,0 +1,60 @@
+const path = require("path");
+const wineRepository = require(path.join(__dirname, "../wine"));
+
+const allWines = (req, res) => {
+ // TODO add "includeWinners"
+ let { limit } = req.query;
+
+ if (limit && isNaN(limit)) {
+ return res.status(400).send({
+ message: "If limit query parameter is provided it must be a number",
+ success: false
+ });
+ } else if (!!!isNaN(limit)) {
+ limit = Number(limit);
+ }
+
+ return wineRepository
+ .allWines(limit)
+ .then(wines =>
+ res.send({
+ wines: wines,
+ message: `All wines.`,
+ success: true
+ })
+ )
+ .catch(error => {
+ const { statusCode, message } = error;
+
+ return res.status(statusCode || 500).send({
+ success: false,
+ message: message || "Unable to fetch all wines."
+ });
+ });
+};
+
+const wineById = (req, res) => {
+ const { id } = req.params;
+
+ return wineRepository
+ .wineById(id)
+ .then(wine => {
+ res.send({
+ wine,
+ success: true
+ });
+ })
+ .catch(error => {
+ const { statusCode, message } = error;
+
+ return res.status(statusCode || 500).send({
+ message: message || "Unexpected error occured while fetching wine by id.",
+ success: false
+ });
+ });
+};
+
+module.exports = {
+ allWines,
+ wineById
+};
diff --git a/api/history.js b/api/history.js
new file mode 100644
index 0000000..46195ce
--- /dev/null
+++ b/api/history.js
@@ -0,0 +1,349 @@
+const path = require("path");
+
+const Winner = require(path.join(__dirname, "/schemas/Highscore"));
+const wineRepository = require(path.join(__dirname, "/wine"));
+
+class HistoryByDateNotFound extends Error {
+ constructor(message = "History for given date not found.") {
+ super(message);
+ this.name = "HistoryByDateNotFound";
+ this.statusCode = 404;
+ }
+}
+
+class HistoryForUserNotFound extends Error {
+ constructor(message = "History for given user not found.") {
+ super(message);
+ this.name = "HistoryForUserNotFound";
+ this.statusCode = 404;
+ }
+}
+
+// highscore
+const addWinnerWithWine = async (winner, wine) => {
+ const exisitingWinner = await Winner.findOne({
+ name: winner.name
+ });
+ const savedWine = await wineRepository.addWine(wine);
+
+ const date = new Date();
+ date.setHours(5, 0, 0, 0);
+ const winObject = {
+ date: date,
+ wine: savedWine,
+ color: winner.color
+ };
+
+ if (exisitingWinner == undefined) {
+ const newWinner = new Winner({
+ name: winner.name,
+ wins: [winObject]
+ });
+
+ await newWinner.save();
+ } else {
+ exisitingWinner.wins.push(winObject);
+ exisitingWinner.markModified("wins");
+ await exisitingWinner.save();
+ }
+
+ return exisitingWinner;
+};
+
+// lottery
+const all = (includeWines = false) => {
+ if (includeWines === false) {
+ return Winner.find().sort("-wins.date");
+ } else {
+ return Winner.find()
+ .sort("-wins.date")
+ .populate("wins.wine");
+ }
+};
+
+// lottery
+const byDate = date => {
+ const startQueryDate = new Date(date.setHours(0, 0, 0, 0));
+ const endQueryDate = new Date(date.setHours(24, 59, 59, 99));
+ const query = [
+ {
+ $match: {
+ "wins.date": {
+ $gte: startQueryDate,
+ $lte: endQueryDate
+ }
+ }
+ },
+ { $unwind: "$wins" },
+ {
+ $match: {
+ "wins.date": {
+ $gte: startQueryDate,
+ $lte: endQueryDate
+ }
+ }
+ },
+ {
+ $lookup: {
+ from: "wines",
+ localField: "wins.wine",
+ foreignField: "_id",
+ as: "wins.wine"
+ }
+ },
+ { $unwind: "$wins.wine" },
+ {
+ $project: {
+ name: "$name",
+ date: "$wins.date",
+ color: "$wins.color",
+ wine: "$wins.wine"
+ }
+ }
+ ];
+
+ return Winner.aggregate(query).then(winners => {
+ if (winners.length == 0) {
+ throw new HistoryByDateNotFound();
+ }
+ return winners;
+ });
+};
+
+// highscore
+const byName = (name, sort = "desc") => {
+ return Winner.findOne({ name }, ["name", "wins"])
+ .sort("-wins.date")
+ .populate("wins.wine")
+ .then(winner => {
+ if (winner) {
+ winner.wins = sort !== "asc" ? winner.wins.reverse() : winner.wins;
+ return winner;
+ } else {
+ throw new HistoryForUserNotFound();
+ }
+ });
+};
+
+// highscore
+const search = (query, sort = "desc") => {
+ return Winner.find({ name: { $regex: query, $options: "i" } }, ["name"]).then(winners => {
+ if (winners) {
+ winners = sort === "desc" ? winners.reverse() : winners;
+ return winners;
+ } else {
+ throw new HistoryForUserNotFound();
+ }
+ });
+};
+
+// lottery
+const latest = () => {
+ const query = [
+ {
+ $unwind: "$wins"
+ },
+ {
+ $lookup: {
+ from: "wines",
+ localField: "wins.wine",
+ foreignField: "_id",
+ as: "wins.wine"
+ }
+ },
+ {
+ $group: {
+ _id: "$wins.date",
+ winners: {
+ $push: {
+ _id: "$_id",
+ name: "$name",
+ color: "$wins.color",
+ wine: "$wins.wine"
+ }
+ }
+ }
+ },
+ {
+ $project: {
+ date: "$_id",
+ winners: "$winners"
+ }
+ },
+ {
+ $sort: {
+ _id: -1
+ }
+ },
+ {
+ $limit: 1
+ }
+ ];
+
+ return Winner.aggregate(query).then(winners => winners[0]);
+};
+
+// lottery - byDate
+const groupByDate = (includeWines = false, sort = "asc") => {
+ const sortDirection = sort == "asc" ? -1 : 1;
+ const query = [
+ {
+ $unwind: "$wins"
+ },
+ {
+ $group: {
+ _id: "$wins.date",
+ winners: {
+ $push: {
+ _id: "$_id",
+ name: "$name",
+ color: "$wins.color",
+ wine: "$wins.wine"
+ }
+ }
+ }
+ },
+ {
+ $project: {
+ date: "$_id",
+ winners: "$winners"
+ }
+ },
+ {
+ $sort: {
+ date: sortDirection
+ }
+ }
+ ];
+
+ if (includeWines) {
+ query.splice(1, 0, {
+ $lookup: {
+ from: "wines",
+ localField: "wins.wine",
+ foreignField: "_id",
+ as: "wins.wine"
+ }
+ });
+ }
+
+ return Winner.aggregate(query);
+};
+
+// highscore - byColor
+const groupByColor = (includeWines = false) => {
+ const query = [
+ {
+ $unwind: "$wins"
+ },
+ {
+ $group: {
+ _id: "$wins.color",
+ winners: {
+ $push: {
+ _id: "$_id",
+ name: "$name",
+ date: "$wins.date",
+ wine: "$wins.wine"
+ }
+ },
+ count: { $sum: 1 }
+ }
+ },
+ {
+ $project: {
+ color: "$_id",
+ count: "$count",
+ winners: "$winners"
+ }
+ },
+ {
+ $sort: {
+ _id: -1
+ }
+ }
+ ];
+
+ if (includeWines) {
+ query.splice(1, 0, {
+ $lookup: {
+ from: "wines",
+ localField: "wins.wine",
+ foreignField: "_id",
+ as: "wins.wine"
+ }
+ });
+ }
+
+ return Winner.aggregate(query);
+};
+
+// highscore - byWineOccurences
+
+// highscore - byWinCount
+const orderByWins = (includeWines = false, limit = undefined) => {
+ let query = [
+ {
+ $project: {
+ name: "$name",
+ wins: "$wins",
+ totalWins: { $size: "$wins" }
+ }
+ },
+ {
+ $sort: {
+ totalWins: -1,
+ "wins.date": -1
+ }
+ }
+ ];
+
+ if (includeWines) {
+ const includeWinesSubQuery = [
+ {
+ $unwind: "$wins"
+ },
+ {
+ $lookup: {
+ from: "wines",
+ localField: "wins.wine",
+ foreignField: "_id",
+ as: "wins.wine"
+ }
+ },
+ {
+ $unwind: "$wins._id"
+ },
+ {
+ $group: {
+ _id: "$_id",
+ name: { $first: "$name" },
+ totalWins: { $first: "$totalWins" },
+ wins: { $push: "$wins" }
+ }
+ }
+ ];
+
+ query = includeWinesSubQuery.concat(query);
+ }
+
+ return Winner.aggregate(query).then(winners => {
+ if (limit == null) {
+ return winners;
+ }
+
+ return winners.slice(0, limit);
+ });
+};
+
+module.exports = {
+ addWinnerWithWine,
+ all,
+ byDate,
+ byName,
+ search,
+ latest,
+ groupByDate,
+ groupByColor,
+ orderByWins
+};
diff --git a/api/lottery.js b/api/lottery.js
index 6783a8c..6b1bf90 100644
--- a/api/lottery.js
+++ b/api/lottery.js
@@ -1,132 +1,263 @@
-const path = require('path');
+const path = require("path");
+const crypto = require("crypto");
-const Highscore = require(path.join(__dirname, '/schemas/Highscore'));
-const Wine = require(path.join(__dirname, '/schemas/Wine'));
+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"));
-// Utils
-const epochToDateString = date => new Date(parseInt(date)).toDateString();
+const Message = require(path.join(__dirname, "/message"));
+const historyRepository = require(path.join(__dirname, "/history"));
+const wineRepository = require(path.join(__dirname, "/wine"));
-const sortNewestFirst = (lotteries) => {
- return lotteries.sort((a, b) => parseInt(a.date) < parseInt(b.date) ? 1 : -1)
-}
+const {
+ WinnerNotFound,
+ NoMoreAttendeesToWin,
+ CouldNotFindNewWinnerAfterNTries,
+ LotteryByDateNotFound
+} = require(path.join(__dirname, "/vinlottisErrors"));
-const groupHighscoreByDate = async (highscore=undefined) => {
- if (highscore == undefined)
- highscore = await Highscore.find();
+const archive = (date, raffles, stolen, wines) => {
+ const { blue, red, yellow, green } = raffles;
+ const bought = blue + red + yellow + green;
- const highscoreByDate = [];
-
- highscore.forEach(person => {
- person.wins.map(win => {
- const epochDate = new Date(win.date).setHours(0,0,0,0);
- const winnerObject = {
- name: person.name,
- color: win.color,
- wine: win.wine,
- date: epochDate
- }
-
- const existingDateIndex = highscoreByDate.findIndex(el => el.date == epochDate)
- if (existingDateIndex > -1)
- highscoreByDate[existingDateIndex].winners.push(winnerObject);
- else
- highscoreByDate.push({
- date: epochDate,
- winners: [winnerObject]
- })
- })
- })
-
- return sortNewestFirst(highscoreByDate);
-}
-
-const resolveWineReferences = (highscoreObject, key) => {
- const listWithWines = highscoreObject[key]
-
- return Promise.all(listWithWines.map(element =>
- Wine.findById(element.wine)
- .then(wine => {
- element.wine = wine
- return element
- }))
- )
- .then(resolvedListWithWines => {
- highscoreObject[key] = resolvedListWithWines;
- return highscoreObject
- })
-}
-// end utils
-
-// Routes
-const all = (req, res) => {
- return Highscore.find()
- .then(highscore => groupHighscoreByDate(highscore))
- .then(lotteries => res.send({
- message: "Lotteries by date!",
- lotteries
- }))
-}
-
-const latest = (req, res) => {
- return groupHighscoreByDate()
- .then(lotteries => lotteries.shift()) // first element in list
- .then(latestLottery => resolveWineReferences(latestLottery, "winners"))
- .then(lottery => res.send({
- message: "Latest lottery!",
- winners: lottery.winners
- })
- )
-}
-
-const byEpochDate = (req, res) => {
- let { date } = req.params;
- date = new Date(new Date(parseInt(date)).setHours(0,0,0,0)).getTime()
- const dateString = epochToDateString(date);
-
- return groupHighscoreByDate()
- .then(lotteries => {
- const lottery = lotteries.filter(lottery => lottery.date == date)
- if (lottery.length > 0) {
- return lottery[0]
- } else {
- return res.status(404).send({
- message: `No lottery found for date: ${ dateString }`
- })
- }
- })
- .then(lottery => resolveWineReferences(lottery, "winners"))
- .then(lottery => res.send({
- message: `Lottery for date: ${ dateString}`,
+ return Promise.all(wines.map(wine => wineRepository.findWine(wine))).then(resolvedWines => {
+ const lottery = new Lottery({
date,
- winners: lottery.winners
- }))
-}
+ blue,
+ red,
+ yellow,
+ green,
+ bought,
+ stolen,
+ wines: resolvedWines
+ });
-const byName = (req, res) => {
- const { name } = req.params;
- const regexName = new RegExp(name, "i"); // lowercase regex of the name
+ return lottery.save();
+ });
+};
- return Highscore.find({ name })
- .then(highscore => {
- if (highscore.length > 0) {
- return highscore[0]
- } else {
- return res.status(404).send({
- message: `Name: ${ name } not found in leaderboards.`
- })
+const lotteryByDate = date => {
+ const startOfDay = new Date(date.setHours(0, 0, 0, 0));
+ const endOfDay = new Date(date.setHours(24, 59, 59, 99));
+
+ const query = [
+ {
+ $match: {
+ date: {
+ $gte: startOfDay,
+ $lte: endOfDay
+ }
}
- })
- .then(highscore => resolveWineReferences(highscore, "wins"))
- .then(highscore => res.send({
- message: `Lottery winnings for name: ${ name }.`,
- name: highscore.name,
- highscore: sortNewestFirst(highscore.wins)
- }))
+ },
+ {
+ $lookup: {
+ from: "wines",
+ localField: "wines",
+ foreignField: "_id",
+ as: "wines"
+ }
+ }
+ ];
+
+ const aggregateLottery = Lottery.aggregate(query);
+ return aggregateLottery.project("-_id -__v").then(lotteries => {
+ if (lotteries.length == 0) {
+ throw new LotteryByDateNotFound(date);
+ }
+ return lotteries[0];
+ });
+};
+
+const allLotteries = (sort = "asc", yearFilter = undefined) => {
+ const sortDirection = sort == "asc" ? 1 : -1;
+
+ let startQueryDate = new Date("1970-01-01");
+ let endQueryDate = new Date("2999-01-01");
+ if (yearFilter) {
+ startQueryDate = new Date(`${yearFilter}-01-01`);
+ endQueryDate = new Date(`${Number(yearFilter) + 1}-01-01`);
+ }
+
+ const query = [
+ {
+ $match: {
+ date: {
+ $gte: startQueryDate,
+ $lte: endQueryDate
+ }
+ }
+ },
+ {
+ $sort: {
+ date: sortDirection
+ }
+ },
+ {
+ $unset: ["_id", "__v"]
+ },
+ {
+ $lookup: {
+ from: "wines",
+ localField: "wines",
+ foreignField: "_id",
+ as: "wines"
+ }
+ }
+ ];
+
+ return Lottery.aggregate(query);
+};
+
+const allLotteriesIncludingWinners = async (sort = "asc", yearFilter = undefined) => {
+ const lotteries = await allLotteries(sort, yearFilter);
+ const allWinners = await historyRepository.groupByDate(false, sort);
+
+ return lotteries.map(lottery => {
+ const { winners } = allWinners.pop();
+
+ return {
+ wines: lottery.wines,
+ date: lottery.date,
+ blue: lottery.blue,
+ green: lottery.green,
+ yellow: lottery.yellow,
+ red: lottery.red,
+ bought: lottery.bought,
+ stolen: lottery.stolen,
+ winners: winners
+ };
+ });
+};
+
+const drawWinner = async () => {
+ let allContestants = await Attendee.find({ winner: false });
+
+ if (allContestants.length == 0) {
+ throw new NoMoreAttendeesToWin();
+ }
+
+ let raffleColors = [];
+ for (let i = 0; i < allContestants.length; i++) {
+ let currentContestant = allContestants[i];
+ for (let blue = 0; blue < currentContestant.blue; blue++) {
+ raffleColors.push("blue");
+ }
+ for (let red = 0; red < currentContestant.red; red++) {
+ raffleColors.push("red");
+ }
+ for (let green = 0; green < currentContestant.green; green++) {
+ raffleColors.push("green");
+ }
+ for (let yellow = 0; yellow < currentContestant.yellow; yellow++) {
+ raffleColors.push("yellow");
+ }
+ }
+
+ raffleColors = shuffle(raffleColors);
+
+ let colorToChooseFrom = raffleColors[Math.floor(Math.random() * raffleColors.length)];
+ let findObject = { winner: false };
+
+ findObject[colorToChooseFrom] = { $gt: 0 };
+
+ let tries = 0;
+ const maxTries = 3;
+ let contestantsToChooseFrom = undefined;
+ while (contestantsToChooseFrom == undefined && tries < maxTries) {
+ const hit = await Attendee.find(findObject);
+ if (hit && hit.length) {
+ contestantsToChooseFrom = hit;
+ break;
+ }
+ tries++;
+ }
+ if (contestantsToChooseFrom == undefined) {
+ throw new CouldNotFindNewWinnerAfterNTries(maxTries);
+ }
+
+ let attendeeListDemocratic = [];
+
+ let currentContestant;
+ for (let i = 0; i < contestantsToChooseFrom.length; i++) {
+ currentContestant = contestantsToChooseFrom[i];
+ for (let y = 0; y < currentContestant[colorToChooseFrom]; y++) {
+ attendeeListDemocratic.push({
+ name: currentContestant.name,
+ phoneNumber: currentContestant.phoneNumber,
+ red: currentContestant.red,
+ blue: currentContestant.blue,
+ green: currentContestant.green,
+ yellow: currentContestant.yellow
+ });
+ }
+ }
+
+ attendeeListDemocratic = shuffle(attendeeListDemocratic);
+
+ let winner = attendeeListDemocratic[Math.floor(Math.random() * attendeeListDemocratic.length)];
+
+ let newWinnerElement = new VirtualWinner({
+ name: winner.name,
+ phoneNumber: winner.phoneNumber,
+ color: colorToChooseFrom,
+ red: winner.red,
+ blue: winner.blue,
+ green: winner.green,
+ yellow: winner.yellow,
+ id: sha512(winner.phoneNumber, genRandomString(10)),
+ timestamp_drawn: new Date().getTime()
+ });
+
+ await newWinnerElement.save();
+ await Attendee.updateOne({ name: winner.name, phoneNumber: winner.phoneNumber }, { $set: { winner: true } });
+
+ let winners = await VirtualWinner.find({ timestamp_sent: undefined }).sort({
+ timestamp_drawn: 1
+ });
+
+ return { winner, color: colorToChooseFrom, winners };
+};
+
+/** - - UTILS - - **/
+const genRandomString = function(length) {
+ return crypto
+ .randomBytes(Math.ceil(length / 2))
+ .toString("hex") /** convert to hexadecimal format */
+ .slice(0, length); /** return required number of characters */
+};
+
+const sha512 = function(password, salt) {
+ var hash = crypto.createHmac("md5", salt); /** Hashing algorithm sha512 */
+ hash.update(password);
+ var value = hash.digest("hex");
+ return value;
+};
+
+function shuffle(array) {
+ let currentIndex = array.length,
+ temporaryValue,
+ randomIndex;
+
+ // While there remain elements to shuffle...
+ while (0 !== currentIndex) {
+ // Pick a remaining element...
+ randomIndex = Math.floor(Math.random() * currentIndex);
+ currentIndex -= 1;
+
+ // And swap it with the current element.
+ temporaryValue = array[currentIndex];
+ array[currentIndex] = array[randomIndex];
+ array[randomIndex] = temporaryValue;
+ }
+
+ return array;
}
module.exports = {
- all,
- latest,
- byEpochDate,
- byName
+ drawWinner,
+ archive,
+ lotteryByDate,
+ allLotteries,
+ allLotteriesIncludingWinners
};
diff --git a/api/message.js b/api/message.js
index e4faf26..15d42da 100644
--- a/api/message.js
+++ b/api/message.js
@@ -2,34 +2,50 @@ const https = require("https");
const path = require("path");
const config = require(path.join(__dirname + "/../config/defaults/lottery"));
-const dateString = (date) => {
- if (typeof(date) == "string") {
+const dateString = date => {
+ if (typeof date == "string") {
date = new Date(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)
+ 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 `${da}-${mo}-${ye}`
+ return `${da}-${mo}-${ye}`;
+};
+
+async function sendInitialMessageToWinners(winners) {
+ const numbers = winners.map(winner => ({ msisdn: `47${winner.phoneNumber}` }));
+
+ const body = {
+ sender: "Vinlottis",
+ message: "Gratulerer som vinner av vinlottisen! Du vil snart få en SMS med oppdatering om hvordan gangen går!",
+ recipients: numbers
+ };
+
+ return gatewayRequest(body);
}
-async function sendWineSelectMessage(winnerObject) {
- winnerObject.timestamp_sent = new Date().getTime();
- winnerObject.timestamp_limit = new Date().getTime() * 600000;
- await winnerObject.save();
+async function sendPrizeSelectionLink(winner) {
+ winner.timestamp_sent = new Date().getTime();
+ winner.timestamp_limit = new Date().getTime() + 1000 * 600;
+ await winner.save();
- let url = new URL(`/#/winner/${winnerObject.id}`, "https://lottis.vin");
+ const { id, name, phoneNumber } = winner;
+ const url = new URL(`/#/winner/${id}`, "https://lottis.vin");
+ const message = `Gratulerer som heldig vinner av vinlotteriet ${name}! Her er linken for \
+å velge hva slags vin du vil ha, du har 10 minutter på å velge ut noe før du blir lagt bakerst \
+i køen. ${url.href}. (Hvis den siden kommer opp som tom må du prøve å refreshe siden noen ganger.`;
- return sendMessageToUser(
- winnerObject.phoneNumber,
- `Gratulerer som heldig vinner av vinlotteriet ${winnerObject.name}! Her er linken for å velge hva slags vin du vil ha, du har 10 minutter på å velge ut noe før du blir lagt bakerst i køen. ${url.href}. (Hvis den siden kommer opp som tom må du prøve å refreshe siden noen ganger.)`
- )
+ return sendMessageToNumber(phoneNumber, message);
}
async function sendWineConfirmation(winnerObject, wineObject, date) {
date = dateString(date);
- return sendMessageToUser(winnerObject.phoneNumber,
- `Bekreftelse på din vin ${ winnerObject.name }.\nDato vunnet: ${ date }.\nVin valgt: ${ wineObject.name }.\nDu vil bli kontaktet av ${ config.name } ang henting. Ha en ellers fin helg!`)
+ return sendMessageToNumber(
+ winnerObject.phoneNumber,
+ `Bekreftelse på din vin ${winnerObject.name}.\nDato vunnet: ${date}.\nVin valgt: ${wineObject.name}.\
+\nDu vil bli kontaktet av ${config.name} ang henting. Ha en ellers fin helg!`
+ );
}
async function sendLastWinnerMessage(winnerObject, wineObject) {
@@ -38,84 +54,69 @@ async function sendLastWinnerMessage(winnerObject, wineObject) {
winnerObject.timestamp_limit = new Date().getTime();
await winnerObject.save();
- return sendMessageToUser(
+ return sendMessageToNumber(
winnerObject.phoneNumber,
- `Gratulerer som heldig vinner av vinlotteriet ${winnerObject.name}! Du har vunnet vinen ${wineObject.name}, du vil bli kontaktet av ${ config.name } ang henting. Ha en ellers fin helg!`
+ `Gratulerer som heldig vinner av vinlotteriet ${winnerObject.name}! Du har vunnet vinen ${wineObject.name}, \
+du vil bli kontaktet av ${config.name} ang henting. Ha en ellers fin helg!`
);
}
async function sendWineSelectMessageTooLate(winnerObject) {
- return sendMessageToUser(
+ return sendMessageToNumber(
winnerObject.phoneNumber,
- `Hei ${winnerObject.name}, du har dessverre brukt mer enn 10 minutter på å velge premie og blir derfor puttet bakerst i køen. Du vil få en ny SMS når det er din tur igjen.`
+ `Hei ${winnerObject.name}, du har dessverre brukt mer enn 10 minutter på å velge premie og blir derfor \
+puttet bakerst i køen. Du vil få en ny SMS når det er din tur igjen.`
);
}
-async function sendMessageToUser(phoneNumber, message) {
- console.log(`Attempting to send message to ${ phoneNumber }.`)
+async function sendMessageToNumber(phoneNumber, message) {
+ console.log(`Attempting to send message to ${phoneNumber}.`);
const body = {
sender: "Vinlottis",
message: message,
- recipients: [{ msisdn: `47${ phoneNumber }`}]
+ recipients: [{ msisdn: `47${phoneNumber}` }]
};
return gatewayRequest(body);
}
-
-async function sendInitialMessageToWinners(winners) {
- let numbers = [];
- for (let i = 0; i < winners.length; i++) {
- numbers.push({ msisdn: `47${winners[i].phoneNumber}` });
- }
-
- const body = {
- sender: "Vinlottis",
- message:
- "Gratulerer som vinner av vinlottisen! Du vil snart få en SMS med oppdatering om hvordan gangen går!",
- recipients: numbers
- }
-
- return gatewayRequest(body);
-}
-
async function gatewayRequest(body) {
return new Promise((resolve, reject) => {
const options = {
hostname: "gatewayapi.com",
post: 443,
- path: `/rest/mtsms?token=${ config.gatewayToken }`,
+ path: `/rest/mtsms?token=${config.gatewayToken}`,
method: "POST",
headers: {
"Content-Type": "application/json"
}
- }
+ };
- const req = https.request(options, (res) => {
- console.log(`statusCode: ${ res.statusCode }`);
- console.log(`statusMessage: ${ res.statusMessage }`);
+ const req = https.request(options, res => {
+ console.log(`statusCode: ${res.statusCode}`);
+ console.log(`statusMessage: ${res.statusMessage}`);
- res.setEncoding('utf8');
+ res.setEncoding("utf8");
if (res.statusCode == 200) {
- res.on("data", (data) => {
- console.log("Response from message gateway:", data)
+ res.on("data", data => {
+ console.log("Response from message gateway:", data);
- resolve(JSON.parse(data))
+ resolve(JSON.parse(data));
});
} else {
- res.on("data", (data) => {
+ res.on("data", data => {
data = JSON.parse(data);
- return reject('Gateway error: ' + data['message'] || data)
+ return reject("Gateway error: " + data["message"] || data);
});
}
- })
+ });
- req.on("error", (error) => {
- console.error(`Error from sms service: ${ error }`);
- reject(`Error from sms service: ${ error }`);
- })
+ req.on("error", error => {
+ console.error(`Error from sms service: ${error}`);
+ reject(`Error from sms service: ${error}`);
+ });
req.write(JSON.stringify(body));
req.end();
@@ -123,9 +124,9 @@ async function gatewayRequest(body) {
}
module.exports = {
- sendWineSelectMessage,
+ sendInitialMessageToWinners,
+ sendPrizeSelectionLink,
sendWineConfirmation,
sendLastWinnerMessage,
- sendWineSelectMessageTooLate,
- sendInitialMessageToWinners
-}
+ sendWineSelectMessageTooLate
+};
diff --git a/api/middleware/alwaysAuthenticatedWhenLocalhost.js b/api/middleware/alwaysAuthenticatedWhenLocalhost.js
new file mode 100644
index 0000000..c799ab3
--- /dev/null
+++ b/api/middleware/alwaysAuthenticatedWhenLocalhost.js
@@ -0,0 +1,6 @@
+const alwaysAuthenticatedWhenLocalhost = (req, res, next) => {
+ req.isAuthenticated = () => true;
+ return next();
+};
+
+module.exports = alwaysAuthenticatedWhenLocalhost;
diff --git a/api/middleware/mustBeAuthenticated.js b/api/middleware/mustBeAuthenticated.js
index 77dfe87..2c44d0e 100644
--- a/api/middleware/mustBeAuthenticated.js
+++ b/api/middleware/mustBeAuthenticated.js
@@ -1,10 +1,4 @@
const mustBeAuthenticated = (req, res, next) => {
- if (process.env.NODE_ENV == "development") {
- console.info(`Restricted endpoint ${req.originalUrl}, allowing with environment development.`)
- req.isAuthenticated = () => true;
- return next();
- }
-
if (!req.isAuthenticated()) {
return res.status(401).send({
success: false,
diff --git a/api/prelotteryWine.js b/api/prelotteryWine.js
new file mode 100644
index 0000000..cfbcfb5
--- /dev/null
+++ b/api/prelotteryWine.js
@@ -0,0 +1,103 @@
+const path = require("path");
+
+const PreLotteryWine = require(path.join(__dirname, "/schemas/PreLotteryWine"));
+const { WineNotFound } = require(path.join(__dirname, "/vinlottisErrors"));
+
+const allWines = () => {
+ return PreLotteryWine.find().populate("winner");
+};
+
+const allWinesWithoutWinner = () => {
+ return PreLotteryWine.find({ winner: { $exists: false } });
+};
+
+const addWines = wines => {
+ const prelotteryWines = wines.map(wine => {
+ let newPrelotteryWine = new PreLotteryWine({
+ name: wine.name,
+ vivinoLink: wine.vivinoLink,
+ rating: wine.rating,
+ year: wine.year,
+ image: wine.image,
+ price: wine.price,
+ country: wine.country,
+ id: wine.id
+ });
+
+ return newPrelotteryWine.save();
+ });
+
+ return Promise.all(prelotteryWines);
+};
+
+const wineById = id => {
+ return PreLotteryWine.findOne({ _id: id }).then(wine => {
+ if (wine == null) {
+ throw new WineNotFound();
+ }
+ return wine;
+ });
+};
+
+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,
+ year: updateModel.year != null ? updateModel.year : wine.year,
+ 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 addWinnerToWine = (wine, winner) => {
+ wine.winner = winner;
+ winner.prize_selected = true;
+ return Promise.all([wine.save(), winner.save()]);
+};
+
+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 wineSchema = () => {
+ let schema = { ...PreLotteryWine.schema.obj };
+ let nulledSchema = Object.keys(schema).reduce((accumulator, current) => {
+ accumulator[current] = "";
+ return accumulator;
+ }, {});
+
+ return Promise.resolve(nulledSchema);
+};
+
+module.exports = {
+ allWines,
+ allWinesWithoutWinner,
+ addWines,
+ wineById,
+ addWinnerToWine,
+ updateWineById,
+ deleteWineById,
+ deleteWines,
+ wineSchema
+};
diff --git a/api/prizeDistribution.js b/api/prizeDistribution.js
new file mode 100644
index 0000000..e04eb89
--- /dev/null
+++ b/api/prizeDistribution.js
@@ -0,0 +1,110 @@
+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 historyRepository = require(path.join(__dirname, "/history"));
+const winnerRepository = require(path.join(__dirname, "/winner"));
+const wineRepository = require(path.join(__dirname, "/wine"));
+const prelotteryWineRepository = require(path.join(__dirname, "/prelotteryWine"));
+
+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 (
+ foundWinner.timestamp_limit == undefined ||
+ foundWinner.timestamp_sent == undefined ||
+ foundWinner.prize_selected == true
+ ) {
+ throw new WineSelectionWinnerNotNextInLine();
+ }
+
+ return Promise.resolve(foundWinner);
+};
+
+const claimPrize = (wine, winner) => {
+ return wineRepository
+ .addWine(wine)
+ .then(_ => prelotteryWineRepository.addWinnerToWine(wine, winner)) // prelotteryWine.deleteById
+ .then(_ => historyRepository.addWinnerWithWine(winner, wine)) // wines.js : addWine
+ .then(_ => message.sendWineConfirmation(winner, wine));
+};
+
+const notifyNextWinner = async () => {
+ let nextWinner = undefined;
+
+ const winnersLeft = await VirtualWinner.find({ prize_selected: false }).sort({ timestamp_drawn: 1 });
+ const winesLeft = await PreLotteryWine.find({ winner: { $exists: false } });
+
+ 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(wine, nextWinner);
+ }
+
+ 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, prize_selected: false });
+ 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();
+
+ notifyNextWinner();
+ }, minutesForTimeout * minute);
+
+ return Promise.resolve();
+}
+
+module.exports = {
+ verifyWinnerNextInLine,
+ claimPrize,
+ notifyNextWinner
+};
diff --git a/api/request.js b/api/request.js
index 60388fa..01ada71 100644
--- a/api/request.js
+++ b/api/request.js
@@ -1,41 +1,20 @@
-const express = require("express");
const path = require("path");
-const RequestedWine = require(path.join(
- __dirname, "/schemas/RequestedWine"
-));
-const Wine = require(path.join(
- __dirname, "/schemas/Wine"
-));
+const RequestedWine = require(path.join(__dirname, "/schemas/RequestedWine"));
+const Wine = require(path.join(__dirname, "/schemas/Wine"));
-const deleteRequestedWineById = async (req, res) => {
- const { id } = req.params;
- if(id == null){
- return res.json({
- message: "Id er ikke definert",
- success: false
- })
+class RequestedWineNotFound extends Error {
+ constructor(message = "Wine with this id was not found.") {
+ super(message);
+ this.name = "RequestedWineNotFound";
+ this.statusCode = 404;
}
-
- await RequestedWine.deleteOne({wineId: id})
- return res.json({
- message: `Slettet vin med id: ${id}`,
- success: true
- });
}
-const getAllRequestedWines = async (req, res) => {
- const allWines = await RequestedWine.find({}).populate("wine");
+const addNew = async wine => {
+ let foundWine = await Wine.findOne({ id: wine.id });
- return res.json(allWines);
-}
-
-const requestNewWine = async (req, res) => {
- const {wine} = req.body
-
- let thisWineIsLOKO = await Wine.findOne({id: wine.id})
-
- if(thisWineIsLOKO == undefined){
- thisWineIsLOKO = new Wine({
+ if (foundWine == undefined) {
+ foundWine = new Wine({
name: wine.name,
vivinoLink: wine.vivinoLink,
rating: null,
@@ -43,27 +22,47 @@ const requestNewWine = async (req, res) => {
image: wine.image,
id: wine.id
});
- await thisWineIsLOKO.save()
+ await foundWine.save();
}
- let requestedWine = await RequestedWine.findOne({ "wineId": wine.id})
+ let requestedWine = await RequestedWine.findOne({ wineId: wine.id });
- if(requestedWine == undefined){
+ if (requestedWine == undefined) {
requestedWine = new RequestedWine({
count: 1,
wineId: wine.id,
- wine: thisWineIsLOKO
- })
+ wine: foundWine
+ });
} else {
requestedWine.count += 1;
}
- await requestedWine.save()
+ await requestedWine.save();
- return res.send(requestedWine);
-}
+ return requestedWine;
+};
+
+const getById = id => {
+ return RequestedWine.findOne({ wineId: id })
+ .populate("wine")
+ .then(wine => {
+ if (wine == null) {
+ throw new RequestedWineNotFound();
+ }
+
+ return wine;
+ });
+};
+
+const deleteById = id => {
+ return getById(id).then(requestedWine => RequestedWine.deleteOne({ _id: requestedWine._id }));
+};
+
+const getAll = () => {
+ return RequestedWine.find({}).populate("wine");
+};
module.exports = {
- requestNewWine,
- getAllRequestedWines,
- deleteRequestedWineById
+ addNew,
+ getAll,
+ deleteById
};
diff --git a/api/retrieve.js b/api/retrieve.js
deleted file mode 100644
index c29133a..0000000
--- a/api/retrieve.js
+++ /dev/null
@@ -1,154 +0,0 @@
-const path = require("path");
-
-const Purchase = require(path.join(__dirname, "/schemas/Purchase"));
-const Wine = require(path.join(__dirname, "/schemas/Wine"));
-const Highscore = require(path.join(__dirname, "/schemas/Highscore"));
-const PreLotteryWine = require(path.join(
- __dirname, "/schemas/PreLotteryWine"
-));
-
-const prelotteryWines = async (req, res) => {
- let wines = await PreLotteryWine.find();
- return res.json(wines);
-};
-
-const allPurchase = async (req, res) => {
- let purchases = await Purchase.find()
- .populate("wines")
- .sort({ date: 1 });
- return res.json(purchases);
-};
-
-const purchaseByColor = async (req, res) => {
- const countColor = await Purchase.find();
- let red = 0;
- let blue = 0;
- let yellow = 0;
- let green = 0;
- let stolen = 0;
- for (let i = 0; i < countColor.length; i++) {
- let element = countColor[i];
- red += element.red;
- blue += element.blue;
- yellow += element.yellow;
- green += element.green;
- if (element.stolen != undefined) {
- stolen += element.stolen;
- }
- }
-
- const highscore = await Highscore.find();
- let redWin = 0;
- let blueWin = 0;
- let yellowWin = 0;
- let greenWin = 0;
- for (let i = 0; i < highscore.length; i++) {
- let element = highscore[i];
- for (let y = 0; y < element.wins.length; y++) {
- let currentWin = element.wins[y];
- switch (currentWin.color) {
- case "blue":
- blueWin += 1;
- break;
- case "red":
- redWin += 1;
- break;
- case "yellow":
- yellowWin += 1;
- break;
- case "green":
- greenWin += 1;
- break;
- }
- }
- }
-
- const total = red + yellow + blue + green;
-
- return res.json({
- red: {
- total: red,
- win: redWin
- },
- blue: {
- total: blue,
- win: blueWin
- },
- green: {
- total: green,
- win: greenWin
- },
- yellow: {
- total: yellow,
- win: yellowWin
- },
- stolen: stolen,
- total: total
- });
-};
-
-const highscore = async (req, res) => {
- const highscore = await Highscore.find().populate("wins.wine");
-
- return res.json(highscore);
-};
-
-const allWines = async (req, res) => {
- const wines = await Wine.find();
-
- return res.json(wines);
-};
-
-const allWinesSummary = async (req, res) => {
- const highscore = await Highscore.find().populate("wins.wine");
- let wines = {};
-
- for (let i = 0; i < highscore.length; i++) {
- let person = highscore[i];
- for (let y = 0; y < person.wins.length; y++) {
- let wine = person.wins[y].wine;
- let date = person.wins[y].date;
- let color = person.wins[y].color;
-
- if (wines[wine._id] == undefined) {
- wines[wine._id] = {
- name: wine.name,
- occurences: wine.occurences,
- vivinoLink: wine.vivinoLink,
- rating: wine.rating,
- image: wine.image,
- id: wine.id,
- _id: wine._id,
- dates: [date],
- winners: [person.name],
- red: 0,
- blue: 0,
- green: 0,
- yellow: 0
- };
- wines[wine._id][color] += 1;
- } else {
- wines[wine._id].dates.push(date);
- wines[wine._id].winners.push(person.name);
- if (wines[wine._id][color] == undefined) {
- wines[wine._id][color] = 1;
- } else {
- wines[wine._id][color] += 1;
- }
- }
- }
- }
-
- wines = Object.values(wines).reverse()
-
- return res.json(wines);
-};
-
-module.exports = {
- prelotteryWines,
- allPurchase,
- purchaseByColor,
- highscore,
- allWines,
- allWinesSummary
-};
diff --git a/api/router.js b/api/router.js
index 4d57057..77e8661 100644
--- a/api/router.js
+++ b/api/router.js
@@ -4,67 +4,97 @@ const path = require("path");
const mustBeAuthenticated = require(path.join(__dirname, "/middleware/mustBeAuthenticated"));
const setAdminHeaderIfAuthenticated = require(path.join(__dirname, "/middleware/setAdminHeaderIfAuthenticated"));
-const update = require(path.join(__dirname, "/update"));
-const retrieve = require(path.join(__dirname, "/retrieve"));
-const request = require(path.join(__dirname, "/request"));
-const subscriptionApi = require(path.join(__dirname, "/subscriptions"));
-const userApi = require(path.join(__dirname, "/user"));
-const wineinfo = require(path.join(__dirname, "/wineinfo"));
-const virtualApi = require(path.join(__dirname, "/virtualLottery"));
-const virtualRegistrationApi = require(path.join(
- __dirname, "/virtualRegistration"
-));
-const lottery = require(path.join(__dirname, "/lottery"));
-const chatHistoryApi = require(path.join(__dirname, "/chatHistory"));
+const requestController = require(path.join(__dirname, "/controllers/requestController"));
+const vinmonopoletController = require(path.join(__dirname, "/controllers/vinmonopoletController"));
+const chatController = require(path.join(__dirname, "/controllers/chatController"));
+const userController = require(path.join(__dirname, "/controllers/userController"));
+const historyController = require(path.join(__dirname, "/controllers/historyController"));
+const attendeeController = require(path.join(__dirname, "/controllers/lotteryAttendeeController"));
+const prelotteryWineController = require(path.join(__dirname, "/controllers/lotteryWineController"));
+const winnerController = require(path.join(__dirname, "/controllers/lotteryWinnerController"));
+const lotteryController = require(path.join(__dirname, "/controllers/lotteryController"));
+const prizeDistributionController = require(path.join(__dirname, "/controllers/prizeDistributionController"));
+const wineController = require(path.join(__dirname, "/controllers/wineController"));
+const messageController = require(path.join(__dirname, "/controllers/messageController"));
const router = express.Router();
-router.get("/wineinfo/search", wineinfo.wineSearch);
+router.get("/vinmonopolet/wine/search", vinmonopoletController.searchWines);
+router.get("/vinmonopolet/wine/by-ean/:ean", vinmonopoletController.wineByEAN);
+router.get("/vinmonopolet/wine/by-id/:id", vinmonopoletController.wineById);
+router.get("/vinmonopolet/stores/", vinmonopoletController.allStores);
+router.get("/vinmonopolet/stores/search", vinmonopoletController.searchStores);
-router.get("/request/all", setAdminHeaderIfAuthenticated, request.getAllRequestedWines);
-router.post("/request/new-wine", request.requestNewWine);
-router.delete("/request/:id", request.deleteRequestedWineById);
+router.get("/requests", setAdminHeaderIfAuthenticated, requestController.allRequests);
+router.post("/request", requestController.addRequest);
+router.delete("/request/:id", mustBeAuthenticated, requestController.deleteRequest);
-router.get("/wineinfo/schema", mustBeAuthenticated, update.schema);
-router.get("/wineinfo/:ean", wineinfo.byEAN);
+router.get("/wines", wineController.allWines); // sort = by-date, by-name, by-occurences
+router.get("/wine/:id", wineController.wineById); // sort = by-date, by-name, by-occurences
+// router.update("/wine/:id", mustBeAuthenticated, wineController.update);
-router.post("/log/wines", mustBeAuthenticated, update.submitWines);
-router.post("/lottery", update.submitLottery);
-router.post("/lottery/wines", update.submitWinesToLottery);
-// router.delete("/lottery/wine/:id", update.deleteWineFromLottery);
-router.post("/lottery/winners", update.submitWinnersToLottery);
+router.get("/history", historyController.all);
+router.get("/history/latest", historyController.latest);
+router.get("/history/by-wins/", historyController.orderByWins);
+router.get("/history/by-color/", historyController.groupByColor);
+router.get("/history/by-date/:date", historyController.byDate);
+router.get("/history/by-name/:name", historyController.byName);
+router.get("/history/search/", historyController.search);
+router.get("/history/by-date/", historyController.groupByDate);
-router.get("/wines/prelottery", retrieve.prelotteryWines);
-router.get("/purchase/statistics", retrieve.allPurchase);
-router.get("/purchase/statistics/color", retrieve.purchaseByColor);
-router.get("/highscore/statistics", retrieve.highscore)
-router.get("/wines/statistics", retrieve.allWines);
-router.get("/wines/statistics/overall", retrieve.allWinesSummary);
+// router.get("/purchases", purchaseController.lotteryPurchases);
+// // returns list per date and count of each colors that where bought
+// router.get("/purchases/summary", purchaseController.lotteryPurchases);
+// // returns total, wins?, stolen
+// router.get("/purchase/:date", purchaseController.lotteryPurchaseByDate);
-router.get("/lottery/all", lottery.all);
-router.get("/lottery/latest", lottery.latest);
-router.get("/lottery/by-date/:date", lottery.byEpochDate);
-router.get("/lottery/by-name/:name", lottery.byName);
+router.get("/lottery/wines", prelotteryWineController.allWines);
+router.get("/lottery/wine/schema", mustBeAuthenticated, prelotteryWineController.wineSchema);
+router.get("/lottery/wine/:id", mustBeAuthenticated, prelotteryWineController.wineById);
+router.post("/lottery/wines", mustBeAuthenticated, prelotteryWineController.addWines);
+router.delete("/lottery/wines", mustBeAuthenticated, prelotteryWineController.deleteWines);
+router.put("/lottery/wine/:id", mustBeAuthenticated, prelotteryWineController.updateWineById);
+router.delete("/lottery/wine/:id", mustBeAuthenticated, prelotteryWineController.deleteWineById);
-router.delete('/virtual/winner/all', mustBeAuthenticated, virtualApi.deleteWinners);
-router.delete('/virtual/attendee/all', mustBeAuthenticated, virtualApi.deleteAttendees);
-router.get('/virtual/winner/draw', virtualApi.drawWinner);
-router.get('/virtual/winner/all', virtualApi.winners);
-router.get('/virtual/winner/all/secure', mustBeAuthenticated, virtualApi.winnersSecure);
-router.post('/virtual/finish', mustBeAuthenticated, virtualApi.finish);
-router.get('/virtual/attendee/all', virtualApi.attendees);
-router.get('/virtual/attendee/all/secure', mustBeAuthenticated, virtualApi.attendeesSecure);
-router.post('/virtual/attendee/add', mustBeAuthenticated, virtualApi.addAttendee);
+router.get("/lottery/attendees", setAdminHeaderIfAuthenticated, attendeeController.allAttendees);
+router.delete("/lottery/attendees", mustBeAuthenticated, attendeeController.deleteAttendees);
+router.post("/lottery/attendee", mustBeAuthenticated, attendeeController.addAttendee);
+router.put("/lottery/attendee/:id", mustBeAuthenticated, attendeeController.updateAttendeeById);
+router.delete("/lottery/attendee/:id", mustBeAuthenticated, attendeeController.deleteAttendeeById);
-router.post('/winner/notify/:id', virtualRegistrationApi.sendNotificationToWinnerById);
-router.get('/winner/:id', virtualRegistrationApi.getWinesToWinnerById);
-router.post('/winner/:id', virtualRegistrationApi.registerWinnerSelection);
+router.get("/lottery/winners", winnerController.allWinners);
+router.get("/lottery/winner/:id", winnerController.winnerById);
+router.post("/lottery/winners", mustBeAuthenticated, winnerController.addWinners);
+router.delete("/lottery/winners", mustBeAuthenticated, winnerController.deleteWinners);
+router.put("/lottery/winner/:id", mustBeAuthenticated, winnerController.updateWinnerById);
+router.delete("/lottery/winner/:id", mustBeAuthenticated, winnerController.deleteWinnerById);
-router.get('/chat/history', chatHistoryApi.getAllHistory)
-router.delete('/chat/history', mustBeAuthenticated, chatHistoryApi.deleteHistory)
+router.get("/lottery/draw", mustBeAuthenticated, lotteryController.drawWinner);
+router.post("/lottery/archive", mustBeAuthenticated, lotteryController.archiveLottery);
+router.get("/lottery/:epoch", lotteryController.lotteryByDate);
+router.get("/lotteries/", lotteryController.allLotteries);
-router.post('/login', userApi.login);
-router.post('/register', mustBeAuthenticated, userApi.register);
-router.get('/logout', userApi.logout);
+// router.get("/lottery/prize-distribution/status", mustBeAuthenticated, prizeDistributionController.status);
+router.post("/lottery/prize-distribution/start", mustBeAuthenticated, prizeDistributionController.start);
+// router.post("/lottery/prize-distribution/stop", mustBeAuthenticated, prizeDistributionController.stop);
+router.get("/lottery/prize-distribution/prizes/:id", prizeDistributionController.getPrizesForWinnerById);
+router.post("/lottery/prize-distribution/prize/:id", prizeDistributionController.submitPrizeForWinnerById);
+
+router.post("/lottery/messages/winner/:id", mustBeAuthenticated, messageController.notifyWinnerById);
+
+router.get("/chat/history", chatController.getAllHistory);
+router.delete("/chat/history", mustBeAuthenticated, chatController.deleteHistory);
+
+router.post("/login", userController.login);
+router.post("/register", mustBeAuthenticated, userController.register);
+router.get("/logout", userController.logout);
+
+// router.get("/", documentation.apiInfo);
+
+// router.get("/wine/schema", mustBeAuthenticated, update.schema);
+// router.get("/purchase/statistics", retrieve.allPurchase);
+// router.get("/highscore/statistics", retrieve.highscore);
+// router.get("/wines/statistics", retrieve.allWines);
+// router.get("/wines/statistics/overall", retrieve.allWinesSummary);
module.exports = router;
diff --git a/api/schemas/PreLotteryWine.js b/api/schemas/PreLotteryWine.js
index 69295b5..64f950c 100644
--- a/api/schemas/PreLotteryWine.js
+++ b/api/schemas/PreLotteryWine.js
@@ -6,9 +6,14 @@ const PreLotteryWine = new Schema({
vivinoLink: String,
rating: Number,
id: String,
+ year: Number,
image: String,
price: String,
- country: String
+ country: String,
+ winner: {
+ type: Schema.Types.ObjectId,
+ ref: "VirtualWinner"
+ }
});
module.exports = mongoose.model("PreLotteryWine", PreLotteryWine);
diff --git a/api/schemas/VirtualWinner.js b/api/schemas/VirtualWinner.js
index 94f69d1..f18ced1 100644
--- a/api/schemas/VirtualWinner.js
+++ b/api/schemas/VirtualWinner.js
@@ -10,6 +10,10 @@ const VirtualWinner = new Schema({
red: Number,
yellow: Number,
id: String,
+ prize_selected: {
+ type: Boolean,
+ default: false
+ },
timestamp_drawn: Number,
timestamp_sent: Number,
timestamp_limit: Number
diff --git a/api/schemas/Wine.js b/api/schemas/Wine.js
index a24fc83..b3c9029 100644
--- a/api/schemas/Wine.js
+++ b/api/schemas/Wine.js
@@ -1,15 +1,16 @@
const mongoose = require("mongoose");
const Schema = mongoose.Schema;
-const Wine = new Schema({
+const WineSchema = new Schema({
name: String,
vivinoLink: String,
rating: Number,
occurences: Number,
id: String,
+ year: Number,
image: String,
price: String,
country: String
});
-module.exports = mongoose.model("Wine", Wine);
+module.exports = mongoose.model("Wine", WineSchema);
diff --git a/api/update.js b/api/update.js
deleted file mode 100644
index 88df56a..0000000
--- a/api/update.js
+++ /dev/null
@@ -1,142 +0,0 @@
-const express = require("express");
-const path = require("path");
-
-const sub = require(path.join(__dirname, "/subscriptions"));
-
-const _wineFunctions = require(path.join(__dirname, "/wine"));
-const _personFunctions = require(path.join(__dirname, "/person"));
-const Subscription = require(path.join(__dirname, "/schemas/Subscription"));
-const Lottery = require(path.join(__dirname, "/schemas/Purchase"));
-const PreLotteryWine = require(path.join(
- __dirname, "/schemas/PreLotteryWine"
-));
-
-const submitWines = async (req, res) => {
- const wines = req.body;
- for (let i = 0; i < wines.length; i++) {
- let wine = wines[i];
- let newWonWine = new PreLotteryWine({
- name: wine.name,
- vivinoLink: wine.vivinoLink,
- rating: wine.rating,
- image: wine.image,
- price: wine.price,
- country: wine.country,
- id: wine.id
- });
- await newWonWine.save();
- }
-
- let subs = await Subscription.find();
- console.log("Sending new wines w/ push notification to all subscribers.")
- for (let i = 0; i < subs.length; i++) {
- let subscription = subs[i]; //get subscription from your databse here.
-
- const message = JSON.stringify({
- message: "Dagens vin er lagt til, se den på lottis.vin/dagens!",
- title: "Ny vin!",
- link: "/#/dagens"
- });
-
- try {
- sub.sendNotification(subscription, message);
- } catch (error) {
- console.error("Error when trying to send push notification to subscriber.");
- console.error(error);
- }
- }
-
- return res.send({
- message: "Submitted and notified push subscribers of new wines!",
- success: true
- });
-};
-
-const schema = async (req, res) => {
- let schema = { ...PreLotteryWine.schema.obj };
- let nulledSchema = Object.keys(schema).reduce((accumulator, current) => {
- accumulator[current] = "";
- return accumulator
- }, {});
-
- return res.send(nulledSchema);
-}
-
-// TODO IMPLEMENT WITH FRONTEND (unused)
-const submitWinesToLottery = async (req, res) => {
- const { lottery } = req.body;
- const { date, wines } = lottery;
- const wineObjects = await Promise.all(wines.map(async (wine) => await _wineFunctions.findSaveWine(wine)))
-
- return Lottery.findOneAndUpdate({ date: date }, {
- date: date,
- wines: wineObjects
- }, {
- upsert: true
- }).then(_ => res.send(true))
- .catch(err => res.status(500).send({ message: 'Unexpected error while updating/saving wine to lottery.',
- success: false,
- exception: err.message }));
-}
-
- /**
- * @apiParam (Request body) {Array} winners List of winners
- */
-const submitWinnersToLottery = async (req, res) => {
- const { lottery } = req.body;
- const { winners, date } = lottery;
-
- for (let i = 0; i < winners.length; i++) {
- let currentWinner = winners[i];
- let wonWine = await _wineFunctions.findSaveWine(currentWinner.wine); // TODO rename to findAndSaveWineToLottery
- await _personFunctions.findSavePerson(currentWinner, wonWine, date); // TODO rename to findAndSaveWineToPerson
- }
-
- return res.json(true);
-}
-
- /**
- * @apiParam (Request body) {Date} date Date of lottery
- * @apiParam (Request body) {Number} blue Number of blue tickets
- * @apiParam (Request body) {Number} red Number of red tickets
- * @apiParam (Request body) {Number} green Number of green tickets
- * @apiParam (Request body) {Number} yellow Number of yellow tickets
- * @apiParam (Request body) {Number} bought Number of tickets bought
- * @apiParam (Request body) {Number} stolen Number of tickets stolen
- */
-const submitLottery = async (req, res) => {
- const { lottery } = req.body
-
- const { date,
- blue,
- red,
- yellow,
- green,
- bought,
- stolen } = lottery;
-
- return Lottery.findOneAndUpdate({ date: date }, {
- date: date,
- blue: blue,
- yellow: yellow,
- red: red,
- green: green,
- bought: bought,
- stolen: stolen
- }, {
- upsert: true
- }).then(_ => res.send(true))
- .catch(err => res.status(500).send({ message: 'Unexpected error while updating/saving lottery.',
- success: false,
- exception: err.message }));
-
- return res.send(true);
-};
-
-module.exports = {
- submitWines,
- schema,
- submitLottery,
- submitWinnersToLottery,
- submitWinesToLottery
-};
diff --git a/api/user.js b/api/user.js
index 7a2ce9e..34c7af4 100644
--- a/api/user.js
+++ b/api/user.js
@@ -1,51 +1,90 @@
const passport = require("passport");
const path = require("path");
const User = require(path.join(__dirname, "/schemas/User"));
-const router = require("express").Router();
-const register = (req, res, next) => {
- User.register(
- new User({ username: req.body.username }),
- req.body.password,
- function(err) {
+class UserExistsError extends Error {
+ constructor(message = "Username already exists.") {
+ super(message);
+ this.name = "UserExists";
+ this.statusCode = 409;
+ }
+}
+
+class MissingUsernameError extends Error {
+ constructor(message = "No username given.") {
+ super(message);
+ this.name = "MissingUsernameError";
+ this.statusCode = 400;
+ }
+}
+
+class MissingPasswordError extends Error {
+ constructor(message = "No password given.") {
+ super(message);
+ this.name = "MissingPasswordError";
+ this.statusCode = 400;
+ }
+}
+
+class IncorrectUserCredentialsError extends Error {
+ constructor(message = "Incorrect username or password") {
+ super(message);
+ this.name = "IncorrectUserCredentialsError";
+ this.statusCode = 404;
+ }
+}
+
+function userAuthenticationErrorHandler(err) {
+ if (err.name == "UserExistsError") {
+ throw new UserExistsError(err.message);
+ } else if (err.name == "MissingUsernameError") {
+ throw new MissingUsernameError(err.message);
+ } else if (err.name == "MissingPasswordError") {
+ throw new MissingPasswordError(err.message);
+ }
+
+ throw err;
+}
+
+const register = (username, password) => {
+ return User.register(new User({ username: username }), password).catch(userAuthenticationErrorHandler);
+};
+
+const authenticate = req => {
+ return new Promise((resolve, reject) => {
+ const { username, password } = req.body;
+
+ if (username == undefined) throw new MissingUsernameError();
+ if (password == undefined) throw new MissingPasswordError();
+
+ passport.authenticate("local", function(err, user, info) {
if (err) {
- if (err.name == "UserExistsError")
- res.status(409).send({ success: false, message: err.message })
- else if (err.name == "MissingUsernameError" || err.name == "MissingPasswordError")
- res.status(400).send({ success: false, message: err.message })
- return next(err);
+ reject(err);
}
- return res.status(200).send({ message: "Bruker registrert. Velkommen " + req.body.username, success: true })
- }
- );
+ if (!user) {
+ reject(new IncorrectUserCredentialsError());
+ }
+
+ resolve(user);
+ })(req);
+ });
};
-const login = (req, res, next) => {
- passport.authenticate("local", function(err, user, info) {
- if (err) {
- if (err.name == "MissingUsernameError" || err.name == "MissingPasswordError")
- return res.status(400).send({ message: err.message, success: false })
- return next(err);
- }
+const login = (req, user) => {
+ return new Promise((resolve, reject) => {
+ req.logIn(user, err => {
+ if (err) {
+ reject(err);
+ }
- if (!user) return res.status(404).send({ message: "Incorrect username or password", success: false })
-
- req.logIn(user, (err) => {
- if (err) { return next(err) }
-
- return res.status(200).send({ message: "Velkommen " + user.username, success: true })
- })
- })(req, res, next);
-};
-
-const logout = (req, res) => {
- req.logout();
- res.redirect("/");
+ resolve(user);
+ });
+ });
};
module.exports = {
register,
- login,
- logout
+ authenticate,
+ login
};
diff --git a/api/vinlottisErrors.js b/api/vinlottisErrors.js
new file mode 100644
index 0000000..d812627
--- /dev/null
+++ b/api/vinlottisErrors.js
@@ -0,0 +1,90 @@
+class UserNotFound extends Error {
+ constructor(message = "User not found.") {
+ super(message);
+ this.name = "UserNotFound";
+ this.statusCode = 404;
+ }
+
+ // 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 WinnersTimelimitExpired extends Error {
+ constructor(message = "Timelimit expired, you will need to wait until it's your turn again.") {
+ super(message);
+ this.name = "WinnersTimelimitExpired";
+ this.statusCode = 403;
+ }
+}
+
+class WineSelectionWinnerNotNextInLine extends Error {
+ constructor(message = "Not the winner next in line!") {
+ super(message);
+ this.name = "WineSelectionWinnerNotNextInLine";
+ this.statusCode = 403;
+ }
+
+ // TODO log missing user
+}
+
+class NoMoreAttendeesToWin extends Error {
+ constructor(message = "No more attendees left to drawn from.") {
+ super(message);
+ this.name = "NoMoreAttendeesToWin";
+ this.statusCode = 404;
+ }
+}
+
+class CouldNotFindNewWinnerAfterNTries extends Error {
+ constructor(tries) {
+ let message = `Could not a new winner after ${tries} tries.`;
+ super(message);
+ this.name = "CouldNotFindNewWinnerAfterNTries";
+ this.statusCode = 404;
+ }
+}
+
+class LotteryByDateNotFound extends Error {
+ constructor(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);
+
+ const dateString = `${ye}-${mo}-${da}`;
+ const dateUnix = date.getTime();
+ const message = `Could not find lottery for date: ${dateString}.`;
+ super(message);
+ this.name = "LotteryByDateNotFoundError";
+ this.statusCode = 404;
+ }
+}
+
+module.exports = {
+ UserNotFound,
+ WineNotFound,
+ WinnerNotFound,
+ WinnersTimelimitExpired,
+ WineSelectionWinnerNotNextInLine,
+ NoMoreAttendeesToWin,
+ CouldNotFindNewWinnerAfterNTries,
+ LotteryByDateNotFound
+};
diff --git a/api/vinmonopolet.js b/api/vinmonopolet.js
new file mode 100644
index 0000000..164e09f
--- /dev/null
+++ b/api/vinmonopolet.js
@@ -0,0 +1,114 @@
+const fetch = require("node-fetch");
+const path = require("path");
+const config = require(path.join(__dirname + "/../config/env/lottery.config"));
+
+const convertToOurWineObject = wine => {
+ if (wine.basic.ageLimit === "18") {
+ return {
+ name: wine.basic.productShortName,
+ vivinoLink: "https://www.vinmonopolet.no/p/" + wine.basic.productId,
+ rating: wine.basic.alcoholContent,
+ occurences: 0,
+ id: wine.basic.productId,
+ year: wine.basic.vintage,
+ image: `https://bilder.vinmonopolet.no/cache/500x500-0/${wine.basic.productId}-1.jpg`,
+ price: wine.prices[0].salesPrice.toString(),
+ country: wine.origins.origin.country
+ };
+ }
+};
+
+const convertToOurStoreObject = store => {
+ return {
+ id: store.storeId,
+ name: store.storeName,
+ ...store.address
+ };
+};
+
+const searchWinesByName = async (name, page = 1) => {
+ const pageSize = 15;
+ let url = new URL(
+ `https://apis.vinmonopolet.no/products/v0/details-normal?productShortNameContains=gato&maxResults=15`
+ );
+ url.searchParams.set("maxResults", pageSize);
+ url.searchParams.set("start", pageSize * (page - 1));
+ url.searchParams.set("productShortNameContains", name);
+
+ const vinmonopoletResponse = await fetch(url, {
+ headers: {
+ "Ocp-Apim-Subscription-Key": config.vinmonopoletToken
+ }
+ })
+ .then(resp => resp.json())
+ .catch(err => console.error(err));
+
+ if (vinmonopoletResponse.errors != null) {
+ return vinmonopoletResponse.errors.map(error => {
+ if (error.type == "UnknownProductError") {
+ return res.status(404).json({
+ message: error.message
+ });
+ } else {
+ return next();
+ }
+ });
+ }
+ const winesConverted = vinmonopoletResponse.map(convertToOurWineObject).filter(Boolean);
+
+ return winesConverted;
+};
+
+const wineByEAN = ean => {
+ const url = `https://app.vinmonopolet.no/vmpws/v2/vmp/products/barCodeSearch/${ean}`;
+ return fetch(url)
+ .then(resp => resp.json())
+ .then(response => response.map(convertToOurWineObject));
+};
+
+const wineById = id => {
+ const url = `https://apis.vinmonopolet.no/products/v0/details-normal?productId=${id}`;
+ const options = {
+ headers: {
+ "Ocp-Apim-Subscription-Key": config.vinmonopoletToken
+ }
+ };
+
+ return fetch(url, options)
+ .then(resp => resp.json())
+ .then(response => response.map(convertToOurWineObject));
+};
+
+const allStores = () => {
+ const url = `https://apis.vinmonopolet.no/stores/v0/details`;
+ const options = {
+ headers: {
+ "Ocp-Apim-Subscription-Key": config.vinmonopoletToken
+ }
+ };
+
+ return fetch(url, options)
+ .then(resp => resp.json())
+ .then(response => response.map(convertToOurStoreObject));
+};
+
+const searchStoresByName = name => {
+ const url = `https://apis.vinmonopolet.no/stores/v0/details?storeNameContains=${name}`;
+ const options = {
+ headers: {
+ "Ocp-Apim-Subscription-Key": config.vinmonopoletToken
+ }
+ };
+
+ return fetch(url, options)
+ .then(resp => resp.json())
+ .then(response => response.map(convertToOurStoreObject));
+};
+
+module.exports = {
+ searchWinesByName,
+ wineByEAN,
+ wineById,
+ allStores,
+ searchStoresByName
+};
diff --git a/api/virtualLottery.js b/api/virtualLottery.js
deleted file mode 100644
index 706016b..0000000
--- a/api/virtualLottery.js
+++ /dev/null
@@ -1,281 +0,0 @@
-const path = require("path");
-const crypto = require("crypto");
-
-const config = require(path.join(__dirname, "/../config/defaults/lottery"));
-const Message = require(path.join(__dirname, "/message"));
-const { findAndNotifyNextWinner } = require(path.join(__dirname, "/virtualRegistration"));
-
-const Attendee = require(path.join(__dirname, "/schemas/Attendee"));
-const VirtualWinner = require(path.join(__dirname, "/schemas/VirtualWinner"));
-const PreLotteryWine = require(path.join(__dirname, "/schemas/PreLotteryWine"));
-
-
-const winners = async (req, res) => {
- let winners = await VirtualWinner.find();
- let winnersRedacted = [];
- let winner;
- for (let i = 0; i < winners.length; i++) {
- winner = winners[i];
- winnersRedacted.push({
- name: winner.name,
- color: winner.color
- });
- }
- res.json(winnersRedacted);
-};
-
-const winnersSecure = async (req, res) => {
- let winners = await VirtualWinner.find();
-
- return res.json(winners);
-};
-
-const deleteWinners = async (req, res) => {
- await VirtualWinner.deleteMany();
- var io = req.app.get('socketio');
- io.emit("refresh_data", {});
- return res.json(true);
-};
-
-const attendees = async (req, res) => {
- let attendees = await Attendee.find();
- let attendeesRedacted = [];
- let attendee;
- for (let i = 0; i < attendees.length; i++) {
- attendee = attendees[i];
- attendeesRedacted.push({
- name: attendee.name,
- raffles: attendee.red + attendee.blue + attendee.yellow + attendee.green,
- red: attendee.red,
- blue: attendee.blue,
- green: attendee.green,
- yellow: attendee.yellow
- });
- }
- return res.json(attendeesRedacted);
-};
-
-const attendeesSecure = async (req, res) => {
- let attendees = await Attendee.find();
-
- return res.json(attendees);
-};
-
-const addAttendee = async (req, res) => {
- const attendee = req.body;
- const { red, blue, yellow, green } = attendee;
-
- let newAttendee = new Attendee({
- name: attendee.name,
- red,
- blue,
- green,
- yellow,
- phoneNumber: attendee.phoneNumber,
- winner: false
- });
- await newAttendee.save();
-
-
- var io = req.app.get('socketio');
- io.emit("new_attendee", {});
-
- return res.send(true);
-};
-
-const deleteAttendees = async (req, res) => {
- await Attendee.deleteMany();
- var io = req.app.get('socketio');
- io.emit("refresh_data", {});
- return res.json(true);
-};
-
-const drawWinner = async (req, res) => {
- let allContestants = await Attendee.find({ winner: false });
-
- if (allContestants.length == 0) {
- return res.json({
- success: false,
- message: "No attendees left that have not won."
- });
- }
- let raffleColors = [];
- for (let i = 0; i < allContestants.length; i++) {
- let currentContestant = allContestants[i];
- for (let blue = 0; blue < currentContestant.blue; blue++) {
- raffleColors.push("blue");
- }
- for (let red = 0; red < currentContestant.red; red++) {
- raffleColors.push("red");
- }
- for (let green = 0; green < currentContestant.green; green++) {
- raffleColors.push("green");
- }
- for (let yellow = 0; yellow < currentContestant.yellow; yellow++) {
- raffleColors.push("yellow");
- }
- }
-
- raffleColors = shuffle(raffleColors);
-
- let colorToChooseFrom =
- raffleColors[Math.floor(Math.random() * raffleColors.length)];
- let findObject = { winner: false };
-
- findObject[colorToChooseFrom] = { $gt: 0 };
-
- let tries = 0;
- const maxTries = 3;
- let contestantsToChooseFrom = undefined;
- while (contestantsToChooseFrom == undefined && tries < maxTries) {
- const hit = await Attendee.find(findObject);
- if (hit && hit.length) {
- contestantsToChooseFrom = hit;
- break;
- }
- tries++;
- }
- if (contestantsToChooseFrom == undefined) {
- return res.status(404).send({
- success: false,
- message: `Klarte ikke trekke en vinner etter ${maxTries} forsøk.`
- });
- }
-
- let attendeeListDemocratic = [];
-
- let currentContestant;
- for (let i = 0; i < contestantsToChooseFrom.length; i++) {
- currentContestant = contestantsToChooseFrom[i];
- for (let y = 0; y < currentContestant[colorToChooseFrom]; y++) {
- attendeeListDemocratic.push({
- name: currentContestant.name,
- phoneNumber: currentContestant.phoneNumber,
- red: currentContestant.red,
- blue: currentContestant.blue,
- green: currentContestant.green,
- yellow: currentContestant.yellow
- });
- }
- }
-
- attendeeListDemocratic = shuffle(attendeeListDemocratic);
-
- let winner =
- attendeeListDemocratic[
- Math.floor(Math.random() * attendeeListDemocratic.length)
- ];
-
- let winners = await VirtualWinner.find({ timestamp_sent: undefined }).sort({
- timestamp_drawn: 1
- });
-
- var io = req.app.get('socketio');
- io.emit("winner", {
- color: colorToChooseFrom,
- name: winner.name,
- winner_count: winners.length + 1
- });
-
- let newWinnerElement = new VirtualWinner({
- name: winner.name,
- phoneNumber: winner.phoneNumber,
- color: colorToChooseFrom,
- red: winner.red,
- blue: winner.blue,
- green: winner.green,
- yellow: winner.yellow,
- id: sha512(winner.phoneNumber, genRandomString(10)),
- timestamp_drawn: new Date().getTime()
- });
-
- await Attendee.update(
- { name: winner.name, phoneNumber: winner.phoneNumber },
- { $set: { winner: true } }
- );
-
- await newWinnerElement.save();
- return res.json({
- success: true,
- winner
- });
-};
-
-const finish = async (req, res) => {
- if (!config.gatewayToken) {
- return res.json({
- message: "Missing api token for sms gateway.",
- success: false
- });
- }
-
- let winners = await VirtualWinner.find({ timestamp_sent: undefined }).sort({
- timestamp_drawn: 1
- });
-
- if (winners.length == 0) {
- return res.json({
- message: "No winners to draw from.",
- success: false
- });
- }
-
- Message.sendInitialMessageToWinners(winners.slice(1));
-
- return findAndNotifyNextWinner()
- .then(() => res.json({
- success: true,
- message: "Sent wine select message to first winner and update message to rest of winners."
- }))
- .catch(error => res.json({
- message: error["message"] || "Unable to send message to first winner.",
- success: false
- }))
-};
-
-const genRandomString = function(length) {
- return crypto
- .randomBytes(Math.ceil(length / 2))
- .toString("hex") /** convert to hexadecimal format */
- .slice(0, length); /** return required number of characters */
-};
-
-const sha512 = function(password, salt) {
- var hash = crypto.createHmac("md5", salt); /** Hashing algorithm sha512 */
- hash.update(password);
- var value = hash.digest("hex");
- return value;
-};
-
-function shuffle(array) {
- let currentIndex = array.length,
- temporaryValue,
- randomIndex;
-
- // While there remain elements to shuffle...
- while (0 !== currentIndex) {
- // Pick a remaining element...
- randomIndex = Math.floor(Math.random() * currentIndex);
- currentIndex -= 1;
-
- // And swap it with the current element.
- temporaryValue = array[currentIndex];
- array[currentIndex] = array[randomIndex];
- array[randomIndex] = temporaryValue;
- }
-
- return array;
-}
-
-module.exports = {
- deleteWinners,
- deleteAttendees,
- winners,
- winnersSecure,
- drawWinner,
- finish,
- attendees,
- attendeesSecure,
- addAttendee
-}
-
diff --git a/api/virtualRegistration.js b/api/virtualRegistration.js
deleted file mode 100644
index ec869a3..0000000
--- a/api/virtualRegistration.js
+++ /dev/null
@@ -1,200 +0,0 @@
-const path = require("path");
-
-const _wineFunctions = require(path.join(__dirname, "/wine"));
-const _personFunctions = require(path.join(__dirname, "/person"));
-const Message = require(path.join(__dirname, "/message"));
-const VirtualWinner = require(path.join(
- __dirname, "/schemas/VirtualWinner"
-));
-const PreLotteryWine = require(path.join(
- __dirname, "/schemas/PreLotteryWine"
-));
-
-
-const getWinesToWinnerById = async (req, res) => {
- let id = req.params.id;
- let foundWinner = await VirtualWinner.findOne({ id: id });
-
- if (!foundWinner) {
- return res.json({
- success: false,
- message: "No winner with this id.",
- existing: false,
- turn: false
- });
- }
-
- let allWinners = await VirtualWinner.find().sort({ timestamp_drawn: 1 });
- if (
- allWinners[0].id != foundWinner.id ||
- foundWinner.timestamp_limit == undefined ||
- foundWinner.timestamp_sent == undefined
- ) {
- return res.json({
- success: false,
- message: "Not the winner next in line!",
- existing: true,
- turn: false
- });
- }
-
- return res.json({
- success: true,
- existing: true,
- turn: true,
- name: foundWinner.name,
- color: foundWinner.color
- });
-};
-
-const registerWinnerSelection = async (req, res) => {
- let id = req.params.id;
- let wineName = req.body.wineName;
- let foundWinner = await VirtualWinner.findOne({ id: id });
-
- if (!foundWinner) {
- return res.json({
- success: false,
- message: "No winner with this id."
- })
- } else if (foundWinner.timestamp_limit < new Date().getTime()) {
- return res.json({
- success: false,
- message: "Timelimit expired, you will receive a wine after other users have chosen.",
- limit: true
- })
- }
-
- let date = new Date();
- date.setHours(5, 0, 0, 0);
- let prelotteryWine = await PreLotteryWine.findOne({ name: wineName });
-
- if (!prelotteryWine) {
- return res.json({
- success: false,
- message: "No wine with this name.",
- wine: false
- });
- }
-
- let wonWine = await _wineFunctions.findSaveWine(prelotteryWine);
- await prelotteryWine.delete();
- await _personFunctions.findSavePerson(foundWinner, wonWine, date);
- await Message.sendWineConfirmation(foundWinner, wonWine, date);
-
- await foundWinner.delete();
- console.info("Saved winners choice.");
-
- return findAndNotifyNextWinner()
- .then(() => res.json({
- message: "Choice saved and next in line notified.",
- success: true
- }))
- .catch(error => res.json({
- message: error["message"] || "Error when notifing next winner.",
- success: false
- }))
-};
-
-const chooseLastWineForUser = (winner, preLotteryWine) => {
- let date = new Date();
- date.setHours(5, 0, 0, 0);
-
- return _wineFunctions.findSaveWine(preLotteryWine)
- .then(wonWine => _personFunctions.findSavePerson(winner, wonWine, date))
- .then(() => preLotteryWine.delete())
- .then(() => Message.sendLastWinnerMessage(winner, preLotteryWine))
- .then(() => winner.delete())
- .catch(err => {
- console.log("Error thrown from chooseLastWineForUser: " + err);
- throw err;
- })
-}
-
-const findAndNotifyNextWinner = async () => {
- let nextWinner = undefined;
-
- let winnersLeft = await VirtualWinner.find().sort({ timestamp_drawn: 1 });
- let 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 chooseLastWineForUser(nextWinner, wine);
- }
-
- if (nextWinner) {
- return Message.sendWineSelectMessage(nextWinner)
- .then(messageResponse => startTimeout(nextWinner.id))
- } else {
- console.info("All winners notified. Could start cleanup here.");
- return Promise.resolve({
- message: "All winners notified."
- })
- }
-};
-
-const sendNotificationToWinnerById = async (req, res) => {
- const { id } = req.params;
- let winner = await VirtualWinner.findOne({ id: id });
-
- if (!winner) {
- return res.json({
- message: "No winner with this id.",
- success: false
- })
- }
-
- return Message.sendWineSelectMessage(winner)
- .then(success => res.json({
- success: success,
- message: `Message sent to winner ${id} successfully!`
- }))
- .catch(err => res.json({
- success: false,
- message: "Error while trying to send sms.",
- error: err
- }))
-}
-
-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 = {
- getWinesToWinnerById,
- registerWinnerSelection,
- findAndNotifyNextWinner,
-
- sendNotificationToWinnerById
-};
diff --git a/api/wine.js b/api/wine.js
index d953013..cd35bb4 100644
--- a/api/wine.js
+++ b/api/wine.js
@@ -1,27 +1,63 @@
const path = require("path");
const Wine = require(path.join(__dirname, "/schemas/Wine"));
-async function findSaveWine(prelotteryWine) {
- let wonWine = await Wine.findOne({ name: prelotteryWine.name });
- if (wonWine == undefined) {
- let newWonWine = new Wine({
- name: prelotteryWine.name,
- vivinoLink: prelotteryWine.vivinoLink,
- rating: prelotteryWine.rating,
+const { WineNotFound } = require(path.join(__dirname, "/vinlottisErrors"));
+
+const addWine = async wine => {
+ let existingWine = await Wine.findOne({ name: wine.name, id: wine.id, year: wine.year });
+
+ if (existingWine == undefined) {
+ let newWine = new Wine({
+ name: wine.name,
+ vivinoLink: wine.vivinoLink,
+ rating: wine.rating,
occurences: 1,
- image: prelotteryWine.image,
- id: prelotteryWine.id
+ id: wine.id,
+ year: wine.year,
+ image: wine.image,
+ price: wine.price,
+ country: wine.country
});
- await newWonWine.save();
- wonWine = newWonWine;
+ await newWine.save();
+ return newWine;
} else {
- wonWine.occurences += 1;
- wonWine.image = prelotteryWine.image;
- wonWine.id = prelotteryWine.id;
- await wonWine.save();
+ existingWine.occurences += 1;
+ await existingWine.save();
+ return existingWine;
}
+};
- return wonWine;
-}
+const allWines = (limit = undefined) => {
+ if (limit) {
+ return Wine.find().limit(limit);
+ } else {
+ return Wine.find();
+ }
+};
-module.exports.findSaveWine = findSaveWine;
+const wineById = id => {
+ return Wine.findOne({ _id: id }).then(wine => {
+ if (wine == null) {
+ throw new WineNotFound();
+ }
+
+ return wine;
+ });
+};
+
+const findWine = wine => {
+ return Wine.findOne({ name: wine.name, id: wine.id, year: wine.year }).then(wine => {
+ if (wine == null) {
+ throw new WineNotFound();
+ }
+
+ return wine;
+ });
+};
+
+module.exports = {
+ addWine,
+ allWines,
+ wineById,
+ findWine
+};
diff --git a/api/wineinfo.js b/api/wineinfo.js
deleted file mode 100644
index e52039f..0000000
--- a/api/wineinfo.js
+++ /dev/null
@@ -1,72 +0,0 @@
-const fetch = require('node-fetch')
-const path = require('path')
-const config = require(path.join(__dirname + "/../config/env/lottery.config"));
-
-const convertToOurWineObject = wine => {
- if(wine.basic.ageLimit === "18"){
- return {
- name: wine.basic.productShortName,
- vivinoLink: "https://www.vinmonopolet.no/p/" + wine.basic.productId,
- rating: wine.basic.alcoholContent,
- occurences: 0,
- id: wine.basic.productId,
- image: `https://bilder.vinmonopolet.no/cache/500x500-0/${wine.basic.productId}-1.jpg`,
- price: wine.prices[0].salesPrice.toString(),
- country: wine.origins.origin.country
- }
- }
-}
-
-const wineSearch = async (req, res) => {
- const {query} = req.query
- let url = new URL(`https://apis.vinmonopolet.no/products/v0/details-normal?productShortNameContains=test&maxResults=15`)
- url.searchParams.set('productShortNameContains', query)
-
- const vinmonopoletResponse = await fetch(url, {
- headers: {
- "Ocp-Apim-Subscription-Key": config.vinmonopoletToken
- }
- })
- .then(resp => resp.json())
- .catch(err => console.error(err))
-
-
- if (vinmonopoletResponse.errors != null) {
- return vinmonopoletResponse.errors.map(error => {
- if (error.type == "UnknownProductError") {
- return res.status(404).json({
- message: error.message
- })
- } else {
- return next()
- }
- })
- }
- const winesConverted = vinmonopoletResponse.map(convertToOurWineObject).filter(Boolean)
-
- return res.send(winesConverted);
-}
-
-const byEAN = async (req, res) => {
- const vinmonopoletResponse = await fetch("https://app.vinmonopolet.no/vmpws/v2/vmp/products/barCodeSearch/" + req.params.ean)
- .then(resp => resp.json())
-
- if (vinmonopoletResponse.errors != null) {
- return vinmonopoletResponse.errors.map(error => {
- if (error.type == "UnknownProductError") {
- return res.status(404).json({
- message: error.message
- })
- } else {
- return next()
- }
- })
- }
-
- return res.send(vinmonopoletResponse);
-};
-
-module.exports = {
- byEAN,
- wineSearch
-};
diff --git a/api/winner.js b/api/winner.js
new file mode 100644
index 0000000..85514ed
--- /dev/null
+++ b/api/winner.js
@@ -0,0 +1,95 @@
+const path = require("path");
+
+const VirtualWinner = require(path.join(__dirname, "/schemas/VirtualWinner"));
+const { WinnerNotFound } = require(path.join(__dirname, "/vinlottisErrors"));
+
+const redactWinnerInfoMapper = winner => {
+ return {
+ name: winner.name,
+ color: winner.color
+ };
+};
+
+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 sortQuery = { timestamp_drawn: 1 };
+
+ if (!isAdmin) {
+ return VirtualWinner.find()
+ .sort(sortQuery)
+ .then(winners => winners.map(redactWinnerInfoMapper));
+ } else {
+ return VirtualWinner.find().sort(sortQuery);
+ }
+};
+
+const winnerById = (id, isAdmin = false) => {
+ return VirtualWinner.findOne({ id: id }).then(winner => {
+ if (winner == null) {
+ throw new WinnerNotFound();
+ }
+
+ if (!isAdmin) {
+ return redactWinnerInfoMapper(winner);
+ }
+ return winner;
+ });
+};
+
+const updateWinnerById = (id, updateModel) => {
+ return VirtualWinner.findOne({ id: id }).then(winner => {
+ if (winner == null) {
+ throw new WinnerNotFound();
+ }
+
+ const updatedWinner = {
+ name: updateModel.name != null ? updateModel.name : winner.name,
+ phoneNumber: updateModel.phoneNumber != null ? updateModel.phoneNumber : winner.phoneNumber,
+ red: updateModel.red != null ? updateModel.red : winner.red,
+ green: updateModel.green != null ? updateModel.green : winner.green,
+ blue: updateModel.blue != null ? updateModel.blue : winner.blue,
+ yellow: updateModel.yellow != null ? updateModel.yellow : winner.yellow,
+ timestamp_drawn: updateModel.timestamp_drawn != null ? updateModel.timestamp_drawn : winner.timestamp_drawn,
+ timestamp_limit: updateModel.timestamp_limit != null ? updateModel.timestamp_limit : winner.timestamp_limit,
+ timestamp_sent: updateModel.timestamp_sent != null ? updateModel.timestamp_sent : winner.timestamp_sent
+ };
+
+ return VirtualWinner.updateOne({ id: id }, updatedWinner).then(_ => updatedWinner);
+ });
+};
+
+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();
+};
+
+module.exports = {
+ addWinners,
+ allWinners,
+ winnerById,
+ updateWinnerById,
+ deleteWinnerById,
+ deleteWinners
+};
diff --git a/frontend/api.js b/frontend/api.js
index ca17f6a..d3526aa 100644
--- a/frontend/api.js
+++ b/frontend/api.js
@@ -26,7 +26,8 @@ const allRequestedWines = () => {;
return fetch("/api/request/all")
.then(resp => {
const isAdmin = resp.headers.get("vinlottis-admin") == "true";
- return Promise.all([resp.json(), isAdmin]);
+ const getWinesFromBody = (resp) => resp.json().then(body => body.wines);
+ return Promise.all([getWinesFromBody(resp), isAdmin]);
});
};
@@ -109,8 +110,7 @@ const deleteRequestedWine = wineToBeDeleted => {
headers: {
"Content-Type": "application/json"
},
- method: "DELETE",
- body: JSON.stringify(wineToBeDeleted)
+ method: "DELETE"
};
return fetch("api/request/" + wineToBeDeleted.id, options)
@@ -148,14 +148,12 @@ const attendees = () => {
const requestNewWine = (wine) => {
const options = {
- body: JSON.stringify({
- wine: wine
- }),
+ method: "POST",
headers: {
'Accept': 'application/json',
'Content-Type': 'application/json'
},
- method: "post"
+ body: JSON.stringify({ wine })
}
return fetch("/api/request/new-wine", options)
diff --git a/frontend/components/AdminPage.vue b/frontend/components/AdminPage.vue
index db9d31b..7c76d77 100644
--- a/frontend/components/AdminPage.vue
+++ b/frontend/components/AdminPage.vue
@@ -1,34 +1,72 @@
Ingen har foreslått noe enda!Admin-side
Alle foreslåtte viner
-
+
Fant ingen viner med det navnet!
+Loading...
\ No newline at end of file + diff --git a/frontend/components/TodaysPage.vue b/frontend/components/TodaysPage.vue index f6431c7..13a989f 100644 --- a/frontend/components/TodaysPage.vue +++ b/frontend/components/TodaysPage.vue @@ -23,7 +23,9 @@ export default { }; }, async mounted() { - prelottery().then(wines => this.wines = wines); + fetch("/api/lottery/wines") + .then(resp => resp.json()) + .then(response => (this.wines = response.wines)); } }; @@ -42,19 +44,18 @@ h1 { } .wines-container { - display: grid; - grid-template-columns: repeat(auto-fit, minmax(250px, 1fr)); - grid-gap: 2rem; - gap: 2rem; + width: 90vw; + padding: 5vw; + + @include desktop { + width: 80vw; + padding: 0 10vw; + } @media (min-width: 1500px) { max-width: 1500px; margin: 0 auto; } - - @include mobile { - flex-direction: column; - } } h3 { @@ -65,23 +66,6 @@ h3 { } } -.inner-wine-container { - display: flex; - flex-direction: row; - margin: auto; - width: 500px; - font-family: Arial; - margin-bottom: 30px; - - @include desktop { - justify-content: center; - } - - @include mobile { - width: auto; - } -} - .right { display: flex; flex-direction: column; diff --git a/frontend/components/VinlottisPage.vue b/frontend/components/VinlottisPage.vue index bbc0719..e41e1f1 100644 --- a/frontend/components/VinlottisPage.vue +++ b/frontend/components/VinlottisPage.vue @@ -1,8 +1,6 @@Trykk her for å delta
+ +Trykk her for å delta
Scroll for å se vinnere og annen gøy statistikk
- Følg med på utviklingen og chat om trekningen -
++ Følg med på utviklingen og + chat om trekningen + +
Her er valgene for dagens lotteri, du har 10 minutter å velge etter du fikk SMS-en.
-Du får mer info om henting snarest!
@@ -24,15 +26,13 @@ + + diff --git a/frontend/components/admin/PushPage.vue b/frontend/components/admin/PushPage.vue new file mode 100644 index 0000000..929cd8e --- /dev/null +++ b/frontend/components/admin/PushPage.vue @@ -0,0 +1,59 @@ + +