sotlas-api/server.js

355 wiersze
9.3 KiB
JavaScript

const express = require('express');
const config = require('./config');
const assert = require('assert');
const app = express();
const expressWs = require('express-ws')(app);
const cacheControl = require('express-cache-controller');
const bearerToken = require('express-bearer-token');
const wsManager = require('./ws-manager');
const SotaSpotReceiver = require('./sotaspots');
const RbnReceiver = require('./rbn');
const db = require('./db');
const alerts = require('./alerts');
const geoexport = require('./geoexport');
const users = require('./users');
const activations = require('./activations');
const utils = require('./utils');
const photos_router = require('./photos_router');
const solardata = require('./solardata');
const maxmind = require('maxmind');
const cronjobs = require('./cronjobs');
const moment = require('moment');
let geoLookup;
import('geolite2-redist').then((geolite2) => {
return geolite2.open(
'GeoLite2-City',
(dbPath) => maxmind.open(dbPath)
)
}).then((reader) => {
geoLookup = reader;
});
let dbChecker = (req, res, next) => {
if (db.getDb() == null) {
console.error('DB error');
res.status(500).end();
return;
}
next();
};
app.enable('trust proxy');
app.use(express.json());
app.use(dbChecker);
app.use(cacheControl({
maxAge: 3600
}));
app.use(bearerToken());
app.use('/ws', wsManager.router);
app.use('/alerts', alerts);
app.use('/geoexport', geoexport);
app.use('/activations', activations);
app.use('/users', users);
app.use('/photos', photos_router);
app.use('/solardata', solardata);
db.waitDb(() => {
let sotaSpotReceiver = new SotaSpotReceiver();
sotaSpotReceiver.start();
let rbnReceiver = new RbnReceiver();
rbnReceiver.start();
})
cronjobs();
app.get('/summits/search', (req, res) => {
let limit = 100;
if (req.query.limit) {
let limitOverride = parseInt(req.query.limit);
if (limitOverride > 0 && limitOverride < limit) {
limit = limitOverride;
}
}
db.getDb().collection('summits').find({
$or: [{code: {'$regex': req.query.q, '$options': 'i'}}, {name: {'$regex': req.query.q, '$options': 'i'}}, {nameNd: {'$regex': req.query.q, '$options': 'i'}}],
retired: {$in: [null, false]}
}, {projection: {'_id': false, 'photos': false, 'routes': false, 'links': false, 'resources': false}}).limit(limit).toArray((err, summits) => {
if (err) {
console.error(err);
res.status(500).end();
return;
}
res.json(summits);
});
});
app.get('/summits/near', (req, res) => {
let limit = 100;
if (req.query.limit) {
let limitOverride = parseInt(req.query.limit);
if (limitOverride > 0 && limitOverride < limit) {
limit = limitOverride;
}
}
let query = {
coordinates: {$near: {$geometry: {type: "Point", coordinates: [parseFloat(req.query.lon), parseFloat(req.query.lat)]}}},
retired: {$in: [null, false]}
};
if (req.query.maxDistance) {
query.coordinates.$near.$maxDistance = parseFloat(req.query.maxDistance);
}
if (!req.query.inactive) {
query.validFrom = {$lte: new Date()};
query.validTo = {$gte: new Date()};
}
db.getDb().collection('summits').find(query, {projection: {'_id': false, 'photos': false, 'routes': false, 'links': false, 'resources': false}}).limit(limit).toArray((err, summits) => {
if (err) {
console.error(err);
res.status(500).end();
return;
}
res.json(summits);
});
});
app.get('/summits/recent_photos/:associations/:days', (req, res) => {
let limit = 100;
let days = req.params.days;
if (req.query.limit) {
let limitOverride = parseInt(req.query.limit);
if (limitOverride > 0 && limitOverride < limit) {
limit = limitOverride;
}
}
let query = {
"photos.uploadDate": {$gte: new Date((new Date().getTime() - (days * 24 * 60 * 60 * 1000)))}
}
if (/^([A-Z0-9]{1,3}\|?)+$/.test(req.params.associations)) {
query.code = {$regex: new RegExp("^(" + req.params.associations + ")/")};
}
db.getDb().collection('summits').find(query, {projection: {'_id': false, 'routes': false, 'links': false, 'resources': false}}).sort({"photos.uploadDate": -1}).limit(limit).toArray((err, summits) => {
if (err) {
console.error(err);
res.status(500).end();
return;
}
res.json(summits);
});
});
app.get('/summits/:association/:code', (req, res) => {
res.cacheControl = {
noCache: true
};
db.getDb().collection('summits').findOne({code: req.params.association + '/' + req.params.code, retired: {$in: [null, false]}}, {projection: {'_id': false}}, (err, summit) => {
if (err) {
console.error(err);
res.status(500).end();
return;
}
if (!summit) {
res.status(404).end();
return;
}
let associationCode = summit.code.substring(0, summit.code.indexOf('/'));
db.getDb().collection('associations').findOne({code: associationCode}, (err, association) => {
if (association) {
summit.isoCode = association.isoCode;
summit.continent = association.continent;
}
res.json(summit);
});
});
});
// Dummy POST endpoint to help browser invalidate cache after uploading photos
app.post('/summits/:association/:code', (req, res) => {
res.cacheControl = {
noCache: true
};
res.status(204).end();
});
app.get('/associations/all', (req, res) => {
db.getDb().collection('associations').find({}, {projection: {'_id': false}}).toArray((err, associations) => {
if (err) {
console.error(err);
res.status(500).end();
return;
}
res.json(associations);
});
});
app.get('/associations/:association', (req, res) => {
db.getDb().collection('associations').findOne({code: req.params.association}, {projection: {'_id': false}}, (err, association) => {
if (err) {
console.error(err);
res.status(500).end();
return;
}
if (!association) {
res.status(404).end();
return;
}
res.json(association);
});
});
app.get('/regions/:association/:region', (req, res) => {
let region = req.params.association + '/' + req.params.region;
if (!region.match(/^[A-Z0-9]+\/[A-Z0-9]+$/)) {
res.status(400).end();
return;
}
db.getDb().collection('summits').find({code: {'$regex': '^' + region}, retired: {$in: [null, false]}}, {projection: {'_id': false, 'routes': false, 'links': false, 'resources': false}}).toArray((err, summits) => {
if (err) {
console.error(err);
res.status(500).end();
return;
}
summits.forEach(summit => {
if (summit.photos && summit.photos.length > 0) {
summit.hasPhotos = true;
}
delete summit.photos;
});
res.json(summits);
});
});
app.get('/activators/search', (req, res) => {
let skip = 0;
if (req.query.skip) {
skip = parseInt(req.query.skip);
}
let limit = 100;
if (req.query.limit) {
if (parseInt(req.query.limit) <= limit) {
limit = parseInt(req.query.limit);
}
}
let sortField = 'score';
let sortDirection = -1;
if (req.query.sort === 'callsign' || req.query.sort === 'points' || req.query.sort === 'bonusPoints' || req.query.sort === 'score' || req.query.sort === 'summits' || req.query.sort === 'avgPoints') {
sortField = req.query.sort;
}
if (req.query.sortDirection == 'desc') {
sortDirection = -1;
} else {
sortDirection = 1;
}
let sort = {};
sort[sortField] = sortDirection;
let query = {};
if (req.query.q !== undefined && req.query.q !== '') {
query = {callsign: {'$regex': req.query.q, '$options': 'i'}};
}
db.getDb().collection('activators').countDocuments(query, (err, count) => {
if (err) {
console.error(err);
res.status(500).end();
return;
}
let cursor = db.getDb().collection('activators').find(query, {projection: {'_id': false}}).sort(sort);
cursor.skip(skip).limit(limit).toArray((err, activators) => {
res.json({activators, total: count});
cursor.close();
});
});
});
app.get('/activators/:callsign', (req, res) => {
let query = {callsign: req.params.callsign}
if (/^[0-9]+$/.test(req.params.callsign)) {
// User ID
query = {userId: parseInt(req.params.callsign)}
}
db.getDb().collection('activators').findOne(query, {projection: {'_id': false}}, (err, activator) => {
if (err) {
console.error(err);
res.status(500).end();
return;
}
if (!activator) {
// Try alternative variations
db.getDb().collection('activators').findOne({callsign: { $in: utils.makeCallsignVariations(req.params.callsign) }}, {projection: {'_id': false}}, (err, activator) => {
if (activator) {
res.json(activator);
} else {
res.status(404).end();
}
});
return;
}
res.json(activator);
});
});
app.get('/map_server', (req, res) => {
if (!geoLookup) {
res.status(503).end();
return;
}
let mapServer = 'us';
let geo = geoLookup.get(req.ip);
if (geo.continent.code === 'AF' || geo.continent.code === 'EU') {
mapServer = 'eu';
}
res.json({mapServer});
});
app.get('/my_coordinates', (req, res) => {
if (!geoLookup) {
res.status(503).end();
return;
}
let geo = geoLookup.get(req.ip);
if (!geo) {
res.json({});
} else {
res.json({latitude: geo.location.latitude, longitude: geo.location.longitude});
}
});
app.get('/my_country', (req, res) => {
if (!geoLookup) {
res.status(503).end();
return;
}
let geo = geoLookup.get(req.ip);
if (!geo) {
res.json({});
} else {
res.json({country: geo.country.iso_code});
}
});
app.post('/mapsession', (req, res) => {
let type = req.body.type;
if (!type) {
type = 'unknown';
}
let date = moment().format('YYYY-MM-DD');
db.getDb().collection('mapsessions').updateOne({date, type}, {"$inc": {"count": 1}}, {upsert: true});
res.json({});
});
app.listen(config.http.port, config.http.host);