Start implementing MySQL data fetching.
gitea/icedream/vizon-countdown-website/develop There was a failure building this commit
Details
gitea/icedream/vizon-countdown-website/develop There was a failure building this commit
Details
- 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.develop
parent
9bea67e1b1
commit
202c35b517
|
@ -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:
|
||||
|
|
94
index.js
94
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')}/`);
|
||||
});
|
||||
|
|
|
@ -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",
|
||||
|
|
|
@ -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"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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
|
14
src/App.jsx
14
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')}.
|
||||
</p>
|
||||
<Countdown date={nextUpcomingDate} />
|
||||
|
||||
<p>
|
||||
Last Drawing:
|
||||
</p>
|
||||
<Drawing
|
||||
numbers={[1, 2, 3, 4, 5, 6]}
|
||||
ranking={[
|
||||
{ rank: '1st', winners: 0 },
|
||||
{ rank: '2nd', winners: 0 },
|
||||
{ rank: '3rd', winners: 0 },
|
||||
{ rank: 'Consolation', winners: 4 },
|
||||
]}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<Footer>
|
||||
|
|
|
@ -0,0 +1,52 @@
|
|||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import style from './Drawing.sass';
|
||||
|
||||
const Numbers = ({ numbers }) => (
|
||||
<div className={style.numbers}>
|
||||
{
|
||||
numbers.map((number, index) => (
|
||||
<div key={index} className={style.number}>
|
||||
{number}
|
||||
</div>
|
||||
))
|
||||
}
|
||||
</div>
|
||||
);
|
||||
|
||||
Numbers.propTypes = {
|
||||
numbers: PropTypes.arrayOf(PropTypes.number).isRequired,
|
||||
};
|
||||
|
||||
export default class Drawing extends React.Component {
|
||||
static propTypes = {
|
||||
ranking: PropTypes.arrayOf(PropTypes.shape({
|
||||
rank: PropTypes.string,
|
||||
winners: PropTypes.int,
|
||||
})).isRequired,
|
||||
numbers: PropTypes.arrayOf(PropTypes.number).isRequired,
|
||||
}
|
||||
|
||||
static defaultProps = {
|
||||
|
||||
}
|
||||
|
||||
render() {
|
||||
const { ranking, numbers } = this.props;
|
||||
return (
|
||||
<div className={style.drawing}>
|
||||
<Numbers numbers={numbers} />
|
||||
<div className={style.ranking}>
|
||||
{
|
||||
ranking.map(({ rank, winners }) => (
|
||||
<div key={rank} className={style.rank}>
|
||||
<div className={style.rankName}>{rank}</div>
|
||||
<div className={style.winners}>{winners > 0 ? winners : 'No'} winners</div>
|
||||
</div>
|
||||
))
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,32 @@
|
|||
.drawing
|
||||
|
||||
.numbers
|
||||
display: flex
|
||||
flex-direction: row
|
||||
width: 100%
|
||||
|
||||
.number
|
||||
flex: 1 0 auto
|
||||
text-align: center
|
||||
font-size: 2.5em
|
||||
font-weight: bold
|
||||
|
||||
.ranking
|
||||
display: table
|
||||
width: 100%
|
||||
|
||||
.rank
|
||||
display: table-row
|
||||
|
||||
.rankName, .winners
|
||||
display: table-cell
|
||||
|
||||
.rankName
|
||||
text-align: right
|
||||
width: 50%
|
||||
padding-right: .25em
|
||||
|
||||
.winners
|
||||
text-align: left
|
||||
width: 50%
|
||||
padding-left: .25em
|
Loading…
Reference in New Issue