This commit is contained in:
Samuel Ortion 2024-02-04 07:07:37 +01:00
parent db316e45e3
commit 079556a061
15 changed files with 782 additions and 72 deletions

2
app.js
View File

@ -74,7 +74,7 @@ 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('/api/0', apiRouter);
app.use('/api/', apiRouter);
const csrfProtection = csrf({ cookie: true });
app.use(csrfProtection);

View File

@ -7,13 +7,6 @@ const { getRegion } = require('./region');
const QUIZZ_SIZE = process.env.QUIZZ_SIZE ? process.env.QUIZZ_SIZE : 5;
function getHome(req, res) {
res.render('api', {
title: "SoundBirder API",
version: 0
});
}
async function region(req, res) {
let {lat, lon} = req.query;
lat = parseFloat(lat);
@ -27,6 +20,7 @@ async function region(req, res) {
async function quizz(req, res) {
debug('Generating quizz');
const { region } = req.query;
req.session.region = region;
// debug(`Coordinates: ${lat}, ${lng}`);
const locale = req.i18n.locale;
debugLocale("Locale:", locale);
@ -77,13 +71,29 @@ function check(req, res) {
res.json(result);
}
async function birdSpeciesCompletion(req, res) {
const { q } = req.query;
const locale = req.i18n.locale;
const region = req.session.region;
if (region === undefined) {
res.status(400).json({error: "Your session is not associated with any region yet"});
return;
}
if (q === undefined) {
res.status(400).json({error: "No query provided"});
return;
}
let speciesCompletion = await quizzController.getSpeciesCompletion(region, locale, q);
res.json(speciesCompletion);
}
const game = {
check,
quizz
quizz,
birdSpeciesCompletion
}
module.exports = {
getHome,
game,
region
}

View File

@ -66,37 +66,17 @@ async function getSpeciesSelection(region, number) {
const regionCode = region;
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) {
let names;
try {
names = { speciesCode, sciName, comName } = await getLocalizedName(code, locale);
cache.cacheResponse(`${code}-${locale}`, names);
allNames.push(names);
} catch (error) {
console.error(error);
}
}
const localizedNames = [];
for (const speciesCode of speciesCodes) {
const localized = await getLocalizedName(speciesCode, locale);
localizedNames.push(localized);
}
return allNames;
return localizedNames;
}
function getLocalizedName(speciesCode, locale) {
@ -127,7 +107,6 @@ async function getSpeciesList(regionCode) {
} else {
return eBird.product.spplist.in(regionCode)()
.then(species => {
species = species.filter((sp) => !sp.includes(" x "));
cache.cacheResponse(`spplist-${regionCode}`, species);
return species;
})
@ -142,7 +121,6 @@ function getAudio(speciesScientificName) {
name: speciesScientificName,
quality: 'A'
}).then(response => {
debugResponses(response);
const { recordings } = response;
const randomRecord = choice(recordings);
const audio = randomRecord.file;
@ -152,8 +130,23 @@ function getAudio(speciesScientificName) {
});
}
async function getSpeciesCompletion(region, locale, term) {
let speciesList = await getSpeciesList(region);
let localizedSpeciesList = await getLocalizedNames(speciesList, locale);
let termLower = term.toLowerCase();
let speciesCompletion = localizedSpeciesList.filter(speciesNames => {
let { comName, sciName } = speciesNames;
let name = comName.toLowerCase();
let sci = sciName.toLowerCase();
return name.includes(termLower) || sci.includes(termLower);
});
return speciesCompletion;
}
module.exports = {
generateQuizz,
getQuizzCached,
getSpeciesCompletion,
cacheQuizz
}

View File

@ -13,5 +13,6 @@
"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.",
"Author": "Author",
"The project is made with ♥ by Samuel Ortion.": "The project is made with ♥ by Samuel Ortion.",
"Correct!": "Correct!"
"Correct!": "Correct!",
"Set": "Set"
}

View File

@ -14,5 +14,7 @@
"Geolocalize yourself on the map": "Geolocalize yourself on the map",
"Start the game": "Start the game",
"Start a new quizz": "Start a new quizz",
"Return to the map": "Return to the map"
"Return to the map": "Return to the map",
"About SoundBirder": "About SoundBirder",
"Set": "Set"
}

428
package-lock.json generated
View File

@ -27,6 +27,8 @@
"leaflet": "^1.8.0",
"lodash": "^4.17.21",
"morgan": "~1.9.1",
"postcss": "^8.4.33",
"postcss-cli": "^11.0.0",
"pug": "^3.0.2",
"redis": "^4.3.0",
"tailwindcss": "^3.1.8"
@ -216,6 +218,17 @@
"@redis/client": "^1.0.0"
}
},
"node_modules/@sindresorhus/merge-streams": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/@sindresorhus/merge-streams/-/merge-streams-1.0.0.tgz",
"integrity": "sha512-rUV5WyJrJLoloD4NDN1V1+LDMDWOa4OTsT4yYJwQNpTU6FWxkxHpL7eu4w+DmiH8x/EAM1otkPE1+LaspIbplw==",
"engines": {
"node": ">=18"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/@unclesamulus/ebird-api": {
"version": "0.0.0",
"resolved": "https://registry.npmjs.org/@unclesamulus/ebird-api/-/ebird-api-0.0.0.tgz",
@ -255,6 +268,28 @@
"node": ">=0.4.0"
}
},
"node_modules/ansi-regex": {
"version": "5.0.1",
"resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz",
"integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==",
"engines": {
"node": ">=8"
}
},
"node_modules/ansi-styles": {
"version": "4.3.0",
"resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz",
"integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==",
"dependencies": {
"color-convert": "^2.0.1"
},
"engines": {
"node": ">=8"
},
"funding": {
"url": "https://github.com/chalk/ansi-styles?sponsor=1"
}
},
"node_modules/any-promise": {
"version": "1.3.0",
"resolved": "https://registry.npmjs.org/any-promise/-/any-promise-1.3.0.tgz",
@ -536,9 +571,9 @@
}
},
"node_modules/caniuse-lite": {
"version": "1.0.30001517",
"resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001517.tgz",
"integrity": "sha512-Vdhm5S11DaFVLlyiKu4hiUTkpZu+y1KA/rZZqVQfOD5YdDT/eQKlkt7NaE0WGOFgX32diqt9MiP9CAiFeRklaA==",
"version": "1.0.30001579",
"resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001579.tgz",
"integrity": "sha512-u5AUVkixruKHJjw/pj9wISlcMpgFWzSrczLZbrqBSxukQixmg0SJ5sZTpvaFvxU0HoQKd4yoyAogyrAz9pzJnA==",
"funding": [
{
"type": "opencollective",
@ -604,6 +639,19 @@
"resolved": "https://registry.npmjs.org/classnames/-/classnames-2.3.2.tgz",
"integrity": "sha512-CSbhY4cFEJRe6/GQzIk5qXZ4Jeg5pcsP7b5peFSDpffpe1cqjASH/n9UTjBwOp6XpMSTwQ8Za2K5V02ueA7Tmw=="
},
"node_modules/cliui": {
"version": "8.0.1",
"resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz",
"integrity": "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==",
"dependencies": {
"string-width": "^4.2.0",
"strip-ansi": "^6.0.1",
"wrap-ansi": "^7.0.0"
},
"engines": {
"node": ">=12"
}
},
"node_modules/cluster-key-slot": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/cluster-key-slot/-/cluster-key-slot-1.1.2.tgz",
@ -617,6 +665,22 @@
"resolved": "git+ssh://git@github.com/hlaw/codegrid-js.git#38decfe10dced9006c6b722cc403d7c1217abe98",
"license": "WTFPL"
},
"node_modules/color-convert": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz",
"integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==",
"dependencies": {
"color-name": "~1.1.4"
},
"engines": {
"node": ">=7.0.0"
}
},
"node_modules/color-name": {
"version": "1.1.4",
"resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz",
"integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA=="
},
"node_modules/combined-stream": {
"version": "1.0.8",
"resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz",
@ -835,6 +899,14 @@
"node": ">= 0.6"
}
},
"node_modules/dependency-graph": {
"version": "0.11.0",
"resolved": "https://registry.npmjs.org/dependency-graph/-/dependency-graph-0.11.0.tgz",
"integrity": "sha512-JeMq7fEshyepOWDfcfHK06N3MhyPhz++vtqWhMT5O9A3K42rdsEDpfdVqjaqaAhsw6a+ZqeDvQVtD0hFHQWrzg==",
"engines": {
"node": ">= 0.6.0"
}
},
"node_modules/destroy": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/destroy/-/destroy-1.2.0.tgz",
@ -880,6 +952,11 @@
"resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.477.tgz",
"integrity": "sha512-shUVy6Eawp33dFBFIoYbIwLHrX0IZ857AlH9ug2o4rvbWmpaCUdBpQ5Zw39HRrfzAFm4APJE9V+E2A/WB0YqJw=="
},
"node_modules/emoji-regex": {
"version": "8.0.0",
"resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz",
"integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A=="
},
"node_modules/encodeurl": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz",
@ -1091,9 +1168,9 @@
}
},
"node_modules/fast-glob": {
"version": "3.3.1",
"resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.1.tgz",
"integrity": "sha512-kNFPyjhh5cKjrUltxs+wFx+ZkbRaxxmZ+X0ZU31SOsxCEtP9VPgtq2teZw1DebupL5GmDaNQ6yKMMVcM41iqDg==",
"version": "3.3.2",
"resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.2.tgz",
"integrity": "sha512-oX2ruAFQwf/Orj8m737Y5adxDQO0LAB7/S5MnxCdTNDd4p6BsyIVsv9JQsATbTSq8KHRpLwIHbVlUNatxd+1Ow==",
"dependencies": {
"@nodelib/fs.stat": "^2.0.2",
"@nodelib/fs.walk": "^1.2.3",
@ -1240,6 +1317,19 @@
"node": ">= 0.6"
}
},
"node_modules/fs-extra": {
"version": "11.2.0",
"resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-11.2.0.tgz",
"integrity": "sha512-PmDi3uwK5nFuXh7XDTlVnS17xJS7vW36is2+w3xcv8SVxiB4NyATf4ctkVY5bkSjX0Y4nbvZCq1/EjtEyr9ktw==",
"dependencies": {
"graceful-fs": "^4.2.0",
"jsonfile": "^6.0.1",
"universalify": "^2.0.0"
},
"engines": {
"node": ">=14.14"
}
},
"node_modules/fs.realpath": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz",
@ -1279,6 +1369,14 @@
"just-clone": "^6.2.0"
}
},
"node_modules/get-caller-file": {
"version": "2.0.5",
"resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz",
"integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==",
"engines": {
"node": "6.* || 8.* || >= 10.*"
}
},
"node_modules/get-intrinsic": {
"version": "1.2.1",
"resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.1.tgz",
@ -1293,6 +1391,17 @@
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/get-stdin": {
"version": "9.0.0",
"resolved": "https://registry.npmjs.org/get-stdin/-/get-stdin-9.0.0.tgz",
"integrity": "sha512-dVKBjfWisLAicarI2Sf+JuBE/DghV4UzNAVe9yhEJuzeREd3JhOTE9cUaJTeSa77fsbQUK3pcOpJfM59+VKZaA==",
"engines": {
"node": ">=12"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/glob": {
"version": "7.1.6",
"resolved": "https://registry.npmjs.org/glob/-/glob-7.1.6.tgz",
@ -1323,6 +1432,30 @@
"node": ">=10.13.0"
}
},
"node_modules/globby": {
"version": "14.0.0",
"resolved": "https://registry.npmjs.org/globby/-/globby-14.0.0.tgz",
"integrity": "sha512-/1WM/LNHRAOH9lZta77uGbq0dAEQM+XjNesWwhlERDVenqothRbnzTrL3/LrIoEPPjeUHC3vrS6TwoyxeHs7MQ==",
"dependencies": {
"@sindresorhus/merge-streams": "^1.0.0",
"fast-glob": "^3.3.2",
"ignore": "^5.2.4",
"path-type": "^5.0.0",
"slash": "^5.1.0",
"unicorn-magic": "^0.1.0"
},
"engines": {
"node": ">=18"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/graceful-fs": {
"version": "4.2.11",
"resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz",
"integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ=="
},
"node_modules/has": {
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz",
@ -1420,6 +1553,14 @@
"node": ">=0.10.0"
}
},
"node_modules/ignore": {
"version": "5.3.0",
"resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.0.tgz",
"integrity": "sha512-g7dmpshy+gD7mh88OC9NwSGTKoc3kyLAZQRU1mt53Aw/vnvfXnbC+F/7F7QoYVKbV+KNvJx8wArewKy1vXMtlg==",
"engines": {
"node": ">= 4"
}
},
"node_modules/inflight": {
"version": "1.0.6",
"resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz",
@ -1481,6 +1622,14 @@
"node": ">=0.10.0"
}
},
"node_modules/is-fullwidth-code-point": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz",
"integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==",
"engines": {
"node": ">=8"
}
},
"node_modules/is-glob": {
"version": "4.0.3",
"resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz",
@ -1521,9 +1670,9 @@
}
},
"node_modules/jiti": {
"version": "1.19.1",
"resolved": "https://registry.npmjs.org/jiti/-/jiti-1.19.1.tgz",
"integrity": "sha512-oVhqoRDaBXf7sjkll95LHVS6Myyyb1zaunVwk4Z0+WPSW4gjS0pl01zYKHScTuyEhQsFxV5L4DR5r+YqSyqyyg==",
"version": "1.21.0",
"resolved": "https://registry.npmjs.org/jiti/-/jiti-1.21.0.tgz",
"integrity": "sha512-gFqAIbuKyyso/3G2qhiO2OM6shY6EPP/R0+mkDbyspxKazh8BXDC5FiFsUjlczgdNz/vfra0da2y+aHrusLG/Q==",
"bin": {
"jiti": "bin/jiti.js"
}
@ -1533,6 +1682,17 @@
"resolved": "https://registry.npmjs.org/js-stringify/-/js-stringify-1.0.2.tgz",
"integrity": "sha512-rtS5ATOo2Q5k1G+DADISilDA6lv79zIiwFd6CcjuIxGKLFm5C+RLImRscVap9k55i+MOZwgliw+NejvkLuGD5g=="
},
"node_modules/jsonfile": {
"version": "6.1.0",
"resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.1.0.tgz",
"integrity": "sha512-5dgndWOriYSm5cnYaJNhalLNDKOqFwyDB/rr1E9ZsGciGvKPs8R2xYGCacuf3z6K1YKDz182fd+fY3cn3pMqXQ==",
"dependencies": {
"universalify": "^2.0.0"
},
"optionalDependencies": {
"graceful-fs": "^4.1.6"
}
},
"node_modules/jstransformer": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/jstransformer/-/jstransformer-1.0.0.tgz",
@ -1683,9 +1843,9 @@
}
},
"node_modules/nanoid": {
"version": "3.3.6",
"resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.6.tgz",
"integrity": "sha512-BGcqMMJuToF7i1rt+2PWSNVnWIkGCU78jBG3RxO/bZlnZPK2Cmi2QaffxGO/2RvWi9sL+FAiRiXMgsyxQ1DIDA==",
"version": "3.3.7",
"resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.7.tgz",
"integrity": "sha512-eSRppjcPIatRIMC1U6UngP8XFcz8MQWGQdt1MTBQ7NaAmvXDfvNxbvWV3x2y6CdEUciCSsDHDQZbhYaB8QEo2g==",
"funding": [
{
"type": "github",
@ -1805,6 +1965,17 @@
"resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.7.tgz",
"integrity": "sha512-5DFkuoqlv1uYQKxy8omFBeJPQcdoE07Kv2sferDCrAq1ohOU+MSDswDIbnx3YAM60qIOnYa53wBhXW0EbMonrQ=="
},
"node_modules/path-type": {
"version": "5.0.0",
"resolved": "https://registry.npmjs.org/path-type/-/path-type-5.0.0.tgz",
"integrity": "sha512-5HviZNaZcfqP95rwpv+1HDgUamezbqdSYTyzjTvwtJSnIH+3vnbmWsItli8OFEndS984VT55M3jduxZbX351gg==",
"engines": {
"node": ">=12"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/picocolors": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.0.tgz",
@ -1838,9 +2009,9 @@
}
},
"node_modules/postcss": {
"version": "8.4.27",
"resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.27.tgz",
"integrity": "sha512-gY/ACJtJPSmUFPDCHtX78+01fHa64FaU4zaaWfuh1MhGJISufJAH4cun6k/8fwsHYeK4UQmENQK+tRLCFJE8JQ==",
"version": "8.4.33",
"resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.33.tgz",
"integrity": "sha512-Kkpbhhdjw2qQs2O2DGX+8m5OVqEcbB9HRBvuYM9pgrjEFUg30A9LmXNlTAUj4S9kgtGyrMbTzVjH7E+s5Re2yg==",
"funding": [
{
"type": "opencollective",
@ -1856,7 +2027,7 @@
}
],
"dependencies": {
"nanoid": "^3.3.6",
"nanoid": "^3.3.7",
"picocolors": "^1.0.0",
"source-map-js": "^1.0.2"
},
@ -1864,6 +2035,76 @@
"node": "^10 || ^12 || >=14"
}
},
"node_modules/postcss-cli": {
"version": "11.0.0",
"resolved": "https://registry.npmjs.org/postcss-cli/-/postcss-cli-11.0.0.tgz",
"integrity": "sha512-xMITAI7M0u1yolVcXJ9XTZiO9aO49mcoKQy6pCDFdMh9kGqhzLVpWxeD/32M/QBmkhcGypZFFOLNLmIW4Pg4RA==",
"dependencies": {
"chokidar": "^3.3.0",
"dependency-graph": "^0.11.0",
"fs-extra": "^11.0.0",
"get-stdin": "^9.0.0",
"globby": "^14.0.0",
"picocolors": "^1.0.0",
"postcss-load-config": "^5.0.0",
"postcss-reporter": "^7.0.0",
"pretty-hrtime": "^1.0.3",
"read-cache": "^1.0.0",
"slash": "^5.0.0",
"yargs": "^17.0.0"
},
"bin": {
"postcss": "index.js"
},
"engines": {
"node": ">=18"
},
"peerDependencies": {
"postcss": "^8.0.0"
}
},
"node_modules/postcss-cli/node_modules/lilconfig": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/lilconfig/-/lilconfig-3.0.0.tgz",
"integrity": "sha512-K2U4W2Ff5ibV7j7ydLr+zLAkIg5JJ4lPn1Ltsdt+Tz/IjQ8buJ55pZAxoP34lqIiwtF9iAvtLv3JGv7CAyAg+g==",
"engines": {
"node": ">=14"
}
},
"node_modules/postcss-cli/node_modules/postcss-load-config": {
"version": "5.0.2",
"resolved": "https://registry.npmjs.org/postcss-load-config/-/postcss-load-config-5.0.2.tgz",
"integrity": "sha512-Q8QR3FYbqOKa0bnC1UQ2bFq9/ulHX5Bi34muzitMr8aDtUelO5xKeJEYC/5smE0jNE9zdB/NBnOwXKexELbRlw==",
"funding": [
{
"type": "opencollective",
"url": "https://opencollective.com/postcss/"
},
{
"type": "github",
"url": "https://github.com/sponsors/ai"
}
],
"dependencies": {
"lilconfig": "^3.0.0",
"yaml": "^2.3.4"
},
"engines": {
"node": ">= 18"
},
"peerDependencies": {
"jiti": ">=1.21.0",
"postcss": ">=8.0.9"
},
"peerDependenciesMeta": {
"jiti": {
"optional": true
},
"postcss": {
"optional": true
}
}
},
"node_modules/postcss-import": {
"version": "15.1.0",
"resolved": "https://registry.npmjs.org/postcss-import/-/postcss-import-15.1.0.tgz",
@ -1944,6 +2185,31 @@
"postcss": "^8.2.14"
}
},
"node_modules/postcss-reporter": {
"version": "7.1.0",
"resolved": "https://registry.npmjs.org/postcss-reporter/-/postcss-reporter-7.1.0.tgz",
"integrity": "sha512-/eoEylGWyy6/DOiMP5lmFRdmDKThqgn7D6hP2dXKJI/0rJSO1ADFNngZfDzxL0YAxFvws+Rtpuji1YIHj4mySA==",
"funding": [
{
"type": "opencollective",
"url": "https://opencollective.com/postcss/"
},
{
"type": "github",
"url": "https://github.com/sponsors/ai"
}
],
"dependencies": {
"picocolors": "^1.0.0",
"thenby": "^1.3.4"
},
"engines": {
"node": ">=10"
},
"peerDependencies": {
"postcss": "^8.1.0"
}
},
"node_modules/postcss-selector-parser": {
"version": "6.0.13",
"resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-6.0.13.tgz",
@ -1961,6 +2227,14 @@
"resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-4.2.0.tgz",
"integrity": "sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ=="
},
"node_modules/pretty-hrtime": {
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/pretty-hrtime/-/pretty-hrtime-1.0.3.tgz",
"integrity": "sha512-66hKPCr+72mlfiSjlEB1+45IjXSqvVAIy6mocupoww4tBFE9R9IhwwUGoI4G++Tc9Aq+2rxOt0RFU6gPcrte0A==",
"engines": {
"node": ">= 0.8"
}
},
"node_modules/promise": {
"version": "7.3.1",
"resolved": "https://registry.npmjs.org/promise/-/promise-7.3.1.tgz",
@ -2237,6 +2511,14 @@
"@redis/time-series": "1.0.4"
}
},
"node_modules/require-directory": {
"version": "2.1.1",
"resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz",
"integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==",
"engines": {
"node": ">=0.10.0"
}
},
"node_modules/resolve": {
"version": "1.22.2",
"resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.2.tgz",
@ -2419,6 +2701,17 @@
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/slash": {
"version": "5.1.0",
"resolved": "https://registry.npmjs.org/slash/-/slash-5.1.0.tgz",
"integrity": "sha512-ZA6oR3T/pEyuqwMgAKT0/hAv8oAXckzbkmR0UkUosQ+Mc4RxGoJkRmwHgHufaenlyAgE1Mxgpdcrf75y6XcnDg==",
"engines": {
"node": ">=14.16"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/source-map-js": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.0.2.tgz",
@ -2440,6 +2733,30 @@
"node": ">= 0.6"
}
},
"node_modules/string-width": {
"version": "4.2.3",
"resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz",
"integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==",
"dependencies": {
"emoji-regex": "^8.0.0",
"is-fullwidth-code-point": "^3.0.0",
"strip-ansi": "^6.0.1"
},
"engines": {
"node": ">=8"
}
},
"node_modules/strip-ansi": {
"version": "6.0.1",
"resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz",
"integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==",
"dependencies": {
"ansi-regex": "^5.0.1"
},
"engines": {
"node": ">=8"
}
},
"node_modules/sucrase": {
"version": "3.34.0",
"resolved": "https://registry.npmjs.org/sucrase/-/sucrase-3.34.0.tgz",
@ -2508,6 +2825,11 @@
"node": ">=14.0.0"
}
},
"node_modules/thenby": {
"version": "1.3.4",
"resolved": "https://registry.npmjs.org/thenby/-/thenby-1.3.4.tgz",
"integrity": "sha512-89Gi5raiWA3QZ4b2ePcEwswC3me9JIg+ToSgtE0JWeCynLnLxNr/f9G+xfo9K+Oj4AFdom8YNJjibIARTJmapQ=="
},
"node_modules/thenify": {
"version": "3.3.1",
"resolved": "https://registry.npmjs.org/thenify/-/thenify-3.3.1.tgz",
@ -2595,6 +2917,25 @@
"node": ">= 0.8"
}
},
"node_modules/unicorn-magic": {
"version": "0.1.0",
"resolved": "https://registry.npmjs.org/unicorn-magic/-/unicorn-magic-0.1.0.tgz",
"integrity": "sha512-lRfVq8fE8gz6QMBuDM6a+LO3IAzTi05H6gCVaUpir2E1Rwpo4ZUog45KpNXKC/Mn3Yb9UDuHumeFTo9iV/D9FQ==",
"engines": {
"node": ">=18"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/universalify": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.1.tgz",
"integrity": "sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==",
"engines": {
"node": ">= 10.0.0"
}
},
"node_modules/unpipe": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz",
@ -2675,23 +3016,72 @@
"node": ">= 10.0.0"
}
},
"node_modules/wrap-ansi": {
"version": "7.0.0",
"resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz",
"integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==",
"dependencies": {
"ansi-styles": "^4.0.0",
"string-width": "^4.1.0",
"strip-ansi": "^6.0.0"
},
"engines": {
"node": ">=10"
},
"funding": {
"url": "https://github.com/chalk/wrap-ansi?sponsor=1"
}
},
"node_modules/wrappy": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz",
"integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ=="
},
"node_modules/y18n": {
"version": "5.0.8",
"resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz",
"integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==",
"engines": {
"node": ">=10"
}
},
"node_modules/yallist": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz",
"integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A=="
},
"node_modules/yaml": {
"version": "2.3.1",
"resolved": "https://registry.npmjs.org/yaml/-/yaml-2.3.1.tgz",
"integrity": "sha512-2eHWfjaoXgTBC2jNM1LRef62VQa0umtvRiDSk6HSzW7RvS5YtkabJrwYLLEKWBc8a5U2PTSCs+dJjUTJdlHsWQ==",
"version": "2.3.4",
"resolved": "https://registry.npmjs.org/yaml/-/yaml-2.3.4.tgz",
"integrity": "sha512-8aAvwVUSHpfEqTQ4w/KMlf3HcRdt50E5ODIQJBw1fQ5RL34xabzxtUlzTXVqc4rkZsPbvrXKWnABCD7kWSmocA==",
"engines": {
"node": ">= 14"
}
},
"node_modules/yargs": {
"version": "17.7.2",
"resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz",
"integrity": "sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==",
"dependencies": {
"cliui": "^8.0.1",
"escalade": "^3.1.1",
"get-caller-file": "^2.0.5",
"require-directory": "^2.1.1",
"string-width": "^4.2.3",
"y18n": "^5.0.5",
"yargs-parser": "^21.1.1"
},
"engines": {
"node": ">=12"
}
},
"node_modules/yargs-parser": {
"version": "21.1.1",
"resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz",
"integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==",
"engines": {
"node": ">=12"
}
}
}
}

