Rest api updated
This commit is contained in:
parent
d551dea425
commit
eebbdda064
7 changed files with 591 additions and 205 deletions
322
imports/api/Rest/Rest.js
Normal file
322
imports/api/Rest/Rest.js
Normal file
|
|
@ -0,0 +1,322 @@
|
||||||
|
/* global Restivus */
|
||||||
|
/* eslint-disable import/no-absolute-path */
|
||||||
|
|
||||||
|
import { Meteor } from 'meteor/meteor';
|
||||||
|
import { NumberBetween } from '/imports/modules/server/other-checks';
|
||||||
|
import Fires from '/imports/api/Fires/Fires';
|
||||||
|
import { check } from 'meteor/check';
|
||||||
|
import SiteSettings from '/imports/api/SiteSettings/SiteSettings';
|
||||||
|
import ActiveFiresCollection from '/imports/api/ActiveFires/ActiveFires';
|
||||||
|
import { countRealFires } from '/imports/api/ActiveFires/server/countFires';
|
||||||
|
import { whichAreFalsePositives, firesUnion } from '/imports/api/FalsePositives/server/publications';
|
||||||
|
import FalsePositives from '/imports/api/FalsePositives/FalsePositives';
|
||||||
|
import Industries from '/imports/api/Industries/Industries';
|
||||||
|
import Subscriptions from '/imports/api/Subscriptions/Subscriptions';
|
||||||
|
import jsend from 'jsend';
|
||||||
|
import { Random } from 'meteor/random';
|
||||||
|
import { subscriptionsInsert } from '/imports/api/Subscriptions/methods.js';
|
||||||
|
|
||||||
|
const debug = false;
|
||||||
|
|
||||||
|
const uptime = new Date();
|
||||||
|
|
||||||
|
|
||||||
|
function fail(e) {
|
||||||
|
return jsend.error(`Unexpected error in REST call: ${e}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
function defaultFailParams(e) {
|
||||||
|
return jsend.error(`Wrong REST params: ${e}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
function checkAuthToken(token) {
|
||||||
|
if (token !== Meteor.settings.private.internalApiToken) {
|
||||||
|
const message = `Unauthorized auth token '${token}' in REST API`;
|
||||||
|
console.warn(message);
|
||||||
|
return jsend.error(message);
|
||||||
|
}
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
function checkLatLonDist(km, lat, lng) {
|
||||||
|
check(lng, NumberBetween(-180, 180));
|
||||||
|
check(lat, NumberBetween(-90, 90));
|
||||||
|
check(km, NumberBetween(0, Meteor.isDevelopment ? 1000 : 100));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// export
|
||||||
|
const apiV1 = new Restivus({
|
||||||
|
useDefaultAuth: true,
|
||||||
|
apiPath: 'api',
|
||||||
|
version: 'v1',
|
||||||
|
prettyJson: true
|
||||||
|
});
|
||||||
|
|
||||||
|
// Generates: POST on /api/users and GET, DELETE /api/users/:id for
|
||||||
|
// Meteor.users collection
|
||||||
|
/* apiV1.addCollection(Meteor.users, {
|
||||||
|
* excludedEndpoints: ['getAll', 'put', 'delete', 'patch', 'get'],
|
||||||
|
* routeOptions: {
|
||||||
|
* authRequired: true
|
||||||
|
* },
|
||||||
|
* endpoints: {
|
||||||
|
* post: {
|
||||||
|
* authRequired: false
|
||||||
|
* },
|
||||||
|
* delete: {
|
||||||
|
* roleRequired: 'admin'
|
||||||
|
* }
|
||||||
|
* }
|
||||||
|
* }); */
|
||||||
|
|
||||||
|
apiV1.addCollection(Fires, {
|
||||||
|
excludedEndpoints: ['put', 'post', 'patch', 'delete'],
|
||||||
|
// excludedEndpoints: ['getAll', 'put', 'post', 'patch', 'delete'],
|
||||||
|
routeOptions: {
|
||||||
|
authRequired: false
|
||||||
|
},
|
||||||
|
endpoints: {
|
||||||
|
get: {
|
||||||
|
action: function getFire() {
|
||||||
|
return Fires.findOne(new Meteor.Collection.ObjectID(this.urlParams.id));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Maps to: /api/v1/status/last-fire-check
|
||||||
|
apiV1.addRoute('status/last-fire-check', { authRequired: false }, {
|
||||||
|
get: function get() {
|
||||||
|
return SiteSettings.findOne({ name: 'last-fire-check' });
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Maps to: /api/v1/status/last-fire-detected
|
||||||
|
apiV1.addRoute('status/last-fire-detected', { authRequired: false }, {
|
||||||
|
get: function get() {
|
||||||
|
return ActiveFiresCollection.findOne({}, { sort: { when: -1 } });
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Maps to: /api/v1/status/active-fires-count
|
||||||
|
apiV1.addRoute('status/active-fires-count', { authRequired: false }, {
|
||||||
|
get: function get() {
|
||||||
|
return { total: ActiveFiresCollection.find({}).count() };
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Maps to: /api/v1/status/uptime
|
||||||
|
apiV1.addRoute('status/uptime', { authRequired: false }, {
|
||||||
|
get: function get() {
|
||||||
|
return { ms: new Date() - uptime };
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
function getFires(route, full) {
|
||||||
|
const lat = Number(route.urlParams.lat);
|
||||||
|
const lng = Number(route.urlParams.lng);
|
||||||
|
const km = Number(route.urlParams.km);
|
||||||
|
const { token } = route.urlParams;
|
||||||
|
try {
|
||||||
|
checkLatLonDist(km, lat, lng);
|
||||||
|
check(token, String);
|
||||||
|
} catch (e) {
|
||||||
|
return defaultFailParams(e);
|
||||||
|
}
|
||||||
|
|
||||||
|
const failed = checkAuthToken(token);
|
||||||
|
if (failed) return failed;
|
||||||
|
|
||||||
|
if (debug) console.log(`Query for fires in ${lat}, ${lng} in ${km} km radius`);
|
||||||
|
|
||||||
|
const fires = ActiveFiresCollection.find({
|
||||||
|
ourid: {
|
||||||
|
$near: {
|
||||||
|
$geometry: {
|
||||||
|
type: 'Point',
|
||||||
|
coordinates: [lng, lat]
|
||||||
|
},
|
||||||
|
$maxDistance: km * 1000,
|
||||||
|
$minDistance: 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}, {
|
||||||
|
fields: {
|
||||||
|
lat: 1,
|
||||||
|
lon: 1,
|
||||||
|
when: 1,
|
||||||
|
scan: 1
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
const result = { total: fires.count(), real: countRealFires(fires) };
|
||||||
|
|
||||||
|
if (!full) {
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO only get real
|
||||||
|
const firesA = fires.fetch();
|
||||||
|
if (firesA.length > 0) {
|
||||||
|
const union = firesUnion(fires);
|
||||||
|
const falsePos = whichAreFalsePositives(FalsePositives, union);
|
||||||
|
const industries = whichAreFalsePositives(Industries, union);
|
||||||
|
result.fires = firesA;
|
||||||
|
result.industries = industries.fetch();
|
||||||
|
result.falsePos = falsePos.fetch();
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
// otherwise
|
||||||
|
return {
|
||||||
|
total: 0, real: 0, fires: [], falsePos: [], industries: []
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// Maps to: /api/v1/fires-in/:lat/:lng/:km
|
||||||
|
// 100 km max
|
||||||
|
// Ex: http://127.0.0.1:3000/api/v1/fires-in/token/38.736946/-9.142685/100
|
||||||
|
apiV1.addRoute('fires-in/:token/:lat/:lng/:km', { authRequired: false }, {
|
||||||
|
get: function get() {
|
||||||
|
return getFires(this, false);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
apiV1.addRoute('fires-in-full/:token/:lat/:lng/:km', { authRequired: false }, {
|
||||||
|
get: function get() {
|
||||||
|
return getFires(this, true);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Add mobile user:
|
||||||
|
// curl -X POST http://localhost:3000/api/v1/users/mobile -d "token: thisAppAutToken" -d "mobileToken: user-mobile-firebase-token"
|
||||||
|
// Response:
|
||||||
|
//
|
||||||
|
// https://docs.meteor.com/api/passwords.html#Accounts-createUser
|
||||||
|
apiV1.addRoute('mobile/users', { authRequired: false }, {
|
||||||
|
post: function post() {
|
||||||
|
const { token, mobileToken, lang } = this.bodyParams;
|
||||||
|
try {
|
||||||
|
check(token, String);
|
||||||
|
check(lang, String);
|
||||||
|
check(mobileToken, String);
|
||||||
|
} catch (e) {
|
||||||
|
return defaultFailParams(e);
|
||||||
|
}
|
||||||
|
|
||||||
|
const failed = checkAuthToken(token);
|
||||||
|
if (failed) return failed;
|
||||||
|
|
||||||
|
let username;
|
||||||
|
|
||||||
|
do {
|
||||||
|
username = Random.id(15);
|
||||||
|
} while (Meteor.users.find({ username }).count() !== 0);
|
||||||
|
|
||||||
|
// FIXME check valid lang
|
||||||
|
|
||||||
|
const now = new Date();
|
||||||
|
// Accounts.createUser({ username: mobileToken, lang: 'FIXME' profile: {} });
|
||||||
|
const result = Meteor.users.upsert({
|
||||||
|
fireBaseToken: mobileToken
|
||||||
|
}, {
|
||||||
|
$set: {
|
||||||
|
username,
|
||||||
|
fireBaseToken: mobileToken,
|
||||||
|
lang,
|
||||||
|
profile: { },
|
||||||
|
createdAt: now,
|
||||||
|
updatedAt: now
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
if (debug) {
|
||||||
|
console.log(this.bodyParams);
|
||||||
|
console.log(this.urlParams);
|
||||||
|
console.log(this.queryParams);
|
||||||
|
}
|
||||||
|
|
||||||
|
const newUser = Meteor.users.findOne({ username });
|
||||||
|
|
||||||
|
return jsend.success({
|
||||||
|
upsertResult: result,
|
||||||
|
username,
|
||||||
|
userId: newUser._id,
|
||||||
|
lang: newUser.lang,
|
||||||
|
mobileToken: newUser.fireBaseToken
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
// max sitance: 100km
|
||||||
|
apiV1.addRoute('mobile/subscriptions', { authRequired: false }, {
|
||||||
|
post: function post() {
|
||||||
|
const {
|
||||||
|
token,
|
||||||
|
mobileToken,
|
||||||
|
lat,
|
||||||
|
lon,
|
||||||
|
distance
|
||||||
|
} = this.bodyParams;
|
||||||
|
try {
|
||||||
|
check(token, String);
|
||||||
|
check(mobileToken, String);
|
||||||
|
check(lat, Number);
|
||||||
|
check(lon, Number);
|
||||||
|
check(distance, Number);
|
||||||
|
checkLatLonDist(distance, lat, lon);
|
||||||
|
} catch (e) {
|
||||||
|
return defaultFailParams(e);
|
||||||
|
}
|
||||||
|
|
||||||
|
const failed = checkAuthToken(token);
|
||||||
|
if (failed) return failed;
|
||||||
|
|
||||||
|
const user = Meteor.users.findOne({ fireBaseToken: mobileToken });
|
||||||
|
if (!user) return failed;
|
||||||
|
|
||||||
|
const newSubs = {};
|
||||||
|
newSubs.location = {};
|
||||||
|
newSubs.location.lat = lat;
|
||||||
|
newSubs.location.lon = lon;
|
||||||
|
newSubs.distance = distance;
|
||||||
|
|
||||||
|
let result;
|
||||||
|
try {
|
||||||
|
result = subscriptionsInsert(newSubs, user._id, 'mobile');
|
||||||
|
} catch (e) {
|
||||||
|
return fail(e);
|
||||||
|
}
|
||||||
|
return jsend.success({ subsId: result._str });
|
||||||
|
},
|
||||||
|
delete: function del() {
|
||||||
|
const {
|
||||||
|
token,
|
||||||
|
mobileToken,
|
||||||
|
subsId
|
||||||
|
} = this.bodyParams;
|
||||||
|
try {
|
||||||
|
check(token, String);
|
||||||
|
check(mobileToken, String);
|
||||||
|
check(subsId, String);
|
||||||
|
} catch (e) {
|
||||||
|
return defaultFailParams(e);
|
||||||
|
}
|
||||||
|
|
||||||
|
const failed = checkAuthToken(token);
|
||||||
|
if (failed) return failed;
|
||||||
|
|
||||||
|
const user = Meteor.users.findOne({ fireBaseToken: mobileToken });
|
||||||
|
if (!user) return failed;
|
||||||
|
|
||||||
|
try {
|
||||||
|
Subscriptions.remove({ owner: user._id, _id: new Meteor.Collection.ObjectID(subsId) });
|
||||||
|
} catch (e) {
|
||||||
|
return fail(e);
|
||||||
|
}
|
||||||
|
|
||||||
|
return jsend.success({});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
@ -10,30 +10,48 @@ function geo(doc) {
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function subscriptionsInsert(doc, userId, type) {
|
||||||
|
check(doc, {
|
||||||
|
location: Match.ObjectIncluding({ lat: Number, lon: Number }),
|
||||||
|
distance: Number
|
||||||
|
});
|
||||||
|
const newDoc = {
|
||||||
|
owner: userId,
|
||||||
|
type,
|
||||||
|
geo: geo(doc),
|
||||||
|
...doc
|
||||||
|
};
|
||||||
|
// console.log(newDoc);
|
||||||
|
const already = Subscriptions.findOne(newDoc);
|
||||||
|
if (already) {
|
||||||
|
throw new Meteor.Error('on-already-subscribed', 'The user is already subscribed to this area');
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
return Subscriptions.insert(newDoc);
|
||||||
|
} catch (exception) {
|
||||||
|
// console.error(exception);
|
||||||
|
throw new Meteor.Error('500', exception);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export function subscriptionsRemove(subscriptionId) {
|
||||||
|
check(subscriptionId, Meteor.Collection.ObjectID);
|
||||||
|
|
||||||
|
try {
|
||||||
|
return Subscriptions.remove(subscriptionId);
|
||||||
|
} catch (exception) {
|
||||||
|
console.error(exception);
|
||||||
|
throw new Meteor.Error('500', exception);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
Meteor.methods({
|
Meteor.methods({
|
||||||
'subscriptions.insert': function subscriptionsInsert(doc) {
|
'subscriptions.insert': function subscriptionInsertMethod(doc) {
|
||||||
check(doc, {
|
check(doc, {
|
||||||
location: Match.ObjectIncluding({ lat: Number, lon: Number }),
|
location: Match.ObjectIncluding({ lat: Number, lon: Number }),
|
||||||
distance: Number
|
distance: Number
|
||||||
});
|
});
|
||||||
const type = 'web';
|
return subscriptionsInsert(doc, this.userId, 'web');
|
||||||
const newDoc = {
|
|
||||||
owner: this.userId,
|
|
||||||
type,
|
|
||||||
geo: geo(doc),
|
|
||||||
...doc
|
|
||||||
};
|
|
||||||
// console.log(newDoc);
|
|
||||||
const already = Subscriptions.findOne(newDoc);
|
|
||||||
if (already) {
|
|
||||||
throw new Meteor.Error('on-already-subscribed', 'The user is already subscribed to this area');
|
|
||||||
}
|
|
||||||
try {
|
|
||||||
return Subscriptions.insert(newDoc);
|
|
||||||
} catch (exception) {
|
|
||||||
// console.error(exception);
|
|
||||||
throw new Meteor.Error('500', exception);
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
'subscriptions.update': function subscriptionsUpdate(doc) {
|
'subscriptions.update': function subscriptionsUpdate(doc) {
|
||||||
check(doc, {
|
check(doc, {
|
||||||
|
|
@ -52,15 +70,9 @@ Meteor.methods({
|
||||||
throw new Meteor.Error('500', exception);
|
throw new Meteor.Error('500', exception);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
'subscriptions.remove': function subscriptionsRemove(subscriptionId) {
|
'subscriptions.remove': function subscriptionsRemoveMethod(subscriptionId) {
|
||||||
check(subscriptionId, Meteor.Collection.ObjectID);
|
check(subscriptionId, Meteor.Collection.ObjectID);
|
||||||
|
return subscriptionsRemove(subscriptionId);
|
||||||
try {
|
|
||||||
return Subscriptions.remove(subscriptionId);
|
|
||||||
} catch (exception) {
|
|
||||||
console.error(exception);
|
|
||||||
throw new Meteor.Error('500', exception);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -107,6 +107,7 @@ const schemaUser = new SimpleSchema({
|
||||||
telegramUsername: { type: String, optional: true },
|
telegramUsername: { type: String, optional: true },
|
||||||
telegramFirstName: { type: String, optional: true },
|
telegramFirstName: { type: String, optional: true },
|
||||||
telegramLanguageCode: { type: String, optional: true },
|
telegramLanguageCode: { type: String, optional: true },
|
||||||
|
fireBaseToken: { type: String, optional: true },
|
||||||
createdAt: defaultCreatedAt,
|
createdAt: defaultCreatedAt,
|
||||||
updatedAt: defaultUpdateAt
|
updatedAt: defaultUpdateAt
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -1,161 +1 @@
|
||||||
/* global Restivus */
|
import '../../api/Rest/Rest.js';
|
||||||
/* eslint-disable import/no-absolute-path */
|
|
||||||
|
|
||||||
import { Meteor } from 'meteor/meteor';
|
|
||||||
import { NumberBetween } from '/imports/modules/server/other-checks';
|
|
||||||
import Fires from '/imports/api/Fires/Fires';
|
|
||||||
import { check } from 'meteor/check';
|
|
||||||
import SiteSettings from '/imports/api/SiteSettings/SiteSettings';
|
|
||||||
import ActiveFiresCollection from '/imports/api/ActiveFires/ActiveFires';
|
|
||||||
import { countRealFires } from '/imports/api/ActiveFires/server/countFires';
|
|
||||||
import { whichAreFalsePositives, firesUnion } from '/imports/api/FalsePositives/server/publications';
|
|
||||||
import FalsePositives from '/imports/api/FalsePositives/FalsePositives';
|
|
||||||
import Industries from '/imports/api/Industries/Industries';
|
|
||||||
|
|
||||||
Meteor.startup(() => {
|
|
||||||
const uptime = new Date();
|
|
||||||
|
|
||||||
const apiV1 = new Restivus({
|
|
||||||
useDefaultAuth: true,
|
|
||||||
apiPath: 'api',
|
|
||||||
version: 'v1',
|
|
||||||
prettyJson: true
|
|
||||||
});
|
|
||||||
|
|
||||||
// Generates: POST on /api/users and GET, DELETE /api/users/:id for
|
|
||||||
// Meteor.users collection
|
|
||||||
/* apiV1.addCollection(Meteor.users, {
|
|
||||||
* excludedEndpoints: ['getAll', 'put', 'delete', 'patch', 'get'],
|
|
||||||
* routeOptions: {
|
|
||||||
* authRequired: true
|
|
||||||
* },
|
|
||||||
* endpoints: {
|
|
||||||
* post: {
|
|
||||||
* authRequired: false
|
|
||||||
* },
|
|
||||||
* delete: {
|
|
||||||
* roleRequired: 'admin'
|
|
||||||
* }
|
|
||||||
* }
|
|
||||||
* }); */
|
|
||||||
|
|
||||||
apiV1.addCollection(Fires, {
|
|
||||||
excludedEndpoints: ['put', 'post', 'patch', 'delete'],
|
|
||||||
// excludedEndpoints: ['getAll', 'put', 'post', 'patch', 'delete'],
|
|
||||||
routeOptions: {
|
|
||||||
authRequired: false
|
|
||||||
},
|
|
||||||
endpoints: {
|
|
||||||
get: {
|
|
||||||
action: function getFire() {
|
|
||||||
return Fires.findOne(new Meteor.Collection.ObjectID(this.urlParams.id));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
// Maps to: /api/v1/status/last-fire-check
|
|
||||||
apiV1.addRoute('status/last-fire-check', { authRequired: false }, {
|
|
||||||
get: function get() {
|
|
||||||
return SiteSettings.findOne({ name: 'last-fire-check' });
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
// Maps to: /api/v1/status/last-fire-detected
|
|
||||||
apiV1.addRoute('status/last-fire-detected', { authRequired: false }, {
|
|
||||||
get: function get() {
|
|
||||||
return ActiveFiresCollection.findOne({}, { sort: { when: -1 } });
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
// Maps to: /api/v1/status/last-fires-count
|
|
||||||
apiV1.addRoute('status/active-fires-count', { authRequired: false }, {
|
|
||||||
get: function get() {
|
|
||||||
return { total: ActiveFiresCollection.find({}).count() };
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
// Maps to: /api/v1/status/uptime
|
|
||||||
apiV1.addRoute('status/uptime', { authRequired: false }, {
|
|
||||||
get: function get() {
|
|
||||||
return { ms: new Date() - uptime };
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
function getFires(route, full) {
|
|
||||||
const lat = Number(route.urlParams.lat);
|
|
||||||
const lng = Number(route.urlParams.lng);
|
|
||||||
const km = Number(route.urlParams.km);
|
|
||||||
const { token } = route.urlParams;
|
|
||||||
check(lng, NumberBetween(-180, 180));
|
|
||||||
check(lat, NumberBetween(-90, 90));
|
|
||||||
check(km, NumberBetween(0, Meteor.isDevelopment ? 1000 : 100));
|
|
||||||
check(token, String);
|
|
||||||
|
|
||||||
if (token !== Meteor.settings.private.internalApiToken) {
|
|
||||||
console.warn(`WARNING: Query for fires in ${lat}, ${lng} in ${km} km radius with wrong token`);
|
|
||||||
return { error: 'Unauthorized' };
|
|
||||||
}
|
|
||||||
|
|
||||||
console.log(`Query for fires in ${lat}, ${lng} in ${km} km radius`);
|
|
||||||
|
|
||||||
const fires = ActiveFiresCollection.find({
|
|
||||||
ourid: {
|
|
||||||
$near: {
|
|
||||||
$geometry: {
|
|
||||||
type: 'Point',
|
|
||||||
coordinates: [lng, lat]
|
|
||||||
},
|
|
||||||
$maxDistance: km * 1000,
|
|
||||||
$minDistance: 0
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}, {
|
|
||||||
fields: {
|
|
||||||
lat: 1,
|
|
||||||
lon: 1,
|
|
||||||
when: 1,
|
|
||||||
scan: 1
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
const result = { total: fires.count(), real: countRealFires(fires) };
|
|
||||||
|
|
||||||
if (!full) {
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO only get real
|
|
||||||
const firesA = fires.fetch();
|
|
||||||
if (firesA.length > 0) {
|
|
||||||
const union = firesUnion(fires);
|
|
||||||
const falsePos = whichAreFalsePositives(FalsePositives, union);
|
|
||||||
const industries = whichAreFalsePositives(Industries, union);
|
|
||||||
result.fires = firesA;
|
|
||||||
result.industries = industries.fetch();
|
|
||||||
result.falsePos = falsePos.fetch();
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
// otherwise
|
|
||||||
return {
|
|
||||||
total: 0, real: 0, fires: [], falsePos: [], industries: []
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
// Maps to: /api/v1/fires-in/:lat/:lng/:km
|
|
||||||
// 100 km max
|
|
||||||
// Ex: http://127.0.0.1:3000/api/v1/fires-in/token/38.736946/-9.142685/100
|
|
||||||
apiV1.addRoute('fires-in/:token/:lat/:lng/:km', { authRequired: false }, {
|
|
||||||
get: function get() {
|
|
||||||
return getFires(this, false);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
apiV1.addRoute('fires-in-full/:token/:lat/:lng/:km', { authRequired: false }, {
|
|
||||||
get: function get() {
|
|
||||||
return getFires(this, true);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
|
||||||
25
package-lock.json
generated
25
package-lock.json
generated
|
|
@ -6206,7 +6206,6 @@
|
||||||
"hawk": {
|
"hawk": {
|
||||||
"version": "3.1.3",
|
"version": "3.1.3",
|
||||||
"bundled": true,
|
"bundled": true,
|
||||||
"dev": true,
|
|
||||||
"requires": {
|
"requires": {
|
||||||
"boom": "2.10.1",
|
"boom": "2.10.1",
|
||||||
"cryptiles": "2.0.5",
|
"cryptiles": "2.0.5",
|
||||||
|
|
@ -6383,7 +6382,6 @@
|
||||||
"nopt": {
|
"nopt": {
|
||||||
"version": "4.0.1",
|
"version": "4.0.1",
|
||||||
"bundled": true,
|
"bundled": true,
|
||||||
"dev": true,
|
|
||||||
"optional": true,
|
"optional": true,
|
||||||
"requires": {
|
"requires": {
|
||||||
"abbrev": "1.1.0",
|
"abbrev": "1.1.0",
|
||||||
|
|
@ -6440,7 +6438,6 @@
|
||||||
"osenv": {
|
"osenv": {
|
||||||
"version": "0.1.4",
|
"version": "0.1.4",
|
||||||
"bundled": true,
|
"bundled": true,
|
||||||
"dev": true,
|
|
||||||
"optional": true,
|
"optional": true,
|
||||||
"requires": {
|
"requires": {
|
||||||
"os-homedir": "1.0.2",
|
"os-homedir": "1.0.2",
|
||||||
|
|
@ -6449,30 +6446,25 @@
|
||||||
},
|
},
|
||||||
"path-is-absolute": {
|
"path-is-absolute": {
|
||||||
"version": "1.0.1",
|
"version": "1.0.1",
|
||||||
"bundled": true,
|
"bundled": true
|
||||||
"dev": true
|
|
||||||
},
|
},
|
||||||
"performance-now": {
|
"performance-now": {
|
||||||
"version": "0.2.0",
|
"version": "0.2.0",
|
||||||
"bundled": true,
|
"bundled": true,
|
||||||
"dev": true,
|
|
||||||
"optional": true
|
"optional": true
|
||||||
},
|
},
|
||||||
"process-nextick-args": {
|
"process-nextick-args": {
|
||||||
"version": "1.0.7",
|
"version": "1.0.7",
|
||||||
"bundled": true,
|
"bundled": true
|
||||||
"dev": true
|
|
||||||
},
|
},
|
||||||
"punycode": {
|
"punycode": {
|
||||||
"version": "1.4.1",
|
"version": "1.4.1",
|
||||||
"bundled": true,
|
"bundled": true,
|
||||||
"dev": true,
|
|
||||||
"optional": true
|
"optional": true
|
||||||
},
|
},
|
||||||
"qs": {
|
"qs": {
|
||||||
"version": "6.4.0",
|
"version": "6.4.0",
|
||||||
"bundled": true,
|
"bundled": true,
|
||||||
"dev": true,
|
|
||||||
"optional": true
|
"optional": true
|
||||||
},
|
},
|
||||||
"rc": {
|
"rc": {
|
||||||
|
|
@ -6489,7 +6481,6 @@
|
||||||
"minimist": {
|
"minimist": {
|
||||||
"version": "1.2.0",
|
"version": "1.2.0",
|
||||||
"bundled": true,
|
"bundled": true,
|
||||||
"dev": true,
|
|
||||||
"optional": true
|
"optional": true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -6545,31 +6536,26 @@
|
||||||
},
|
},
|
||||||
"safe-buffer": {
|
"safe-buffer": {
|
||||||
"version": "5.0.1",
|
"version": "5.0.1",
|
||||||
"bundled": true,
|
"bundled": true
|
||||||
"dev": true
|
|
||||||
},
|
},
|
||||||
"semver": {
|
"semver": {
|
||||||
"version": "5.3.0",
|
"version": "5.3.0",
|
||||||
"bundled": true,
|
"bundled": true,
|
||||||
"dev": true,
|
|
||||||
"optional": true
|
"optional": true
|
||||||
},
|
},
|
||||||
"set-blocking": {
|
"set-blocking": {
|
||||||
"version": "2.0.0",
|
"version": "2.0.0",
|
||||||
"bundled": true,
|
"bundled": true,
|
||||||
"dev": true,
|
|
||||||
"optional": true
|
"optional": true
|
||||||
},
|
},
|
||||||
"signal-exit": {
|
"signal-exit": {
|
||||||
"version": "3.0.2",
|
"version": "3.0.2",
|
||||||
"bundled": true,
|
"bundled": true,
|
||||||
"dev": true,
|
|
||||||
"optional": true
|
"optional": true
|
||||||
},
|
},
|
||||||
"sntp": {
|
"sntp": {
|
||||||
"version": "1.0.9",
|
"version": "1.0.9",
|
||||||
"bundled": true,
|
"bundled": true,
|
||||||
"dev": true,
|
|
||||||
"requires": {
|
"requires": {
|
||||||
"hoek": "2.16.3"
|
"hoek": "2.16.3"
|
||||||
}
|
}
|
||||||
|
|
@ -10021,6 +10007,11 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"jsend": {
|
||||||
|
"version": "1.0.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/jsend/-/jsend-1.0.2.tgz",
|
||||||
|
"integrity": "sha1-ld99RvvM8fLpVnQ0j5R36TWmyaU="
|
||||||
|
},
|
||||||
"jsesc": {
|
"jsesc": {
|
||||||
"version": "1.3.0",
|
"version": "1.3.0",
|
||||||
"resolved": "https://registry.npmjs.org/jsesc/-/jsesc-1.3.0.tgz",
|
"resolved": "https://registry.npmjs.org/jsesc/-/jsesc-1.3.0.tgz",
|
||||||
|
|
|
||||||
|
|
@ -37,6 +37,7 @@
|
||||||
"ismobilejs": "^0.4.1",
|
"ismobilejs": "^0.4.1",
|
||||||
"jquery": "^2.2.4",
|
"jquery": "^2.2.4",
|
||||||
"jquery-validation": "^1.17.0",
|
"jquery-validation": "^1.17.0",
|
||||||
|
"jsend": "^1.0.2",
|
||||||
"juice": "^4.2.2",
|
"juice": "^4.2.2",
|
||||||
"leaflet": "^1.3.1",
|
"leaflet": "^1.3.1",
|
||||||
"leaflet-geodesy": "^0.2.1",
|
"leaflet-geodesy": "^0.2.1",
|
||||||
|
|
|
||||||
219
test/rest.test.js
Normal file
219
test/rest.test.js
Normal file
|
|
@ -0,0 +1,219 @@
|
||||||
|
/* eslint-disable import/no-absolute-path */
|
||||||
|
/* eslint-env mocha */
|
||||||
|
/* eslint-disable func-names, prefer-arrow-callback */
|
||||||
|
|
||||||
|
import { chai } from 'meteor/practicalmeteor:chai';
|
||||||
|
import { Meteor } from 'meteor/meteor';
|
||||||
|
import { HTTP } from 'meteor/http';
|
||||||
|
import { Accounts } from 'meteor/accounts-base';
|
||||||
|
import Subscriptions from '/imports/api/Subscriptions/Subscriptions';
|
||||||
|
|
||||||
|
function url(path) {
|
||||||
|
return `http://127.0.0.1:3000/${path}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
describe('basic api v1 returns', () => {
|
||||||
|
it('should return uptime', async done =>
|
||||||
|
HTTP.get(url('api/v1/status/uptime'), {
|
||||||
|
data: {
|
||||||
|
}
|
||||||
|
}, (error, result) => {
|
||||||
|
chai.expect(error, null);
|
||||||
|
chai.expect(result.statusCode).to.equal(200);
|
||||||
|
chai.expect(typeof result.data.ms).to.equal('number');
|
||||||
|
done();
|
||||||
|
}));
|
||||||
|
|
||||||
|
it('should return last-fire', async done =>
|
||||||
|
HTTP.get(url('api/v1/status/active-fires-count'), {
|
||||||
|
data: {
|
||||||
|
}
|
||||||
|
}, (error, result) => {
|
||||||
|
chai.expect(error, null);
|
||||||
|
chai.expect(result.statusCode).equal(200);
|
||||||
|
chai.expect(typeof result.data.total).to.equal('number');
|
||||||
|
done();
|
||||||
|
}));
|
||||||
|
|
||||||
|
const token = Meteor.settings.private.internalApiToken;
|
||||||
|
|
||||||
|
it('should return fires in some location', async done =>
|
||||||
|
HTTP.get(url(`api/v1/fires-in/${token}/38.736946/-9.142685/100`), {
|
||||||
|
data: {
|
||||||
|
}
|
||||||
|
}, (error, result) => {
|
||||||
|
chai.expect(error, null);
|
||||||
|
chai.expect(result.statusCode).equal(200);
|
||||||
|
chai.expect(typeof result.data.total).to.equal('number');
|
||||||
|
chai.expect(typeof result.data.real).to.equal('number');
|
||||||
|
done();
|
||||||
|
}));
|
||||||
|
|
||||||
|
it('should return full fires in some location', async done =>
|
||||||
|
HTTP.get(url(`api/v1/fires-in-full/${token}/38.736946/-9.142685/100`), {
|
||||||
|
data: {
|
||||||
|
}
|
||||||
|
}, (error, result) => {
|
||||||
|
chai.expect(error, null);
|
||||||
|
chai.expect(result.statusCode).equal(200);
|
||||||
|
chai.expect(typeof result.data.total).to.equal('number');
|
||||||
|
chai.expect(typeof result.data.real).to.equal('number');
|
||||||
|
chai.expect(typeof result.data.falsePos).to.equal('object');
|
||||||
|
chai.expect(typeof result.data.industries).to.equal('object');
|
||||||
|
chai.expect(typeof result.data.fires).to.equal('object');
|
||||||
|
done();
|
||||||
|
}));
|
||||||
|
|
||||||
|
it('should not return full fires with some wrong token', async done =>
|
||||||
|
HTTP.get(url('api/v1/fires-in-full/token/38.736946/-9.142685/100'), {
|
||||||
|
data: {
|
||||||
|
}
|
||||||
|
}, (error, result) => {
|
||||||
|
chai.expect(error, null);
|
||||||
|
chai.expect(result.data.status).equal('error');
|
||||||
|
chai.expect(result.statusCode).equal(200);
|
||||||
|
done();
|
||||||
|
}));
|
||||||
|
|
||||||
|
it('should not return fires with some wrong token', async done =>
|
||||||
|
HTTP.get(url('api/v1/fires-in/token/38.736946/-9.142685/100'), {
|
||||||
|
data: {
|
||||||
|
}
|
||||||
|
}, (error, result) => {
|
||||||
|
chai.expect(error, null);
|
||||||
|
chai.expect(result.data.status).equal('error');
|
||||||
|
chai.expect(result.statusCode).equal(200);
|
||||||
|
done();
|
||||||
|
}));
|
||||||
|
|
||||||
|
it('should not return fires with some wrong distance', async done =>
|
||||||
|
HTTP.get(url(`api/v1/fires-in-full/${token}/38.736946/-9.142685/1100`), (error, result) => {
|
||||||
|
chai.expect(error, null);
|
||||||
|
chai.expect(result.data.status).equal('error');
|
||||||
|
chai.expect(result.statusCode).equal(200);
|
||||||
|
done();
|
||||||
|
}));
|
||||||
|
|
||||||
|
const mobileToken = 'user-mobile-firebase-token';
|
||||||
|
|
||||||
|
let testUserId;
|
||||||
|
|
||||||
|
it('should create mobile users', async (done) => {
|
||||||
|
// this are removed in the test database but we are testing in dev/ci database
|
||||||
|
|
||||||
|
// Delete the test account if it's still present
|
||||||
|
// Meteor.users.remove({ fireBaseToken: mobileToken });
|
||||||
|
|
||||||
|
HTTP.post(url('api/v1/mobile/users'), {
|
||||||
|
data: {
|
||||||
|
token,
|
||||||
|
mobileToken,
|
||||||
|
lang: 'en'
|
||||||
|
}
|
||||||
|
}, (error, result) => {
|
||||||
|
chai.expect(error, null);
|
||||||
|
const jsendResult = result.data;
|
||||||
|
chai.expect(jsendResult.status).equal('success');
|
||||||
|
chai.expect(result.statusCode).equal(200);
|
||||||
|
chai.expect(jsendResult.data.upsertResult.numberAffected).to.equal(1);
|
||||||
|
chai.expect(jsendResult.data.mobileToken).to.equal(mobileToken);
|
||||||
|
chai.expect(jsendResult.data.lang).to.equal('en');
|
||||||
|
chai.expect(typeof jsendResult.data.username).to.equal('string');
|
||||||
|
testUserId = jsendResult.data.userId;
|
||||||
|
chai.expect(typeof testUserId).to.equal('string');
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should not create mobile users with wrong token', async (done) => {
|
||||||
|
// this are removed in the test database but we are testing in dev/ci database
|
||||||
|
// Delete the test account if it's still present
|
||||||
|
// Meteor.users.remove({ fireBaseToken: mobileToken });
|
||||||
|
|
||||||
|
HTTP.post(url('api/v1/mobile/users'), {
|
||||||
|
data: {
|
||||||
|
token: 'wrong token',
|
||||||
|
mobileToken,
|
||||||
|
lang: 'en'
|
||||||
|
}
|
||||||
|
}, (error, result) => {
|
||||||
|
chai.expect(error, null);
|
||||||
|
chai.expect(result.data.status).equal('error');
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
let subsId;
|
||||||
|
|
||||||
|
it('should create mobile user subscriptions', async (done) => {
|
||||||
|
// this are removed in the test database but we are testing in dev/ci database
|
||||||
|
|
||||||
|
// Remove previous subs of this user
|
||||||
|
// Subscriptions.remove({ owner: testUserId });
|
||||||
|
// chai.expect(Subscriptions.find({ owner: testUserId }).count()).to.equal(0);
|
||||||
|
HTTP.post(url('api/v1/mobile/subscriptions'), {
|
||||||
|
data: {
|
||||||
|
token,
|
||||||
|
mobileToken,
|
||||||
|
lat: 3.106111,
|
||||||
|
lon: 53.5775,
|
||||||
|
distance: 100
|
||||||
|
}
|
||||||
|
}, (error, result) => {
|
||||||
|
chai.expect(error, null);
|
||||||
|
chai.expect(result.statusCode).equal(200);
|
||||||
|
const jsendResult = result.data;
|
||||||
|
chai.expect(jsendResult.status).equal('success');
|
||||||
|
subsId = jsendResult.data.subsId;
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should remove mobile user subscriptions', async (done) => {
|
||||||
|
// this are removed in the test database but we are testing in dev/ci database
|
||||||
|
|
||||||
|
HTTP.del(url('api/v1/mobile/subscriptions'), {
|
||||||
|
data: {
|
||||||
|
token,
|
||||||
|
mobileToken,
|
||||||
|
subsId
|
||||||
|
}
|
||||||
|
}, (error, result) => {
|
||||||
|
chai.expect(error, null);
|
||||||
|
chai.expect(result.statusCode).equal(200);
|
||||||
|
const jsendResult = result.data;
|
||||||
|
chai.expect(jsendResult.status).equal('success');
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should not create mobile user subscriptions with wrong token', async (done) => {
|
||||||
|
HTTP.post(url('api/v1/mobile/subscriptions'), {
|
||||||
|
data: {
|
||||||
|
token: 'wrongOne',
|
||||||
|
mobileToken,
|
||||||
|
lat: 3.106111,
|
||||||
|
lon: 53.5775,
|
||||||
|
distance: 100
|
||||||
|
}
|
||||||
|
}, (error, result) => {
|
||||||
|
chai.expect(error, null);
|
||||||
|
chai.expect(result.data.status).equal('error');
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should not remove mobile user subscriptions with wrong token', async (done) => {
|
||||||
|
HTTP.del(url('api/v1/mobile/subscriptions'), {
|
||||||
|
data: {
|
||||||
|
token: 'wrongOne',
|
||||||
|
mobileToken,
|
||||||
|
subsId
|
||||||
|
}
|
||||||
|
}, (error, result) => {
|
||||||
|
chai.expect(error, null);
|
||||||
|
chai.expect(result.data.status).equal('error');
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
Loading…
Add table
Add a link
Reference in a new issue