Compare commits
4 Commits
232c69bdde
...
0c47fd2338
Author | SHA1 | Date |
---|---|---|
Samuel Ortion | 0c47fd2338 | |
Samuel Ortion | 13f37a14e8 | |
Samuel Ortion | 9e8404db11 | |
Samuel Ortion | 7e8ef90fb4 |
|
@ -66,3 +66,6 @@ typings/
|
||||||
|
|
||||||
data/*
|
data/*
|
||||||
!/data/.gitkeep
|
!/data/.gitkeep
|
||||||
|
|
||||||
|
push.sh
|
||||||
|
.rsyncignore
|
22
app.js
22
app.js
|
@ -2,11 +2,14 @@ require('dotenv').config();
|
||||||
const createError = require('http-errors');
|
const createError = require('http-errors');
|
||||||
const express = require('express');
|
const express = require('express');
|
||||||
const session = require('express-session');
|
const session = require('express-session');
|
||||||
|
let { redisClient } = require('./redis');
|
||||||
|
let RedisStore = require('connect-redis')(session);
|
||||||
const csrf = require('csurf');
|
const csrf = require('csurf');
|
||||||
const path = require('path');
|
const path = require('path');
|
||||||
const cookieParser = require('cookie-parser');
|
const cookieParser = require('cookie-parser');
|
||||||
const logger = require('morgan');
|
const logger = require('morgan');
|
||||||
const i18n = require('i18n-2');
|
const i18n = require('i18n-2');
|
||||||
|
const debugLocale = require('debug')('soundbirder:locale');
|
||||||
|
|
||||||
const indexRouter = require('./routes/index');
|
const indexRouter = require('./routes/index');
|
||||||
const apiRouter = require('./routes/api');
|
const apiRouter = require('./routes/api');
|
||||||
|
@ -34,6 +37,7 @@ const sess = {
|
||||||
if (app.get('env') === 'production') {
|
if (app.get('env') === 'production') {
|
||||||
app.set('trust proxy', 1); // trust first proxy
|
app.set('trust proxy', 1); // trust first proxy
|
||||||
sess.cookie.secure = true; // serve secure cookies
|
sess.cookie.secure = true; // serve secure cookies
|
||||||
|
sess.store = new RedisStore({ client: redisClient });
|
||||||
}
|
}
|
||||||
|
|
||||||
app.use(session(sess));
|
app.use(session(sess));
|
||||||
|
@ -41,7 +45,8 @@ app.use(session(sess));
|
||||||
i18n.expressBind(app, {
|
i18n.expressBind(app, {
|
||||||
locales: ['en', 'es', 'fr', 'de'],
|
locales: ['en', 'es', 'fr', 'de'],
|
||||||
defaultLocale: 'en',
|
defaultLocale: 'en',
|
||||||
cookieName: 'locale'
|
cookieName: 'locale',
|
||||||
|
extension: '.json'
|
||||||
});
|
});
|
||||||
|
|
||||||
app.use(function (req, res, next) {
|
app.use(function (req, res, next) {
|
||||||
|
@ -52,11 +57,11 @@ app.use(function (req, res, next) {
|
||||||
if (rxLocale.test(req.url)) {
|
if (rxLocale.test(req.url)) {
|
||||||
const arr = rxLocale.exec(req.url);
|
const arr = rxLocale.exec(req.url);
|
||||||
const locale = arr[1];
|
const locale = arr[1];
|
||||||
|
debugLocale("Setting locale from url prefix", locale);
|
||||||
req.i18n.setLocale(locale);
|
req.i18n.setLocale(locale);
|
||||||
|
debugLocale("Locale set to", req.i18n.locale);
|
||||||
}
|
}
|
||||||
if (req.cookies.locale === undefined) {
|
res.cookie('locale', req.i18n.locale, { maxAge: 900000, sameSite: true });
|
||||||
res.cookie('locale', req.i18n.locale, { maxAge: 90000 });
|
|
||||||
}
|
|
||||||
// add extra logic
|
// add extra logic
|
||||||
next();
|
next();
|
||||||
});
|
});
|
||||||
|
@ -65,15 +70,6 @@ app.use('/dist/leaflet', express.static('node_modules/leaflet/dist'));
|
||||||
app.use('/dist/feather', express.static('node_modules/feather-icons/dist'));
|
app.use('/dist/feather', express.static('node_modules/feather-icons/dist'));
|
||||||
app.use('/dist/axios', express.static('node_modules/axios/dist'));
|
app.use('/dist/axios', express.static('node_modules/axios/dist'));
|
||||||
|
|
||||||
app.use(function (req, res, next) {
|
|
||||||
res.header('Access-Control-Allow-Origin', 'http://localhost:3000');
|
|
||||||
res.header(
|
|
||||||
'Access-Control-Allow-Headers',
|
|
||||||
'Origin, X-Requested-With, Content-Type, Accept'
|
|
||||||
);
|
|
||||||
next();
|
|
||||||
});
|
|
||||||
|
|
||||||
app.use('/api/0', apiRouter);
|
app.use('/api/0', apiRouter);
|
||||||
|
|
||||||
const csrfProtection = csrf({ cookie: true });
|
const csrfProtection = csrf({ cookie: true });
|
||||||
|
|
|
@ -6,17 +6,22 @@ const quizzController = require('./quizz');
|
||||||
|
|
||||||
const QUIZZ_SIZE = process.env.QUIZZ_SIZE ? process.env.QUIZZ_SIZE : 5;
|
const QUIZZ_SIZE = process.env.QUIZZ_SIZE ? process.env.QUIZZ_SIZE : 5;
|
||||||
|
|
||||||
function check(req, res) {
|
function getHome(req, res) {
|
||||||
|
res.render('api', {
|
||||||
|
title: "SoundBirder api",
|
||||||
|
version: 0
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
function quizz(req, res) {
|
function quizz(req, res) {
|
||||||
debug('Generating quizz');
|
debug('Generating quizz');
|
||||||
const { lat, lng } = req.body;
|
const { lat, lng } = req.query;
|
||||||
|
debug(`Coordinates: ${lat}, ${lng}`);
|
||||||
const locale = req.i18n.locale;
|
const locale = req.i18n.locale;
|
||||||
debugLocale("Locale:", locale);
|
debugLocale("Locale:", locale);
|
||||||
quizzController.generateQuizz({ lat, lng }, locale, QUIZZ_SIZE)
|
quizzController.generateQuizz({ lat, lng }, locale, QUIZZ_SIZE)
|
||||||
.then(({ species, correct, audio }) => {
|
.then(({ species, answer, audio }) => {
|
||||||
req.session.correct = correct;
|
req.session.answer = answer;
|
||||||
res.json({ species, audio });
|
res.json({ species, audio });
|
||||||
debug("Quizz sent");
|
debug("Quizz sent");
|
||||||
})
|
})
|
||||||
|
@ -27,12 +32,37 @@ function quizz(req, res) {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function check(req, res) {
|
||||||
function getHome(req, res) {
|
let answer, correct;
|
||||||
res.render('api', {
|
try {
|
||||||
title: "SoundBirder api",
|
answer = req.query.species;
|
||||||
version: 0
|
correct = req.session.answer;
|
||||||
});
|
} catch (error) {
|
||||||
|
console.error(error);
|
||||||
|
}
|
||||||
|
let result = {};
|
||||||
|
try {
|
||||||
|
if (correct === undefined) {
|
||||||
|
console.error("No answer found in session");
|
||||||
|
} else if (answer === correct.speciesCode) {
|
||||||
|
debug("Correct answer");
|
||||||
|
result = {
|
||||||
|
correct: true,
|
||||||
|
message: req.i18n.__('Correct!'),
|
||||||
|
answer: correct
|
||||||
|
};
|
||||||
|
} else {
|
||||||
|
debug("Wrong answer");
|
||||||
|
result = {
|
||||||
|
correct: false,
|
||||||
|
message: req.i18n.__('Wrong!'),
|
||||||
|
answer: correct
|
||||||
|
};
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error(error);
|
||||||
|
}
|
||||||
|
res.json(result);
|
||||||
}
|
}
|
||||||
|
|
||||||
const game = {
|
const game = {
|
||||||
|
|
|
@ -1,16 +1,5 @@
|
||||||
require('dotenv').config();
|
const { redisClient } = require('../redis');
|
||||||
const debug = require('debug')('soundbirder:cache');
|
const debug = require('debug')('soundbirder:cache');
|
||||||
const redis = require('redis');
|
|
||||||
const host = process.env.REDIS_HOST ? process.env.REDIS_HOST : 'localhost';
|
|
||||||
const port = process.env.REDIS_PORT ? process.env.REDIS_PORT : 6379;
|
|
||||||
const url = `redis://${host}:${port}`;
|
|
||||||
const redisClient = redis.createClient({
|
|
||||||
url
|
|
||||||
});
|
|
||||||
|
|
||||||
(async () => {
|
|
||||||
redisClient.connect();
|
|
||||||
})();0
|
|
||||||
|
|
||||||
function cacheResponse(request, response) {
|
function cacheResponse(request, response) {
|
||||||
debug("Caching response", request);
|
debug("Caching response", request);
|
||||||
|
@ -18,9 +7,9 @@ function cacheResponse(request, response) {
|
||||||
}
|
}
|
||||||
|
|
||||||
async function getCached(request) {
|
async function getCached(request) {
|
||||||
debug("Getting cached response", request);
|
|
||||||
const cached = await redisClient.get(request);
|
const cached = await redisClient.get(request);
|
||||||
if (cached) {
|
if (cached) {
|
||||||
|
debug("Got cached response", request);
|
||||||
return JSON.parse(cached);
|
return JSON.parse(cached);
|
||||||
}
|
}
|
||||||
return null;
|
return null;
|
||||||
|
|
|
@ -16,10 +16,17 @@ async function generateQuizz(coordinates, locale, size) {
|
||||||
debugResponses('Localized species selection:', speciesSelectionLocalized);
|
debugResponses('Localized species selection:', speciesSelectionLocalized);
|
||||||
quizz.species = speciesSelectionLocalized;
|
quizz.species = speciesSelectionLocalized;
|
||||||
debug("Got species selection", quizz.species);
|
debug("Got species selection", quizz.species);
|
||||||
const answer = await choice(speciesSelectionLocalized);
|
let answer;
|
||||||
debug("Got answer", answer);
|
do {
|
||||||
quizz.correct = answer.speciesCode;
|
answer = choice(speciesSelectionLocalized);
|
||||||
|
quizz.answer = answer;
|
||||||
quizz.audio = await getAudio(answer.sciName);
|
quizz.audio = await getAudio(answer.sciName);
|
||||||
|
if (quizz.audio === undefined) {
|
||||||
|
debug("No audio found for species", answer.sciName);
|
||||||
|
debug("Trying again...");
|
||||||
|
}
|
||||||
|
} while (quizz.audio === undefined);
|
||||||
|
debug("Got answer", answer);
|
||||||
debug("Got audio", quizz.audio);
|
debug("Got audio", quizz.audio);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
debug("Error raised while generating quizz");
|
debug("Error raised while generating quizz");
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
version: '3.9'
|
version: '3'
|
||||||
|
|
||||||
services:
|
services:
|
||||||
express:
|
express:
|
||||||
|
@ -10,8 +10,10 @@ services:
|
||||||
- NODE_ENV=production
|
- NODE_ENV=production
|
||||||
- REDIS_HOST=${REDIS_HOST:-soundbirder_redis}
|
- REDIS_HOST=${REDIS_HOST:-soundbirder_redis}
|
||||||
- REDIS_PORT=${REDIS_PORT:-6379}
|
- REDIS_PORT=${REDIS_PORT:-6379}
|
||||||
|
- DEBUG=${DEBUG:-""}
|
||||||
ports:
|
ports:
|
||||||
- "${EXPRESS_PORT:-3000}:3000"
|
- "${EXPRESS_PORT:-3000}:3000"
|
||||||
|
restart: unless-stopped
|
||||||
networks:
|
networks:
|
||||||
- soundbirder_network
|
- soundbirder_network
|
||||||
depends_on:
|
depends_on:
|
||||||
|
|
|
@ -2,12 +2,12 @@ FROM node:16.17.0
|
||||||
|
|
||||||
WORKDIR /usr/src/app
|
WORKDIR /usr/src/app
|
||||||
|
|
||||||
COPY package*.json .
|
COPY package*.json ./
|
||||||
|
|
||||||
RUN npm install
|
RUN npm install
|
||||||
RUN npm ci --only=production
|
RUN npm ci --only=production
|
||||||
|
|
||||||
COPY . .
|
COPY . ./
|
||||||
|
|
||||||
EXPOSE 3000
|
EXPOSE 3000
|
||||||
CMD [ "./bin/www" ]
|
CMD [ "./bin/www" ]
|
|
@ -1,4 +1,4 @@
|
||||||
{
|
{
|
||||||
"Game": "Game",
|
"Game": "Spiel",
|
||||||
"About": "About"
|
"About": "About"
|
||||||
}
|
}
|
|
@ -8,5 +8,8 @@
|
||||||
"The project is made with ♥ by Samuel ORTION": "The project is made with ♥ by Samuel ORTION",
|
"The project is made with ♥ by Samuel ORTION": "The project is made with ♥ by Samuel ORTION",
|
||||||
"SoundBirder is an open-source web application to learn bird song identification. It is based on bird records from Xeno-Canto and data from eBird.": "SoundBirder is an open-source web application to learn bird song identification. It is based on bird records from Xeno-Canto and data from eBird.",
|
"SoundBirder is an open-source web application to learn bird song identification. It is based on bird records from Xeno-Canto and data from eBird.": "SoundBirder is an open-source web application to learn bird song identification. It is based on bird records from Xeno-Canto and data from eBird.",
|
||||||
"The project is made with ♥ by Samuel ORTION.": "The project is made with ♥ by Samuel ORTION.",
|
"The project is made with ♥ by Samuel ORTION.": "The project is made with ♥ by Samuel ORTION.",
|
||||||
"Welcome to SoundBirder' API": "Welcome to SoundBirder' API"
|
"Welcome to SoundBirder' API": "Welcome to SoundBirder' API",
|
||||||
|
"Wrong!": "Wrong!",
|
||||||
|
"Correct!": "Correct!",
|
||||||
|
"It was a": "It was a"
|
||||||
}
|
}
|
|
@ -1,4 +0,0 @@
|
||||||
{
|
|
||||||
"Home": "Inicio",
|
|
||||||
"About": "Acerca de",
|
|
||||||
}
|
|
|
@ -0,0 +1,4 @@
|
||||||
|
{
|
||||||
|
"Home": "Inicio",
|
||||||
|
"About": "Acerca de"
|
||||||
|
}
|
|
@ -4,5 +4,8 @@
|
||||||
"Contact": "Contact",
|
"Contact": "Contact",
|
||||||
"SoundBirder is an open-source web application to learn bird song identification. It is based on bird records from Xeno-Canto and data from eBird": "SoundBirder est une application web open-source qui permet d'apprendre à reconnaitre les chants d'oiseaux. Elle se base sur des enregistrements audios de Xeno-Canto et sur les données de répartitions d'eBird",
|
"SoundBirder is an open-source web application to learn bird song identification. It is based on bird records from Xeno-Canto and data from eBird": "SoundBirder est une application web open-source qui permet d'apprendre à reconnaitre les chants d'oiseaux. Elle se base sur des enregistrements audios de Xeno-Canto et sur les données de répartitions d'eBird",
|
||||||
"Author": "Auteur",
|
"Author": "Auteur",
|
||||||
"The project is made with ♥ by Samuel ORTION": "Ce projet est fait avec ♥ par Samuel ORTION"
|
"The project is made with ♥ by Samuel ORTION": "Ce projet est fait avec ♥ par Samuel ORTION",
|
||||||
|
"Correct!": "Correct!",
|
||||||
|
"Wrong!": "Incorrect!",
|
||||||
|
"It was a": "C'était un"
|
||||||
}
|
}
|
|
@ -11,6 +11,7 @@
|
||||||
"@unclesamulus/ebird-api": "^0.0.0",
|
"@unclesamulus/ebird-api": "^0.0.0",
|
||||||
"@unclesamulus/xeno-canto-api": "^0.0.0",
|
"@unclesamulus/xeno-canto-api": "^0.0.0",
|
||||||
"axios": "^0.27.2",
|
"axios": "^0.27.2",
|
||||||
|
"connect-redis": "^6.1.3",
|
||||||
"cookie-parser": "~1.4.4",
|
"cookie-parser": "~1.4.4",
|
||||||
"csurf": "^1.11.0",
|
"csurf": "^1.11.0",
|
||||||
"debug": "~2.6.9",
|
"debug": "~2.6.9",
|
||||||
|
@ -282,6 +283,14 @@
|
||||||
"node": ">= 0.8"
|
"node": ">= 0.8"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/connect-redis": {
|
||||||
|
"version": "6.1.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/connect-redis/-/connect-redis-6.1.3.tgz",
|
||||||
|
"integrity": "sha512-aaNluLlAn/3JPxRwdzw7lhvEoU6Enb+d83xnokUNhC9dktqBoawKWL+WuxinxvBLTz6q9vReTnUDnUslaz74aw==",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=12"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/constantinople": {
|
"node_modules/constantinople": {
|
||||||
"version": "4.0.1",
|
"version": "4.0.1",
|
||||||
"resolved": "https://registry.npmjs.org/constantinople/-/constantinople-4.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/constantinople/-/constantinople-4.0.1.tgz",
|
||||||
|
@ -1545,6 +1554,11 @@
|
||||||
"delayed-stream": "~1.0.0"
|
"delayed-stream": "~1.0.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"connect-redis": {
|
||||||
|
"version": "6.1.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/connect-redis/-/connect-redis-6.1.3.tgz",
|
||||||
|
"integrity": "sha512-aaNluLlAn/3JPxRwdzw7lhvEoU6Enb+d83xnokUNhC9dktqBoawKWL+WuxinxvBLTz6q9vReTnUDnUslaz74aw=="
|
||||||
|
},
|
||||||
"constantinople": {
|
"constantinople": {
|
||||||
"version": "4.0.1",
|
"version": "4.0.1",
|
||||||
"resolved": "https://registry.npmjs.org/constantinople/-/constantinople-4.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/constantinople/-/constantinople-4.0.1.tgz",
|
||||||
|
|
|
@ -9,6 +9,7 @@
|
||||||
"@unclesamulus/ebird-api": "^0.0.0",
|
"@unclesamulus/ebird-api": "^0.0.0",
|
||||||
"@unclesamulus/xeno-canto-api": "^0.0.0",
|
"@unclesamulus/xeno-canto-api": "^0.0.0",
|
||||||
"axios": "^0.27.2",
|
"axios": "^0.27.2",
|
||||||
|
"connect-redis": "^6.1.3",
|
||||||
"cookie-parser": "~1.4.4",
|
"cookie-parser": "~1.4.4",
|
||||||
"csurf": "^1.11.0",
|
"csurf": "^1.11.0",
|
||||||
"debug": "~2.6.9",
|
"debug": "~2.6.9",
|
||||||
|
|
|
@ -1,4 +1,3 @@
|
||||||
|
|
||||||
const API_VERSION = "0";
|
const API_VERSION = "0";
|
||||||
const API_URL = `/api/${API_VERSION}`;
|
const API_URL = `/api/${API_VERSION}`;
|
||||||
|
|
||||||
|
@ -30,11 +29,11 @@ function getQuizz(lat, lng) {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
function checkResponse(speciesId) {
|
function checkAnswer(speciesId) {
|
||||||
return query('game/check', {
|
return query(`game/check`, {
|
||||||
species: speciesId
|
species: speciesId
|
||||||
}).then(response => {
|
}).then(data => {
|
||||||
return response.data.correct
|
return data;
|
||||||
}).catch(error => {
|
}).catch(error => {
|
||||||
throw error;
|
throw error;
|
||||||
});
|
});
|
||||||
|
@ -42,7 +41,7 @@ function checkResponse(speciesId) {
|
||||||
|
|
||||||
const client = {
|
const client = {
|
||||||
getQuizz,
|
getQuizz,
|
||||||
checkResponse
|
checkAnswer
|
||||||
}
|
}
|
||||||
|
|
||||||
export default client;
|
export default client;
|
|
@ -19,7 +19,9 @@ function quizzStep() {
|
||||||
|
|
||||||
// Retrieve coordinates from former done geolocation (TODO: fix the need of cookie)
|
// Retrieve coordinates from former done geolocation (TODO: fix the need of cookie)
|
||||||
const coordinates = getCoordinates();
|
const coordinates = getCoordinates();
|
||||||
client.getQuizz(coordinates)
|
const [lat, lng] = coordinates;
|
||||||
|
console.log("Coordinates: " + `${lat}, ${lng}`);
|
||||||
|
client.getQuizz(lat, lng)
|
||||||
.then(quizz => {
|
.then(quizz => {
|
||||||
// Display the quizz
|
// Display the quizz
|
||||||
displayQuizz(quizz);
|
displayQuizz(quizz);
|
||||||
|
@ -38,12 +40,43 @@ function displayQuizz(quizz) {
|
||||||
proposal.classList.add('proposal');
|
proposal.classList.add('proposal');
|
||||||
let button = document.createElement('button');
|
let button = document.createElement('button');
|
||||||
button.classList.add('proposal-button');
|
button.classList.add('proposal-button');
|
||||||
button.value = sp.code;
|
button.value = sp.speciesCode;
|
||||||
button.innerText = sp.comName;
|
button.innerText = sp.comName;
|
||||||
proposal.appendChild(button);
|
proposal.appendChild(button);
|
||||||
proposals.appendChild(proposal);
|
proposals.appendChild(proposal);
|
||||||
|
button.addEventListener('click', verifyAnswer);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
function verifyAnswer(event) {
|
||||||
|
let answer = event.target.value;
|
||||||
|
client.checkAnswer(answer)
|
||||||
|
.then(data => {
|
||||||
|
let message_class;
|
||||||
|
if (data.correct)
|
||||||
|
message_class = 'success';
|
||||||
|
else
|
||||||
|
message_class = 'error';
|
||||||
|
displayResult(message_class, data.message, data.answer);
|
||||||
|
}).catch(error => {
|
||||||
|
console.log(error);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function displayResult(message_class, message, species) {
|
||||||
|
let result = document.querySelector('.game-results-step .message');
|
||||||
|
document.querySelector('.game-quizz-step').classList.toggle('none');
|
||||||
|
result.classList.remove('success', 'error');
|
||||||
|
result.classList.add(message_class);
|
||||||
|
result.innerText = message;
|
||||||
|
document.querySelector('.game-results-step').classList.remove('none');
|
||||||
|
document.querySelector('.game-results-step .restart-button').addEventListener('click', restart);
|
||||||
|
document.querySelector('.game-results-step .species .com').innerText = species.comName;
|
||||||
|
document.querySelector('.game-results-step .species .sci').innerText = species.sciName;
|
||||||
|
}
|
||||||
|
|
||||||
|
function restart() {
|
||||||
|
window.location.reload();
|
||||||
|
}
|
||||||
|
|
||||||
function game() {
|
function game() {
|
||||||
geolocationStep();
|
geolocationStep();
|
||||||
|
|
|
@ -7,7 +7,7 @@ body {
|
||||||
margin: 0;
|
margin: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
body > * {
|
body>* {
|
||||||
padding: 1em;
|
padding: 1em;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -54,12 +54,12 @@ main {
|
||||||
min-height: calc(100vh - 15em);
|
min-height: calc(100vh - 15em);
|
||||||
}
|
}
|
||||||
|
|
||||||
#map { height: 50vh; }
|
#map {
|
||||||
|
height: 50vh;
|
||||||
nav {
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
nav {}
|
||||||
|
|
||||||
nav li {
|
nav li {
|
||||||
list-style: none;
|
list-style: none;
|
||||||
}
|
}
|
||||||
|
@ -73,3 +73,7 @@ nav ul {
|
||||||
.none {
|
.none {
|
||||||
display: none;
|
display: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.species span {
|
||||||
|
margin-left: 1em;
|
||||||
|
}
|
|
@ -0,0 +1,17 @@
|
||||||
|
const redis = require('redis');
|
||||||
|
|
||||||
|
const redisHost = process.env.REDIS_HOST ? process.env.REDIS_HOST : 'localhost';
|
||||||
|
const redisPort = process.env.REDIS_PORT ? process.env.REDIS_PORT : 6379;
|
||||||
|
const url = `redis://${redisHost}:${redisPort}`;
|
||||||
|
const redisClient = redis.createClient({
|
||||||
|
url,
|
||||||
|
legacyMode: true
|
||||||
|
});
|
||||||
|
|
||||||
|
(async () => {
|
||||||
|
redisClient.connect()
|
||||||
|
})();
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
redisClient
|
||||||
|
}
|
|
@ -9,6 +9,6 @@ router.route('/game/quizz')
|
||||||
.get(apiController.game.quizz);
|
.get(apiController.game.quizz);
|
||||||
|
|
||||||
router.route('/game/check')
|
router.route('/game/check')
|
||||||
.post(apiController.game.check);
|
.get(apiController.game.check);
|
||||||
|
|
||||||
module.exports = router;
|
module.exports = router;
|
|
@ -12,7 +12,7 @@ function choices(array, number) {
|
||||||
if (result.includes(c)) {
|
if (result.includes(c)) {
|
||||||
i--;
|
i--;
|
||||||
} else {
|
} else {
|
||||||
result.push(choice(array));
|
result.push(c);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return result;
|
return result;
|
||||||
|
|
|
@ -8,9 +8,13 @@
|
||||||
.game-quizz-step.none
|
.game-quizz-step.none
|
||||||
ul.proposals
|
ul.proposals
|
||||||
audio
|
audio
|
||||||
.game-result-step.none
|
.game-results-step.none
|
||||||
p.result
|
p.message
|
||||||
button.button.restart-button.disabled
|
.species.answer
|
||||||
|
p #{ __('It was a') }
|
||||||
|
span.com
|
||||||
|
span.sci
|
||||||
|
button.button.restart-button
|
||||||
i(data-feather="repeat")
|
i(data-feather="repeat")
|
||||||
link(rel="stylesheet" href="/dist/leaflet/leaflet.css")
|
link(rel="stylesheet" href="/dist/leaflet/leaflet.css")
|
||||||
script(src="/dist/leaflet/leaflet.js")
|
script(src="/dist/leaflet/leaflet.js")
|
||||||
|
|
Loading…
Reference in New Issue