View File

@ -26,6 +26,8 @@
"leaflet": "^1.8.0",
"lodash": "^4.17.21",
"morgan": "~1.9.1",
"postcss": "^8.4.33",
"postcss-cli": "^11.0.0",
"pug": "^3.0.2",
"redis": "^4.3.0",
"tailwindcss": "^3.1.8"

View File

@ -1,5 +1,4 @@
const API_VERSION = "0";
const API_URL = `/api/${API_VERSION}`;
const API_URL = '/api';
const TOKEN = document.querySelector('meta[name="csrf-token"]').getAttribute('content');
@ -34,6 +33,12 @@ function checkAnswer(species) {
})
}
function getSpeciesCompletion(term) {
return query('game/species/completion', {
q: term
})
}
const client = {
getQuizz,
getRegion,

View File

@ -51,3 +51,4 @@ footer .link:after {
.proposal-button {
margin: 1em 0;
}

0
public/stylesheets/spinner.css Normal file → Executable file
View File

View File

@ -166,15 +166,15 @@ input,
optgroup,
select,
textarea {
font-family: inherit;
font-feature-settings: inherit;
font-variation-settings: inherit;
font-size: 100%;
font-weight: inherit;
line-height: inherit;
color: inherit;
margin: 1em;
padding: 1em;
font-family: inherit; /* 1 */
font-feature-settings: inherit; /* 1 */
font-variation-settings: inherit; /* 1 */
font-size: 100%; /* 1 */
font-weight: inherit; /* 1 */
line-height: inherit; /* 1 */
color: inherit; /* 1 */
margin: 0; /* 2 */
padding: 0; /* 3 */
}
/*
@ -492,6 +492,7 @@ h1 {
color: var(--tw-main-color);
}
.button {
margin: 0.5rem;
border-radius: 0.25rem;
--tw-bg-opacity: 1;
background-color: rgb(34 197 94 / var(--tw-bg-opacity));
@ -507,21 +508,56 @@ h1 {
--tw-bg-opacity: 1;
background-color: rgb(21 128 61 / var(--tw-bg-opacity));
}
.pointer-events-none {
pointer-events: none;
}
.absolute {
position: absolute;
}
.relative {
position: relative;
}
.-top-1 {
top: -0.25rem;
}
.-top-1\.5 {
top: -0.375rem;
}
.left-0 {
left: 0px;
}
.block {
display: block;
}
.flex {
display: flex;
}
.h-10 {
height: 2.5rem;
}
.h-full {
height: 100%;
}
.min-h-screen {
min-height: 100vh;
}
.w-10 {
width: 2.5rem;
}
.w-full {
width: 100%;
}
.min-w-\[100px\] {
min-width: 100px;
}
.flex-1 {
flex: 1 1 0%;
}
.select-none {
-webkit-user-select: none;
-moz-user-select: none;
user-select: none;
}
.flex-row {
flex-direction: row;
}
@ -531,17 +567,286 @@ h1 {
.justify-evenly {
justify-content: space-evenly;
}
.rounded-\[7px\] {
border-radius: 7px;
}
.border {
border-width: 1px;
}
.border-t-transparent {
border-top-color: transparent;
}
.bg-black {
--tw-bg-opacity: 1;
background-color: rgb(0 0 0 / var(--tw-bg-opacity));
}
.bg-transparent {
background-color: transparent;
}
.p-1 {
padding: 0.25rem;
}
.p-5 {
padding: 1.25rem;
}
.px-3 {
padding-left: 0.75rem;
padding-right: 0.75rem;
}
.py-2 {
padding-top: 0.5rem;
padding-bottom: 0.5rem;
}
.py-2\.5 {
padding-top: 0.625rem;
padding-bottom: 0.625rem;
}
.text-center {
text-align: center;
}
.font-sans {
font-family: ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, "Noto Sans", sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji";
}
.text-\[11px\] {
font-size: 11px;
}
.text-sm {
font-size: 0.875rem;
line-height: 1.25rem;
}
.font-normal {
font-weight: 400;
}
.leading-tight {
line-height: 1.25;
}
.text-white {
--tw-text-opacity: 1;
color: rgb(255 255 255 / var(--tw-text-opacity));
}
.outline {
outline-style: solid;
}
.outline-0 {
outline-width: 0px;
}
.transition-all {
transition-property: all;
transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1);
transition-duration: 150ms;
}
.before\:pointer-events-none::before {
content: var(--tw-content);
pointer-events: none;
}
.before\:mr-1::before {
content: var(--tw-content);
margin-right: 0.25rem;
}
.before\:mt-\[6\.5px\]::before {
content: var(--tw-content);
margin-top: 6.5px;
}
.before\:box-border::before {
content: var(--tw-content);
box-sizing: border-box;
}
.before\:block::before {
content: var(--tw-content);
display: block;
}
.before\:h-1::before {
content: var(--tw-content);
height: 0.25rem;
}
.before\:h-1\.5::before {
content: var(--tw-content);
height: 0.375rem;
}
.before\:w-2::before {
content: var(--tw-content);
width: 0.5rem;
}
.before\:w-2\.5::before {
content: var(--tw-content);
width: 0.625rem;
}
.before\:rounded-tl-md::before {
content: var(--tw-content);
border-top-left-radius: 0.375rem;
}
.before\:border-l::before {
content: var(--tw-content);
border-left-width: 1px;
}
.before\:border-t::before {
content: var(--tw-content);
border-top-width: 1px;
}
.before\:transition-all::before {
content: var(--tw-content);
transition-property: all;
transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1);
transition-duration: 150ms;
}
.after\:pointer-events-none::after {
content: var(--tw-content);
pointer-events: none;
}
.after\:ml-1::after {
content: var(--tw-content);
margin-left: 0.25rem;
}
.after\:mt-\[6\.5px\]::after {
content: var(--tw-content);
margin-top: 6.5px;
}
.after\:box-border::after {
content: var(--tw-content);
box-sizing: border-box;
}
.after\:block::after {
content: var(--tw-content);
display: block;
}
.after\:h-1::after {
content: var(--tw-content);
height: 0.25rem;
}
.after\:h-1\.5::after {
content: var(--tw-content);
height: 0.375rem;
}
.after\:w-2::after {
content: var(--tw-content);
width: 0.5rem;
}
.after\:w-2\.5::after {
content: var(--tw-content);
width: 0.625rem;
}
.after\:flex-grow::after {
content: var(--tw-content);
flex-grow: 1;
}
.after\:rounded-tr-md::after {
content: var(--tw-content);
border-top-right-radius: 0.375rem;
}
.after\:border-r::after {
content: var(--tw-content);
border-right-width: 1px;
}
.after\:border-t::after {
content: var(--tw-content);
border-top-width: 1px;
}
.after\:transition-all::after {
content: var(--tw-content);
transition-property: all;
transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1);
transition-duration: 150ms;
}
.placeholder-shown\:border:-moz-placeholder-shown {
border-width: 1px;
}
.placeholder-shown\:border:placeholder-shown {
border-width: 1px;
}
.empty\:\!bg-gray-900:empty {
--tw-bg-opacity: 1 !important;
background-color: rgb(17 24 39 / var(--tw-bg-opacity)) !important;
}
.focus\:border-2:focus {
border-width: 2px;
}
.focus\:border-gray-900:focus {
--tw-border-opacity: 1;
border-color: rgb(17 24 39 / var(--tw-border-opacity));
}
.focus\:border-t-transparent:focus {
border-top-color: transparent;
}
.focus\:outline-0:focus {
outline-width: 0px;
}
.disabled\:border-0:disabled {
border-width: 0px;
}
.peer:-moz-placeholder-shown ~ .peer-placeholder-shown\:text-sm {
font-size: 0.875rem;
line-height: 1.25rem;
}
.peer:placeholder-shown ~ .peer-placeholder-shown\:text-sm {
font-size: 0.875rem;
line-height: 1.25rem;
}
.peer:-moz-placeholder-shown ~ .peer-placeholder-shown\:leading-\[3\.75\] {
line-height: 3.75;
}
.peer:placeholder-shown ~ .peer-placeholder-shown\:leading-\[3\.75\] {
line-height: 3.75;
}
.peer:-moz-placeholder-shown ~ .peer-placeholder-shown\:before\:border-transparent::before {
content: var(--tw-content);
border-color: transparent;
}
.peer:placeholder-shown ~ .peer-placeholder-shown\:before\:border-transparent::before {
content: var(--tw-content);
border-color: transparent;
}
.peer:-moz-placeholder-shown ~ .peer-placeholder-shown\:after\:border-transparent::after {
content: var(--tw-content);
border-color: transparent;
}
.peer:placeholder-shown ~ .peer-placeholder-shown\:after\:border-transparent::after {
content: var(--tw-content);
border-color: transparent;
}
.peer:focus ~ .peer-focus\:text-\[11px\] {
font-size: 11px;
}
.peer:focus ~ .peer-focus\:leading-tight {
line-height: 1.25;
}
.peer:focus ~ .peer-focus\:text-gray-900 {
--tw-text-opacity: 1;
color: rgb(17 24 39 / var(--tw-text-opacity));
}
.peer:focus ~ .peer-focus\:before\:border-l-2::before {
content: var(--tw-content);
border-left-width: 2px;
}
.peer:focus ~ .peer-focus\:before\:border-t-2::before {
content: var(--tw-content);
border-top-width: 2px;
}
.peer:focus ~ .peer-focus\:before\:border-gray-900::before {
content: var(--tw-content);
--tw-border-opacity: 1;
border-color: rgb(17 24 39 / var(--tw-border-opacity));
}
.peer:focus ~ .peer-focus\:after\:border-r-2::after {
content: var(--tw-content);
border-right-width: 2px;
}
.peer:focus ~ .peer-focus\:after\:border-t-2::after {
content: var(--tw-content);
border-top-width: 2px;
}
.peer:focus ~ .peer-focus\:after\:border-gray-900::after {
content: var(--tw-content);
--tw-border-opacity: 1;
border-color: rgb(17 24 39 / var(--tw-border-opacity));
}
.peer:disabled ~ .peer-disabled\:text-transparent {
color: transparent;
}
.peer:disabled ~ .peer-disabled\:before\:border-transparent::before {
content: var(--tw-content);
border-color: transparent;
}
.peer:disabled ~ .peer-disabled\:after\:border-transparent::after {
content: var(--tw-content);
border-color: transparent;
}

View File

@ -13,6 +13,6 @@
}
.button {
@apply text-white font-bold py-2 px-4 rounded bg-green-500 hover:bg-green-700;
@apply text-white font-bold py-2 px-4 m-2 rounded bg-green-500 hover:bg-green-700;
}
}

View File

@ -2,14 +2,15 @@ const express = require('express');
const router = express.Router();
const apiController = require('../controllers/api.js');
router.route('/')
.get(apiController.getHome);
router.route('/game/quizz')
.get(apiController.game.quizz);
router.route('/game/check')
.get(apiController.game.check);
router.route('/species/completion')
.get(apiController.game.birdSpeciesCompletion);
router.route('/region')
.get(apiController.region);

0
utils/extract-i18n.js Normal file → Executable file
View File

0
utils/translate.sh Normal file → Executable file
View File