quality: web debt batch (tests runner, FireContainer, rate-limit, maps, i18n)
Independent-of-prod quality debt from PENDIENTE.md §2: - test: migrate broken Jest -> meteortesting:mocha. Tests rewritten to chai + Meteor 3 async APIs, moved to test/server/ (server-only). test/server/ 00-setup.test.js re-runs the collection2/accounts init that `meteor test` skips (no server/main.js). New comments method + mediaAnalyzers coverage. Dropped rest.test.js (removed meteor/http; covered by smoke/). 36 passing. - fix: scope FireContainer read by the URL _id on the archive route instead of a selector-less FiresCollection.findOne() (imports/ui/pages/Fires/Fires.js). - feat: rate-limit abusable publications via rateLimitSubscriptions (fireFrom* and comments.forReference 5/1000ms; geo subs 10/1000ms). - perf: append loading=async to the Google Maps loader URL (Gkeys.js). - deps: meteor-accounts-t9n 2.0 -> 2.6 (no gl build -> keep gl->es fallback, documented); add 3 missing gl/common.json keys (0 missing now). - deps: drop jest/babel/enzyme, add chai.
This commit is contained in:
parent
bc778bfd97
commit
6b6f02d92d
27 changed files with 602 additions and 7042 deletions
|
|
@ -9,3 +9,15 @@ export default ({ methods, limit, timeRange }) => {
|
|||
}, limit, timeRange);
|
||||
}
|
||||
};
|
||||
|
||||
// Same idea for subscriptions. DDPRateLimiter matches subscribe calls only when
|
||||
// the rule pins `type: 'subscription'`, so this is a separate helper.
|
||||
export const rateLimitSubscriptions = ({ subscriptions, limit, timeRange }) => {
|
||||
if (Meteor.isServer) {
|
||||
DDPRateLimiter.addRule({
|
||||
type: 'subscription',
|
||||
name(name) { return subscriptions.indexOf(name) > -1; },
|
||||
connectionId() { return true; },
|
||||
}, limit, timeRange);
|
||||
}
|
||||
};
|
||||
|
|
|
|||
|
|
@ -13,6 +13,11 @@ class GkeysC {
|
|||
// console.log(google.maps);
|
||||
GoogleMapsLoader.KEY = key;
|
||||
GoogleMapsLoader.LIBRARIES = ['places'];
|
||||
// The google-maps npm package (v3.2.1) builds the script URL without
|
||||
// `loading=async`, which Google now warns about. It exposes no hook for
|
||||
// extra params, so wrap createUrl to append it (no node_modules edit).
|
||||
const origCreateUrl = GoogleMapsLoader.createUrl;
|
||||
GoogleMapsLoader.createUrl = () => `${origCreateUrl()}&loading=async`;
|
||||
GoogleMapsLoader.load(() => {
|
||||
self.gmapkey.set(key);
|
||||
console.log('GMaps script just loaded');
|
||||
|
|
|
|||
|
|
@ -51,6 +51,8 @@ if (sendMissing && Meteor.isDevelopment) {
|
|||
}
|
||||
|
||||
function setT9(lang) {
|
||||
// meteor-accounts-t9n has no Galician build, so use Spanish for gl account
|
||||
// error messages (the closest shipped language). See common/i18n.js.
|
||||
if (lang === 'gl') {
|
||||
T9n.setLanguage('es');
|
||||
} else {
|
||||
|
|
|
|||
|
|
@ -3,7 +3,9 @@ import moment from 'moment';
|
|||
// Load the js langs
|
||||
import es from 'meteor-accounts-t9n/build/es';
|
||||
import en from 'meteor-accounts-t9n/build/en';
|
||||
// TODO ask for translation of this
|
||||
// meteor-accounts-t9n (2.6.0) ships no Galician build (build/gl.js), so account
|
||||
// error messages fall back to Spanish for gl — see setT9() in client/i18n.js.
|
||||
// Re-enable this import if the upstream package ever adds a gl translation.
|
||||
// import gl from 'meteor-accounts-t9n/build/gl';
|
||||
|
||||
const backOpts = {
|
||||
|
|
|
|||
|
|
@ -15,8 +15,6 @@ import '../../api/FireAlerts/server/publications';
|
|||
import '../../api/Subscriptions/methods';
|
||||
import '../../api/Subscriptions/server/publications';
|
||||
|
||||
// TODO add rate-limit to these publications
|
||||
|
||||
import '../../api/Notifications/methods';
|
||||
import '../../api/Notifications/server/publications';
|
||||
|
||||
|
|
@ -28,3 +26,26 @@ import '../../api/SiteSettings/server/publications';
|
|||
|
||||
import '../../api/FalsePositives/methods';
|
||||
import '../../api/FalsePositives/server/publications';
|
||||
|
||||
import { rateLimitSubscriptions } from '../../modules/rate-limit';
|
||||
|
||||
// Rate-limit the abusable publications (the old TODO above). Single-fire
|
||||
// lookups and the comments feed are cheap but brute-forceable, so keep them
|
||||
// tight; the geo subs fire on every map pan/zoom, so give them more headroom.
|
||||
rateLimitSubscriptions({
|
||||
subscriptions: [
|
||||
'fireFromHash', 'fireFromAlertId', 'fireFromActiveId', 'fireFromId',
|
||||
'comments.forReference'
|
||||
],
|
||||
limit: 5,
|
||||
timeRange: 1000
|
||||
});
|
||||
|
||||
rateLimitSubscriptions({
|
||||
subscriptions: [
|
||||
'activefiresmyloc', 'activefiresunionmyloc', 'fireAlerts',
|
||||
'falsePositivesMyloc', 'industriesMyloc'
|
||||
],
|
||||
limit: 10,
|
||||
timeRange: 1000
|
||||
});
|
||||
|
|
|
|||
|
|
@ -290,7 +290,22 @@ const FireContainer = withTracker(({ match }) => {
|
|||
// console.log(`Type of '${fireType}' fire, active: ${active}, archive: ${archive}, fromHash: ${fromHash}`);
|
||||
// console.log(`Subs ready: ${subscription.ready()}, fire: ${JSON.stringify(FiresCollection.findOne())}`);
|
||||
const loading = !subscription.ready();
|
||||
const notfound = !loading && FiresCollection.find().count() === 0;
|
||||
// Scope the client-side read to the fire named in the URL. Only the archive
|
||||
// route's param is a Fires `_id` (as 24-char hex, per hexId()); active/alert
|
||||
// params are ActiveFire/Alert ids and hash is an encrypted blob, so their
|
||||
// publications each put exactly one Fires doc in minimongo and an empty
|
||||
// selector is the correct read. Scoping archive by _id stops a lingering doc
|
||||
// from a previously-viewed fire being returned by a selector-less findOne().
|
||||
let selector = {};
|
||||
if (archive && id) {
|
||||
try {
|
||||
selector = new Meteor.Collection.ObjectID(id);
|
||||
} catch (e) {
|
||||
selector = {};
|
||||
}
|
||||
}
|
||||
const fire = FiresCollection.findOne(selector);
|
||||
const notfound = !loading && !fire;
|
||||
/* console.log(`loading fire: ${loading}`);
|
||||
* console.log(`Not found fire: ${notfound}`); */
|
||||
const falsePositives = FalsePositivesCollection.find().fetch().map(falsePositivesRemap);
|
||||
|
|
@ -302,9 +317,9 @@ const FireContainer = withTracker(({ match }) => {
|
|||
fromHash,
|
||||
falsePositives,
|
||||
industries,
|
||||
fire: FiresCollection.findOne(),
|
||||
fire,
|
||||
notfound,
|
||||
when: subscription.ready() && FiresCollection.findOne() ? FiresCollection.findOne().when : null
|
||||
when: fire ? fire.when : null
|
||||
};
|
||||
})(Fire);
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue