game&api: Added API queries for geolocalized species and audio retrieval
This commit is contained in:
parent
f1f87afbae
commit
585e578e48
@ -1,15 +0,0 @@
|
||||
# EditorConfig is awesome: https://EditorConfig.org
|
||||
|
||||
# top-most EditorConfig file
|
||||
root = true
|
||||
|
||||
[*]
|
||||
indent_style = space
|
||||
indent_size = 4
|
||||
end_of_line = lf
|
||||
charset = utf-8
|
||||
trim_trailing_whitespace = false
|
||||
insert_final_newline = false
|
||||
|
||||
[pug]
|
||||
indent_size = 2
|
7
.gitignore
vendored
7
.gitignore
vendored
@ -54,7 +54,7 @@ typings/
|
||||
# Yarn Integrity file
|
||||
.yarn-integrity
|
||||
|
||||
# dotenv environment variables file
|
||||
# dotenv environment constiables file
|
||||
.env
|
||||
|
||||
# next.js build output
|
||||
@ -62,4 +62,7 @@ typings/
|
||||
|
||||
# IDE junks
|
||||
.vscode
|
||||
.ideas
|
||||
.ideas
|
||||
|
||||
data/*
|
||||
!/data/.gitkeep
|
30
README.md
30
README.md
@ -1,3 +1,31 @@
|
||||
# soundbirder
|
||||
|
||||
A web application to play with bird sound identification with data from eBird and Xeno-Canto.
|
||||
A web application to play with bird sound identification with data from eBird and Xeno-Canto.
|
||||
|
||||
## Requirements
|
||||
|
||||
- NodeJS, NPM
|
||||
- MariaDB (MySQL)
|
||||
- eBird API key
|
||||
|
||||
## Installation
|
||||
|
||||
```bash
|
||||
git clone https://forge.chapril.org/UncleSamulus/soundbirder
|
||||
cd soundbirder
|
||||
npm install
|
||||
```
|
||||
|
||||
Setup the EBIRD_API_KEY in `.env`:
|
||||
```text
|
||||
EBIRD_API_KEY="secret"
|
||||
```
|
||||
|
||||
```bash
|
||||
npm start
|
||||
```
|
||||
|
||||
Then go to [http://localhost:3000](http://localhost:3000), and have fun !
|
||||
|
||||
|
||||
TODO: add database configuration.
|
49
app.js
49
app.js
@ -1,13 +1,17 @@
|
||||
var createError = require('http-errors');
|
||||
var express = require('express');
|
||||
var path = require('path');
|
||||
var cookieParser = require('cookie-parser');
|
||||
var logger = require('morgan');
|
||||
|
||||
var indexRouter = require('./routes/index');
|
||||
const createError = require('http-errors');
|
||||
const express = require('express');
|
||||
const session = require('express-session');
|
||||
const csrf = require('csurf');
|
||||
const path = require('path');
|
||||
const cookieParser = require('cookie-parser');
|
||||
const logger = require('morgan');
|
||||
const i18n = require('i18n-2');
|
||||
|
||||
var app = express();
|
||||
const indexRouter = require('./routes/index');
|
||||
const apiRouter = require('./routes/api');
|
||||
const authRouter = require('./routes/auth');
|
||||
|
||||
const app = express();
|
||||
|
||||
// view engine setup
|
||||
app.set('views', path.join(__dirname, 'views'));
|
||||
@ -19,6 +23,19 @@ app.use(express.urlencoded({ extended: false }));
|
||||
app.use(cookieParser());
|
||||
app.use(express.static(path.join(__dirname, 'public')));
|
||||
|
||||
const sess = {
|
||||
secret: 'keyboard cat',
|
||||
resave: true,
|
||||
saveUninitialized: true
|
||||
}
|
||||
|
||||
if (app.get('env') === 'production') {
|
||||
app.set('trust proxy', 1); // trust first proxy
|
||||
sess.cookie.secure = true; // serve secure cookies
|
||||
}
|
||||
|
||||
app.use(session(sess));
|
||||
|
||||
i18n.expressBind(app, {
|
||||
locales: ['en', 'es', 'fr', 'de'],
|
||||
defaultLocale: 'en',
|
||||
@ -27,7 +44,7 @@ i18n.expressBind(app, {
|
||||
|
||||
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();
|
||||
@ -35,7 +52,21 @@ app.use(function(req, res, next) {
|
||||
next();
|
||||
});
|
||||
|
||||
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);
|
||||
|
||||
const csrfProtection = csrf({ cookie: true });
|
||||
app.use(csrfProtection);
|
||||
app.use('/', indexRouter);
|
||||
app.use('/auth', authRouter);
|
||||
|
||||
// catch 404 and forward to error handler
|
||||
app.use(function(req, res, next) {
|
||||
|
18
bin/www
18
bin/www
@ -4,22 +4,22 @@
|
||||
* Module dependencies.
|
||||
*/
|
||||
|
||||
var app = require('../app');
|
||||
var debug = require('debug')('soundbirder:server');
|
||||
var http = require('http');
|
||||
const app = require('../app');
|
||||
const debug = require('debug')('soundbirder:server');
|
||||
const http = require('http');
|
||||
|
||||
/**
|
||||
* Get port from environment and store in Express.
|
||||
*/
|
||||
|
||||
var port = normalizePort(process.env.PORT || '3000');
|
||||
const port = normalizePort(process.env.PORT || '3000');
|
||||
app.set('port', port);
|
||||
|
||||
/**
|
||||
* Create HTTP server.
|
||||
*/
|
||||
|
||||
var server = http.createServer(app);
|
||||
const server = http.createServer(app);
|
||||
|
||||
/**
|
||||
* Listen on provided port, on all network interfaces.
|
||||
@ -34,7 +34,7 @@ server.on('listening', onListening);
|
||||
*/
|
||||
|
||||
function normalizePort(val) {
|
||||
var port = parseInt(val, 10);
|
||||
const port = parseInt(val, 10);
|
||||
|
||||
if (isNaN(port)) {
|
||||
// named pipe
|
||||
@ -58,7 +58,7 @@ function onError(error) {
|
||||
throw error;
|
||||
}
|
||||
|
||||
var bind = typeof port === 'string'
|
||||
const bind = typeof port === 'string'
|
||||
? 'Pipe ' + port
|
||||
: 'Port ' + port;
|
||||
|
||||
@ -82,8 +82,8 @@ function onError(error) {
|
||||
*/
|
||||
|
||||
function onListening() {
|
||||
var addr = server.address();
|
||||
var bind = typeof addr === 'string'
|
||||
const addr = server.address();
|
||||
const bind = typeof addr === 'string'
|
||||
? 'pipe ' + addr
|
||||
: 'port ' + addr.port;
|
||||
debug('Listening on ' + bind);
|
||||
|
@ -1,3 +1,156 @@
|
||||
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');
|
||||
|
||||
function getRecord(req, res) {
|
||||
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)
|
||||
.then(({ species, correct, audio }) => {
|
||||
req.session.correct = correct;
|
||||
res.json({ species, audio });
|
||||
debug("Quizz sent");
|
||||
})
|
||||
.catch(error => {
|
||||
debug("Faced error while generating quizz");
|
||||
res.json({ error });
|
||||
throw error;
|
||||
});
|
||||
}
|
||||
|
||||
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;
|
||||
});
|
||||
}
|
||||
|
||||
const game = {
|
||||
check,
|
||||
quizz
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
getHome,
|
||||
game
|
||||
}
|
48
controllers/auth.js
Normal file
48
controllers/auth.js
Normal file
@ -0,0 +1,48 @@
|
||||
/**
|
||||
* Auth controller
|
||||
*/
|
||||
|
||||
function indexPage(req, res) {
|
||||
res.redirect('/auth/login');
|
||||
}
|
||||
|
||||
function loginPage(req, res) {
|
||||
res.render('auth/login');
|
||||
}
|
||||
|
||||
function login(req, res) {
|
||||
|
||||
}
|
||||
|
||||
function logout(req, res) {
|
||||
|
||||
}
|
||||
|
||||
function registerPage(req, res) {
|
||||
res.render('auth/register');
|
||||
}
|
||||
|
||||
function register(req, res) {
|
||||
|
||||
}
|
||||
|
||||
function forgotPassword(req, res) {
|
||||
|
||||
}
|
||||
|
||||
function resetPassword(req, res) {
|
||||
|
||||
}
|
||||
|
||||
const auth = {
|
||||
indexPage,
|
||||
login,
|
||||
loginPage,
|
||||
logout,
|
||||
register,
|
||||
registerPage,
|
||||
forgotPassword,
|
||||
resetPassword
|
||||
}
|
||||
|
||||
module.exports = auth;
|
@ -1,6 +1,9 @@
|
||||
|
||||
function getIndex(req, res) {
|
||||
res.render('index', { title: 'SoundBirder' });
|
||||
res.render('index', {
|
||||
title: 'SoundBirder',
|
||||
csrf_token: req.csrfToken()
|
||||
});
|
||||
}
|
||||
|
||||
function getAbout(req, res) {
|
0
data/.gitkeep
Normal file
0
data/.gitkeep
Normal file
@ -7,5 +7,6 @@
|
||||
"Author": "Author",
|
||||
"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.",
|
||||
"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"
|
||||
}
|
373
package-lock.json
generated
373
package-lock.json
generated
@ -8,14 +8,20 @@
|
||||
"name": "soundbirder",
|
||||
"version": "0.0.0",
|
||||
"dependencies": {
|
||||
"@unclesamulus/ebird-api": "^0.0.0",
|
||||
"@unclesamulus/xeno-canto-api": "^0.0.0",
|
||||
"axios": "^0.27.2",
|
||||
"cookie-parser": "~1.4.4",
|
||||
"csurf": "^1.11.0",
|
||||
"debug": "~2.6.9",
|
||||
"dotenv": "^16.0.1",
|
||||
"express": "~4.16.1",
|
||||
"express-session": "^1.17.3",
|
||||
"feather-icons": "^4.29.0",
|
||||
"http-errors": "~1.6.3",
|
||||
"i18n-2": "^0.7.3",
|
||||
"leaflet": "^1.8.0",
|
||||
"lodash": "^4.17.21",
|
||||
"morgan": "~1.9.1",
|
||||
"pug": "^3.0.2"
|
||||
}
|
||||
@ -37,9 +43,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@babel/parser": {
|
||||
"version": "7.18.11",
|
||||
"resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.18.11.tgz",
|
||||
"integrity": "sha512-9JKn5vN+hDt0Hdqn1PiJ2guflwP+B6Ga8qbDuoF0PzzVhrzsKIJo8yGqVk6CmMHiMei9w1C1Bp9IMJSIK+HPIQ==",
|
||||
"version": "7.18.13",
|
||||
"resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.18.13.tgz",
|
||||
"integrity": "sha512-dgXcIfMuQ0kgzLB2b9tRZs7TTFFaGM2AbtA4fJgUUYukzGH4jwsS7hzQHEGs67jdehpm22vkgKwvbU+aEflgwg==",
|
||||
"bin": {
|
||||
"parser": "bin/babel-parser.js"
|
||||
},
|
||||
@ -48,9 +54,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@babel/types": {
|
||||
"version": "7.18.10",
|
||||
"resolved": "https://registry.npmjs.org/@babel/types/-/types-7.18.10.tgz",
|
||||
"integrity": "sha512-MJvnbEiiNkpjo+LknnmRrqbY1GPUUggjv+wQVjetM/AONoupqRALB7I6jGqNUAZsKcRIEu2J6FRFvsczljjsaQ==",
|
||||
"version": "7.18.13",
|
||||
"resolved": "https://registry.npmjs.org/@babel/types/-/types-7.18.13.tgz",
|
||||
"integrity": "sha512-ePqfTihzW0W6XAU+aMw2ykilisStJfDnsejDCXRchCcMJ4O0+8DhPXf2YUbZ6wjBlsEmZwLK/sPweWtu8hcJYQ==",
|
||||
"dependencies": {
|
||||
"@babel/helper-string-parser": "^7.18.10",
|
||||
"@babel/helper-validator-identifier": "^7.18.6",
|
||||
@ -60,6 +66,22 @@
|
||||
"node": ">=6.9.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@unclesamulus/ebird-api": {
|
||||
"version": "0.0.0",
|
||||
"resolved": "https://registry.npmjs.org/@unclesamulus/ebird-api/-/ebird-api-0.0.0.tgz",
|
||||
"integrity": "sha512-cQca/wS+35LuuLoVVB3jDeD3czVBy1FNQBO7uubsnwH9gYZzQEr+TpCeNcMO0S9/ZIjl36fJ3qyFH6R/AsHMFA==",
|
||||
"dependencies": {
|
||||
"axios": "^0.27.2"
|
||||
}
|
||||
},
|
||||
"node_modules/@unclesamulus/xeno-canto-api": {
|
||||
"version": "0.0.0",
|
||||
"resolved": "https://registry.npmjs.org/@unclesamulus/xeno-canto-api/-/xeno-canto-api-0.0.0.tgz",
|
||||
"integrity": "sha512-dVMpX6pbhQvxyjR4eyIMAvR9VNJ/qXnoPc+UQRnA5ESz2JcqVGxcFCByhu88Ym41/QzlfpFwO+/glsGHtzfZWw==",
|
||||
"dependencies": {
|
||||
"axios": "^0.27.2"
|
||||
}
|
||||
},
|
||||
"node_modules/accepts": {
|
||||
"version": "1.3.8",
|
||||
"resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz",
|
||||
@ -249,15 +271,83 @@
|
||||
"integrity": "sha512-QADzlaHc8icV8I7vbaJXJwod9HWYp8uCqf1xa4OfNu1T7JVxQIrUgOWtHdNDtPiywmFbiS12VjotIXLrKM3orQ=="
|
||||
},
|
||||
"node_modules/core-js": {
|
||||
"version": "3.24.1",
|
||||
"resolved": "https://registry.npmjs.org/core-js/-/core-js-3.24.1.tgz",
|
||||
"integrity": "sha512-0QTBSYSUZ6Gq21utGzkfITDylE8jWC9Ne1D2MrhvlsZBI1x39OdDIVbzSqtgMndIy6BlHxBXpMGqzZmnztg2rg==",
|
||||
"version": "3.25.0",
|
||||
"resolved": "https://registry.npmjs.org/core-js/-/core-js-3.25.0.tgz",
|
||||
"integrity": "sha512-CVU1xvJEfJGhyCpBrzzzU1kjCfgsGUxhEvwUV2e/cOedYWHdmluamx+knDnmhqALddMG16fZvIqvs9aijsHHaA==",
|
||||
"hasInstallScript": true,
|
||||
"funding": {
|
||||
"type": "opencollective",
|
||||
"url": "https://opencollective.com/core-js"
|
||||
}
|
||||
},
|
||||
"node_modules/csrf": {
|
||||
"version": "3.1.0",
|
||||
"resolved": "https://registry.npmjs.org/csrf/-/csrf-3.1.0.tgz",
|
||||
"integrity": "sha512-uTqEnCvWRk042asU6JtapDTcJeeailFy4ydOQS28bj1hcLnYRiqi8SsD2jS412AY1I/4qdOwWZun774iqywf9w==",
|
||||
"dependencies": {
|
||||
"rndm": "1.2.0",
|
||||
"tsscmp": "1.0.6",
|
||||
"uid-safe": "2.1.5"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.8"
|
||||
}
|
||||
},
|
||||
"node_modules/csurf": {
|
||||
"version": "1.11.0",
|
||||
"resolved": "https://registry.npmjs.org/csurf/-/csurf-1.11.0.tgz",
|
||||
"integrity": "sha512-UCtehyEExKTxgiu8UHdGvHj4tnpE/Qctue03Giq5gPgMQ9cg/ciod5blZQ5a4uCEenNQjxyGuzygLdKUmee/bQ==",
|
||||
"dependencies": {
|
||||
"cookie": "0.4.0",
|
||||
"cookie-signature": "1.0.6",
|
||||
"csrf": "3.1.0",
|
||||
"http-errors": "~1.7.3"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.8.0"
|
||||
}
|
||||
},
|
||||
"node_modules/csurf/node_modules/cookie": {
|
||||
"version": "0.4.0",
|
||||
"resolved": "https://registry.npmjs.org/cookie/-/cookie-0.4.0.tgz",
|
||||
"integrity": "sha512-+Hp8fLp57wnUSt0tY0tHEXh4voZRDnoIrZPqlo3DPiI4y9lwg/jqx+1Om94/W6ZaPDOUbnjOt/99w66zk+l1Xg==",
|
||||
"engines": {
|
||||
"node": ">= 0.6"
|
||||
}
|
||||
},
|
||||
"node_modules/csurf/node_modules/http-errors": {
|
||||
"version": "1.7.3",
|
||||
"resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.7.3.tgz",
|
||||
"integrity": "sha512-ZTTX0MWrsQ2ZAhA1cejAwDLycFsd7I7nVtnkT3Ol0aqodaKW+0CTZDQ1uBv5whptCnc8e8HeRRJxRs0kmm/Qfw==",
|
||||
"dependencies": {
|
||||
"depd": "~1.1.2",
|
||||
"inherits": "2.0.4",
|
||||
"setprototypeof": "1.1.1",
|
||||
"statuses": ">= 1.5.0 < 2",
|
||||
"toidentifier": "1.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.6"
|
||||
}
|
||||
},
|
||||
"node_modules/csurf/node_modules/inherits": {
|
||||
"version": "2.0.4",
|
||||
"resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz",
|
||||
"integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ=="
|
||||
},
|
||||
"node_modules/csurf/node_modules/setprototypeof": {
|
||||
"version": "1.1.1",
|
||||
"resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.1.1.tgz",
|
||||
"integrity": "sha512-JvdAWfbXeIGaZ9cILp38HntZSFSo3mWg6xGcJJsd+d4aRMOqauag1C63dJfDw7OaMYwEbHMOxEZ1lqVRYP2OAw=="
|
||||
},
|
||||
"node_modules/csurf/node_modules/statuses": {
|
||||
"version": "1.5.0",
|
||||
"resolved": "https://registry.npmjs.org/statuses/-/statuses-1.5.0.tgz",
|
||||
"integrity": "sha512-OpZ3zP+jT1PI7I8nemJX4AKmAX070ZkYPVWV/AaKTJl+tXCTGyVdC1a4SL8RUQYEwk/f34ZX8UTykN68FwrqAA==",
|
||||
"engines": {
|
||||
"node": ">= 0.6"
|
||||
}
|
||||
},
|
||||
"node_modules/debug": {
|
||||
"version": "2.6.9",
|
||||
"resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz",
|
||||
@ -292,6 +382,14 @@
|
||||
"resolved": "https://registry.npmjs.org/doctypes/-/doctypes-1.1.0.tgz",
|
||||
"integrity": "sha512-LLBi6pEqS6Do3EKQ3J0NqHWV5hhb78Pi8vvESYwyOy2c31ZEZVdtitdzsQsKb7878PEERhzUk0ftqGhG6Mz+pQ=="
|
||||
},
|
||||
"node_modules/dotenv": {
|
||||
"version": "16.0.1",
|
||||
"resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.0.1.tgz",
|
||||
"integrity": "sha512-1K6hR6wtk2FviQ4kEiSjFiH5rpzEVi8WW0x96aztHVMhEspNpc4DVOUTEHtEva5VThQ8IaBX1Pe4gSzpVVUsKQ==",
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
}
|
||||
},
|
||||
"node_modules/ee-first": {
|
||||
"version": "1.1.1",
|
||||
"resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz",
|
||||
@ -358,6 +456,59 @@
|
||||
"node": ">= 0.10.0"
|
||||
}
|
||||
},
|
||||
"node_modules/express-session": {
|
||||
"version": "1.17.3",
|
||||
"resolved": "https://registry.npmjs.org/express-session/-/express-session-1.17.3.tgz",
|
||||
"integrity": "sha512-4+otWXlShYlG1Ma+2Jnn+xgKUZTMJ5QD3YvfilX3AcocOAbIkVylSWEklzALe/+Pu4qV6TYBj5GwOBFfdKqLBw==",
|
||||
"dependencies": {
|
||||
"cookie": "0.4.2",
|
||||
"cookie-signature": "1.0.6",
|
||||
"debug": "2.6.9",
|
||||
"depd": "~2.0.0",
|
||||
"on-headers": "~1.0.2",
|
||||
"parseurl": "~1.3.3",
|
||||
"safe-buffer": "5.2.1",
|
||||
"uid-safe": "~2.1.5"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.8.0"
|
||||
}
|
||||
},
|
||||
"node_modules/express-session/node_modules/cookie": {
|
||||
"version": "0.4.2",
|
||||
"resolved": "https://registry.npmjs.org/cookie/-/cookie-0.4.2.tgz",
|
||||
"integrity": "sha512-aSWTXFzaKWkvHO1Ny/s+ePFpvKsPnjc551iI41v3ny/ow6tBG5Vd+FuqGNhh1LxOmVzOlGUriIlOaokOvhaStA==",
|
||||
"engines": {
|
||||
"node": ">= 0.6"
|
||||
}
|
||||
},
|
||||
"node_modules/express-session/node_modules/depd": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz",
|
||||
"integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==",
|
||||
"engines": {
|
||||
"node": ">= 0.8"
|
||||
}
|
||||
},
|
||||
"node_modules/express-session/node_modules/safe-buffer": {
|
||||
"version": "5.2.1",
|
||||
"resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz",
|
||||
"integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==",
|
||||
"funding": [
|
||||
{
|
||||
"type": "github",
|
||||
"url": "https://github.com/sponsors/feross"
|
||||
},
|
||||
{
|
||||
"type": "patreon",
|
||||
"url": "https://www.patreon.com/feross"
|
||||
},
|
||||
{
|
||||
"type": "consulting",
|
||||
"url": "https://feross.org/support"
|
||||
}
|
||||
]
|
||||
},
|
||||
"node_modules/express/node_modules/cookie": {
|
||||
"version": "0.3.1",
|
||||
"resolved": "https://registry.npmjs.org/cookie/-/cookie-0.3.1.tgz",
|
||||
@ -616,6 +767,11 @@
|
||||
"resolved": "https://registry.npmjs.org/leaflet/-/leaflet-1.8.0.tgz",
|
||||
"integrity": "sha512-gwhMjFCQiYs3x/Sf+d49f10ERXaEFCPr+nVTryhAW8DWbMGqJqt9G4XuIaHmFW08zYvhgdzqXGr8AlW8v8dQkA=="
|
||||
},
|
||||
"node_modules/lodash": {
|
||||
"version": "4.17.21",
|
||||
"resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz",
|
||||
"integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg=="
|
||||
},
|
||||
"node_modules/media-typer": {
|
||||
"version": "0.3.0",
|
||||
"resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz",
|
||||
@ -877,6 +1033,14 @@
|
||||
"node": ">=0.6"
|
||||
}
|
||||
},
|
||||
"node_modules/random-bytes": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/random-bytes/-/random-bytes-1.0.0.tgz",
|
||||
"integrity": "sha512-iv7LhNVO047HzYR3InF6pUcUsPQiHTM1Qal51DcGSuZFBil1aBBWG5eHPNek7bvILMaYJ/8RU1e8w1AMdHmLQQ==",
|
||||
"engines": {
|
||||
"node": ">= 0.8"
|
||||
}
|
||||
},
|
||||
"node_modules/range-parser": {
|
||||
"version": "1.2.1",
|
||||
"resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz",
|
||||
@ -915,6 +1079,11 @@
|
||||
"url": "https://github.com/sponsors/ljharb"
|
||||
}
|
||||
},
|
||||
"node_modules/rndm": {
|
||||
"version": "1.2.0",
|
||||
"resolved": "https://registry.npmjs.org/rndm/-/rndm-1.2.0.tgz",
|
||||
"integrity": "sha512-fJhQQI5tLrQvYIYFpOnFinzv9dwmR7hRnUz1XqP3OJ1jIweTNOd6aTO4jwQSgcBSFUB+/KHJxuGneime+FdzOw=="
|
||||
},
|
||||
"node_modules/safe-buffer": {
|
||||
"version": "5.1.2",
|
||||
"resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz",
|
||||
@ -999,11 +1168,27 @@
|
||||
"node": ">=4"
|
||||
}
|
||||
},
|
||||
"node_modules/toidentifier": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.0.tgz",
|
||||
"integrity": "sha512-yaOH/Pk/VEhBWWTlhI+qXxDFXlejDGcQipMlyxda9nthulaxLZUNcUqFxokp0vcYnvteJln5FNQDRrxj3YcbVw==",
|
||||
"engines": {
|
||||
"node": ">=0.6"
|
||||
}
|
||||
},
|
||||
"node_modules/token-stream": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/token-stream/-/token-stream-1.0.0.tgz",
|
||||
"integrity": "sha512-VSsyNPPW74RpHwR8Fc21uubwHY7wMDeJLys2IX5zJNih+OnAnaifKHo+1LHT7DAdloQ7apeaaWg8l7qnf/TnEg=="
|
||||
},
|
||||
"node_modules/tsscmp": {
|
||||
"version": "1.0.6",
|
||||
"resolved": "https://registry.npmjs.org/tsscmp/-/tsscmp-1.0.6.tgz",
|
||||
"integrity": "sha512-LxhtAkPDTkVCMQjt2h6eBVY28KCjikZqZfMcC15YBeNjkgUpdCfBu5HoiOTDu86v6smE8yOjyEktJ8hlbANHQA==",
|
||||
"engines": {
|
||||
"node": ">=0.6.x"
|
||||
}
|
||||
},
|
||||
"node_modules/type-is": {
|
||||
"version": "1.6.18",
|
||||
"resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz",
|
||||
@ -1016,6 +1201,17 @@
|
||||
"node": ">= 0.6"
|
||||
}
|
||||
},
|
||||
"node_modules/uid-safe": {
|
||||
"version": "2.1.5",
|
||||
"resolved": "https://registry.npmjs.org/uid-safe/-/uid-safe-2.1.5.tgz",
|
||||
"integrity": "sha512-KPHm4VL5dDXKz01UuEd88Df+KzynaohSL9fBh096KWAxSKZQDI2uBrVqtvRM4rwrIrRRKsdLNML/lnaaVSRioA==",
|
||||
"dependencies": {
|
||||
"random-bytes": "~1.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.8"
|
||||
}
|
||||
},
|
||||
"node_modules/unpipe": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz",
|
||||
@ -1075,20 +1271,36 @@
|
||||
"integrity": "sha512-MmetCkz9ej86nJQV+sFCxoGGrUbU3q02kgLciwkrt9QqEB7cP39oKEY0PakknEO0Gu20SskMRi+AYZ3b1TpN9g=="
|
||||
},
|
||||
"@babel/parser": {
|
||||
"version": "7.18.11",
|
||||
"resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.18.11.tgz",
|
||||
"integrity": "sha512-9JKn5vN+hDt0Hdqn1PiJ2guflwP+B6Ga8qbDuoF0PzzVhrzsKIJo8yGqVk6CmMHiMei9w1C1Bp9IMJSIK+HPIQ=="
|
||||
"version": "7.18.13",
|
||||
"resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.18.13.tgz",
|
||||
"integrity": "sha512-dgXcIfMuQ0kgzLB2b9tRZs7TTFFaGM2AbtA4fJgUUYukzGH4jwsS7hzQHEGs67jdehpm22vkgKwvbU+aEflgwg=="
|
||||
},
|
||||
"@babel/types": {
|
||||
"version": "7.18.10",
|
||||
"resolved": "https://registry.npmjs.org/@babel/types/-/types-7.18.10.tgz",
|
||||
"integrity": "sha512-MJvnbEiiNkpjo+LknnmRrqbY1GPUUggjv+wQVjetM/AONoupqRALB7I6jGqNUAZsKcRIEu2J6FRFvsczljjsaQ==",
|
||||
"version": "7.18.13",
|
||||
"resolved": "https://registry.npmjs.org/@babel/types/-/types-7.18.13.tgz",
|
||||
"integrity": "sha512-ePqfTihzW0W6XAU+aMw2ykilisStJfDnsejDCXRchCcMJ4O0+8DhPXf2YUbZ6wjBlsEmZwLK/sPweWtu8hcJYQ==",
|
||||
"requires": {
|
||||
"@babel/helper-string-parser": "^7.18.10",
|
||||
"@babel/helper-validator-identifier": "^7.18.6",
|
||||
"to-fast-properties": "^2.0.0"
|
||||
}
|
||||
},
|
||||
"@unclesamulus/ebird-api": {
|
||||
"version": "0.0.0",
|
||||
"resolved": "https://registry.npmjs.org/@unclesamulus/ebird-api/-/ebird-api-0.0.0.tgz",
|
||||
"integrity": "sha512-cQca/wS+35LuuLoVVB3jDeD3czVBy1FNQBO7uubsnwH9gYZzQEr+TpCeNcMO0S9/ZIjl36fJ3qyFH6R/AsHMFA==",
|
||||
"requires": {
|
||||
"axios": "^0.27.2"
|
||||
}
|
||||
},
|
||||
"@unclesamulus/xeno-canto-api": {
|
||||
"version": "0.0.0",
|
||||
"resolved": "https://registry.npmjs.org/@unclesamulus/xeno-canto-api/-/xeno-canto-api-0.0.0.tgz",
|
||||
"integrity": "sha512-dVMpX6pbhQvxyjR4eyIMAvR9VNJ/qXnoPc+UQRnA5ESz2JcqVGxcFCByhu88Ym41/QzlfpFwO+/glsGHtzfZWw==",
|
||||
"requires": {
|
||||
"axios": "^0.27.2"
|
||||
}
|
||||
},
|
||||
"accepts": {
|
||||
"version": "1.3.8",
|
||||
"resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz",
|
||||
@ -1239,9 +1451,64 @@
|
||||
"integrity": "sha512-QADzlaHc8icV8I7vbaJXJwod9HWYp8uCqf1xa4OfNu1T7JVxQIrUgOWtHdNDtPiywmFbiS12VjotIXLrKM3orQ=="
|
||||
},
|
||||
"core-js": {
|
||||
"version": "3.24.1",
|
||||
"resolved": "https://registry.npmjs.org/core-js/-/core-js-3.24.1.tgz",
|
||||
"integrity": "sha512-0QTBSYSUZ6Gq21utGzkfITDylE8jWC9Ne1D2MrhvlsZBI1x39OdDIVbzSqtgMndIy6BlHxBXpMGqzZmnztg2rg=="
|
||||
"version": "3.25.0",
|
||||
"resolved": "https://registry.npmjs.org/core-js/-/core-js-3.25.0.tgz",
|
||||
"integrity": "sha512-CVU1xvJEfJGhyCpBrzzzU1kjCfgsGUxhEvwUV2e/cOedYWHdmluamx+knDnmhqALddMG16fZvIqvs9aijsHHaA=="
|
||||
},
|
||||
"csrf": {
|
||||
"version": "3.1.0",
|
||||
"resolved": "https://registry.npmjs.org/csrf/-/csrf-3.1.0.tgz",
|
||||
"integrity": "sha512-uTqEnCvWRk042asU6JtapDTcJeeailFy4ydOQS28bj1hcLnYRiqi8SsD2jS412AY1I/4qdOwWZun774iqywf9w==",
|
||||
"requires": {
|
||||
"rndm": "1.2.0",
|
||||
"tsscmp": "1.0.6",
|
||||
"uid-safe": "2.1.5"
|
||||
}
|
||||
},
|
||||
"csurf": {
|
||||
"version": "1.11.0",
|
||||
"resolved": "https://registry.npmjs.org/csurf/-/csurf-1.11.0.tgz",
|
||||
"integrity": "sha512-UCtehyEExKTxgiu8UHdGvHj4tnpE/Qctue03Giq5gPgMQ9cg/ciod5blZQ5a4uCEenNQjxyGuzygLdKUmee/bQ==",
|
||||
"requires": {
|
||||
"cookie": "0.4.0",
|
||||
"cookie-signature": "1.0.6",
|
||||
"csrf": "3.1.0",
|
||||
"http-errors": "~1.7.3"
|
||||
},
|
||||
"dependencies": {
|
||||
"cookie": {
|
||||
"version": "0.4.0",
|
||||
"resolved": "https://registry.npmjs.org/cookie/-/cookie-0.4.0.tgz",
|
||||
"integrity": "sha512-+Hp8fLp57wnUSt0tY0tHEXh4voZRDnoIrZPqlo3DPiI4y9lwg/jqx+1Om94/W6ZaPDOUbnjOt/99w66zk+l1Xg=="
|
||||
},
|
||||
"http-errors": {
|
||||
"version": "1.7.3",
|
||||
"resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.7.3.tgz",
|
||||
"integrity": "sha512-ZTTX0MWrsQ2ZAhA1cejAwDLycFsd7I7nVtnkT3Ol0aqodaKW+0CTZDQ1uBv5whptCnc8e8HeRRJxRs0kmm/Qfw==",
|
||||
"requires": {
|
||||
"depd": "~1.1.2",
|
||||
"inherits": "2.0.4",
|
||||
"setprototypeof": "1.1.1",
|
||||
"statuses": ">= 1.5.0 < 2",
|
||||
"toidentifier": "1.0.0"
|
||||
}
|
||||
},
|
||||
"inherits": {
|
||||
"version": "2.0.4",
|
||||
"resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz",
|
||||
"integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ=="
|
||||
},
|
||||
"setprototypeof": {
|
||||
"version": "1.1.1",
|
||||
"resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.1.1.tgz",
|
||||
"integrity": "sha512-JvdAWfbXeIGaZ9cILp38HntZSFSo3mWg6xGcJJsd+d4aRMOqauag1C63dJfDw7OaMYwEbHMOxEZ1lqVRYP2OAw=="
|
||||
},
|
||||
"statuses": {
|
||||
"version": "1.5.0",
|
||||
"resolved": "https://registry.npmjs.org/statuses/-/statuses-1.5.0.tgz",
|
||||
"integrity": "sha512-OpZ3zP+jT1PI7I8nemJX4AKmAX070ZkYPVWV/AaKTJl+tXCTGyVdC1a4SL8RUQYEwk/f34ZX8UTykN68FwrqAA=="
|
||||
}
|
||||
}
|
||||
},
|
||||
"debug": {
|
||||
"version": "2.6.9",
|
||||
@ -1271,6 +1538,11 @@
|
||||
"resolved": "https://registry.npmjs.org/doctypes/-/doctypes-1.1.0.tgz",
|
||||
"integrity": "sha512-LLBi6pEqS6Do3EKQ3J0NqHWV5hhb78Pi8vvESYwyOy2c31ZEZVdtitdzsQsKb7878PEERhzUk0ftqGhG6Mz+pQ=="
|
||||
},
|
||||
"dotenv": {
|
||||
"version": "16.0.1",
|
||||
"resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.0.1.tgz",
|
||||
"integrity": "sha512-1K6hR6wtk2FviQ4kEiSjFiH5rpzEVi8WW0x96aztHVMhEspNpc4DVOUTEHtEva5VThQ8IaBX1Pe4gSzpVVUsKQ=="
|
||||
},
|
||||
"ee-first": {
|
||||
"version": "1.1.1",
|
||||
"resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz",
|
||||
@ -1335,6 +1607,38 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"express-session": {
|
||||
"version": "1.17.3",
|
||||
"resolved": "https://registry.npmjs.org/express-session/-/express-session-1.17.3.tgz",
|
||||
"integrity": "sha512-4+otWXlShYlG1Ma+2Jnn+xgKUZTMJ5QD3YvfilX3AcocOAbIkVylSWEklzALe/+Pu4qV6TYBj5GwOBFfdKqLBw==",
|
||||
"requires": {
|
||||
"cookie": "0.4.2",
|
||||
"cookie-signature": "1.0.6",
|
||||
"debug": "2.6.9",
|
||||
"depd": "~2.0.0",
|
||||
"on-headers": "~1.0.2",
|
||||
"parseurl": "~1.3.3",
|
||||
"safe-buffer": "5.2.1",
|
||||
"uid-safe": "~2.1.5"
|
||||
},
|
||||
"dependencies": {
|
||||
"cookie": {
|
||||
"version": "0.4.2",
|
||||
"resolved": "https://registry.npmjs.org/cookie/-/cookie-0.4.2.tgz",
|
||||
"integrity": "sha512-aSWTXFzaKWkvHO1Ny/s+ePFpvKsPnjc551iI41v3ny/ow6tBG5Vd+FuqGNhh1LxOmVzOlGUriIlOaokOvhaStA=="
|
||||
},
|
||||
"depd": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz",
|
||||
"integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw=="
|
||||
},
|
||||
"safe-buffer": {
|
||||
"version": "5.2.1",
|
||||
"resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz",
|
||||
"integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ=="
|
||||
}
|
||||
}
|
||||
},
|
||||
"feather-icons": {
|
||||
"version": "4.29.0",
|
||||
"resolved": "https://registry.npmjs.org/feather-icons/-/feather-icons-4.29.0.tgz",
|
||||
@ -1522,6 +1826,11 @@
|
||||
"resolved": "https://registry.npmjs.org/leaflet/-/leaflet-1.8.0.tgz",
|
||||
"integrity": "sha512-gwhMjFCQiYs3x/Sf+d49f10ERXaEFCPr+nVTryhAW8DWbMGqJqt9G4XuIaHmFW08zYvhgdzqXGr8AlW8v8dQkA=="
|
||||
},
|
||||
"lodash": {
|
||||
"version": "4.17.21",
|
||||
"resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz",
|
||||
"integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg=="
|
||||
},
|
||||
"media-typer": {
|
||||
"version": "0.3.0",
|
||||
"resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz",
|
||||
@ -1744,6 +2053,11 @@
|
||||
"resolved": "https://registry.npmjs.org/qs/-/qs-6.5.2.tgz",
|
||||
"integrity": "sha512-N5ZAX4/LxJmF+7wN74pUD6qAh9/wnvdQcjq9TZjevvXzSUo7bfmw91saqMjzGS2xq91/odN2dW/WOl7qQHNDGA=="
|
||||
},
|
||||
"random-bytes": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/random-bytes/-/random-bytes-1.0.0.tgz",
|
||||
"integrity": "sha512-iv7LhNVO047HzYR3InF6pUcUsPQiHTM1Qal51DcGSuZFBil1aBBWG5eHPNek7bvILMaYJ/8RU1e8w1AMdHmLQQ=="
|
||||
},
|
||||
"range-parser": {
|
||||
"version": "1.2.1",
|
||||
"resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz",
|
||||
@ -1770,6 +2084,11 @@
|
||||
"supports-preserve-symlinks-flag": "^1.0.0"
|
||||
}
|
||||
},
|
||||
"rndm": {
|
||||
"version": "1.2.0",
|
||||
"resolved": "https://registry.npmjs.org/rndm/-/rndm-1.2.0.tgz",
|
||||
"integrity": "sha512-fJhQQI5tLrQvYIYFpOnFinzv9dwmR7hRnUz1XqP3OJ1jIweTNOd6aTO4jwQSgcBSFUB+/KHJxuGneime+FdzOw=="
|
||||
},
|
||||
"safe-buffer": {
|
||||
"version": "5.1.2",
|
||||
"resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz",
|
||||
@ -1836,11 +2155,21 @@
|
||||
"resolved": "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-2.0.0.tgz",
|
||||
"integrity": "sha512-/OaKK0xYrs3DmxRYqL/yDc+FxFUVYhDlXMhRmv3z915w2HF1tnN1omB354j8VUGO/hbRzyD6Y3sA7v7GS/ceog=="
|
||||
},
|
||||
"toidentifier": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.0.tgz",
|
||||
"integrity": "sha512-yaOH/Pk/VEhBWWTlhI+qXxDFXlejDGcQipMlyxda9nthulaxLZUNcUqFxokp0vcYnvteJln5FNQDRrxj3YcbVw=="
|
||||
},
|
||||
"token-stream": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/token-stream/-/token-stream-1.0.0.tgz",
|
||||
"integrity": "sha512-VSsyNPPW74RpHwR8Fc21uubwHY7wMDeJLys2IX5zJNih+OnAnaifKHo+1LHT7DAdloQ7apeaaWg8l7qnf/TnEg=="
|
||||
},
|
||||
"tsscmp": {
|
||||
"version": "1.0.6",
|
||||
"resolved": "https://registry.npmjs.org/tsscmp/-/tsscmp-1.0.6.tgz",
|
||||
"integrity": "sha512-LxhtAkPDTkVCMQjt2h6eBVY28KCjikZqZfMcC15YBeNjkgUpdCfBu5HoiOTDu86v6smE8yOjyEktJ8hlbANHQA=="
|
||||
},
|
||||
"type-is": {
|
||||
"version": "1.6.18",
|
||||
"resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz",
|
||||
@ -1850,6 +2179,14 @@
|
||||
"mime-types": "~2.1.24"
|
||||
}
|
||||
},
|
||||
"uid-safe": {
|
||||
"version": "2.1.5",
|
||||
"resolved": "https://registry.npmjs.org/uid-safe/-/uid-safe-2.1.5.tgz",
|
||||
"integrity": "sha512-KPHm4VL5dDXKz01UuEd88Df+KzynaohSL9fBh096KWAxSKZQDI2uBrVqtvRM4rwrIrRRKsdLNML/lnaaVSRioA==",
|
||||
"requires": {
|
||||
"random-bytes": "~1.0.0"
|
||||
}
|
||||
},
|
||||
"unpipe": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz",
|
||||
|
@ -6,14 +6,20 @@
|
||||
"start": "node ./bin/www"
|
||||
},
|
||||
"dependencies": {
|
||||
"@unclesamulus/ebird-api": "^0.0.0",
|
||||
"@unclesamulus/xeno-canto-api": "^0.0.0",
|
||||
"axios": "^0.27.2",
|
||||
"cookie-parser": "~1.4.4",
|
||||
"csurf": "^1.11.0",
|
||||
"debug": "~2.6.9",
|
||||
"dotenv": "^16.0.1",
|
||||
"express": "~4.16.1",
|
||||
"express-session": "^1.17.3",
|
||||
"feather-icons": "^4.29.0",
|
||||
"http-errors": "~1.6.3",
|
||||
"i18n-2": "^0.7.3",
|
||||
"leaflet": "^1.8.0",
|
||||
"lodash": "^4.17.21",
|
||||
"morgan": "~1.9.1",
|
||||
"pug": "^3.0.2"
|
||||
}
|
||||
|
49
public/javascripts/api-client.js
Normal file
49
public/javascripts/api-client.js
Normal file
@ -0,0 +1,49 @@
|
||||
|
||||
const API_VERSION = "0";
|
||||
const API_URL = `/api/${API_VERSION}`;
|
||||
|
||||
const TOKEN = document.querySelector('meta[name="csrf-token"]').getAttribute('content');
|
||||
|
||||
async function get(url, params) {
|
||||
return await axios.get(url, {
|
||||
params: params,
|
||||
headers: {
|
||||
'X-CSRF-Token': TOKEN
|
||||
}
|
||||
}).then(response => {
|
||||
return response.data;
|
||||
}).catch(error => {
|
||||
throw error;
|
||||
});
|
||||
}
|
||||
|
||||
async function query(endpoint, params) {
|
||||
return await get(`${API_URL}/${endpoint}`, params);
|
||||
}
|
||||
|
||||
function getQuizz(lat, lng) {
|
||||
return query('game/quizz', { lat, lng })
|
||||
.then(data => {
|
||||
console.log(data);
|
||||
return data;
|
||||
}).catch(error => {
|
||||
throw error;
|
||||
});
|
||||
}
|
||||
|
||||
function checkResponse(speciesId) {
|
||||
return query('game/check', {
|
||||
species: speciesId
|
||||
}).then(response => {
|
||||
return response.data.correct
|
||||
}).catch(error => {
|
||||
throw error;
|
||||
});
|
||||
}
|
||||
|
||||
const client = {
|
||||
getQuizz,
|
||||
checkResponse
|
||||
}
|
||||
|
||||
export default client;
|
@ -1,13 +1 @@
|
||||
|
||||
import { geoLocationHandler } from './map.js';
|
||||
import './game.js';
|
||||
|
||||
feather.replace();
|
||||
|
||||
(function () {
|
||||
if (document.getElementById('map') != undefined)
|
||||
geoLocationHandler();
|
||||
let start_button = document.querySelector('.game .start-button');
|
||||
if (start_button != undefined)
|
||||
start_button.addEventListener('click', launchGame);
|
||||
}());
|
||||
feather.replace();
|
@ -1,15 +1,52 @@
|
||||
const API_VERSION = "1";
|
||||
import { geolocationHandler, getCoordinates } from './map.js';
|
||||
import client from './api-client.js';
|
||||
|
||||
function launch() {
|
||||
|
||||
const API_VERSION = "0";
|
||||
|
||||
function geolocationStep() {
|
||||
if (document.getElementById('map') != undefined)
|
||||
geolocationHandler();
|
||||
let start_button = document.querySelector('.game .start-button');
|
||||
if (start_button != undefined)
|
||||
start_button.addEventListener('click', quizzStep);
|
||||
}
|
||||
|
||||
function get_new_record() {
|
||||
const endpoint = `/api/${API_VERSION}/record`;
|
||||
function quizzStep() {
|
||||
// Start by disallowing geolocation step
|
||||
document.querySelector('.game-map-step').classList.toggle('none');
|
||||
// Then allow the quizz step
|
||||
document.querySelector('.game-quizz-step').classList.remove('none');
|
||||
|
||||
// Retrieve coordinates from former done geolocation (TODO: fix the need of cookie)
|
||||
const coordinates = getCoordinates();
|
||||
client.getQuizz(coordinates)
|
||||
.then(quizz => {
|
||||
// Display the quizz
|
||||
displayQuizz(quizz);
|
||||
}).catch(error => {
|
||||
console.log(error);
|
||||
});
|
||||
}
|
||||
|
||||
const Game = {
|
||||
launch
|
||||
function displayQuizz(quizz) {
|
||||
let audio = document.querySelector('.game-quizz-step audio');
|
||||
audio.src = quizz.audio;
|
||||
audio.play();
|
||||
let proposals = document.querySelector('.game-quizz-step .proposals');
|
||||
quizz.species.forEach(sp => {
|
||||
let proposal = document.createElement('li');
|
||||
proposal.classList.add('proposal');
|
||||
let button = document.createElement('button');
|
||||
button.classList.add('proposal-button');
|
||||
button.value = sp.code;
|
||||
button.innerText = sp.comName;
|
||||
proposal.appendChild(button);
|
||||
proposals.appendChild(proposal);
|
||||
});
|
||||
}
|
||||
|
||||
export default Game;
|
||||
function game() {
|
||||
geolocationStep();
|
||||
}
|
||||
|
||||
game();
|
@ -1,6 +1,6 @@
|
||||
import { getCookie, setCookie } from './utils.js'
|
||||
|
||||
function geoLocationHandler() {
|
||||
function getCoordinates() {
|
||||
let location = [51.505, -0.09]; // London by default on leaflet
|
||||
let lat = getCookie("lat");
|
||||
let lng = getCookie("lng");
|
||||
@ -8,15 +8,25 @@ function geoLocationHandler() {
|
||||
location = [lat, lng];
|
||||
console.log(`Got a previous geolocation cookie at ${location[0]}, ${location[1]}`)
|
||||
}
|
||||
return location;
|
||||
}
|
||||
|
||||
function updateLocationCookies([lat, lng]) {
|
||||
setCookie("lat", lat, 10);
|
||||
setCookie("lng", lng, 10);
|
||||
}
|
||||
|
||||
function geolocationHandler() {
|
||||
|
||||
let location = getCoordinates();
|
||||
let message = document.querySelector('.message');
|
||||
// Init map
|
||||
let map = L.map('map').setView(location, 15);
|
||||
L.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', {
|
||||
attribution: '© <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a>'
|
||||
}).addTo(map);
|
||||
// Init marker
|
||||
let marker = L.marker(location).addTo(map);
|
||||
|
||||
let marker = undefined;
|
||||
// Get geolocation
|
||||
function getLocation() {
|
||||
if (navigator.geolocation) {
|
||||
@ -28,17 +38,41 @@ function geoLocationHandler() {
|
||||
|
||||
function setLocation(position) {
|
||||
location = [position.coords.latitude, position.coords.longitude];
|
||||
marker.setLatLng(location);
|
||||
if (marker === undefined) {
|
||||
marker = new L.marker(location, { draggable: 'true' });
|
||||
marker.on('dragend', updateMarker);
|
||||
marker.addTo(map);
|
||||
} else {
|
||||
marker.setLatLng(location);
|
||||
}
|
||||
map.setView(location, 15);
|
||||
setCookie("lat", position.coords.latitude, 10);
|
||||
setCookie("lng", position.coords.longitude, 10);
|
||||
console.log("Geolocation cookie saved for future games");
|
||||
updateLocationCookies(location);
|
||||
}
|
||||
|
||||
function updateMap(event) {
|
||||
if (marker === undefined) {
|
||||
marker = new L.marker(event.latlng, { draggable: 'true' });
|
||||
marker.on('dragend', updateMarker);
|
||||
marker.addTo(map);
|
||||
} else {
|
||||
marker.setLatLng(event.latlng);
|
||||
}
|
||||
updateLocationCookies([event.latlng.lat, event.latlng.lng]);
|
||||
}
|
||||
|
||||
function updateMarker(event) {
|
||||
let position = marker.getLatLng();
|
||||
map.panTo([position.lat, position.lng]);
|
||||
updateLocationCookies([position.lat, position.lng]);
|
||||
}
|
||||
|
||||
map.on('click', updateMap);
|
||||
|
||||
document.querySelector('.geolocation-button')
|
||||
.addEventListener('click', getLocation);
|
||||
}
|
||||
|
||||
export {
|
||||
geoLocationHandler
|
||||
geolocationHandler,
|
||||
getCoordinates
|
||||
}
|
@ -2,7 +2,7 @@ function setCookie(cname, cvalue, exdays) {
|
||||
const d = new Date();
|
||||
d.setTime(d.getTime() + (exdays * 24 * 60 * 60 * 1000));
|
||||
let expires = "expires=" + d.toUTCString();
|
||||
document.cookie = cname + "=" + cvalue + ";" + expires + ";path=/";
|
||||
document.cookie = cname + "=" + cvalue + ";" + expires + ";path=/" + ";sameSite=strict;";
|
||||
}
|
||||
|
||||
function getCookie(cname) {
|
||||
|
@ -69,3 +69,7 @@ nav ul {
|
||||
flex-direction: row;
|
||||
justify-content: space-evenly;
|
||||
}
|
||||
|
||||
.none {
|
||||
display: none;
|
||||
}
|
||||
|
14
routes/api.js
Normal file
14
routes/api.js
Normal file
@ -0,0 +1,14 @@
|
||||
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')
|
||||
.post(apiController.game.check);
|
||||
|
||||
module.exports = router;
|
11
routes/auth.js
Normal file
11
routes/auth.js
Normal file
@ -0,0 +1,11 @@
|
||||
const express = require('express');
|
||||
const router = express.Router();
|
||||
const authController = require('../controllers/auth');
|
||||
|
||||
router.route('/')
|
||||
.get(authController.indexPage);
|
||||
|
||||
router.route('/login')
|
||||
.get(authController.loginPage);
|
||||
|
||||
module.exports = router;
|
@ -1,12 +1,11 @@
|
||||
var express = require('express');
|
||||
var router = express.Router();
|
||||
var homeController = require('../controllers/home');
|
||||
/* GET home page. */
|
||||
const express = require('express');
|
||||
const router = express.Router();
|
||||
const indexController = require('../controllers/index');
|
||||
|
||||
router.route('/')
|
||||
.get(homeController.getIndex);
|
||||
.get(indexController.getIndex);
|
||||
|
||||
router.route('/about')
|
||||
.get(homeController.getAbout);
|
||||
|
||||
.get(indexController.getAbout);
|
||||
|
||||
module.exports = router;
|
||||
|
34
utils/choices.js
Normal file
34
utils/choices.js
Normal file
@ -0,0 +1,34 @@
|
||||
/**
|
||||
* Get a random selection from an array.
|
||||
*
|
||||
* @param {Array} array
|
||||
* @param {Number} number
|
||||
* @returns `number` random elements from the array
|
||||
*/
|
||||
function choices(array, number) {
|
||||
const result = [];
|
||||
for (let i = 0; i < number; i++) {
|
||||
let c = choice(array);
|
||||
if (result.includes(c)) {
|
||||
i--;
|
||||
} else {
|
||||
result.push(choice(array));
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a random element from an array.
|
||||
*
|
||||
* @param {Array} array
|
||||
* @returns a random element from the array
|
||||
*/
|
||||
function choice(array) {
|
||||
return array[Math.floor(Math.random() * array.length)];
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
choices,
|
||||
choice
|
||||
}
|
@ -1,8 +1,8 @@
|
||||
extends layout.pug
|
||||
|
||||
block content
|
||||
p #{ __("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.") }
|
||||
p #{ __("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.") }
|
||||
|
||||
h2 #{ __("Author") }
|
||||
p #{ __("The project is made with ♥ by Samuel ORTION.") }
|
||||
|
||||
h2 #{ __("Author") }
|
||||
p #{ __("The project is made with ♥ by Samuel ORTION.") }
|
||||
|
||||
|
5
views/api.pug
Normal file
5
views/api.pug
Normal file
@ -0,0 +1,5 @@
|
||||
extends layout
|
||||
|
||||
block content
|
||||
h2= __('Welcome to SoundBirder\'s API')
|
||||
.version api v#{version}
|
@ -1,6 +1,6 @@
|
||||
extends layout
|
||||
|
||||
block content
|
||||
h1= message
|
||||
h2= error.status
|
||||
pre #{error.stack}
|
||||
h1= message
|
||||
h2= error.status
|
||||
pre #{error.stack}
|
||||
|
@ -1,8 +1,18 @@
|
||||
.game
|
||||
#map
|
||||
button.button.geolocation-button
|
||||
i(data-feather="map-pin")
|
||||
button.button.start-button
|
||||
i(data-feather="play")
|
||||
link(rel="stylesheet", href="/dist/leaflet/leaflet.css")
|
||||
script(src="/dist/leaflet/leaflet.js")
|
||||
.game-map-step
|
||||
#map
|
||||
button.button.geolocation-button
|
||||
i(data-feather="map-pin")
|
||||
button.button.start-button
|
||||
i(data-feather="play")
|
||||
.game-quizz-step.none
|
||||
ul.proposals
|
||||
audio
|
||||
.game-result-step.none
|
||||
p.result
|
||||
button.button.restart-button.disabled
|
||||
i(data-feather="repeat")
|
||||
link(rel="stylesheet" href="/dist/leaflet/leaflet.css")
|
||||
script(src="/dist/leaflet/leaflet.js")
|
||||
script(src="/dist/axios/axios.min.js")
|
||||
script(src="/javascripts/game.js" type="module")
|
@ -1,4 +1,4 @@
|
||||
extends layout
|
||||
|
||||
block content
|
||||
include game
|
||||
include game
|
||||
|
@ -1,26 +1,26 @@
|
||||
doctype html
|
||||
html
|
||||
head
|
||||
title= title
|
||||
meta(name="iewport", content="width=device-width, initial-scale=1.0")
|
||||
link(rel="stylesheet", href="/stylesheets/style.css")
|
||||
body
|
||||
header
|
||||
h1= title
|
||||
nav
|
||||
ul
|
||||
li
|
||||
a(href="/") #{ __("Game") }
|
||||
li
|
||||
a(href="/about") #{ __("About") }
|
||||
li
|
||||
a(href="/contact") #{ __("Contact") }
|
||||
main
|
||||
block content
|
||||
footer
|
||||
.description
|
||||
.copyright Copyright © 2022 -
|
||||
span.author
|
||||
a(href="https://samuel.ortion.fr", class="link") Samuel ORTION
|
||||
script(src="/javascripts/app.js" type="module")
|
||||
script(src="/dist/feather/feather.min.js")
|
||||
head
|
||||
title= title
|
||||
meta(name="viewport" content="width=device-width, initial-scale=1.0")
|
||||
link(rel="stylesheet" href="/stylesheets/style.css")
|
||||
if csrf_token
|
||||
meta(name="csrf-token" content=csrf_token)
|
||||
body
|
||||
header
|
||||
h1= title
|
||||
nav
|
||||
ul
|
||||
li
|
||||
a(href="/") #{ __("Game") }
|
||||
li
|
||||
a(href="/about") #{ __("About") }
|
||||
main
|
||||
block content
|
||||
footer
|
||||
.description
|
||||
.copyright Copyright © 2022 -
|
||||
span.author
|
||||
a(href="https://samuel.ortion.fr" class="link") Samuel ORTION
|
||||
script(src="/javascripts/app.js" type="module")
|
||||
script(src="/dist/feather/feather.min.js")
|
Loading…
Reference in New Issue
Block a user