From 202c35b5174117edae83f9c0b06253640ebdb4a8 Mon Sep 17 00:00:00 2001
From: Carl Kittelberger
Date: Wed, 23 Aug 2017 14:59:12 +0200
Subject: [PATCH] Start implementing MySQL data fetching.
- Create Drawing component to display drawing information.
- Add SQL file to create view to filter for amount of correctly matched numbers.
- Use pooled connections for MySQL connections.
- Implement API endpoint "status", not done yet, needs to query from above mentioned view.
- Add more npm scripts that interact with docker/docker-compose.
- Allow web container to write back to source folder.
---
docker-compose.local.yml | 2 +-
index.js | 94 ++++++++++++++++++++++++++++++++++++++
package-lock.json | 42 +++++++++++------
package.json | 8 +++-
sql/vizon_web_rankings.sql | 91 ++++++++++++++++++++++++++++++++++++
src/App.jsx | 14 ++++++
src/Drawing.jsx | 52 +++++++++++++++++++++
src/Drawing.sass | 32 +++++++++++++
8 files changed, 317 insertions(+), 18 deletions(-)
create mode 100644 sql/vizon_web_rankings.sql
create mode 100644 src/Drawing.jsx
create mode 100644 src/Drawing.sass
diff --git a/docker-compose.local.yml b/docker-compose.local.yml
index 5f4cc2d..3ccd699 100644
--- a/docker-compose.local.yml
+++ b/docker-compose.local.yml
@@ -28,7 +28,7 @@ services:
build: docker/node
command: sh ./docker/web.sh
volumes:
- - ".:/src:ro"
+ - ".:/src"
- "web_npm_cache:/var/cache/npm"
- "/src/node_modules"
ports:
diff --git a/index.js b/index.js
index f70272a..18a628e 100644
--- a/index.js
+++ b/index.js
@@ -1,8 +1,32 @@
const path = require('path');
+const mysql = require('mysql');
const express = require('express');
+const moment = require('moment-timezone');
const fs = require('fs');
const bodyParser = require('body-parser');
+/* Database setup */
+
+const dbPool = mysql.createPool({
+ host: process.env.MYSQL_HOST || 'localhost',
+ user: process.env.MYSQL_USER || 'root',
+ password: process.env.MYSQL_PASSWORD || undefined,
+ database: process.env.MYSQL_DATABASE || 'vizon',
+ socketPath: process.env.MYSQL_SOCKET_PATH || undefined,
+ timezone: process.env.TZ || 'local',
+
+ debug: process.env.NODE_ENV === 'development',
+
+});
+
+const tableNames = {
+ drawings: process.env.MYSQL_TABLE_DRAWINGS || 'vizon_drawings',
+ users: process.env.MYSQL_TABLE_USERS || 'vizon_users',
+ rankings: 'vizon_web_rankings',
+};
+
+/* Frontend server setup */
+
const frontendDir = path.resolve(__dirname, 'dist');
const app = express();
@@ -29,6 +53,76 @@ fs.stat(frontendDir, (err) => {
app.use(bodyParser.json());
app.use(bodyParser.urlencoded({ extended: true }));
+// Additional middleware which will set headers that we need on each request.
+app.use((req, res, next) => {
+ // Disable caching so we'll always get the latest comments.
+ res.setHeader('Cache-Control', 'no-cache');
+ next();
+});
+
+function calculateRankings(id: Number) {
+ db.query('SELECT * FROM `vizon_web_rankings` WHERE `vizon_drawings_id`=?', [id], (err, results, fields) => {
+ if (err) {
+ console.error('Failed to query rankings from database:', err);
+ res.status(500).json({
+ error: 'Database query failed',
+ });
+ }
+ });
+}
+
+app.get('/api/status', (req, res) => {
+ dbPool.getConnection((err, db) => {
+ if (err) {
+ console.error('Failed to connect to database:', err);
+ res.status(500).json({
+ error: 'Database connection failed',
+ });
+ }
+
+ db.query('SELECT * FROM ?? ORDER BY ?? DESC LIMIT 0, 1', [
+ tableNames.drawings,
+ 'id',
+ ], (qerr, results, fields) => {
+ if (qerr) {
+ console.error('Failed to request drawings:', err);
+ res.status(500).json({
+ error: 'Database query failed',
+ });
+ return;
+ }
+
+ const row = results[0];
+
+ const id = row.id;
+
+ const date = moment(row.drawing_date);
+
+ const numbers = [
+ row.first,
+ row.second,
+ row.third,
+ row.fourth,
+ row.fifth,
+ row.sixth,
+ ].map(i => parseInt(i, 10));
+
+ const ranking = [
+ // @TODO - calculate ranking from mysql tables {drawings} and {bets}
+ ];
+
+ res.json({
+ lastDrawing: {
+ id,
+ date,
+ numbers,
+ ranking,
+ },
+ });
+ });
+ });
+});
+
app.listen(app.get('port'), () => {
console.log(`Server started: http://localhost:${app.get('port')}/`);
});
diff --git a/package-lock.json b/package-lock.json
index 930b122..01bd5b4 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -1643,6 +1643,11 @@
"integrity": "sha1-TK2iGTZS6zyp7I5VyQFWacmAaXg=",
"dev": true
},
+ "bignumber.js": {
+ "version": "4.0.2",
+ "resolved": "https://registry.npmjs.org/bignumber.js/-/bignumber.js-4.0.2.tgz",
+ "integrity": "sha1-LR3DfuWWiGfs6pC22k0W5oYI0h0="
+ },
"binary-extensions": {
"version": "1.10.0",
"resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-1.10.0.tgz",
@@ -2474,8 +2479,7 @@
"core-util-is": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz",
- "integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac=",
- "dev": true
+ "integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac="
},
"cosmiconfig": {
"version": "2.2.2",
@@ -5518,8 +5522,7 @@
"isarray": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz",
- "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=",
- "dev": true
+ "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE="
},
"isbinaryfile": {
"version": "3.0.2",
@@ -6691,14 +6694,12 @@
"moment": {
"version": "2.18.1",
"resolved": "https://registry.npmjs.org/moment/-/moment-2.18.1.tgz",
- "integrity": "sha1-w2GT3Tzhwu7SrbfIAtu8d6gbHA8=",
- "dev": true
+ "integrity": "sha1-w2GT3Tzhwu7SrbfIAtu8d6gbHA8="
},
"moment-timezone": {
"version": "0.5.13",
"resolved": "https://registry.npmjs.org/moment-timezone/-/moment-timezone-0.5.13.tgz",
"integrity": "sha1-mc5cfYJyYusPH3AgRBd/YHRde5A=",
- "dev": true,
"requires": {
"moment": "2.18.1"
}
@@ -6730,6 +6731,17 @@
"integrity": "sha1-MHXOk7whuPq0PhvE2n6BFe0ee6s=",
"dev": true
},
+ "mysql": {
+ "version": "2.14.1",
+ "resolved": "https://registry.npmjs.org/mysql/-/mysql-2.14.1.tgz",
+ "integrity": "sha512-ZPXqQeYH7L1QPDyC77Rcp32cNCQnNjz8Y4BbF17tOjm5yhSfjFa3xS4PvuxWJtEEmwVc4ccI7sSntj4eyYRq0A==",
+ "requires": {
+ "bignumber.js": "4.0.2",
+ "readable-stream": "2.3.3",
+ "safe-buffer": "5.1.1",
+ "sqlstring": "2.2.0"
+ }
+ },
"nan": {
"version": "2.6.2",
"resolved": "https://registry.npmjs.org/nan/-/nan-2.6.2.tgz",
@@ -8940,8 +8952,7 @@
"process-nextick-args": {
"version": "1.0.7",
"resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-1.0.7.tgz",
- "integrity": "sha1-FQ4gt1ZZCtP5EJPyWk8q2L/zC6M=",
- "dev": true
+ "integrity": "sha1-FQ4gt1ZZCtP5EJPyWk8q2L/zC6M="
},
"progress": {
"version": "2.0.0",
@@ -9291,7 +9302,6 @@
"version": "2.3.3",
"resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.3.tgz",
"integrity": "sha512-m+qzzcn7KUxEmd1gMbchF+Y2eIUbieUaxkWtptyHywrX0rE8QEYqPC07Vuy4Wm32/xE16NcdBctb8S0Xe/5IeQ==",
- "dev": true,
"requires": {
"core-util-is": "1.0.2",
"inherits": "2.0.3",
@@ -9774,8 +9784,7 @@
"safe-buffer": {
"version": "5.1.1",
"resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.1.tgz",
- "integrity": "sha512-kKvNJn6Mm93gAczWVJg7wH+wGYWNrDHdWvpUmHyEsgCtIwwo3bqPtV4tR5tuPaUhTOo/kvhVwd8XwwOllGYkbg==",
- "dev": true
+ "integrity": "sha512-kKvNJn6Mm93gAczWVJg7wH+wGYWNrDHdWvpUmHyEsgCtIwwo3bqPtV4tR5tuPaUhTOo/kvhVwd8XwwOllGYkbg=="
},
"sass-graph": {
"version": "2.2.4",
@@ -10471,6 +10480,11 @@
"integrity": "sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw=",
"dev": true
},
+ "sqlstring": {
+ "version": "2.2.0",
+ "resolved": "https://registry.npmjs.org/sqlstring/-/sqlstring-2.2.0.tgz",
+ "integrity": "sha1-wxNcTqirzX5+50GklmqJHYak8ZE="
+ },
"sshpk": {
"version": "1.13.1",
"resolved": "https://registry.npmjs.org/sshpk/-/sshpk-1.13.1.tgz",
@@ -10554,7 +10568,6 @@
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.0.3.tgz",
"integrity": "sha512-4AH6Z5fzNNBcH+6XDMfA/BTt87skxqJlO0lAh3Dker5zThcAxG6mKz+iGu308UKoPPQ8Dcqx/4JhujzltRa+hQ==",
- "dev": true,
"requires": {
"safe-buffer": "5.1.1"
}
@@ -11128,8 +11141,7 @@
"util-deprecate": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz",
- "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=",
- "dev": true
+ "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8="
},
"utila": {
"version": "0.4.0",
diff --git a/package.json b/package.json
index 51fda19..de727e3 100644
--- a/package.json
+++ b/package.json
@@ -10,6 +10,9 @@
"docker-compose": "docker-compose -f docker-compose.local.yml",
"docker:down:clean": "npm run -s docker:down -- --rmi all -v",
"docker:down": "npm run -s docker-compose -- down",
+ "docker:logs": "npm run -s docker-compose -- logs",
+ "docker:logs:follow": "npm run -s docker:logs -- -f",
+ "docker:restart": "npm run -s docker-compose -- restart",
"docker:up:daemon": "npm run -s docker:up -- -d",
"docker:up": "npm run -s docker-compose -- up --build",
"docker": "npm run -s docker:up",
@@ -51,7 +54,6 @@
"eslint-plugin-react": "^7.2.1",
"eslint": "^4.5.0",
"file-loader": "^0.11.2",
- "moment-timezone": "^0.5.13",
"normalize-scss": "^7.0.0",
"nwb-sass": "^0.8.1",
"nwb": "^0.18.10",
@@ -68,6 +70,8 @@
},
"dependencies": {
"body-parser": "^1.17.2",
- "express": "^4.15.4"
+ "express": "^4.15.4",
+ "moment-timezone": "^0.5.13",
+ "mysql": "^2.14.1"
}
}
diff --git a/sql/vizon_web_rankings.sql b/sql/vizon_web_rankings.sql
new file mode 100644
index 0000000..e4588af
--- /dev/null
+++ b/sql/vizon_web_rankings.sql
@@ -0,0 +1,91 @@
+CREATE OR REPLACE VIEW `vizon_web_rankings` AS
+SELECT
+ bets.id,
+ vizon_drawings_id,
+ COUNT(*) AS richtige
+FROM
+ (
+ SELECT
+ vizon_users_id AS id,
+ vizon_drawings_id,
+ FIRST AS NUMBER
+ FROM
+ vizon_bets
+ UNION ALL
+SELECT
+ vizon_users_id,
+ vizon_drawings_id,
+ SECOND
+FROM
+ vizon_bets
+UNION ALL
+SELECT
+ vizon_users_id,
+ vizon_drawings_id,
+ third
+FROM
+ vizon_bets
+UNION ALL
+SELECT
+ vizon_users_id,
+ vizon_drawings_id,
+ fourth
+FROM
+ vizon_bets
+UNION ALL
+SELECT
+ vizon_users_id,
+ vizon_drawings_id,
+ fifth
+FROM
+ vizon_bets
+UNION ALL
+SELECT
+ vizon_users_id,
+ vizon_drawings_id,
+ sixth
+FROM
+ vizon_bets
+) bets,
+(
+ SELECT
+ id,
+ FIRST AS NUMBER
+ FROM
+ vizon_drawings
+ UNION ALL
+SELECT
+ id,
+ SECOND
+FROM
+ vizon_drawings
+UNION ALL
+SELECT
+ id,
+ third
+FROM
+ vizon_drawings
+UNION ALL
+SELECT
+ id,
+ fourth
+FROM
+ vizon_drawings
+UNION ALL
+SELECT
+ id,
+ fifth
+FROM
+ vizon_drawings
+UNION ALL
+SELECT
+ id,
+ sixth
+FROM
+ vizon_drawings
+) drawings
+WHERE
+ bets.vizon_drawings_id = drawings.id AND bets.number = drawings.number
+GROUP BY
+ bets.id,
+ vizon_drawings_id
\ No newline at end of file
diff --git a/src/App.jsx b/src/App.jsx
index 8de7a2d..58d7de4 100644
--- a/src/App.jsx
+++ b/src/App.jsx
@@ -4,6 +4,7 @@ import moment from 'moment-timezone';
import 'react-fontawesome';
import WebFont from 'webfontloader';
import Countdown from './Countdown';
+import Drawing from './Drawing';
import Header from './Header';
import Footer from './Footer';
import getUpcomingDate from './getUpcomingDate';
@@ -79,6 +80,19 @@ class App extends React.Component {
The next VIzon draw is on {nextUpcomingDate.format('dddd')}, {nextUpcomingDate.format('L LT z')}.
+
+
+ Last Drawing:
+
+