Add sms capabilities, and online-fixing of everything
This commit is contained in:
		
							
								
								
									
										70
									
								
								api/message.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										70
									
								
								api/message.js
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,70 @@ | ||||
| const request = require("request"); | ||||
| const path = require("path"); | ||||
| const config = require(path.join(__dirname + "/../config/defaults/lottery")); | ||||
|  | ||||
| async function sendMessage(winnerObject) { | ||||
|   console.log("sent message to ", winnerObject); | ||||
|   winnerObject.timestamp_sent = new Date().getTime(); | ||||
|   winnerObject.timestamp_limit = new Date().getTime() * (600000 * 10000); | ||||
|   await winnerObject.save(); | ||||
|  | ||||
|   await 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. /#/winner/${winnerObject.id}` | ||||
|   ); | ||||
|  | ||||
|   return true; | ||||
| } | ||||
|  | ||||
| async function sendMessageTooLate(winnerObject) { | ||||
|   await sendMessageToUser( | ||||
|     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.` | ||||
|   ); | ||||
| } | ||||
|  | ||||
| async function sendMessageToUser(phoneNumber, message) { | ||||
|   console.log("num", phoneNumber); | ||||
|   console.log("message", message); | ||||
|  | ||||
|   request.post( | ||||
|     { | ||||
|       url: `https://gatewayapi.com/rest/mtsms?token=${config.token}`, | ||||
|       json: true, | ||||
|       body: { | ||||
|         sender: "Vinlottis", | ||||
|         message: message, | ||||
|         recipients: [{ msisdn: `47${phoneNumber}` }] | ||||
|       } | ||||
|     }, | ||||
|     function(err, r, body) { | ||||
|       console.log(err ? err : body); | ||||
|     } | ||||
|   ); | ||||
| } | ||||
|  | ||||
| async function sendUpdate(winners) { | ||||
|   let numbers = []; | ||||
|   for (let i = 0; i < winners.length; i++) { | ||||
|     numbers.push({ msisdn: `47${winners[i].phoneNumber}` }); | ||||
|   } | ||||
|   request.post( | ||||
|     { | ||||
|       url: `https://gatewayapi.com/rest/mtsms?token=${config.token}`, | ||||
|       json: true, | ||||
|       body: { | ||||
|         sender: "Vinlottis", | ||||
|         message: | ||||
|           "Gratulerer som vinner av vinlottisen! Du vil snart få en SMS med oppdatering om hvordan gangen går!", | ||||
|         recipients: numbers | ||||
|       } | ||||
|     }, | ||||
|     function(err, r, body) { | ||||
|       console.log(err ? err : body); | ||||
|     } | ||||
|   ); | ||||
| } | ||||
|  | ||||
| module.exports.sendUpdate = sendUpdate; | ||||
| module.exports.sendMessage = sendMessage; | ||||
| module.exports.sendMessageTooLate = sendMessageTooLate; | ||||
| @@ -1,6 +1,7 @@ | ||||
| const express = require("express"); | ||||
| const path = require("path"); | ||||
| const router = express.Router(); | ||||
| const crypto = require("crypto"); | ||||
| const mongoose = require("mongoose"); | ||||
| mongoose.connect("mongodb://localhost:27017/vinlottis", { | ||||
|   useNewUrlParser: true | ||||
| @@ -9,11 +10,13 @@ let io; | ||||
| const mustBeAuthenticated = require(path.join( | ||||
|   __dirname + "/../middleware/mustBeAuthenticated" | ||||
| )); | ||||
| const config = require(path.join(__dirname + "/../config/defaults/lottery")); | ||||
|  | ||||
| const Attendee = require(path.join(__dirname + "/../schemas/Attendee")); | ||||
| const VirtualWinner = require(path.join( | ||||
|   __dirname + "/../schemas/VirtualWinner" | ||||
| )); | ||||
| const Message = require(path.join(__dirname + "/../api/message")); | ||||
|  | ||||
| router.use((req, res, next) => { | ||||
|   next(); | ||||
| @@ -133,7 +136,9 @@ router.route("/winner").get(mustBeAuthenticated, async (req, res) => { | ||||
|     red: winner.red, | ||||
|     blue: winner.blue, | ||||
|     green: winner.green, | ||||
|     yellow: winner.yellow | ||||
|     yellow: winner.yellow, | ||||
|     id: sha512(winner.phoneNumber, genRandomString(10)), | ||||
|     timestamp_drawn: new Date().getTime() | ||||
|   }); | ||||
|  | ||||
|   await Attendee.update( | ||||
| @@ -145,6 +150,56 @@ router.route("/winner").get(mustBeAuthenticated, async (req, res) => { | ||||
|   res.json(winner); | ||||
| }); | ||||
|  | ||||
| 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; | ||||
| }; | ||||
|  | ||||
| router.route("/finish").get(mustBeAuthenticated, async (req, res) => { | ||||
|   if (!config.token) { | ||||
|     res.json(false); | ||||
|     return; | ||||
|   } | ||||
|   let winners = await VirtualWinner.find({ timestamp_sent: undefined }).sort({ | ||||
|     timestamp_drawn: 1 | ||||
|   }); | ||||
|  | ||||
|   if (winners.length == 0) { | ||||
|     res.json(false); | ||||
|     return; | ||||
|   } | ||||
|  | ||||
|   let firstWinner = winners[0]; | ||||
|   messageSent = false; | ||||
|   try { | ||||
|     let messageSent = await Message.sendMessage(firstWinner); | ||||
|     Message.sendUpdate(winners.slice(1)); | ||||
|     if (!messageSent) { | ||||
|       res.json(false); | ||||
|       return; | ||||
|     } | ||||
|   } catch (e) { | ||||
|     res.json(false); | ||||
|     return; | ||||
|   } | ||||
|  | ||||
|   firstWinner.timestamp_sent = new Date().getTime(); | ||||
|   firstWinner.timestamp_limit = new Date().getTime() + 600000; | ||||
|  | ||||
|   await firstWinner.save(); | ||||
|   res.json(true); | ||||
|   return; | ||||
| }); | ||||
|  | ||||
| router.route("/attendees").get(async (req, res) => { | ||||
|   let attendees = await Attendee.find(); | ||||
|   let attendeesRedacted = []; | ||||
|   | ||||
							
								
								
									
										173
									
								
								api/virtualRegistration.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										173
									
								
								api/virtualRegistration.js
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,173 @@ | ||||
| const express = require("express"); | ||||
| const path = require("path"); | ||||
| const router = express.Router(); | ||||
| const mongoose = require("mongoose"); | ||||
|  | ||||
| mongoose.connect("mongodb://localhost:27017/vinlottis", { | ||||
|   useNewUrlParser: true | ||||
| }); | ||||
|  | ||||
| const Message = require(path.join(__dirname + "/../api/message")); | ||||
| const config = require(path.join(__dirname + "/../config/defaults/push")); | ||||
| const VirtualWinner = require(path.join( | ||||
|   __dirname + "/../schemas/VirtualWinner" | ||||
| )); | ||||
| const Highscore = require(path.join(__dirname + "/../schemas/Highscore")); | ||||
| const Wine = require(path.join(__dirname + "/../schemas/Wine")); | ||||
| const PreLotteryWine = require(path.join( | ||||
|   __dirname + "/../schemas/PreLotteryWine" | ||||
| )); | ||||
| const lotteryConfig = require(path.join( | ||||
|   __dirname + "/../config/defaults/lottery" | ||||
| )); | ||||
|  | ||||
| router.use((req, res, next) => { | ||||
|   next(); | ||||
| }); | ||||
|  | ||||
| router.route("/winner/:id").get((req, res) => { | ||||
|   res.redirect(`/#/winner/${req.params.id}`); | ||||
| }); | ||||
|  | ||||
| router.route("/:id").get(async (req, res) => { | ||||
|   let id = req.params.id; | ||||
|  | ||||
|   let foundWinner = await VirtualWinner.findOne({ id: id }); | ||||
|  | ||||
|   if (!foundWinner) { | ||||
|     res.json({ | ||||
|       existing: false, | ||||
|       turn: false | ||||
|     }); | ||||
|     return; | ||||
|   } | ||||
|  | ||||
|   let allWinners = await VirtualWinner.find().sort({ timestamp_drawn: 1 }); | ||||
|  | ||||
|   if ( | ||||
|     allWinners[0].id != foundWinner.id || | ||||
|     foundWinner.timestamp_limit == undefined || | ||||
|     foundWinner.timestamp_sent == undefined | ||||
|   ) { | ||||
|     res.json({ existing: true, turn: false }); | ||||
|     return; | ||||
|   } | ||||
|  | ||||
|   res.json({ | ||||
|     existing: true, | ||||
|     turn: true, | ||||
|     name: foundWinner.name, | ||||
|     color: foundWinner.color | ||||
|   }); | ||||
|   return; | ||||
| }); | ||||
|  | ||||
| router.route("/:id").post(async (req, res) => { | ||||
|   let id = req.params.id; | ||||
|   let wineName = req.body.wineName; | ||||
|   let foundWinner = await VirtualWinner.findOne({ id: id }); | ||||
|  | ||||
|   if (!foundWinner) { | ||||
|     res.json(false); | ||||
|     return; | ||||
|   } | ||||
|  | ||||
|   if (foundWinner.timestamp_limit < new Date().getTime()) { | ||||
|     res.json({ | ||||
|       success: false, | ||||
|       limit: true | ||||
|     }); | ||||
|     return; | ||||
|   } | ||||
|   let date = new Date(); | ||||
|  | ||||
|   let prelotteryWine = await PreLotteryWine.findOne({ name: wineName }); | ||||
|  | ||||
|   if (!prelotteryWine) { | ||||
|     res.json({ | ||||
|       success: false, | ||||
|       wine: true | ||||
|     }); | ||||
|   } | ||||
|  | ||||
|   let wonWine = await Wine.findOne({ name: prelotteryWine.name }); | ||||
|   if (wonWine == undefined) { | ||||
|     let newWonWine = new Wine({ | ||||
|       name: prelotteryWine.name, | ||||
|       vivinoLink: prelotteryWine.vivinoLink, | ||||
|       rating: prelotteryWine.rating, | ||||
|       occurences: 1, | ||||
|       image: prelotteryWine.image, | ||||
|       id: prelotteryWine.id | ||||
|     }); | ||||
|     await newWonWine.save(); | ||||
|     wonWine = newWonWine; | ||||
|   } else { | ||||
|     wonWine.occurences += 1; | ||||
|     wonWine.image = prelotteryWine.image; | ||||
|     wonWine.id = prelotteryWine.id; | ||||
|     await wonWine.save(); | ||||
|   } | ||||
|  | ||||
|   await prelotteryWine.delete(); | ||||
|  | ||||
|   const person = await Highscore.findOne({ | ||||
|     name: foundWinner.name | ||||
|   }); | ||||
|  | ||||
|   if (person == undefined) { | ||||
|     let newPerson = new Highscore({ | ||||
|       name: foundWinner.name, | ||||
|       wins: [ | ||||
|         { | ||||
|           color: foundWinner.color, | ||||
|           date: date, | ||||
|           wine: wonWine | ||||
|         } | ||||
|       ] | ||||
|     }); | ||||
|  | ||||
|     await newPerson.save(); | ||||
|   } else { | ||||
|     person.wins.push({ | ||||
|       color: foundWinner.color, | ||||
|       date: date, | ||||
|       wine: wonWine | ||||
|     }); | ||||
|     person.markModified("wins"); | ||||
|     await person.save(); | ||||
|   } | ||||
|  | ||||
|   await foundWinner.delete(); | ||||
|  | ||||
|   let nextWinner = await VirtualWinner.find().sort({ timestamp_drawn: 1 }); | ||||
|   if (nextWinner.length > 0) { | ||||
|     Message.sendMessage(nextWinner[0]); | ||||
|   } | ||||
|  | ||||
|   startTimeout(id); | ||||
|  | ||||
|   res.json({ | ||||
|     success: true | ||||
|   }); | ||||
|   return; | ||||
| }); | ||||
|  | ||||
| function startTimeout(id) { | ||||
|   setTimeout(async () => { | ||||
|     let virtualWinner = await VirtualWinner.findOne({ id: id }); | ||||
|     if (!virtualWinner) { | ||||
|       return; | ||||
|     } | ||||
|  | ||||
|     Message.sendMessageTooLate(virtualWinner); | ||||
|  | ||||
|     virtualWinner.timestamp_drawn; | ||||
|     virtualWinner.timestamp_limit = null; | ||||
|     virtualWinner.timestamp_sent = null; | ||||
|  | ||||
|     await virtualWinner.save(); | ||||
|   }, 600000); | ||||
| } | ||||
|  | ||||
| module.exports = router; | ||||
| @@ -11,6 +11,7 @@ try { | ||||
|     message: "INSERT MESSAGE", | ||||
|     date: 5, | ||||
|     hours: 15, | ||||
|     apiUrl: "https://lottis.vin" | ||||
|     apiUrl: "https://lottis.vin", | ||||
|     token: undefined | ||||
|   }; | ||||
| } | ||||
|   | ||||
							
								
								
									
										3
									
								
								config/env/lottery.config.example.js
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										3
									
								
								config/env/lottery.config.example.js
									
									
									
									
										vendored
									
									
								
							| @@ -5,5 +5,6 @@ module.exports = { | ||||
|   message: "VINLOTTERI", | ||||
|   date: 5, | ||||
|   hours: 15, | ||||
|   apiUrl: undefined | ||||
|   apiUrl: undefined, | ||||
|   token: undefined | ||||
| }; | ||||
|   | ||||
							
								
								
									
										36
									
								
								package-lock.json
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										36
									
								
								package-lock.json
									
									
									
										generated
									
									
									
								
							| @@ -9302,9 +9302,9 @@ | ||||
|       "integrity": "sha1-8FKijacOYYkX7wqKw0wa5aaChrM=" | ||||
|     }, | ||||
|     "psl": { | ||||
|       "version": "1.7.0", | ||||
|       "resolved": "https://registry.npmjs.org/psl/-/psl-1.7.0.tgz", | ||||
|       "integrity": "sha512-5NsSEDv8zY70ScRnOTn7bK7eanl2MvFrOrS/R6x+dBt5g1ghnj9Zv90kO8GwT8gxcu2ANyFprnFYB85IogIJOQ==" | ||||
|       "version": "1.8.0", | ||||
|       "resolved": "https://registry.npmjs.org/psl/-/psl-1.8.0.tgz", | ||||
|       "integrity": "sha512-RIdOzyoavK+hA18OGGWDqUTsCLhtA7IcZ/6NCs4fFJaHBDab+pDDmDIByWFRQJq2Cd7r1OoQxBGKOaztq+hjIQ==" | ||||
|     }, | ||||
|     "public-encrypt": { | ||||
|       "version": "4.0.3", | ||||
| @@ -9356,7 +9356,8 @@ | ||||
|     "punycode": { | ||||
|       "version": "1.4.1", | ||||
|       "resolved": "https://registry.npmjs.org/punycode/-/punycode-1.4.1.tgz", | ||||
|       "integrity": "sha1-wNWmOycYgArY4esPpSachN1BhF4=" | ||||
|       "integrity": "sha1-wNWmOycYgArY4esPpSachN1BhF4=", | ||||
|       "dev": true | ||||
|     }, | ||||
|     "q": { | ||||
|       "version": "1.5.1", | ||||
| @@ -9821,9 +9822,9 @@ | ||||
|       } | ||||
|     }, | ||||
|     "request": { | ||||
|       "version": "2.88.0", | ||||
|       "resolved": "https://registry.npmjs.org/request/-/request-2.88.0.tgz", | ||||
|       "integrity": "sha512-NAqBSrijGLZdM0WZNsInLJpkJokL72XYjUpnB0iwsRgxh7dB6COrHnTBNwN0E+lHDAJzu7kLAkDeY08z2/A0hg==", | ||||
|       "version": "2.88.2", | ||||
|       "resolved": "https://registry.npmjs.org/request/-/request-2.88.2.tgz", | ||||
|       "integrity": "sha512-MsvtOrfG9ZcrOwAW+Qi+F6HbD0CWXEh9ou77uOb7FM2WPhwT7smM833PzanhJLsgXjN89Ir6V2PczXNnMpwKhw==", | ||||
|       "requires": { | ||||
|         "aws-sign2": "~0.7.0", | ||||
|         "aws4": "^1.8.0", | ||||
| @@ -9832,7 +9833,7 @@ | ||||
|         "extend": "~3.0.2", | ||||
|         "forever-agent": "~0.6.1", | ||||
|         "form-data": "~2.3.2", | ||||
|         "har-validator": "~5.1.0", | ||||
|         "har-validator": "~5.1.3", | ||||
|         "http-signature": "~1.2.0", | ||||
|         "is-typedarray": "~1.0.0", | ||||
|         "isstream": "~0.1.2", | ||||
| @@ -9842,7 +9843,7 @@ | ||||
|         "performance-now": "^2.1.0", | ||||
|         "qs": "~6.5.2", | ||||
|         "safe-buffer": "^5.1.2", | ||||
|         "tough-cookie": "~2.4.3", | ||||
|         "tough-cookie": "~2.5.0", | ||||
|         "tunnel-agent": "^0.6.0", | ||||
|         "uuid": "^3.3.2" | ||||
|       }, | ||||
| @@ -11394,12 +11395,19 @@ | ||||
|       "dev": true | ||||
|     }, | ||||
|     "tough-cookie": { | ||||
|       "version": "2.4.3", | ||||
|       "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-2.4.3.tgz", | ||||
|       "integrity": "sha512-Q5srk/4vDM54WJsJio3XNn6K2sCG+CQ8G5Wz6bZhRZoAe/+TxjWB/GlFAnYEbkYVlON9FMk/fE3h2RLpPXo4lQ==", | ||||
|       "version": "2.5.0", | ||||
|       "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-2.5.0.tgz", | ||||
|       "integrity": "sha512-nlLsUzgm1kfLXSXfRZMc1KLAugd4hqJHDTvc2hDIwS3mZAfMEuMbc03SujMF+GEcpaX/qboeycw6iO8JwVv2+g==", | ||||
|       "requires": { | ||||
|         "psl": "^1.1.24", | ||||
|         "punycode": "^1.4.1" | ||||
|         "psl": "^1.1.28", | ||||
|         "punycode": "^2.1.1" | ||||
|       }, | ||||
|       "dependencies": { | ||||
|         "punycode": { | ||||
|           "version": "2.1.1", | ||||
|           "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz", | ||||
|           "integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==" | ||||
|         } | ||||
|       } | ||||
|     }, | ||||
|     "trim-newlines": { | ||||
|   | ||||
| @@ -35,6 +35,7 @@ | ||||
|     "passport-local-mongoose": "^6.0.1", | ||||
|     "qrcode": "^1.4.4", | ||||
|     "referrer-policy": "^1.2.0", | ||||
|     "request": "^2.88.2", | ||||
|     "socket.io": "^2.3.0", | ||||
|     "socket.io-client": "^2.3.0", | ||||
|     "vue": "~2.6", | ||||
|   | ||||
| @@ -8,7 +8,11 @@ const VirtualWinner = new Schema({ | ||||
|   green: Number, | ||||
|   blue: Number, | ||||
|   red: Number, | ||||
|   yellow: Number | ||||
|   yellow: Number, | ||||
|   id: String, | ||||
|   timestamp_drawn: Number, | ||||
|   timestamp_sent: Number, | ||||
|   timestamp_limit: Number | ||||
| }); | ||||
|  | ||||
| module.exports = mongoose.model("VirtualWinner", VirtualWinner); | ||||
|   | ||||
| @@ -12,6 +12,9 @@ const subscriptionApi = require(path.join(__dirname + "/api/subscriptions")); | ||||
| const loginApi = require(path.join(__dirname + "/api/login")); | ||||
| const wineinfoApi = require(path.join(__dirname + "/api/wineinfo")); | ||||
| const virtualApi = require(path.join(__dirname + "/api/virtualLottery")); | ||||
| const virtualRegistrationApi = require(path.join( | ||||
|   __dirname + "/api/virtualRegistration" | ||||
| )); | ||||
|  | ||||
| //This is required for the chat to work | ||||
| const chat = require(path.join(__dirname + "/api/chat"))(io); | ||||
| @@ -92,6 +95,7 @@ app.use("/api/", retrieveApi); | ||||
| app.use("/api/", wineinfoApi); | ||||
| app.use("/api/", chatHistory); | ||||
| app.use("/api/virtual/", virtualApi(io)); | ||||
| app.use("/api/virtual-registration/", virtualRegistrationApi); | ||||
| app.use("/subscription", subscriptionApi); | ||||
|  | ||||
| app.get("/dagens", function(req, res) { | ||||
|   | ||||
							
								
								
									
										207
									
								
								src/api.js
									
									
									
									
									
								
							
							
						
						
									
										207
									
								
								src/api.js
									
									
									
									
									
								
							| @@ -1,58 +1,49 @@ | ||||
| const BASE_URL = __APIURL__ || window.location.origin; | ||||
|  | ||||
| const statistics = () => { | ||||
|   const url = new URL('/api/purchase/statistics', BASE_URL) | ||||
|   const url = new URL("/api/purchase/statistics", BASE_URL); | ||||
|  | ||||
|   return fetch(url.href) | ||||
|     .then(resp => resp.json()) | ||||
| } | ||||
|   return fetch(url.href).then(resp => resp.json()); | ||||
| }; | ||||
|  | ||||
| const colorStatistics = () => { | ||||
|   const url = new URL("/api/purchase/statistics/color", BASE_URL) | ||||
|   const url = new URL("/api/purchase/statistics/color", BASE_URL); | ||||
|  | ||||
|   return fetch(url.href) | ||||
|     .then(resp => resp.json()) | ||||
| } | ||||
|   return fetch(url.href).then(resp => resp.json()); | ||||
| }; | ||||
|  | ||||
| const highscoreStatistics = () => { | ||||
|   const url = new URL("/api/highscore/statistics", BASE_URL) | ||||
|   const url = new URL("/api/highscore/statistics", BASE_URL); | ||||
|  | ||||
|   return fetch(url.href) | ||||
|     .then(resp => resp.json()) | ||||
| } | ||||
|   return fetch(url.href).then(resp => resp.json()); | ||||
| }; | ||||
|  | ||||
| const overallWineStatistics = () => { | ||||
|   const url = new URL("/api/wines/statistics/overall", BASE_URL) | ||||
|  | ||||
|   return fetch(url.href) | ||||
|     .then(resp => resp.json()) | ||||
| } | ||||
|   const url = new URL("/api/wines/statistics/overall", BASE_URL); | ||||
|  | ||||
|   return fetch(url.href).then(resp => resp.json()); | ||||
| }; | ||||
|  | ||||
| const chartWinsByColor = () => { | ||||
|   const url = new URL("/api/purchase/statistics/color", BASE_URL) | ||||
|   const url = new URL("/api/purchase/statistics/color", BASE_URL); | ||||
|  | ||||
|   return fetch(url.href) | ||||
|     .then(resp => resp.json()) | ||||
| } | ||||
|   return fetch(url.href).then(resp => resp.json()); | ||||
| }; | ||||
|  | ||||
| const chartPurchaseByColor = () => { | ||||
|   const url = new URL("/api/purchase/statistics", BASE_URL) | ||||
|  | ||||
|   return fetch(url.href) | ||||
|     .then(resp => resp.json()) | ||||
| } | ||||
|   const url = new URL("/api/purchase/statistics", BASE_URL); | ||||
|  | ||||
|   return fetch(url.href).then(resp => resp.json()); | ||||
| }; | ||||
|  | ||||
| const prelottery = () => { | ||||
|   const url = new URL("/api/wines/prelottery", BASE_URL) | ||||
|   const url = new URL("/api/wines/prelottery", BASE_URL); | ||||
|  | ||||
|   return fetch(url.href) | ||||
|     .then(resp => resp.json()) | ||||
| } | ||||
|   return fetch(url.href).then(resp => resp.json()); | ||||
| }; | ||||
|  | ||||
| const log = (sendObject) => { | ||||
|   const url = new URL("/api/log", BASE_URL) | ||||
| const log = sendObject => { | ||||
|   const url = new URL("/api/log", BASE_URL); | ||||
|  | ||||
|   const options = { | ||||
|     headers: { | ||||
| @@ -60,11 +51,10 @@ const log = (sendObject) => { | ||||
|     }, | ||||
|     method: "POST", | ||||
|     body: JSON.stringify(sendObject) | ||||
|   }  | ||||
|   }; | ||||
|  | ||||
|   return fetch(url.href, options) | ||||
|     .then(resp => resp.json()) | ||||
| } | ||||
|   return fetch(url.href, options).then(resp => resp.json()); | ||||
| }; | ||||
|  | ||||
| const addAttendee = sendObject => { | ||||
|   const url = new URL("/api/virtual/attendee", BASE_URL); | ||||
| @@ -115,7 +105,7 @@ const deleteWinners = () => { | ||||
|   }; | ||||
|  | ||||
|   return fetch(url.href, options).then(resp => resp.json()); | ||||
| } | ||||
| }; | ||||
|  | ||||
| const deleteAttendees = () => { | ||||
|   const url = new URL("/api/virtual/attendees", BASE_URL); | ||||
| @@ -134,10 +124,10 @@ const attendees = () => { | ||||
|   const url = new URL("/api/virtual/attendees", BASE_URL); | ||||
|  | ||||
|   return fetch(url.href).then(resp => resp.json()); | ||||
| } | ||||
| }; | ||||
|  | ||||
| const logWines = (wines) => { | ||||
|   const url = new URL("/api/log/wines", BASE_URL) | ||||
| const logWines = wines => { | ||||
|   const url = new URL("/api/log/wines", BASE_URL); | ||||
|  | ||||
|   const options = { | ||||
|     headers: { | ||||
| @@ -145,95 +135,115 @@ const logWines = (wines) => { | ||||
|     }, | ||||
|     method: "POST", | ||||
|     body: JSON.stringify(wines) | ||||
|   } | ||||
|   }; | ||||
|  | ||||
|   return fetch(url.href, options) | ||||
|     .then(resp => resp.json()) | ||||
| } | ||||
|   return fetch(url.href, options).then(resp => resp.json()); | ||||
| }; | ||||
|  | ||||
| const wineSchema = () => { | ||||
|   const url = new URL("/api/log/schema", BASE_URL) | ||||
|   const url = new URL("/api/log/schema", BASE_URL); | ||||
|  | ||||
|   return fetch(url.href) | ||||
|     .then(resp => resp.json()) | ||||
| } | ||||
|   return fetch(url.href).then(resp => resp.json()); | ||||
| }; | ||||
|  | ||||
| const barcodeToVinmonopolet = (id) => { | ||||
|   const url = new URL("/api/wineinfo/" + id, BASE_URL) | ||||
| const barcodeToVinmonopolet = id => { | ||||
|   const url = new URL("/api/wineinfo/" + id, BASE_URL); | ||||
|  | ||||
|   return fetch(url.href) | ||||
|     .then(async (resp) => { | ||||
|       if (!resp.ok) { | ||||
|         if (resp.status == 404) { | ||||
|           throw await resp.json() | ||||
|         } | ||||
|       } else { | ||||
|         return resp.json() | ||||
|   return fetch(url.href).then(async resp => { | ||||
|     if (!resp.ok) { | ||||
|       if (resp.status == 404) { | ||||
|         throw await resp.json(); | ||||
|       } | ||||
|     }) | ||||
| } | ||||
|     } else { | ||||
|       return resp.json(); | ||||
|     } | ||||
|   }); | ||||
| }; | ||||
|  | ||||
| const handleErrors = async (resp) => { | ||||
| const handleErrors = async resp => { | ||||
|   if ([400, 409].includes(resp.status)) { | ||||
|     throw await resp.json() | ||||
|     throw await resp.json(); | ||||
|   } else { | ||||
|     console.error("Unexpected error occured when login/register user:", resp) | ||||
|     throw await resp.json() | ||||
|     console.error("Unexpected error occured when login/register user:", resp); | ||||
|     throw await resp.json(); | ||||
|   } | ||||
| } | ||||
| }; | ||||
|  | ||||
| const login = (username, password) => { | ||||
|   const url = new URL("/login", BASE_URL) | ||||
|   const url = new URL("/login", BASE_URL); | ||||
|   const options = { | ||||
|     headers: { | ||||
|       "Content-Type": "application/json" | ||||
|     }, | ||||
|     method: "POST", | ||||
|     body: JSON.stringify({ username, password }) | ||||
|   } | ||||
|   }; | ||||
|  | ||||
|   return fetch(url.href, options) | ||||
|     .then(resp => { | ||||
|       if (resp.ok) {         | ||||
|         return resp.json() | ||||
|       } else { | ||||
|         return handleErrors(resp) | ||||
|       } | ||||
|     }) | ||||
| } | ||||
|   return fetch(url.href, options).then(resp => { | ||||
|     if (resp.ok) { | ||||
|       return resp.json(); | ||||
|     } else { | ||||
|       return handleErrors(resp); | ||||
|     } | ||||
|   }); | ||||
| }; | ||||
|  | ||||
| const register = (username, password) => { | ||||
|   const url = new URL("/register", BASE_URL) | ||||
|   const url = new URL("/register", BASE_URL); | ||||
|   const options = { | ||||
|     headers: { | ||||
|       "Content-Type": "application/json" | ||||
|     }, | ||||
|     method: "POST", | ||||
|     body: JSON.stringify({ username, password }) | ||||
|   } | ||||
|   }; | ||||
|  | ||||
|   return fetch(url.href, options) | ||||
|     .then(resp => { | ||||
|       if (resp.ok) { | ||||
|         return resp.json() | ||||
|       } else { | ||||
|         return handleErrors(resp) | ||||
|       } | ||||
|     }) | ||||
| } | ||||
|   return fetch(url.href, options).then(resp => { | ||||
|     if (resp.ok) { | ||||
|       return resp.json(); | ||||
|     } else { | ||||
|       return handleErrors(resp); | ||||
|     } | ||||
|   }); | ||||
| }; | ||||
|  | ||||
|  | ||||
| const getChatHistory = (skip=null, take=null) => { | ||||
| const getChatHistory = (skip = null, take = null) => { | ||||
|   const url = new URL("/api/chat/history", BASE_URL); | ||||
|   if (!isNaN(skip)) | ||||
|     url.searchParams.append("skip", skip); | ||||
|   if (!isNaN(take)) | ||||
|     url.searchParams.append("take", take); | ||||
|   if (!isNaN(skip)) url.searchParams.append("skip", skip); | ||||
|   if (!isNaN(take)) url.searchParams.append("take", take); | ||||
|  | ||||
|   return fetch(url.href) | ||||
|     .then(resp => resp.json()) | ||||
|   return fetch(url.href).then(resp => resp.json()); | ||||
| }; | ||||
|  | ||||
| } | ||||
| const finishedDraw = () => { | ||||
|   const url = new URL("/api/virtual/finish", BASE_URL); | ||||
|  | ||||
|   return fetch(url.href).then(resp => resp.json()); | ||||
| }; | ||||
|  | ||||
| const getAmIWinner = id => { | ||||
|   const url = new URL(`/api/virtual-registration/${id}`, BASE_URL); | ||||
|   return fetch(url.href).then(resp => resp.json()); | ||||
| }; | ||||
|  | ||||
| const postWineChosen = (id, wineName) => { | ||||
|   const url = new URL(`/api/virtual-registration/${id}`, BASE_URL); | ||||
|   const options = { | ||||
|     headers: { | ||||
|       "Content-Type": "application/json" | ||||
|     }, | ||||
|     method: "POST", | ||||
|     body: JSON.stringify({ wineName: wineName }) | ||||
|   }; | ||||
|  | ||||
|   return fetch(url.href, options).then(resp => { | ||||
|     if (resp.ok) { | ||||
|       return resp.json(); | ||||
|     } else { | ||||
|       return handleErrors(resp); | ||||
|     } | ||||
|   }); | ||||
| }; | ||||
|  | ||||
| export { | ||||
|   statistics, | ||||
| @@ -257,5 +267,8 @@ export { | ||||
|   winnersSecure, | ||||
|   deleteWinners, | ||||
|   deleteAttendees, | ||||
|   getChatHistory | ||||
|   getChatHistory, | ||||
|   finishedDraw, | ||||
|   getAmIWinner, | ||||
|   postWineChosen | ||||
| }; | ||||
|   | ||||
| @@ -118,7 +118,8 @@ import { | ||||
|   attendees, | ||||
|   winnersSecure, | ||||
|   deleteWinners, | ||||
|   deleteAttendees | ||||
|   deleteAttendees, | ||||
|   finishedDraw | ||||
| } from "@/api"; | ||||
| import RaffleGenerator from "@/ui/RaffleGenerator"; | ||||
|  | ||||
| @@ -165,6 +166,9 @@ export default { | ||||
|     this.socket.on("new_attendee", async msg => { | ||||
|       this.getAttendees(); | ||||
|     }); | ||||
|  | ||||
|     window.finishedDraw = finishedDraw; | ||||
|     console.log("here"); | ||||
|   }, | ||||
|   methods: { | ||||
|     setWithRandomColors(colors) { | ||||
| @@ -209,6 +213,7 @@ export default { | ||||
|             this.countdown(); | ||||
|           } else { | ||||
|             this.drawingWinner = false; | ||||
|             finishedDraw(); | ||||
|           } | ||||
|           this.getWinners(); | ||||
|           this.getAttendees(); | ||||
|   | ||||
							
								
								
									
										102
									
								
								src/components/WinnerPage.vue
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										102
									
								
								src/components/WinnerPage.vue
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,102 @@ | ||||
| <template> | ||||
|   <div class="container"> | ||||
|     <div v-if="!posted"> | ||||
|       <h1 v-if="name">Gratulerer {{name}}!</h1> | ||||
|       <p | ||||
|         v-if="name" | ||||
|       >Her er valgene for dagens lotteri, du har 10 minutter å velge etter du fikk SMS-en.</p> | ||||
|       <h1 v-else-if="!turn && !existing" class="sent-container">Finner ikke noen vinner her..</h1> | ||||
|       <h1 v-else-if="!turn" class="sent-container">Du må vente på tur..</h1> | ||||
|       <div class="wines-container" v-if="name"> | ||||
|         <br /> | ||||
|         <br /> | ||||
|         <Wine | ||||
|           :wine="wine" | ||||
|           v-for="wine in wines" | ||||
|           :key="wine" | ||||
|           :winner="true" | ||||
|           :fullscreen="true" | ||||
|           :inlineSlot="true" | ||||
|           v-on:chosen="chosenWine" | ||||
|         /> | ||||
|       </div> | ||||
|     </div> | ||||
|     <div v-else-if="posted" class="sent-container"> | ||||
|       <h1>Valget ditt er sendt inn!</h1> | ||||
|       <p>Du får mer info om henting snarest!</p> | ||||
|     </div> | ||||
|   </div> | ||||
| </template> | ||||
|  | ||||
| <script> | ||||
| import { getAmIWinner, postWineChosen } from "@/api"; | ||||
| import Wine from "@/ui/Wine"; | ||||
| export default { | ||||
|   components: { Wine }, | ||||
|   data() { | ||||
|     return { | ||||
|       id: null, | ||||
|       existing: false, | ||||
|       fetched: false, | ||||
|       turn: false, | ||||
|       name: null, | ||||
|       wines: [], | ||||
|       posted: false | ||||
|     }; | ||||
|   }, | ||||
|   async mounted() { | ||||
|     this.id = this.$router.currentRoute.params.id; | ||||
|  | ||||
|     let winnerObject = await getAmIWinner(this.id); | ||||
|     this.fetched = true; | ||||
|     if (!winnerObject || !winnerObject.existing) { | ||||
|       console.error("non existing", winnerObject); | ||||
|       return; | ||||
|     } | ||||
|     this.existing = true; | ||||
|     if (winnerObject.existing && !winnerObject.turn) { | ||||
|       console.error("not your turn yet", winnerObject); | ||||
|       return; | ||||
|     } | ||||
|     this.turn = true; | ||||
|     this.name = winnerObject.name; | ||||
|     const _wines = await fetch("/api/wines/prelottery"); | ||||
|     this.wines = await _wines.json(); | ||||
|     console.log(this.wines); | ||||
|   }, | ||||
|   methods: { | ||||
|     chosenWine: async function(name) { | ||||
|       console.log("chosen a wine"); | ||||
|       let posted = await postWineChosen(this.id, name); | ||||
|       console.log("response", posted); | ||||
|       if (posted.success) { | ||||
|         this.posted = true; | ||||
|       } | ||||
|     } | ||||
|   } | ||||
| }; | ||||
| </script> | ||||
|  | ||||
| <style lang="scss" scoped> | ||||
| @import "./src/styles/global"; | ||||
| .container { | ||||
|   display: flex; | ||||
|   justify-content: center; | ||||
|   margin-top: 4rem; | ||||
| } | ||||
| .sent-container { | ||||
|   width: 100%; | ||||
|   height: 90vh; | ||||
|   display: flex; | ||||
|   justify-content: center; | ||||
|   align-items: center; | ||||
|   text-align: center; | ||||
|   flex-direction: column; | ||||
| } | ||||
|  | ||||
| .wines-container { | ||||
|   justify-content: center; | ||||
|   display: flex; | ||||
|   flex-direction: column; | ||||
| } | ||||
| </style> | ||||
| @@ -8,7 +8,7 @@ import CreatePage from "@/components/CreatePage"; | ||||
|  | ||||
| import AdminPage from "@/components/AdminPage"; | ||||
|  | ||||
| import VirtualLotteryPage from "@/components/VirtualLotteryPage"; | ||||
| import WinnerPage from "@/components/WinnerPage"; | ||||
| import LotteryPage from "@/components/LotteryPage"; | ||||
|  | ||||
| const routes = [ | ||||
| @@ -43,6 +43,10 @@ const routes = [ | ||||
|   { | ||||
|     path: "/lottery/:tab", | ||||
|     component: LotteryPage | ||||
|   }, | ||||
|   { | ||||
|     path: "/winner/:id", | ||||
|     component: WinnerPage | ||||
|   } | ||||
| ]; | ||||
|  | ||||
|   | ||||
| @@ -1,17 +1,32 @@ | ||||
| <template> | ||||
|   <div class="wine-container" :class="{ 'big': fullscreen }"> | ||||
|     <div class="left"> | ||||
|       <img v-if="wine.image" :src="wine.image" class="wine-image" :class="{ 'fullscreen': fullscreen }" /> | ||||
|       <img | ||||
|         v-if="wine.image" | ||||
|         :src="wine.image" | ||||
|         class="wine-image" | ||||
|         :class="{ 'fullscreen': fullscreen }" | ||||
|       /> | ||||
|       <img v-else class="wine-placeholder" alt="Wine image" /> | ||||
|     </div> | ||||
|     <div class="right"> | ||||
|       <div> | ||||
|         <h2 v-if="wine.name">{{ wine.name }}</h2><h2 v-else>(no name)</h2> | ||||
|         <h2 v-if="wine.name">{{ wine.name }}</h2> | ||||
|         <h2 v-else>(no name)</h2> | ||||
|         <span v-if="wine.rating">{{ wine.rating }} rating</span> | ||||
|         <span v-if="wine.price">{{ wine.price }} NOK</span> | ||||
|         <span v-if="wine.country">{{ wine.country }}</span> | ||||
|  | ||||
|         <a v-if="wine.vivinoLink" :href="wine.vivinoLink" class="wine-link">Les mer på {{ hostname(wine.vivinoLink) }}</a> | ||||
|         <a | ||||
|           v-if="wine.vivinoLink" | ||||
|           :href="wine.vivinoLink" | ||||
|           class="wine-link" | ||||
|         >Les mer på {{ hostname(wine.vivinoLink) }}</a> | ||||
|         <button | ||||
|           v-if="winner" | ||||
|           @click="choseWine(wine.name)" | ||||
|           class="vin-button" | ||||
|         >Velg dette som din vin</button> | ||||
|       </div> | ||||
|  | ||||
|       <slot v-if="shouldUseInlineSlot()"></slot> | ||||
| @@ -36,15 +51,27 @@ export default { | ||||
|       type: Boolean, | ||||
|       required: false, | ||||
|       default: false | ||||
|     }, | ||||
|     winner: { | ||||
|       type: Boolean, | ||||
|       required: false, | ||||
|       default: false | ||||
|     } | ||||
|   }, | ||||
|   methods: { | ||||
|     shouldUseInlineSlot() { | ||||
|       return this.inlineSlot && window.innerWidth > 768 | ||||
|       return this.inlineSlot && window.innerWidth > 768; | ||||
|     }, | ||||
|     hostname(url) { | ||||
|       const urlHostname = new URL(url).hostname | ||||
|       return urlHostname.split(".")[(urlHostname.match(/\./g) || []).length - 1] | ||||
|       const urlHostname = new URL(url).hostname; | ||||
|       return urlHostname.split(".")[ | ||||
|         (urlHostname.match(/\./g) || []).length - 1 | ||||
|       ]; | ||||
|     }, | ||||
|     choseWine(name) { | ||||
|       if (window.confirm(`Er du sikker på at du vil ha ${name}?`)) { | ||||
|         this.$emit("chosen", name); | ||||
|       } | ||||
|     } | ||||
|   } | ||||
| }; | ||||
| @@ -52,6 +79,7 @@ export default { | ||||
|  | ||||
| <style lang="scss" scoped> | ||||
| @import "./src/styles/media-queries"; | ||||
| @import "./src/styles/global"; | ||||
| @import "./src/styles/variables"; | ||||
|  | ||||
| .wine-image { | ||||
|   | ||||
		Reference in New Issue
	
	Block a user