SMTL

Die Stadtmeistersteilerliste und das Anmeldeformular - SK Langen e.V.
git clone git://git.oshgnacknak.de/SMTL.git
Log | Files | Refs | README

commit 145978beef7a285461eb33e31816342373c79c2d
parent 278e5554d0c6a20fbd5dddd162bd67ab5bd29254
Author: Oshgnacknak <osh@oshgnacknak.de>
Date:   Sat,  7 Mar 2020 19:16:06 +0100

Added public routes + Cleaned up environment variables via Docker

Diffstat:
MDockerfile | 5+++++
APlayerRoute.js | 40++++++++++++++++++++++++++++++++++++++++
MREADME.md | 8++++++++
Madmin.js | 7+++----
Mdocker-compose.yml | 18+++++++++---------
Mindex.js | 39+++------------------------------------
Mmodels/Player.js | 1-
Apublic/index.html | 121+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
8 files changed, 189 insertions(+), 50 deletions(-)

diff --git a/Dockerfile b/Dockerfile @@ -5,6 +5,11 @@ WORKDIR /usr/src/app COPY package*.json ./ RUN npm install +ENV COOKIE_PASSWORD 'CHANGE ME ASAP' +ENV MONGO_URL 'mongodb://mongo/SMTL' +ENV MINOR_BIRTH_YEAR 2002 +ENV SENIOR_BIRTH_YEAR 1961 + EXPOSE 3000 COPY . . diff --git a/PlayerRoute.js b/PlayerRoute.js @@ -0,0 +1,40 @@ +const Player = require('./models/Player') + +const birthAttr = year => { + if (year >= process.env.MINOR_BIRTH_YEAR) { + return 'J' + } else if (year < process.env.SENIOR_BIRTH_YEAR) { + return 'S' + } + return '' +} + +const genderAttr = gender => { + return gender === 'FEMALE' ? 'W' : '' +} + +module.exports = app => { + + app.route('/players') + + .get((reg, res) => + Player.find({ approved: true }) + .sort({ dwz: -1 }) + .then(players => + players.map(player => { + const { name, birth_year, gender, club, dwz } = player; + const attr = birthAttr(birth_year) + genderAttr(gender) + return { name, club, dwz, attr } + })) + .then(data => res.json(data)) + .catch(err => console.log(err)) + ) + + .post((req, res) => { + const { name, email, birth_year, gender, club, dwz } = req.body; + new Player({ name, email, birth_year, gender, club, dwz }) + .save() + .then(() => res.json({})) + .catch(err => res.status(400).send(err)) + }) +} diff --git a/README.md b/README.md @@ -2,3 +2,11 @@ This webapp is for for the 'Stadtmeisterschaft' players list and the singup. +## Docker + +### Environment Variables + +- COOKIE_PASSWORD: secret password to encrypt cookies +- MINOR_BIRTH_YEAR: Year of birth at which players count a being minor +- SENIOR_BIRTH_YEAR: Year of birth before which some players count a being senior +- MONGO_URL: Url to connect to mongo database diff --git a/admin.js b/admin.js @@ -56,11 +56,10 @@ const adminBro = new AdminBro({ } }) -const cookiePassword = process.env.COOKIE_PASSWORD -if (!cookiePassword) { +const { COOKIE_PASSWORD } = process.env +if (!COOKIE_PASSWORD) { throw new Error('process.env.COOKIE_PASSWORD must be set') } - module.exports = AdminBroExpress.buildAuthenticatedRouter(adminBro, { authenticate: async (email, password) => { const user = await User.findOne({ email }) @@ -72,5 +71,5 @@ module.exports = AdminBroExpress.buildAuthenticatedRouter(adminBro, { } return false }, - cookiePassword: cookiePassword + cookiePassword: COOKIE_PASSWORD }) diff --git a/docker-compose.yml b/docker-compose.yml @@ -1,12 +1,12 @@ version: '3.1' services: - mongo: - image: mongo - restart: always - smtl: - build: . - environment: - COOKIE_PASSWORD: 'Pm9yr39FZamjga8fPUy34/4g7rm0e8uaTqsIxZ/U7TE=' - ports: - - '3000:3000' + mongo: + image: mongo + restart: always + smtl: + build: . + volumes: + - './public:/usr/src/app/public' + ports: + - '3000:3000' diff --git a/index.js b/index.js @@ -6,16 +6,12 @@ const favicon = require('serve-favicon') const bcrypt = require('bcrypt') const User = require('./models/User') -const Player = require('./models/Player') -const PORT = process.env.PORT || 3000 -const MINOR_BIRTH_YEAR = process.env.MINOR_BIRTH_YEAR || 2002 -const SENIOR_BIRTH_YEAR = process.env.SENIOR_BIRTH_YEAR || 1961 -const MONGO_URL = process.env.MONGO_URL || 'mongodb://mongo/SMTL' +const PORT = 3000 const app = express() mongoose.Promise = global.Promise -mongoose.connect(MONGO_URL, { useNewUrlParser: true }) +mongoose.connect(process.env.MONGO_URL, { useNewUrlParser: true }) .then(() => { console.log('MongoDB Connected successfully...') return User.countDocuments({}) @@ -40,36 +36,7 @@ app.use('/admin', require('./admin')) app.use(favicon('public/vereinslogo1.gif')) app.use(bodyParser.json()) -const birthAttr = year => { - if (year >= MINOR_BIRTH_YEAR) { - return 'J' - } else if (year < SENIOR_BIRTH_YEAR) { - return 'S' - } - return '' -} -const genderAttr = gender => { - return gender === 'FEMALE' ? 'W' : '' -} -app.route('/players') - .get((reg, res) => - Player.find({ approved: true }) - .then(players => - players.map(player => { - const { name, birth_year, gender, club, dwz } = player; - const attr = birthAttr(birth_year) + genderAttr(gender) - return { name, club, dwz, attr } - })) - .then(data => res.json(data)) - .catch(err => console.log(err)) - ) - .post((req, res) => { - const { name, email, birth_year, gender, club, dwz } = req.body; - new Player({ name, email, birth_year, gender, club, dwz }) - .save() - .then(() => res.json({})) - .catch(err => res.status(400).send(err)) - }) +require('./PlayerRoute')(app) app.listen(PORT, () => { console.log('SMTL server started on: ' + PORT) diff --git a/models/Player.js b/models/Player.js @@ -19,7 +19,6 @@ const PlayerSchema = new Schema({ type: Number, required: true, min: 1, - max: 2020, validate: [ Number.isInteger ] }, gender: { diff --git a/public/index.html b/public/index.html @@ -0,0 +1,121 @@ +<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01//EN" + "http://www.w3.org/TR/html4/strict.dtd"> +<html lang="en"> + <head> + <meta http-equiv="content-type" content="text/html; charset=utf-8"> + <link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css" integrity="sha384-BVYiiSIFeK1dGmJRAkycuHAHRg32OmUcww7on3RYdg4Va+PmSTsz/K68vbdEjh4u" crossorigin="anonymous"> + <title>Langener Stadtmeisterschaft 2020</title> + + <style> + h1 { + text-align: center; + } + </style> + </head> + <body> + <div class="container"> + <h1>Langener Stadtmeisterschaft 2020</h1> + + <h2>Teilnehmerliste</h2> + + <table class="table table-responsive"> + <thead> + <tr> + <th>Name</th> + <th>Verein</th> + <th>DWZ</th> + <th>Attr.</th> + </tr> + </thead> + <tbody> + </tbody> + </table> + + <h2>Anmeldeformular</h2> + + <form> + <div class="form-group"> + <label for="email">Email Adresse</label> + <input type="email" class="form-control" name="email" required="true" aria-describedby="emailHelp" placeholder="email@example.com"> + <small id="emailHelp" class="form-text text-muted">Ihre Email Adresse wird nicht veröffentlicht</small> + </div> + <div class="form-group"> + <label for="name">Name</label> + <input type="text" class="form-control" name="name" required="true" placeholder="Name, Vorname" /> + </div> + <div class="form-group"> + <label for="birth_year">Geburtsjahr</label> + <input type="number" class="form-control" name="birth_year" required="true" placeholder="1648" /> + </div> + <div class="form-group"> + <label for="gender">Geschlecht</label> + <select class="form-control" name="gender"> + <option value="MALE">Männlich</option> + <option value="FIMALE">Weiblich</option> + <option value="OTHER">Divers</option> + </select> + </div> + <div class="form-group"> + <label for="club">Verein</label> + <input type="text" class="form-control" name="club" placeholder="SK Langen" /> + </div> + <div class="form-group"> + <label for="dwz">DWZ</label> + <input type="number" class="form-control" name="dwz" value="0" /> + </div> + <button type="submit" class="btn btn-primary">Teilnehmen</button> + </form> + </div> + + <script defer> + const table = document.querySelector('table tbody') + + fetch('/players?no_cache=' + Date.now()) + .then(res => res.json()) + .then(players => { + table.innerHTML = '' + players.forEach(player => { + const tr = document.createElement('tr'); + [ 'name', 'club', 'dwz', 'attr' ].forEach(key => { + const td = document.createElement('td') + td.innerText = player[key] + tr.appendChild(td) + }) + table.appendChild(tr) + }) + }) + + const form = document.querySelector('form') + form.addEventListener('submit', e => { + e.preventDefault() + const data = Object.fromEntries(new FormData(form)) + + fetch('/players', { + method: 'POST', + body: JSON.stringify(data), + headers: { 'content-type': 'application/json' } + }) + .then(res => { + if (!res.ok) { + throw res + } + return res.json() + }) + .then(res => { + alert('Ihre Teilnahme wurde erfolgreich erfasst!\nBitte haben Sie Geduld, bis sie bearbeitet wurde.') + form.reset() + }) + .catch(err => err.json().then(errors => { + Array.from(form.elements) + .filter(e => errors.errors.hasOwnProperty(e.name)) + .forEach(e => { + e.setCustomValidity(errors.errors[e.name].message) + e.checkValidity(); + e.addEventListener('change', () => e.setCustomValidity('')) + }); + })) + }) + + </script> + </body> +</html>