Verified Commit 62f67fe3 authored by Phil Booth's avatar Phil Booth

feat(publish): implement basic prediction website

parent 02f8efa7
Pipeline #36209136 passed with stage
in 1 minute and 18 seconds
......@@ -99,7 +99,7 @@ rules:
no-shadow: [ 2, { "hoist": "all" } ]
no-shadow-restricted-names: 2
no-tabs: 2
no-template-curly-in-string: 1
no-template-curly-in-string: 2
no-throw-literal: 1
no-trailing-spaces: 2
no-undef-init: 2
......
......@@ -3,15 +3,18 @@ cache:
untracked: true
paths:
- ingest/node_modules/
- publish/node_modules/
before_script:
- cd ingest
- npm install
- cd publish
- npm i
- cd ../ingest
- npm i
.test_template: &npm_test
script:
- npm run lint
- npm test
- npm t
test:node8:
image: node:8
......
ingest: node ingest
web: cd publish && npm start
......@@ -48,7 +48,7 @@ function fetch () {
}
if (match.time > Date.now() + DAY * 3) {
// Only predict matches within the next week
// Only predict matches within the next three days
return false
}
......
{
"engines": {
"node": "8.11.x"
"node": "10.12.x"
},
"cacheDirectories": [
"ingest/node_modules"
"ingest/node_modules",
"publish/node_modules"
],
"scripts": {
"postinstall": "cd ingest && npm install"
"postinstall": "cd ingest && npm i && cd ../publish && npm i"
}
}
.padded {
padding: 0 1em;
}
.clearfix {
zoom: 1;
}
.clearfix::before, .clearfix::after {
content: '.';
display: block;
height: 0;
overflow: hidden;
}
.clearfix::after {
clear: both;
}
.invisible {
position: absolute !important;
height: 1px;
width: 1px;
overflow: hidden;
clip: rect(1px 1px 1px 1px);
clip: rect(1px, 1px, 1px, 1px);
}
.indent {
margin: 1em 0 1em 1em;
}
.space-below {
margin-bottom: 2em;
}
.no-space-below {
margin-bottom: 0;
}
.smallprint {
margin-top: 3em;
font-size: var(--font-small);
}
.tagline {
margin: -0.5em 0 -0.2em;
color: var(--fg-primary);
font-family: 'Patua One', serif;
font-size: var(--font-tagline);
}
.nav {
display: inline;
float: left;
margin: 0.8em 2em 1.3em 0;
font-size: var(--font-nav);
font-weight: bold;
}
a.nav {
color: var(--fg-body);
}
a.nav:hover, a.nav:active {
color: var(--fg-primary);
}
span.nav {
color: var(--fg-secondary);
}
.section-title, .article-title {
font-size: var(--font-small);
font-weight: bold;
}
.section-title {
margin-bottom: 1.75em;
}
.article-title {
margin-bottom: 0.2em;
}
.article-title a {
color: var(--fg-body);
}
.article-title a:hover, .article-title a:active {
color: var(--fg-primary);
}
.article-time {
font-size: var(--font-small);
margin-bottom: 0.2em;
}
.prediction-details {
display: flex;
flex-flow: column wrap;
justify-content: flex-start;
align-items: flex-start;
align-content: flex-start;
}
.prediction-details.text-only {
margin-bottom: 1em;
}
.prediction {
display: flex;
flex-flow: row nowrap;
justify-content: flex-start;
align-items: center;
align-content: flex-start;
margin-bottom: 1.6em;
}
.prediction-details .prediction {
margin-bottom: 0;
}
.detail {
margin-bottom: 0.5em;
}
.prediction-details .prediction .detail:first-child {
margin-right: 0.6em;
}
.prediction-details dl {
display: flex;
flex-flow: row wrap;
justify-content: flex-start;
align-items: flex-end;
align-content: flex-start;
margin-bottom: 0;
}
.prediction-details dt {
float: none;
clear: none;
font-weight: normal;
}
.prediction-details dd {
margin-right: 0.5em;
}
.thumbnail {
display: block;
box-sizing: border-box;
min-width: 80px;
max-width: 80px;
margin-top: 0.3em;
margin-right: 1em;
}
.responsive {
width: 100%;
}
.chart-container {
display: block;
position: relative;
width: 100%;
padding-bottom: 50%;
margin-bottom: 0.5em;
vertical-align: middle;
overflow: hidden;
}
.chart {
display: inline-block;
position: absolute;
top: 0;
left: 0;
}
.chart-axis {
stroke-width: 2;
stroke: #000;
}
.chart-bar {
fill: var(--fg-primary);
}
.chart-bar-label {
fill: #fff;
}
.centered {
text-align: center;
}
html {
font-size: 100.01%;
}
html, body {
height: 100%;
}
html, body, header, footer, section, article, h1, h2, h3, h4,
p, div, span, pre, code, a, picture, img, ol, ul, li, dl, dt, dd,
abbr, time, svg, table, tr, th, td, form, label {
margin: 0;
padding: 0;
border: 0;
font: inherit;
font-size: 100%;
vertical-align: baseline;
}
body {
background-color: var(--bg-body);
color: var(--fg-body);
font-family: roboto, tahoma, verdana, freesans, clean, sans-serif;
font-size: var(--font-base);
}
header, footer, section {
display: block;
}
h1, h2, h3 {
font-weight: bold;
}
h1 {
margin-top: 0.4em;
font-family: 'Patua One', serif;
font-size: var(--font-title);
letter-spacing: 0.027em;
word-spacing: 0.08em;
color: var(--fg-primary);
}
h2, h3 {
font-size: var(--font-small);
margin-bottom: 1.75em;
}
h4, p, td, dd {
font-weight: normal;
}
h4, p, dt, dd, th, td, li {
font-size: var(--font-copy);
word-spacing: 0.1em;
}
h3, h4, p, dl, table, pre {
margin-bottom: 1.5em;
}
p {
max-width: 32em;
}
dt {
float: left;
clear: left;
margin-right: 0.3em;
font-weight: bold;
}
th {
font-weight: bold;
}
th, td {
padding: 0.2em 1em 0.2em 0;
vertical-align: middle;
text-align: left;
}
li h3, li h4, li p, li dt, li dd, li th, li td {
font-size: var(--font-restore);
}
li p {
max-width: 30em;
}
p img {
width: 100%;
}
table {
box-sizing: border-box;
border-spacing: 0;
page-break-inside: avoid;
}
th:last-child, td:last-child {
padding-right: 0;
}
li {
list-style: none;
}
a {
color: var(--fg-primary);
text-decoration: none;
cursor: pointer;
}
a:hover, a:active {
color: var(--fg-secondary);
}
svg {
margin: 0;
page-break-inside: avoid;
width: 100%;
}
form, label, input {
display: block;
}
form {
margin-bottom: 2em;
}
label {
font-size: 0.8em;
}
input, textarea, select {
margin-bottom: 0.4em;
font: inherit;
font-size: 100%;
}
textarea {
height: 16em;
}
code {
display: inline-block;
box-sizing: border-box;
padding: 0.15em 0.25em 0 0.25em;
border-radius: 3px;
background-color: #e5dfd6;
font-family: consolas, monaco, "lucida console", monospace, serif;
font-size: 0.8em;
}
pre {
color: #888;
}
pre code {
margin-left: 2em;
padding: 0;
background-color: var(--bg-body);
}
@font-face {
font-family: 'Patua One';
font-style: normal;
font-weight: 400;
src: local('Patua One'), local('PatuaOne-Regular'),
url('../fonts/patua-one-v6-latin-regular.8c89de8c058c59c843009a7a76334d0c42aa5769.woff2') format('woff2'),
url('../fonts/patua-one-v6-latin-regular.2ed441ea7e766208c9f6ce6a7aeeccfff7e8f66c.woff') format('woff'),
url('../fonts/patua-one-v6-latin-regular.25f4a1a8f148b930a63ec3df0df05210687617a5ttf') format('truetype');
}
@font-face {
font-family: 'Roboto';
font-style: normal;
font-weight: 400;
src: local('Roboto'), local('Roboto-Regular'),
url('../fonts/roboto-v16-latin-regular.0a59a3b17c93c1093c2514b3a9d51c91395aabd0.woff2') format('woff2'),
url('../fonts/roboto-v16-latin-regular.7a4ddb6733c33dfe9ec94c82a5e7f5da885f5182.woff') format('woff'),
url('../fonts/roboto-v16-latin-regular.0a501f2147cc8cb9d56a27bebe8c296dd454bcec.ttf') format('truetype');
}
@font-face {
font-family: 'Roboto';
font-style: normal;
font-weight: 700;
src: local('Roboto Bold'), local('Roboto-Bold'),
url('../fonts/roboto-v16-latin-700.a1118c5362e2dd34ac5cf34e135042c3ad827b58.woff2') format('woff2'),
url('../fonts/roboto-v16-latin-700.2fc9bb16fbfee39e2559e5cbf5f90b225e0a8b92.woff') format('woff'),
url('../fonts/roboto-v16-latin-700.d229939f0efef78a73c4be8f8bdaff9cc3c376c4.ttf') format('truetype');
}
@import url(variables.css);
@import url(fonts.css);
@import url(elements.css);
@import url(classes.css);
@import url(responsive.css);
@media (min-width: 480px) {
.padded {
padding-left: 2em;
padding-right: 2em;
}
.thumbnail {
margin-top: 0;
}
}
@media (min-width: 640px) {
.section {
display: inline;
float: left;
width: 48%;
}
.section:nth-of-type(2n+1) {
margin-right: 4%;
clear: left;
}
}
@media (min-width: 1024px) {
body {
font-size: var(--font-base-large);
}
th, td {
font-size: 0.9em;
}
.padded {
padding-left: 3em;
padding-right: 3em;
}
.indent {
font-size: var(--font-small);
}
.thumbnail {
min-width: 100px;
max-width: 100px;
}
.chart-label {
font-size: 0.6em;
}
}
:root {
--bg-body: #fff;
--fg-body: #333;
--fg-primary: #20b200;
--fg-secondary: #ff19c8;
--font-base: 100%;
--font-base-large: 150%;
--font-title: 3.47em;
--font-tagline: 0.95em;
--font-nav: 1.15em;
--font-copy: 0.875em;
--font-restore: 1em;
--font-small: 0.75em;
}
// Copyright © 2018 Phil Booth
//
// This file is part of cricketforecast.
//
// cricketforecast is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published
// by the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// cricketforecast is distributed in the hope that it will be useful, but
// WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
// or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public
// License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with cricketforecast. If not, see <http://www.gnu.org/licenses/>.
'use strict'
const fs = require('fs')
const path = require('path')
const express = require('express')
const handlebars = require('express-handlebars').create({
defaultLayout: 'main',
extname: 'html',
helpers: require('./server/view-helpers')
})
const log = require('bunyan').createLogger({ name: 'server' })
const compression = require('compression')
const hash = require('./server/hash')
const routes = require('./server/routes')
const { WEEK } = require('./server/constants')
const STYLE_PATH = path.join(__dirname, './public/style/main.css')
const PATUA_WOFF2_PATH = path.join(__dirname, './public/fonts/patua-one-v6-latin-regular.woff2')
const PATUA_WOFF_PATH = path.join(__dirname, './public/fonts/patua-one-v6-latin-regular.woff')
const PATUA_TTF_PATH = path.join(__dirname, './public/fonts/patua-one-v6-latin-regular.ttf')
const ROBOTO_REGULAR_WOFF2_PATH = path.join(__dirname, './public/fonts/roboto-v16-latin-regular.woff2')
const ROBOTO_REGULAR_WOFF_PATH = path.join(__dirname, './public/fonts/roboto-v16-latin-regular.woff')
const ROBOTO_REGULAR_TTF_PATH = path.join(__dirname, './public/fonts/roboto-v16-latin-regular.ttf')
const ROBOTO_HEAVY_WOFF2_PATH = path.join(__dirname, './public/fonts/roboto-v16-latin-700.woff2')
const ROBOTO_HEAVY_WOFF_PATH = path.join(__dirname, './public/fonts/roboto-v16-latin-700.woff')
const ROBOTO_HEAVY_TTF_PATH = path.join(__dirname, './public/fonts/roboto-v16-latin-700.ttf')
//const IMAGES_PATH = path.join(__dirname, './public/images')
//const IMAGE_PATHS = fs.readdirSync(IMAGES_PATH).map(image => path.join(IMAGES_PATH, image))
const viewData = {
title: 'Cricket Forecast',
tagline: 'AI-generated cricket result predictions',
hashes: {
style: {
main: hash(fs.readFileSync(STYLE_PATH))
},
fonts: {
patua: {
woff2: hash(fs.readFileSync(PATUA_WOFF2_PATH)),
woff: hash(fs.readFileSync(PATUA_WOFF_PATH)),
ttf: hash(fs.readFileSync(PATUA_TTF_PATH))
},
robotoRegular: {
woff2: hash(fs.readFileSync(ROBOTO_REGULAR_WOFF2_PATH)),
woff: hash(fs.readFileSync(ROBOTO_REGULAR_WOFF_PATH)),
ttf: hash(fs.readFileSync(ROBOTO_REGULAR_TTF_PATH))
},
robotoHeavy: {</