diff --git a/api/login.js b/api/login.js index 5c2fa0b..399136a 100644 --- a/api/login.js +++ b/api/login.js @@ -6,42 +6,50 @@ const router = require("express").Router(); router.get("/", function(req, res) { res.sendFile(path.join(__dirname + "/../public/index.html")); }); -/* + router.get("/register", function(req, res) { res.sendFile(path.join(__dirname + "/../public/index.html")); }); -router.post("/register", function(req, res, next) { - console.log("registering user"); - User.register( - new User({ username: req.body.username }), - req.body.password, - function(err) { - if (err) { - console.log("error while user register!", err); - return next(err); - } +// router.post("/register", function(req, res, next) { +// User.register( +// new User({ username: req.body.username }), +// req.body.password, +// function(err) { +// if (err) { +// console.log("error while user register!", 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); +// } - console.log("user registered!"); +// console.log("user registered!", req.body.username); + +// res.redirect("/#/") +// } +// ); +// }); - res.redirect("/"); - } - ); -}); -*/ router.get("/login", function(req, res) { res.sendFile(path.join(__dirname + "/../public/index.html")); }); -router.post( - "/login", - passport.authenticate("local", { - failureRedirect: "/#/login" - }), - function(req, res) { - res.redirect("/#/update"); - } -); +router.post("/login", function(req, res, next) { + passport.authenticate("local", function(err, user, info) { + if (err) { + if (err.name == "MissingUsernameError" || err.name == "MissingPasswordError") + return res.status(400).send({ success: false, message: err.message }) + return next(err); + } + + if (!user) return res.status(404).send({ success: false, message: "Incorrect username or password" }) + + console.log("user logged in:", user) + res.redirect("/#/update") + })(req, res, next); +}); router.get("/logout", function(req, res) { req.logout(); diff --git a/api/subscriptions.js b/api/subscriptions.js index 087f48a..c79779c 100644 --- a/api/subscriptions.js +++ b/api/subscriptions.js @@ -9,6 +9,8 @@ mongoose.connect("mongodb://localhost:27017/vinlottis", { useNewUrlParser: true }); +const mustBeAuthenticated = require(path.join(__dirname + "/../middleware/mustBeAuthenticated")) + const config = require(path.join(__dirname + "/../config/defaults/push")); const Subscription = require(path.join(__dirname + "/../schemas/Subscription")); const lotteryConfig = require(path.join( @@ -67,12 +69,7 @@ const saveToDatabase = async subscription => { } }; -router.route("/send-notification").post(async (req, res) => { - if (!req.isAuthenticated()) { - res.send(false); - return; - } - +router.route("/send-notification").post(mustBeAuthenticated, async (req, res) => { const message = JSON.stringify({ message: req.body.message, title: "Vinlotteri!" diff --git a/api/update.js b/api/update.js index ec1b130..23db875 100644 --- a/api/update.js +++ b/api/update.js @@ -7,6 +7,7 @@ mongoose.connect("mongodb://localhost:27017/vinlottis", { }); const sub = require(path.join(__dirname + "/../api/subscriptions")); +const mustBeAuthenticated = require(path.join(__dirname + "/../middleware/mustBeAuthenticated")) const Subscription = require(path.join(__dirname + "/../schemas/Subscription")); const Purchase = require(path.join(__dirname + "/../schemas/Purchase")); @@ -20,11 +21,7 @@ router.use((req, res, next) => { next(); }); -router.route("/log/wines").post(async (req, res) => { - if (!req.isAuthenticated()) { - res.send(false); - return; - } +router.route("/log/wines").post(mustBeAuthenticated, async (req, res) => { const wines = req.body; for (let i = 0; i < wines.length; i++) { let wine = wines[i]; @@ -51,12 +48,18 @@ router.route("/log/wines").post(async (req, res) => { res.send(true); }); -router.route("/log").post(async (req, res) => { - if (!req.isAuthenticated()) { - res.send(false); - return; - } +router.route("/log/schema").get(mustBeAuthenticated, async (req, res) => { + let schema = {...PreLotteryWine.schema.obj}; + let nulledSchema = Object.keys(schema).reduce( + (accumulator, current) => { + accumulator[current] = ""; + return accumulator + }, {}); + res.send(nulledSchema) +}) + +router.route("/log").post(mustBeAuthenticated, async (req, res) => { await PreLotteryWine.deleteMany(); const purchaseBody = req.body.purchase; diff --git a/api/wineinfo.js b/api/wineinfo.js new file mode 100644 index 0000000..237de19 --- /dev/null +++ b/api/wineinfo.js @@ -0,0 +1,31 @@ +const express = require("express"); +const path = require("path"); +const router = express.Router(); +const fetch = require('node-fetch') + +const mustBeAuthenticated = require(path.join(__dirname + "/../middleware/mustBeAuthenticated")) + +router.use((req, res, next) => { + next(); +}); + +router.route("/wineinfo/:ean").get(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() + } + }) + } + + res.send(vinmonopoletResponse); +}); + +module.exports = router; diff --git a/config/env/lottery.config.example.js b/config/env/lottery.config.example.js index 37f881b..cde3d8c 100644 --- a/config/env/lottery.config.example.js +++ b/config/env/lottery.config.example.js @@ -4,5 +4,6 @@ module.exports = { price: 10, message: "VINLOTTERI", date: 5, - hours: 15 + hours: 15, + apiUrl: undefined }; diff --git a/config/webpack.config.common.js b/config/webpack.config.common.js index 26d6fbf..50680e6 100644 --- a/config/webpack.config.common.js +++ b/config/webpack.config.common.js @@ -78,6 +78,7 @@ const webpackConfig = function(isDev) { __MESSAGE__: JSON.stringify(env.message), __DATE__: env.date, __HOURS__: env.hours, + __APIURL__: JSON.stringify(env.apiUrl), __PUSHENABLED__: JSON.stringify(require("./defaults/push") != false) }) ] diff --git a/config/webpack.config.dev.js b/config/webpack.config.dev.js index 6922179..ca13cfb 100644 --- a/config/webpack.config.dev.js +++ b/config/webpack.config.dev.js @@ -45,7 +45,7 @@ webpackConfig = merge(webpackConfig, { }, plugins: [ new HtmlPlugin({ - template: "src/templates/Index.html", + template: "src/templates/Create.html", chunksSortMode: "dependency" }) ] diff --git a/middleware/mustBeAuthenticated.js b/middleware/mustBeAuthenticated.js new file mode 100644 index 0000000..b537ed5 --- /dev/null +++ b/middleware/mustBeAuthenticated.js @@ -0,0 +1,13 @@ + +const mustBeAuthenticated = (req, res, next) => { + if (!req.isAuthenticated()) { + return res.status(401).send({ + success: false, + message: "Du må være logget inn." + }) + } + + return next() +} + +module.exports = mustBeAuthenticated; \ No newline at end of file diff --git a/package.json b/package.json index e004752..a240431 100644 --- a/package.json +++ b/package.json @@ -13,6 +13,7 @@ "license": "ISC", "dependencies": { "@babel/polyfill": "~7.2", + "@zxing/library": "^0.16.0", "body-parser": "^1.19.0", "chart.js": "^2.9.3", "clean-webpack-plugin": "^3.0.0", diff --git a/server.js b/server.js index a812b10..1f69199 100644 --- a/server.js +++ b/server.js @@ -8,6 +8,7 @@ const updateApi = require(path.join(__dirname + "/api/update")); const retrieveApi = require(path.join(__dirname + "/api/retrieve")); 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 bodyParser = require("body-parser"); const mongoose = require("mongoose"); @@ -80,6 +81,7 @@ app.use("/dist", express.static(path.join(__dirname, "public/dist"))); app.use("/", loginApi); app.use("/api/", updateApi); app.use("/api/", retrieveApi); +app.use("/api/", wineinfoApi); app.use("/subscription", subscriptionApi); app.get("/dagens", function(req, res) { diff --git a/src/api.js b/src/api.js new file mode 100644 index 0000000..a79cfca --- /dev/null +++ b/src/api.js @@ -0,0 +1,177 @@ +const BASE_URL = __APIURL__ || "http://localhost:30030/"; + +const statistics = () => { + const url = new URL('/api/purchase/statistics', BASE_URL) + + return fetch(url.href) + .then(resp => resp.json()) +} + +const colorStatistics = () => { + const url = new URL("/api/purchase/statistics/color", BASE_URL) + + return fetch(url.href) + .then(resp => resp.json()) +} + +const highscoreStatistics = () => { + const url = new URL("/api/highscore/statistics", BASE_URL) + + 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 chartWinsByColor = () => { + const url = new URL("/api/purchase/statistics/color", BASE_URL) + + 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 prelottery = () => { + const url = new URL("/api/wines/prelottery", BASE_URL) + + return fetch(url.href) + .then(resp => resp.json()) +} + +const log = (sendObject) => { + const url = new URL("/api/log", BASE_URL) + + const options = { + headers: { + "Content-Type": "application/json" + }, + method: "POST", + body: JSON.stringify(sendObject) + } + + return fetch(url.href, options) + .then(resp => resp.json()) +} + +const logWines = (wines) => { + const url = new URL("/api/log/wines", BASE_URL) + + const options = { + headers: { + "Content-Type": "application/json" + }, + method: "POST", + body: JSON.stringify(wines) + } + + return fetch(url.href, options) + .then(resp => resp.json()) +} + +const wineSchema = () => { + const url = new URL("/api/log/schema", BASE_URL) + + return fetch(url.href) + .then(resp => resp.json()) +} + +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() + } + }) +} + +const handleErrors = async (resp) => { + if ([400, 409].includes(resp.status)) { + throw await resp.json() + } else { + 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 options = { + headers: { + "Content-Type": "application/json" + }, + method: "POST", + redirect: "follow", + body: JSON.stringify({ username, password }) + } + + return fetch(url.href, options) + .then(resp => { + if (resp.ok) { + if (resp.bodyUsed) + return resp.json() + else + return resp + } else { + return handleErrors(resp) + } + }) +} + +const register = (username, password) => { + const url = new URL("/register", BASE_URL) + const options = { + headers: { + "Content-Type": "application/json" + }, + method: "POST", + redirect: 'follow', + body: JSON.stringify({ username, password }) + } + + return fetch(url.href, options) + .then(resp => { + if (resp.ok) { + if (resp.bodyUsed) + return resp.json() + else + return resp + } else { + return handleErrors(resp) + } + }) +} + +export { + statistics, + colorStatistics, + highscoreStatistics, + overallWineStatistics, + chartWinsByColor, + chartPurchaseByColor, + prelottery, + log, + logWines, + wineSchema, + barcodeToVinmonopolet, + login, + register +} diff --git a/src/components/AllWinesPage.vue b/src/components/AllWinesPage.vue index 6ef37e2..174fc99 100644 --- a/src/components/AllWinesPage.vue +++ b/src/components/AllWinesPage.vue @@ -3,7 +3,7 @@

Alle viner

- +
@@ -13,6 +13,7 @@ import { page, event } from "vue-analytics"; import Banner from "@/ui/Banner"; import Wine from "@/ui/Wine"; +import { overallWineStatistics } from "@/api"; export default { components: { @@ -25,8 +26,8 @@ export default { }; }, async mounted() { - const _wines = await fetch("/api/wines/statistics/overall"); - this.wines = (await _wines.json()).sort((a, b) => + const wines = await overallWineStatistics(); + this.wines = wines.sort((a, b) => a.rating > b.rating ? -1 : 1 ); } diff --git a/src/components/CreatePage.vue b/src/components/CreatePage.vue index 465c5a1..d8cf2e8 100644 --- a/src/components/CreatePage.vue +++ b/src/components/CreatePage.vue @@ -1,57 +1,50 @@ diff --git a/src/components/GeneratePage.vue b/src/components/GeneratePage.vue index bafa984..0c22c72 100644 --- a/src/components/GeneratePage.vue +++ b/src/components/GeneratePage.vue @@ -6,28 +6,28 @@

@@ -53,7 +53,7 @@ >
-
+
Rød: {{ red }} Blå: {{ blue }} Gul: {{ yellow }} @@ -118,7 +118,12 @@ export default { if (time == 5) { this.generating = false; this.generated = true; - if (this.numberOfBallots > 1 && new Set(this.colors).size == 1) { + if (this.numberOfBallots > 1 && + [this.redCheckbox, this.greenCheckbox, this.yellowCheckbox, this.blueCheckbox].filter(value => value == true).length == 1) { + return + } + + if (new Set(this.colors).size == 1) { alert("BINGO"); } @@ -283,10 +288,9 @@ button { input { font-size: 1.5rem; - padding: 8px; + padding: 7px; margin: 0; height: 3rem; - max-height: 3rem; border: 1px solid rgba(#333333, 0.3); } @@ -351,23 +355,6 @@ label .text { } } -button { - border: none; - background: #b7debd; - color: #333; - padding: 10px 30px; - margin: 0; - width: fit-content; - font-size: 1.3rem; - display: block; - height: calc(3rem + 18px); - display: inline-flex; - max-height: calc(3rem + 18px); - - // disable-dbl-tap-zoom - touch-action: manipulation; -} - .colors-text { display: flex; flex-direction: row; diff --git a/src/components/LoginPage.vue b/src/components/LoginPage.vue index 8adc44d..fdf94b6 100644 --- a/src/components/LoginPage.vue +++ b/src/components/LoginPage.vue @@ -1,57 +1,49 @@ diff --git a/src/components/RegisterPage.vue b/src/components/RegisterPage.vue index b9c5f96..cc1c8d3 100644 --- a/src/components/RegisterPage.vue +++ b/src/components/RegisterPage.vue @@ -1,382 +1,448 @@