Compare commits
4 Commits
585e578e48
...
232c69bdde
Author | SHA1 | Date |
---|---|---|
Samuel Ortion | 232c69bdde | |
Samuel Ortion | 0498ea8541 | |
Samuel Ortion | d590582a85 | |
Samuel Ortion | 4bfa6cfe16 |
|
@ -0,0 +1,4 @@
|
|||
node_modules
|
||||
.git
|
||||
.gitignore
|
||||
docker
|
47
app.js
47
app.js
|
@ -1,3 +1,4 @@
|
|||
require('dotenv').config();
|
||||
const createError = require('http-errors');
|
||||
const express = require('express');
|
||||
const session = require('express-session');
|
||||
|
@ -24,14 +25,15 @@ app.use(cookieParser());
|
|||
app.use(express.static(path.join(__dirname, 'public')));
|
||||
|
||||
const sess = {
|
||||
secret: 'keyboard cat',
|
||||
resave: true,
|
||||
saveUninitialized: true
|
||||
secret: 'keyboard cat',
|
||||
resave: true,
|
||||
saveUninitialized: true,
|
||||
cookie: { secure: false }
|
||||
}
|
||||
|
||||
if (app.get('env') === 'production') {
|
||||
app.set('trust proxy', 1); // trust first proxy
|
||||
sess.cookie.secure = true; // serve secure cookies
|
||||
app.set('trust proxy', 1); // trust first proxy
|
||||
sess.cookie.secure = true; // serve secure cookies
|
||||
}
|
||||
|
||||
app.use(session(sess));
|
||||
|
@ -42,17 +44,28 @@ i18n.expressBind(app, {
|
|||
cookieName: 'locale'
|
||||
});
|
||||
|
||||
app.use(function (req, res, next) {
|
||||
req.i18n.setLocaleFromQuery();
|
||||
req.i18n.setLocaleFromCookie();
|
||||
// set locale from url prefix
|
||||
var rxLocale = /^\/(fr|en)/i;
|
||||
if (rxLocale.test(req.url)) {
|
||||
const arr = rxLocale.exec(req.url);
|
||||
const locale = arr[1];
|
||||
req.i18n.setLocale(locale);
|
||||
}
|
||||
if (req.cookies.locale === undefined) {
|
||||
res.cookie('locale', req.i18n.locale, { maxAge: 90000 });
|
||||
}
|
||||
// add extra logic
|
||||
next();
|
||||
});
|
||||
|
||||
app.use('/dist/leaflet', express.static('node_modules/leaflet/dist'));
|
||||
app.use('/dist/feather', express.static('node_modules/feather-icons/dist'));
|
||||
app.use('/dist/axios', express.static('node_modules/axios/dist'));
|
||||
|
||||
app.use(function(req, res, next) {
|
||||
req.i18n.setLocaleFromQuery();
|
||||
req.i18n.setLocaleFromCookie();
|
||||
next();
|
||||
});
|
||||
|
||||
app.use(function(req, res, next) {
|
||||
app.use(function (req, res, next) {
|
||||
res.header('Access-Control-Allow-Origin', 'http://localhost:3000');
|
||||
res.header(
|
||||
'Access-Control-Allow-Headers',
|
||||
|
@ -65,16 +78,18 @@ app.use('/api/0', apiRouter);
|
|||
|
||||
const csrfProtection = csrf({ cookie: true });
|
||||
app.use(csrfProtection);
|
||||
app.use('/', indexRouter);
|
||||
app.use('/auth', authRouter);
|
||||
app.use(/\/(fr|en)/i, indexRouter);
|
||||
app.use(/\/(fr|en)\/auth/i, authRouter);
|
||||
app.use("/", indexRouter);
|
||||
app.use("/auth", authRouter);
|
||||
|
||||
// catch 404 and forward to error handler
|
||||
app.use(function(req, res, next) {
|
||||
app.use(function (req, res, next) {
|
||||
next(createError(404));
|
||||
});
|
||||
|
||||
// error handler
|
||||
app.use(function(err, req, res, next) {
|
||||
app.use(function (err, req, res, next) {
|
||||
// set locals, only providing error in development
|
||||
res.locals.message = err.message;
|
||||
res.locals.error = req.app.get('env') === 'development' ? err : {};
|
||||
|
|
|
@ -1,23 +1,20 @@
|
|||
const debug = require('debug')('soundbirder:api');
|
||||
const debugResponses = require('debug')('soundbirder:api:responses');
|
||||
require('dotenv').config();
|
||||
const axios = require('axios');
|
||||
const eBird = require('@unclesamulus/ebird-api')(process.env.EBIRD_API_KEY);
|
||||
const XenoCanto = require('@unclesamulus/xeno-canto-api');
|
||||
const { choices, choice } = require('../utils/choices');
|
||||
const debug = require('debug')('soundbirder:api');
|
||||
const debugLocale = require('debug')('soundbirder:locale');
|
||||
const debugResponses = require('debug')('soundbirder:api:responses');
|
||||
const quizzController = require('./quizz');
|
||||
|
||||
const OPENCAGE_API_KEY = process.env.OPENCAGE_API_KEY;
|
||||
const OPENCAGE_API_URL = 'https://api.opencagedata.com/geocode/v1/json?q=<lat>+<lon>&key=<key>';
|
||||
const QUIZZ_SIZE = process.env.QUIZZ_SIZE ? process.env.QUIZZ_SIZE : 5;
|
||||
|
||||
function check(req, res) {
|
||||
|
||||
}
|
||||
|
||||
function quizz(req, res) {
|
||||
debug('Generating quizz');
|
||||
const { lat, lng } = req.body;
|
||||
generateQuizz({ lat, lng }, req.locale)
|
||||
const locale = req.i18n.locale;
|
||||
debugLocale("Locale:", locale);
|
||||
quizzController.generateQuizz({ lat, lng }, locale, QUIZZ_SIZE)
|
||||
.then(({ species, correct, audio }) => {
|
||||
req.session.correct = correct;
|
||||
res.json({ species, audio });
|
||||
|
@ -30,118 +27,11 @@ function quizz(req, res) {
|
|||
});
|
||||
}
|
||||
|
||||
async function generateQuizz(coordinates, locale) {
|
||||
const quizz = {}
|
||||
try {
|
||||
const speciesSelection = await getSpeciesSelection(coordinates);
|
||||
const speciesSelectionLocalized = await getLocalizedNames(speciesSelection, locale);
|
||||
quizz.species = await speciesSelectionLocalized;
|
||||
debug("Got species selection", quizz.species);
|
||||
const answer = await choice(speciesSelectionLocalized);
|
||||
debug("Got answer", answer);
|
||||
quizz.correct = answer.code;
|
||||
quizz.audio = await getAudio(answer.sciName);
|
||||
} catch (error) {
|
||||
debug("Error raised while generating quizz");
|
||||
console.error(error);
|
||||
}
|
||||
return quizz;
|
||||
}
|
||||
|
||||
async function getSpeciesSelection(coordinates) {
|
||||
const { lat, lng } = coordinates;
|
||||
const region = await getRegion(lat, lng);
|
||||
region.country_code = region.country_code.toUpperCase();
|
||||
// const regionCode = await getRegionCode(region);
|
||||
const regionCode = region.country_code;
|
||||
const speciesList = await getSpeciesList(regionCode);
|
||||
const speciesSelection = choices(speciesList, QUIZZ_SIZE);
|
||||
debug("Species proposals:", speciesSelection)
|
||||
return speciesSelection;
|
||||
}
|
||||
|
||||
function getHome(req, res) {
|
||||
res.render('api', {
|
||||
title: "SoundBirder api",
|
||||
version: 0
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
function getRegion(lat, lon) {
|
||||
const url = OPENCAGE_API_URL
|
||||
.replace('<lat>', lat)
|
||||
.replace('<lon>', lon)
|
||||
.replace('<key>', OPENCAGE_API_KEY);
|
||||
return axios.get(url).then(response => {
|
||||
const { results } = response.data;
|
||||
const { country_code, country, state, county } = results[0].components;
|
||||
return { country_code, country, state, county };
|
||||
}).catch(error => {
|
||||
throw error;
|
||||
});
|
||||
}
|
||||
|
||||
function getRegionCode({ country_code, country, state, county }) {
|
||||
return eBird.ref.region.list('subnational1', country_code)()
|
||||
.then(states => {
|
||||
console.log(states);
|
||||
const regionCode = states.find(region => region.name === state).code;
|
||||
return regionCode;
|
||||
}).catch(error => {
|
||||
throw error;
|
||||
});
|
||||
}
|
||||
|
||||
async function getLocalizedNames(speciesCodes, locale) {
|
||||
let names = [];
|
||||
for (let i = 0; i < speciesCodes.length; i++) {
|
||||
const code = speciesCodes[i];
|
||||
const { sciName, comName } = await getLocalizedName(code, locale);
|
||||
names.push({ code, sciName, comName });
|
||||
}
|
||||
return names;
|
||||
}
|
||||
|
||||
function getLocalizedName(speciesCode, locale) {
|
||||
return eBird.ref.taxonomy.ebird({
|
||||
fmt: 'json',
|
||||
locale: locale,
|
||||
species: speciesCode
|
||||
}).then(
|
||||
response => {
|
||||
const names = response[0];
|
||||
debug("Got localized species names");
|
||||
debugResponses(names);
|
||||
return names;
|
||||
}
|
||||
).catch(error => {
|
||||
throw error;
|
||||
});
|
||||
}
|
||||
|
||||
function getSpeciesList(regionCode) {
|
||||
return eBird.product.spplist.in(regionCode)()
|
||||
.then(species => {
|
||||
return species;
|
||||
})
|
||||
.catch(error => {
|
||||
throw error;
|
||||
});
|
||||
}
|
||||
|
||||
function getAudio(speciesScientificName) {
|
||||
return XenoCanto.search({
|
||||
name: speciesScientificName,
|
||||
quality: 'A'
|
||||
}).then(response => {
|
||||
debugResponses(response);
|
||||
const { recordings } = response;
|
||||
const randomRecord = choice(recordings);
|
||||
const audio = randomRecord.file;
|
||||
return audio;
|
||||
}).catch(error => {
|
||||
throw error;
|
||||
});
|
||||
}
|
||||
|
||||
|
|
|
@ -7,7 +7,10 @@ function indexPage(req, res) {
|
|||
}
|
||||
|
||||
function loginPage(req, res) {
|
||||
res.render('auth/login');
|
||||
res.render('auth/login', {
|
||||
title: req.i18n.__("SoundBirder - Login"),
|
||||
locale: req.i18n.locale
|
||||
});
|
||||
}
|
||||
|
||||
function login(req, res) {
|
||||
|
@ -19,11 +22,14 @@ function logout(req, res) {
|
|||
}
|
||||
|
||||
function registerPage(req, res) {
|
||||
res.render('auth/register');
|
||||
res.render('auth/register', {
|
||||
title: req.i18n.__("SoundBirder - Register"),
|
||||
locale: req.i18n.locale
|
||||
});
|
||||
}
|
||||
|
||||
function register(req, res) {
|
||||
|
||||
|
||||
}
|
||||
|
||||
function forgotPassword(req, res) {
|
||||
|
|
|
@ -0,0 +1,32 @@
|
|||
require('dotenv').config();
|
||||
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) {
|
||||
debug("Caching response", request);
|
||||
redisClient.set(request, JSON.stringify(response));
|
||||
}
|
||||
|
||||
async function getCached(request) {
|
||||
debug("Getting cached response", request);
|
||||
const cached = await redisClient.get(request);
|
||||
if (cached) {
|
||||
return JSON.parse(cached);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
cacheResponse,
|
||||
getCached
|
||||
}
|
|
@ -2,12 +2,16 @@
|
|||
function getIndex(req, res) {
|
||||
res.render('index', {
|
||||
title: 'SoundBirder',
|
||||
csrf_token: req.csrfToken()
|
||||
csrf_token: req.csrfToken(),
|
||||
locale: req.i18n.locale
|
||||
});
|
||||
}
|
||||
|
||||
function getAbout(req, res) {
|
||||
res.render('about', { title: 'About SoundBirder' });
|
||||
res.render('about', {
|
||||
title: 'About SoundBirder',
|
||||
locale: req.i18n.locale
|
||||
});
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
|
|
|
@ -0,0 +1,120 @@
|
|||
require('dotenv').config();
|
||||
const debug = require('debug')('soundbirder:api:quizz');
|
||||
const debugResponses = require('debug')('soundbirder:api:responses');
|
||||
const cache = require('./cache');
|
||||
const eBird = require('@unclesamulus/ebird-api')(process.env.EBIRD_API_KEY);
|
||||
const XenoCanto = require('@unclesamulus/xeno-canto-api');
|
||||
const { choices, choice } = require('../utils/choices');
|
||||
const { getRegion } = require('./region');
|
||||
|
||||
async function generateQuizz(coordinates, locale, size) {
|
||||
const quizz = {}
|
||||
try {
|
||||
const speciesSelection = await getSpeciesSelection(coordinates, size);
|
||||
debugResponses(`Species selection: ${speciesSelection}`);
|
||||
const speciesSelectionLocalized = await getLocalizedNames(speciesSelection, locale);
|
||||
debugResponses('Localized species selection:', speciesSelectionLocalized);
|
||||
quizz.species = speciesSelectionLocalized;
|
||||
debug("Got species selection", quizz.species);
|
||||
const answer = await choice(speciesSelectionLocalized);
|
||||
debug("Got answer", answer);
|
||||
quizz.correct = answer.speciesCode;
|
||||
quizz.audio = await getAudio(answer.sciName);
|
||||
debug("Got audio", quizz.audio);
|
||||
} catch (error) {
|
||||
debug("Error raised while generating quizz");
|
||||
console.error(error);
|
||||
}
|
||||
return quizz;
|
||||
}
|
||||
|
||||
async function getSpeciesSelection(coordinates, number) {
|
||||
const { lat, lng } = coordinates;
|
||||
const region = await getRegion(lat, lng);
|
||||
// const regionCode = await getRegionCode(region);
|
||||
const regionCode = region.country_code;
|
||||
const speciesList = await getSpeciesList(regionCode);
|
||||
const speciesSelection = choices(speciesList, number);
|
||||
debug("Species proposals:", speciesSelection)
|
||||
return speciesSelection;
|
||||
}
|
||||
|
||||
|
||||
async function getLocalizedNames(speciesCodes, locale) {
|
||||
let allNames = [];
|
||||
for (let i = 0; i < speciesCodes.length; i++) {
|
||||
const code = speciesCodes[i];
|
||||
let flag = false;
|
||||
try {
|
||||
names = await cache.getCached(`${code}-${locale}`);
|
||||
if (names) {
|
||||
allNames.push(names);
|
||||
flag = true;
|
||||
}
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
}
|
||||
if (!flag) {
|
||||
try {
|
||||
const names = { speciesCode, sciName, comName } = await getLocalizedName(code, locale);
|
||||
cache.cacheResponse(`${code}-${locale}`, names);
|
||||
allNames.push(names);
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
}
|
||||
}
|
||||
}
|
||||
return allNames;
|
||||
}
|
||||
|
||||
function getLocalizedName(speciesCode, locale) {
|
||||
return eBird.ref.taxonomy.ebird({
|
||||
fmt: 'json',
|
||||
locale: locale,
|
||||
species: speciesCode
|
||||
}).then(
|
||||
response => {
|
||||
const names = response[0];
|
||||
debug("Got localized species names");
|
||||
debugResponses(names);
|
||||
return names;
|
||||
}
|
||||
).catch(error => {
|
||||
throw error;
|
||||
});
|
||||
}
|
||||
|
||||
async function getSpeciesList(regionCode) {
|
||||
const cached = await cache.getCached(`spplist-${regionCode}`);
|
||||
if (cached) {
|
||||
return cached;
|
||||
} else {
|
||||
return eBird.product.spplist.in(regionCode)()
|
||||
.then(species => {
|
||||
cache.cacheResponse(`spplist-${regionCode}`, species);
|
||||
return species;
|
||||
})
|
||||
.catch(error => {
|
||||
throw error;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
function getAudio(speciesScientificName) {
|
||||
return XenoCanto.search({
|
||||
name: speciesScientificName,
|
||||
quality: 'A'
|
||||
}).then(response => {
|
||||
debugResponses(response);
|
||||
const { recordings } = response;
|
||||
const randomRecord = choice(recordings);
|
||||
const audio = randomRecord.file;
|
||||
return audio;
|
||||
}).catch(error => {
|
||||
throw error;
|
||||
});
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
generateQuizz
|
||||
}
|
|
@ -0,0 +1,46 @@
|
|||
require('dotenv').config();
|
||||
const debug = require('debug')('soundbirder:api:region');
|
||||
const cache = require('./cache');
|
||||
const axios = require('axios');
|
||||
|
||||
const OPENCAGE_API_KEY = process.env.OPENCAGE_API_KEY;
|
||||
const OPENCAGE_API_URL = 'https://api.opencagedata.com/geocode/v1/json?q=<lat>+<lon>&key=<key>';
|
||||
|
||||
async function getRegion(lat, lon) {
|
||||
const url = OPENCAGE_API_URL
|
||||
.replace('<lat>', lat)
|
||||
.replace('<lon>', lon)
|
||||
.replace('<key>', OPENCAGE_API_KEY);
|
||||
try {
|
||||
const cached = await cache.getCached(`region-${lat}-${lon}`);
|
||||
if (cached) {
|
||||
return cached;
|
||||
}
|
||||
} catch (error) {
|
||||
throw error;
|
||||
}
|
||||
return axios.get(url).then(response => {
|
||||
const { results } = response.data;
|
||||
const region = results[0].components;
|
||||
region.country_code = region.country_code.toUpperCase();
|
||||
cache.cacheResponse(url, region);
|
||||
return { country_code, country, state, county } = region;
|
||||
}).catch(error => {
|
||||
throw error;
|
||||
});
|
||||
}
|
||||
|
||||
function getRegionCode({ country_code, country, state, county }) {
|
||||
return eBird.ref.region.list('subnational1', country_code)()
|
||||
.then(states => {
|
||||
console.log(states);
|
||||
const regionCode = states.find(region => region.name === state).code;
|
||||
return regionCode;
|
||||
}).catch(error => {
|
||||
throw error;
|
||||
});
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
getRegion,
|
||||
}
|
|
@ -0,0 +1,28 @@
|
|||
version: '3.9'
|
||||
|
||||
services:
|
||||
express:
|
||||
container_name: soundbirder_express
|
||||
build:
|
||||
context: .
|
||||
dockerfile: ./docker/express/Dockerfile
|
||||
environment:
|
||||
- NODE_ENV=production
|
||||
- REDIS_HOST=${REDIS_HOST:-soundbirder_redis}
|
||||
- REDIS_PORT=${REDIS_PORT:-6379}
|
||||
ports:
|
||||
- "${EXPRESS_PORT:-3000}:3000"
|
||||
networks:
|
||||
- soundbirder_network
|
||||
depends_on:
|
||||
- redis
|
||||
|
||||
redis:
|
||||
container_name: soundbirder_redis
|
||||
image: redis
|
||||
networks:
|
||||
- soundbirder_network
|
||||
|
||||
networks:
|
||||
soundbirder_network:
|
||||
driver: bridge
|
|
@ -0,0 +1,13 @@
|
|||
FROM node:16.17.0
|
||||
|
||||
WORKDIR /usr/src/app
|
||||
|
||||
COPY package*.json .
|
||||
|
||||
RUN npm install
|
||||
RUN npm ci --only=production
|
||||
|
||||
COPY . .
|
||||
|
||||
EXPOSE 3000
|
||||
CMD [ "./bin/www" ]
|
|
@ -1 +1,4 @@
|
|||
{}
|
||||
{
|
||||
"Game": "Game",
|
||||
"About": "About"
|
||||
}
|
|
@ -1,4 +1,4 @@
|
|||
{
|
||||
"Home": "Home",
|
||||
"About": "About"
|
||||
"Home": "Inicio",
|
||||
"About": "Acerca de",
|
||||
}
|
|
@ -2,7 +2,7 @@
|
|||
"Game": "Jeu",
|
||||
"About": "À propos",
|
||||
"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 les enregistrements audio de Xeno-Canto est 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",
|
||||
"The project is made with ♥ by Samuel ORTION": "Ce projet est fait avec ♥ par Samuel ORTION"
|
||||
}
|
|
@ -23,7 +23,8 @@
|
|||
"leaflet": "^1.8.0",
|
||||
"lodash": "^4.17.21",
|
||||
"morgan": "~1.9.1",
|
||||
"pug": "^3.0.2"
|
||||
"pug": "^3.0.2",
|
||||
"redis": "^4.3.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@babel/helper-string-parser": {
|
||||
|
@ -66,6 +67,59 @@
|
|||
"node": ">=6.9.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@redis/bloom": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/@redis/bloom/-/bloom-1.0.2.tgz",
|
||||
"integrity": "sha512-EBw7Ag1hPgFzdznK2PBblc1kdlj5B5Cw3XwI9/oG7tSn85/HKy3X9xHy/8tm/eNXJYHLXHJL/pkwBpFMVVefkw==",
|
||||
"peerDependencies": {
|
||||
"@redis/client": "^1.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@redis/client": {
|
||||
"version": "1.3.0",
|
||||
"resolved": "https://registry.npmjs.org/@redis/client/-/client-1.3.0.tgz",
|
||||
"integrity": "sha512-XCFV60nloXAefDsPnYMjHGtvbtHR8fV5Om8cQ0JYqTNbWcQo/4AryzJ2luRj4blveWazRK/j40gES8M7Cp6cfQ==",
|
||||
"dependencies": {
|
||||
"cluster-key-slot": "1.1.0",
|
||||
"generic-pool": "3.8.2",
|
||||
"yallist": "4.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=14"
|
||||
}
|
||||
},
|
||||
"node_modules/@redis/graph": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/@redis/graph/-/graph-1.0.1.tgz",
|
||||
"integrity": "sha512-oDE4myMCJOCVKYMygEMWuriBgqlS5FqdWerikMoJxzmmTUErnTRRgmIDa2VcgytACZMFqpAOWDzops4DOlnkfQ==",
|
||||
"peerDependencies": {
|
||||
"@redis/client": "^1.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@redis/json": {
|
||||
"version": "1.0.3",
|
||||
"resolved": "https://registry.npmjs.org/@redis/json/-/json-1.0.3.tgz",
|
||||
"integrity": "sha512-4X0Qv0BzD9Zlb0edkUoau5c1bInWSICqXAGrpwEltkncUwcxJIGEcVryZhLgb0p/3PkKaLIWkjhHRtLe9yiA7Q==",
|
||||
"peerDependencies": {
|
||||
"@redis/client": "^1.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@redis/search": {
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/@redis/search/-/search-1.1.0.tgz",
|
||||
"integrity": "sha512-NyFZEVnxIJEybpy+YskjgOJRNsfTYqaPbK/Buv6W2kmFNaRk85JiqjJZA5QkRmWvGbyQYwoO5QfDi2wHskKrQQ==",
|
||||
"peerDependencies": {
|
||||
"@redis/client": "^1.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@redis/time-series": {
|
||||
"version": "1.0.3",
|
||||
"resolved": "https://registry.npmjs.org/@redis/time-series/-/time-series-1.0.3.tgz",
|
||||
"integrity": "sha512-OFp0q4SGrTH0Mruf6oFsHGea58u8vS/iI5+NpYdicaM+7BgqBZH8FFvNZ8rYYLrUO/QRqMq72NpXmxLVNcdmjA==",
|
||||
"peerDependencies": {
|
||||
"@redis/client": "^1.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@unclesamulus/ebird-api": {
|
||||
"version": "0.0.0",
|
||||
"resolved": "https://registry.npmjs.org/@unclesamulus/ebird-api/-/ebird-api-0.0.0.tgz",
|
||||
|
@ -209,6 +263,14 @@
|
|||
"resolved": "https://registry.npmjs.org/classnames/-/classnames-2.3.1.tgz",
|
||||
"integrity": "sha512-OlQdbZ7gLfGarSqxesMesDa5uz7KFbID8Kpq/SxIoNGDqY8lSYs0D+hhtBXhcdB3rcbXArFr7vlHheLk1voeNA=="
|
||||
},
|
||||
"node_modules/cluster-key-slot": {
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/cluster-key-slot/-/cluster-key-slot-1.1.0.tgz",
|
||||
"integrity": "sha512-2Nii8p3RwAPiFwsnZvukotvow2rIHM+yQ6ZcBXGHdniadkYGZYiGmkHJIbZPIV9nfv7m/U1IPMVVcAhoWFeklw==",
|
||||
"engines": {
|
||||
"node": ">=0.10.0"
|
||||
}
|
||||
},
|
||||
"node_modules/combined-stream": {
|
||||
"version": "1.0.8",
|
||||
"resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz",
|
||||
|
@ -596,6 +658,14 @@
|
|||
"resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz",
|
||||
"integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A=="
|
||||
},
|
||||
"node_modules/generic-pool": {
|
||||
"version": "3.8.2",
|
||||
"resolved": "https://registry.npmjs.org/generic-pool/-/generic-pool-3.8.2.tgz",
|
||||
"integrity": "sha512-nGToKy6p3PAbYQ7p1UlWl6vSPwfwU6TMSWK7TTu+WUY4ZjyZQGniGGt2oNVvyNSpyZYSB43zMXVLcBm08MTMkg==",
|
||||
"engines": {
|
||||
"node": ">= 4"
|
||||
}
|
||||
},
|
||||
"node_modules/get-intrinsic": {
|
||||
"version": "1.1.2",
|
||||
"resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.1.2.tgz",
|
||||
|
@ -1063,6 +1133,19 @@
|
|||
"node": ">= 0.8"
|
||||
}
|
||||
},
|
||||
"node_modules/redis": {
|
||||
"version": "4.3.0",
|
||||
"resolved": "https://registry.npmjs.org/redis/-/redis-4.3.0.tgz",
|
||||
"integrity": "sha512-RXRUor0iU1vizu4viHoUyLpe1ZO/RngZp0V9DyXBHTI+7tC7rEz6Wzn4Sv9v0tTJeqGAzdJ+q5YVbNKKQ5hX9A==",
|
||||
"dependencies": {
|
||||
"@redis/bloom": "1.0.2",
|
||||
"@redis/client": "1.3.0",
|
||||
"@redis/graph": "1.0.1",
|
||||
"@redis/json": "1.0.3",
|
||||
"@redis/search": "1.1.0",
|
||||
"@redis/time-series": "1.0.3"
|
||||
}
|
||||
},
|
||||
"node_modules/resolve": {
|
||||
"version": "1.22.1",
|
||||
"resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.1.tgz",
|
||||
|
@ -1257,6 +1340,11 @@
|
|||
"engines": {
|
||||
"node": ">= 10.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/yallist": {
|
||||
"version": "4.0.0",
|
||||
"resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz",
|
||||
"integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A=="
|
||||
}
|
||||
},
|
||||
"dependencies": {
|
||||
|
@ -1285,6 +1373,46 @@
|
|||
"to-fast-properties": "^2.0.0"
|
||||
}
|
||||
},
|
||||
"@redis/bloom": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/@redis/bloom/-/bloom-1.0.2.tgz",
|
||||
"integrity": "sha512-EBw7Ag1hPgFzdznK2PBblc1kdlj5B5Cw3XwI9/oG7tSn85/HKy3X9xHy/8tm/eNXJYHLXHJL/pkwBpFMVVefkw==",
|
||||
"requires": {}
|
||||
},
|
||||
"@redis/client": {
|
||||
"version": "1.3.0",
|
||||
"resolved": "https://registry.npmjs.org/@redis/client/-/client-1.3.0.tgz",
|
||||
"integrity": "sha512-XCFV60nloXAefDsPnYMjHGtvbtHR8fV5Om8cQ0JYqTNbWcQo/4AryzJ2luRj4blveWazRK/j40gES8M7Cp6cfQ==",
|
||||
"requires": {
|
||||
"cluster-key-slot": "1.1.0",
|
||||
"generic-pool": "3.8.2",
|
||||
"yallist": "4.0.0"
|
||||
}
|
||||
},
|
||||
"@redis/graph": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/@redis/graph/-/graph-1.0.1.tgz",
|
||||
"integrity": "sha512-oDE4myMCJOCVKYMygEMWuriBgqlS5FqdWerikMoJxzmmTUErnTRRgmIDa2VcgytACZMFqpAOWDzops4DOlnkfQ==",
|
||||
"requires": {}
|
||||
},
|
||||
"@redis/json": {
|
||||
"version": "1.0.3",
|
||||
"resolved": "https://registry.npmjs.org/@redis/json/-/json-1.0.3.tgz",
|
||||
"integrity": "sha512-4X0Qv0BzD9Zlb0edkUoau5c1bInWSICqXAGrpwEltkncUwcxJIGEcVryZhLgb0p/3PkKaLIWkjhHRtLe9yiA7Q==",
|
||||
"requires": {}
|
||||
},
|
||||
"@redis/search": {
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/@redis/search/-/search-1.1.0.tgz",
|
||||
"integrity": "sha512-NyFZEVnxIJEybpy+YskjgOJRNsfTYqaPbK/Buv6W2kmFNaRk85JiqjJZA5QkRmWvGbyQYwoO5QfDi2wHskKrQQ==",
|
||||
"requires": {}
|
||||
},
|
||||
"@redis/time-series": {
|
||||
"version": "1.0.3",
|
||||
"resolved": "https://registry.npmjs.org/@redis/time-series/-/time-series-1.0.3.tgz",
|
||||
"integrity": "sha512-OFp0q4SGrTH0Mruf6oFsHGea58u8vS/iI5+NpYdicaM+7BgqBZH8FFvNZ8rYYLrUO/QRqMq72NpXmxLVNcdmjA==",
|
||||
"requires": {}
|
||||
},
|
||||
"@unclesamulus/ebird-api": {
|
||||
"version": "0.0.0",
|
||||
"resolved": "https://registry.npmjs.org/@unclesamulus/ebird-api/-/ebird-api-0.0.0.tgz",
|
||||
|
@ -1404,6 +1532,11 @@
|
|||
"resolved": "https://registry.npmjs.org/classnames/-/classnames-2.3.1.tgz",
|
||||
"integrity": "sha512-OlQdbZ7gLfGarSqxesMesDa5uz7KFbID8Kpq/SxIoNGDqY8lSYs0D+hhtBXhcdB3rcbXArFr7vlHheLk1voeNA=="
|
||||
},
|
||||
"cluster-key-slot": {
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/cluster-key-slot/-/cluster-key-slot-1.1.0.tgz",
|
||||
"integrity": "sha512-2Nii8p3RwAPiFwsnZvukotvow2rIHM+yQ6ZcBXGHdniadkYGZYiGmkHJIbZPIV9nfv7m/U1IPMVVcAhoWFeklw=="
|
||||
},
|
||||
"combined-stream": {
|
||||
"version": "1.0.8",
|
||||
"resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz",
|
||||
|
@ -1692,6 +1825,11 @@
|
|||
"resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz",
|
||||
"integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A=="
|
||||
},
|
||||
"generic-pool": {
|
||||
"version": "3.8.2",
|
||||
"resolved": "https://registry.npmjs.org/generic-pool/-/generic-pool-3.8.2.tgz",
|
||||
"integrity": "sha512-nGToKy6p3PAbYQ7p1UlWl6vSPwfwU6TMSWK7TTu+WUY4ZjyZQGniGGt2oNVvyNSpyZYSB43zMXVLcBm08MTMkg=="
|
||||
},
|
||||
"get-intrinsic": {
|
||||
"version": "1.1.2",
|
||||
"resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.1.2.tgz",
|
||||
|
@ -2074,6 +2212,19 @@
|
|||
"unpipe": "1.0.0"
|
||||
}
|
||||
},
|
||||
"redis": {
|
||||
"version": "4.3.0",
|
||||
"resolved": "https://registry.npmjs.org/redis/-/redis-4.3.0.tgz",
|
||||
"integrity": "sha512-RXRUor0iU1vizu4viHoUyLpe1ZO/RngZp0V9DyXBHTI+7tC7rEz6Wzn4Sv9v0tTJeqGAzdJ+q5YVbNKKQ5hX9A==",
|
||||
"requires": {
|
||||
"@redis/bloom": "1.0.2",
|
||||
"@redis/client": "1.3.0",
|
||||
"@redis/graph": "1.0.1",
|
||||
"@redis/json": "1.0.3",
|
||||
"@redis/search": "1.1.0",
|
||||
"@redis/time-series": "1.0.3"
|
||||
}
|
||||
},
|
||||
"resolve": {
|
||||
"version": "1.22.1",
|
||||
"resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.1.tgz",
|
||||
|
@ -2217,6 +2368,11 @@
|
|||
"assert-never": "^1.2.1",
|
||||
"babel-walk": "3.0.0-canary-5"
|
||||
}
|
||||
},
|
||||
"yallist": {
|
||||
"version": "4.0.0",
|
||||
"resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz",
|
||||
"integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A=="
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -21,6 +21,7 @@
|
|||
"leaflet": "^1.8.0",
|
||||
"lodash": "^4.17.21",
|
||||
"morgan": "~1.9.1",
|
||||
"pug": "^3.0.2"
|
||||
"pug": "^3.0.2",
|
||||
"redis": "^4.3.0"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -24,7 +24,6 @@ async function query(endpoint, params) {
|
|||
function getQuizz(lat, lng) {
|
||||
return query('game/quizz', { lat, lng })
|
||||
.then(data => {
|
||||
console.log(data);
|
||||
return data;
|
||||
}).catch(error => {
|
||||
throw error;
|
||||
|
|
|
@ -17,7 +17,7 @@ function updateLocationCookies([lat, lng]) {
|
|||
}
|
||||
|
||||
function geolocationHandler() {
|
||||
|
||||
|
||||
let location = getCoordinates();
|
||||
let message = document.querySelector('.message');
|
||||
// Init map
|
||||
|
@ -26,7 +26,10 @@ function geolocationHandler() {
|
|||
attribution: '© <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a>'
|
||||
}).addTo(map);
|
||||
|
||||
let marker = undefined;
|
||||
let marker = L.marker(location, { draggable: true }).addTo(map);
|
||||
marker.on('dragend', updateMarker);
|
||||
marker.addTo(map);
|
||||
|
||||
// Get geolocation
|
||||
function getLocation() {
|
||||
if (navigator.geolocation) {
|
||||
|
@ -38,11 +41,7 @@ function geolocationHandler() {
|
|||
|
||||
function setLocation(position) {
|
||||
location = [position.coords.latitude, position.coords.longitude];
|
||||
if (marker === undefined) {
|
||||
marker = new L.marker(location, { draggable: 'true' });
|
||||
marker.on('dragend', updateMarker);
|
||||
marker.addTo(map);
|
||||
} else {
|
||||
if (marker !== undefined) {
|
||||
marker.setLatLng(location);
|
||||
}
|
||||
map.setView(location, 15);
|
||||
|
|
|
@ -10,11 +10,12 @@ html
|
|||
header
|
||||
h1= title
|
||||
nav
|
||||
- var i18n_prefix = locale ? '/' + locale : ''
|
||||
ul
|
||||
li
|
||||
a(href="/") #{ __("Game") }
|
||||
a(href=`${i18n_prefix}/`) #{ __("Game") }
|
||||
li
|
||||
a(href="/about") #{ __("About") }
|
||||
a(href=`${i18n_prefix}/about`) #{ __("About") }
|
||||
main
|
||||
block content
|
||||
footer
|
||||
|
|
Loading…
Reference in New Issue