diff --git a/.gitignore b/.gitignore index 603fb12..eeb3622 100644 --- a/.gitignore +++ b/.gitignore @@ -12,8 +12,3 @@ public/locales/undefined/ .screenshots .vscode output.json - -# fase-0 higiene: variantes de settings con secretos (no versionar) -settings-production-*.json -settings-tests.json -*.json~ diff --git a/README.md b/README.md index 4417536..e686902 100644 --- a/README.md +++ b/README.md @@ -33,19 +33,14 @@ TEST_PORT=3000 TEST_WATCH=1 TEST_CLIENT=0 MONGO_URL=mongodb://localhost:27017/fu # and -node_modules/.bin/chimp --watch --ddp=http://localhost:3000 --path=cucumber +chimp --watch --ddp=http://localhost:3000 --path=cucumber # and -node_modules/.bin/chimp --ddp=http://localhost:3000 --path=cucumber +chimp --ddp=http://localhost:3000 --path=cucumber ``` -### FAQ & Troubleshooting - -**Q** - I get something like `(...) /node_modules/fibers/future.js:280 (...) Error: Could not locate the bindings file.` What can I do? -**A** - Try `meteor npm rebuild` - ## Data source acknowledgements *We acknowledge the use of data and imagery from LANCE FIRMS operated by the NASA/GSFC/Earth Science Data and Information System (ESDIS) with funding provided by NASA/HQ*. @@ -56,5 +51,4 @@ GNU APLv3. See our [LICENSE](https://github.com/comunes/todos-contra-el-fuego-we ## Thanks & other acknowlegments -Thanks indeed to: -- Lupe Bao Reixa for Galician translation +(...) diff --git a/cucumber/chimp-config.js b/cucumber/chimp-config.js deleted file mode 100644 index 7ec19dc..0000000 --- a/cucumber/chimp-config.js +++ /dev/null @@ -1,29 +0,0 @@ -module.exports = { - // - - - - CHIMP - - - - - /* watch: false, - * watchTags: '@watch', - * offline: false, - */ - // - - - - CUCUMBER - - - - - /* path: './features', - * jsonOutput: 'output.json', */ - - // '- - - - DEBUGGING - - - - - log: 'info', - debug: false, - seleniumDebug: false, - webdriverLogLevel: false, - // debugBrkCucumber: 5858, - // - - - - WEBDRIVER-IO - - - - - webdriverio: { - waitforTimeout: 10000, - waitforInterval: 250, // KEEP SMALL (!!!) this is the INTERVAL in which waitFor* is looped fo - desiredCapabilities: { - chromeOptions: { - // args: ["headless", "disable-gpu"] - args: ['--disable-gpu', '--no-sandbox', '--headless'] - }, - isHeadless: true - } - } -}; diff --git a/cucumber/features/pages.feature b/cucumber/features/pages.feature index 0807c2c..fd2193b 100644 --- a/cucumber/features/pages.feature +++ b/cucumber/features/pages.feature @@ -40,6 +40,7 @@ Feature: Test all secundary pages | fire/Fe26.2**1a0361ed0384f741403682e26b4bbc3850bee24e775da13c9782b22365ea895f*r_3gmVad5vzkeyqPpo6UcA*1s5fFz3iDGKTYP2RCvXMshof00QCHf4ErDl9K2dxoX0u-J-t6scyOWG8pGp3ehg_FfEtyR_kYcEKU3rE0jaSlZbD09TIvhiIJeS3C6Uc8YD-rit0XBrgsVKfYSxKzTRoOYiYFJ8JYd298hMtfiASePjS05Z58hhicyCcJYYRlarqDScG3LiVY3lL5y2nfcdIMNuSjCiKOJWuMkxwd9nR1UHMudLl0hEoy56mPdnHpDYtP9IYUlIOk1LlWBxcmHKifbXeqHu94p8j13Kk20dh2R49Hw3KsSoE9UbWmGQA9wAZXT82301i3rGF5GPAKjlTlRYcWisQurnPwHSVmx3DhUdiYwKGxt4KeaM5QVI4BE9octvE41OOprB_-Il105diQEh2Y9vdvX51ZVWIRfCboICPM6rJb0Oin7U7F1iM-oD_5s3DGnelfM5LGBcKwiB5paMo5M5vdBMaO-zR216cW9yGVXw9IZqHx8xDQWnoHAZjt8NLHeiGF2QOmIGtEUH7qnwhGpkcvszajmAZzR8saZgoH1qfBfvpVA41YfV14gU**4d030f05e23ad75409cebc609107467fc60be5077d52cf041087cd024fc4dc45*GY97aGFc1MyAsoO3Qxqtgwk9j-MbAPdEGBmEHq6r8VU | Additional information | false | Then I check that all page urls works properly + @watch Scenario: Check that other non visible pages work well Given I logout if logged And a list of non visible pages ids and contents diff --git a/imports/api/ActiveFires/ActiveFires.js b/imports/api/ActiveFires/ActiveFires.js index 16e2760..79437af 100644 --- a/imports/api/ActiveFires/ActiveFires.js +++ b/imports/api/ActiveFires/ActiveFires.js @@ -1,6 +1,5 @@ /* eslint-disable consistent-return */ -import { Meteor } from 'meteor/meteor'; import { Mongo } from 'meteor/mongo'; import SimpleSchema from 'simpl-schema'; import firesCommonSchema from '../Common/FiresSchema'; @@ -19,11 +18,8 @@ ActiveFires.deny({ remove: () => true }); -const activeFiresSchema = firesCommonSchema; -activeFiresSchema.fireUnion = { type: Meteor.Collection.ObjectID, optional: true, blackbox: true }; - -ActiveFires.schema = new SimpleSchema(activeFiresSchema); +ActiveFires.schema = new SimpleSchema(firesCommonSchema); ActiveFires.attachSchema(ActiveFires.schema); diff --git a/imports/api/ActiveFires/server/countFires.js b/imports/api/ActiveFires/server/countFires.js index b3b8511..39008da 100644 --- a/imports/api/ActiveFires/server/countFires.js +++ b/imports/api/ActiveFires/server/countFires.js @@ -32,9 +32,7 @@ const findFiresInRegion = (zone) => { export const countRealFires = (firesCursor) => { const realFires = []; firesCursor.forEach((fire) => { - if (debug) console.log(`${JSON.stringify(fire)} -----`); const union = firesUnion([fire]); - if (debug) console.log(`${JSON.stringify(union)} -----`); const falsePos = whichAreFalsePositives(FalsePositives, union); const industries = whichAreFalsePositives(Industries, union); if (falsePos.count() === 0 && industries.count() === 0) { diff --git a/imports/api/ActiveFires/server/publications.js b/imports/api/ActiveFires/server/publications.js index 03878b3..21e4e5a 100644 --- a/imports/api/ActiveFires/server/publications.js +++ b/imports/api/ActiveFires/server/publications.js @@ -31,8 +31,7 @@ const activefires = (northEastLng, northEastLat, southWestLng, southWestLat, wit lat: 1, lon: 1, when: 1, - scan: 1, - track: 1 + scan: 1 } }); diff --git a/imports/api/ActiveFiresUnion/ActiveFiresUnion.js b/imports/api/ActiveFiresUnion/ActiveFiresUnion.js deleted file mode 100644 index 2437e54..0000000 --- a/imports/api/ActiveFiresUnion/ActiveFiresUnion.js +++ /dev/null @@ -1,71 +0,0 @@ -/* eslint-disable consistent-return */ -/* eslint-disable import/no-absolute-path */ - -import { Mongo } from 'meteor/mongo'; -import SimpleSchema from 'simpl-schema'; -import LocationSchema from '/imports/api/Utility/LocationSchema.js'; -import { defaultCreatedAt, defaultUpdateAt } from '/imports/api/Utility/Utils.js'; - -const ActiveFiresUnion = new Mongo.Collection('activefiresunion', { idGeneration: 'MONGO' }); - -/* Sample: - * - * TODO - * - * */ - -// We have to store polygons and their centroids -// {"type":"Polygon","coordinates":[[[-5.8645,43.5524],[-5.8621,43.5524],[-5.8621,43.5546],[-5.8645,43.5546],[-5.8645,43.5524]]]} - -ActiveFiresUnion.allow({ - insert: () => false, - update: () => false, - remove: () => false -}); - -ActiveFiresUnion.deny({ - insert: () => true, - update: () => true, - remove: () => true -}); - -// https://www.npmjs.com/package/simpl-schema -const GeoJsonSchema = new SimpleSchema({ - type: { - type: String, - allowedValues: ['Polygon'] - }, - coordinates: { - type: Array - }, - 'coordinates.$': { - type: Array - }, - 'coordinates.$.$': { - type: Array - }, - 'coordinates.$.$.$': { - type: Number - } -}); - -const firesUnionSchema = { - centerid: LocationSchema, - - // https://docs.mongodb.com/manual/reference/geojson/ - // : { type: , coordinates: } - shape: GeoJsonSchema, - history: { type: Array, optional: true }, - 'history.$': GeoJsonSchema, - - when: Date, // First date - - createdAt: defaultCreatedAt, - updatedAt: defaultUpdateAt -}; - -ActiveFiresUnion.schema = new SimpleSchema(firesUnionSchema); - -ActiveFiresUnion.attachSchema(ActiveFiresUnion.schema); - -export default ActiveFiresUnion; diff --git a/imports/api/ActiveFiresUnion/server/publications.js b/imports/api/ActiveFiresUnion/server/publications.js deleted file mode 100644 index 6a126d8..0000000 --- a/imports/api/ActiveFiresUnion/server/publications.js +++ /dev/null @@ -1,80 +0,0 @@ -/* global Counter */ -/* eslint-disable import/no-absolute-path */ -/* eslint-disable prefer-arrow-callback */ - -import { Meteor } from 'meteor/meteor'; -import { check } from 'meteor/check'; -import { NumberBetween } from '/imports/modules/server/other-checks'; -// import { whichAreFalsePositives, firesUnion } from '/imports/api/FalsePositives/server/publications'; -// import FalsePositives from '/imports/api/FalsePositives/FalsePositives'; -// import Industries from '/imports/api/Industries/Industries'; -import ActiveFiresUnion from '../ActiveFiresUnion'; - -const counter = new Counter('countActiveFiresUnion', ActiveFiresUnion.find({})); - -Meteor.publish('activefiresuniontotal', function total() { - return counter; -}); - -const activeFiresUnion = (b1, b2, c1, c2, withMarks) => { - // a --- b - // c --- d - const geometry = { - $geometry: { - type: 'Polygon', - coordinates: [[ - [c1, c2], - [c1, b2], - [b1, b2], - [b1, c2], - [c1, c2] - ]] - } - }; - // console.log(JSON.stringify(geometry)); - const fires = ActiveFiresUnion.find({ - shape: { - $geoIntersects: geometry - } - }, { - fields: { - shape: 1, - centerid: 1, - when: 1, - createdAt: 1, - updatedAt: 1 - } - }); - - if (Meteor.isDevelopment) console.log(`Active fires union total: ${fires.count()}`); - - /* - if (withMarks && fires.fetch().length > 0) { - const union = firesUnion(fires); - const falsePos = whichAreFalsePositives(FalsePositives, union); - const industries = whichAreFalsePositives(Industries, union); - return [fires, falsePos, industries]; - } */ - - return fires; -}; - -Meteor.publish('activefiresunionmyloc', function activeInMyLoc(northEastLng, northEastLat, southWestLng, southWestLat, withMarks) { - // latitude -90 and 90 and the longitude between -180 and 180 - check(northEastLng, NumberBetween(-180, 180)); - check(southWestLat, NumberBetween(-90, 90)); - check(southWestLng, NumberBetween(-180, 180)); - check(northEastLat, NumberBetween(-90, 90)); - check(withMarks, Boolean); - - // I use this for fire stats - // if (!Meteor.isDevelopment) return this.ready(); // empty - return activeFiresUnion(northEastLng, northEastLat, southWestLng, southWestLat, withMarks); -}); - -// Warning: this increase always by one the fire stats -Meteor.publish('lastFireUnionDetected', function lastFireDetected() { - // I use this for fire stats - // if (!Meteor.isDevelopment) return this.ready(); // empty - return ActiveFiresUnion.find({}, { limit: 1, sort: { when: -1 } }); -}); diff --git a/imports/api/FalsePositives/server/publications.js b/imports/api/FalsePositives/server/publications.js index a79ae1a..1816344 100644 --- a/imports/api/FalsePositives/server/publications.js +++ b/imports/api/FalsePositives/server/publications.js @@ -5,8 +5,7 @@ import { Meteor } from 'meteor/meteor'; import { check } from 'meteor/check'; import { NumberBetween } from '/imports/modules/server/other-checks'; -import '/imports/startup/server/leaflet-workaround.js'; -import L from 'leaflet'; +import L from 'leaflet-headless'; import calcUnion from '/imports/ui/components/Maps/SubsUnion/Unify'; import Industries from '/imports/api/Industries/Industries'; import FalsePositives from '../FalsePositives'; @@ -18,26 +17,20 @@ Meteor.publish('falsePositivesTotal', function total() { }); export const firesUnion = (fires) => { + const group = new L.FeatureGroup(); const firesArray = Array.isArray(fires) ? fires : fires.fetch(); // if not is a cursor const remap = firesArray.map(function remap(doc) { - const isNASA = doc.type === 'modis' || doc.type === 'viirs'; - const pixelSize = doc.type === 'viirs' ? 0.375 : 1; // viirs has 375m pixel size, modis 1000m - // default 1 km for neightbor alerts - const map = { - location: { lat: doc.lat, lon: doc.lon }, - distance: isNASA ? doc.scan * pixelSize : 1, - distanceY: isNASA ? doc.track * pixelSize : 1 - }; - // console.log(map); - return map; + // default scan: 1 for neightbor alerts + return { location: { lat: doc.lat, lon: doc.lon }, distance: doc.scan || 1 }; }); - const union = calcUnion(L, remap, sub => sub, false); + const union = calcUnion(remap, group, sub => sub); return union; }; export const zoneToUnion = (lat, lon, distance) => { + const group = new L.FeatureGroup(); const remap = [{ location: { lat, lon }, distance }]; - const union = calcUnion(L, remap, sub => sub, true); + const union = calcUnion(remap, group, sub => sub); return union; }; diff --git a/imports/api/FireAlerts/server/publications.js b/imports/api/FireAlerts/server/publications.js index 6642f35..b0e9412 100644 --- a/imports/api/FireAlerts/server/publications.js +++ b/imports/api/FireAlerts/server/publications.js @@ -33,8 +33,7 @@ Meteor.publish('fireAlerts', function fireAlerts(northEastLng, northEastLat, sou fields: { lat: 1, lon: 1, - scan: 1, - track: 1 + scan: 1 } }); if (Meteor.isDevelopment) console.log(`Neighbour alerts total: ${fires.count()}`); diff --git a/imports/api/Fires/server/publications.js b/imports/api/Fires/server/publications.js index 3862beb..acb4d1e 100644 --- a/imports/api/Fires/server/publications.js +++ b/imports/api/Fires/server/publications.js @@ -147,7 +147,7 @@ export function fireFromHash(fireEnc, params) { // const unsealed = Promise.await(urlEnc.decrypt(fireEnc)); const unsealed = Promise.await(unsealW(fireEnc)); if (unsealed === undefined) { - throw Error(`Fail to unseal '${fireEnc}'`); + throw Error('Undefined unsealed'); } const w = unsealed.when; unsealed.when = new Date(w); diff --git a/imports/api/Rest/Rest.js b/imports/api/Rest/Rest.js index ae5e383..dc47750 100644 --- a/imports/api/Rest/Rest.js +++ b/imports/api/Rest/Rest.js @@ -18,7 +18,6 @@ import { Random } from 'meteor/random'; import { subscriptionsInsert } from '/imports/api/Subscriptions/methods.js'; import { Mongo } from 'meteor/mongo'; import { upsertFalsePositive } from '/imports/api/FalsePositives/methods.js'; - const debug = false; const uptime = new Date(); @@ -157,14 +156,11 @@ if (!Meteor.settings.private.internalApiToken) { lat: 1, lon: 1, when: 1, - type: 1, // modis, viirs, vecinal - scan: 1, - track: 1 + scan: 1 } }); const result = { total: fires.count(), real: countRealFires(fires) }; - if (debug) console.log(`Query for fires in ${lat}, ${lng} in ${km} km radius ${result}`); if (!full) { return result; @@ -443,15 +439,11 @@ if (!Meteor.settings.private.internalApiToken) { const user = Meteor.users.findOne({ fireBaseToken: mobileToken }); if (!user) return failMsg('User not found'); - console.log(`Marking hash fire as '${type}' false positive: ${sealed}`); - const fireC = fireFromHash(sealed, { id: sealed }); - if (fireC && typeof fireC === 'object') { - const fire = fireC.fetch()[0]; - console.log(`Marking fire as false positive: ${fire._id}`); - const result = upsertFalsePositive(type, user._id, fire); - return jsend.success({ upsert: result }); - } - return failMsg('Cannot mark fire as false positive'); + // TODO + const fire = fireFromHash(sealed, {}); + console.log(`Marking fire as false positive: ${JSON.stringify(fire)}`); + const result = upsertFalsePositive(type, user._id, fire); + return jsend.success({ upsert: result }); } }); } diff --git a/imports/modules/server/notificationsProcess.js b/imports/modules/server/notificationsProcess.js deleted file mode 100644 index 657aa2a..0000000 --- a/imports/modules/server/notificationsProcess.js +++ /dev/null @@ -1,153 +0,0 @@ -/* eslint-disable import/no-absolute-path */ - -import { Meteor } from 'meteor/meteor'; -import Notifications from '/imports/api/Notifications/Notifications'; -import i18n from 'i18next'; -import moment from 'moment'; -import { dateLongFormat } from '/imports/api/Common/dates'; -// import sendMail from '/imports/startup/server/email'; -import sendEmail, { subjectTruncate } from '/imports/modules/server/send-email'; -import { isMailServerMaster } from '/imports/startup/server/email'; -// import { hr } from '/imports/startup/server/email'; -import getEmailOf from '/imports/modules/get-email-of-user'; -import image from 'google-maps-image-api-url'; -import gcm from 'node-gcm'; -import { trim } from '/imports/ui/components/NotificationsObserver/util.js'; -import ravenLogger from '/imports/startup/server/ravenLogger'; - -let validFcmSender = true; - -if (!(Meteor.settings.private.fcmApiToken && Meteor.settings.private.fcmApiToken.length > 0)) { - console.warn('Missing settings.private.fcmApiToken key, mobile notifications will not work'); - validFcmSender = false; -} - -// https://www.npmjs.com/package/google-maps-image-api-url -// https://stackoverflow.com/questions/24355007/is-there-no-way-to-embed-a-google-map-into-an-html-email -// https://developers.google.com/maps/documentation/static-maps/intro -function imgUrl(lat, lng) { - return image({ - key: Meteor.settings.gmaps.key, - type: 'staticmap', - center: `${lat},${lng}`, - size: '640x480', - zoom: 16, - maptype: 'hybrid', - language: 'es', - markers: `icon: ${Meteor.settings.private.fireIconUrl}|${lat},${lng}` - }); -} - -/* - function imgEl(lat, lng) { - return ``; - } */ - -// Cutover flag (fase 1a): channels listed in -// Meteor.settings.private.notifDisabledChannels (e.g. ['mobile','web']) are -// handled by the tcef-notifications microservice, so the old observer/cron must -// NOT send them. Mutual exclusion per channel — reversible by editing settings, -// no code deploy needed. ES5-compatible on purpose (Meteor 1.6 / Node 8). -const notifDisabledChannels = (Meteor.settings.private && Meteor.settings.private.notifDisabledChannels) || []; - -const processNotif = (notif) => { - if (notifDisabledChannels.indexOf(notif.type) !== -1) { - // This channel was migrated to tcef-notifications; do nothing here. - return; - } - if (isMailServerMaster && validFcmSender && notif.type === 'mobile' && notif.notified !== true) { - const fcmSender = new gcm.Sender(Meteor.settings.private.fcmApiToken); - const user = Meteor.users.findOne({ _id: notif.userId }); - moment.locale(user.lang); - // duplicate code below - const body = `${trim(notif.content)}`; - - // https://firebase.google.com/docs/cloud-messaging/concept-options - const msg = new gcm.Message(); - - msg.addNotification({ - title: i18n.t('Alerta de fuego'), - body, - click_action: 'FLUTTER_NOTIFICATION_CLICK', - tag: notif._id, // prevent duplication of fire notifications - sound: 'default', // Indicates sound to be played. Supports only default currently. - icon: 'launch_image' // 'ic_launcher' - }); - - msg.addData('id', notif._id._str); - msg.addData('description', body); - msg.addData('lat', notif.geo.coordinates[1]); - msg.addData('lon', notif.geo.coordinates[0]); - msg.addData('when', notif.when); - msg.addData('subsId', notif.subsId._str); - msg.addData('sealed', notif.sealed); - - const registrationTokens = []; - if (!user.fireBaseToken) { - console.warn('This mobile user doesn\'t have a firebase registration token'); - } else { - registrationTokens.push(user.fireBaseToken); - // FIXME: better join users - if (validFcmSender) { - fcmSender.send(msg, { registrationTokens }, Meteor.bindEnvironment(function processResult(err, response) { - if (err) { - console.error(`FCM error: ${err}`); - // FIXME send to sentry - ravenLogger.log(err); - } else { - // console.log(`FCM response: ${response}`); - Notifications.update(notif._id, { $set: { notified: true, notifiedAt: new Date() } }); - } - })); - } - } - } - if (isMailServerMaster && notif.type === 'web' && notif.emailNotified !== true) { - const user = Meteor.users.findOne({ _id: notif.userId }); - const { firstName, emailAddress } = getEmailOf(user); - - if (emailAddress) { - const img = imgUrl(notif.geo.coordinates[1], notif.geo.coordinates[0]); - // const url = imgUrl(notif.geo.coordinates[1], notif.geo.coordinates[0]); - const fireUrl = `${Meteor.absoluteUrl('fire/')}${notif.sealed}`; - // const fireHtmlUrl = `${i18n.t('Más información sobre este fuego')}`; - // TODO get _id of fire - // const fireTextUrl = `${i18n.t('Más información sobre este fuego')}:\n${fireUrl}`; - // FIXME use our map as url and static map as img - moment.locale(user.lang); - // moment user tz ? - const message = `${trim(notif.content)} (${i18n.t('fireDetectedAt', { when: dateLongFormat(notif.when) })}).`; - - // TODO Comunes Address - - const emailOpts = { - to: emailAddress, - // userName: firstName, - // sendAt: new Date(), - subject: subjectTruncate.apply(message), - // text: `${message}\n\n${fireTextUrl}\n\n`, - // template: '

{{appName}}

{{{html}}}', - lang: user.lang, - template: 'new-fire', - templateVars: { - applicationName: i18n.t('AppName'), - firstName, - message, - fireUrl, - img, - subsUrl: Meteor.absoluteUrl('subscriptions') - } - }; - sendEmail(emailOpts).catch((error) => { - throw new Meteor.Error('500', `${error}`); - }); - // sendMail(emailOpts, true); - Notifications.update(notif._id, { $set: { emailNotified: true, emailNotifiedAt: new Date() } }); - } else { - // Not email or not verified -> remove notif so we don't retry to send it - Notifications.remove({ _id: notif._id }); - } - } -}; - -export default processNotif; diff --git a/imports/startup/client/i18n.js b/imports/startup/client/i18n.js index 384053f..2fdc6b4 100644 --- a/imports/startup/client/i18n.js +++ b/imports/startup/client/i18n.js @@ -17,7 +17,7 @@ import i18nOpts from '../common/i18n'; const detectorOptions = { // order and from where user language should be detected - order: ['querystring', 'navigator', 'cookie', 'localStorage', 'htmlTag'], + order: ['querystring', 'cookie', 'localStorage', 'navigator', 'htmlTag'], // keys or params to lookup language from lookupQuerystring: 'lng', @@ -61,14 +61,6 @@ if (sendMissing && Meteor.isDevelopment) { }; } -function setT9(lang) { - if (lang === 'gl') { - T9n.setLanguage('es'); - } else { - T9n.setLanguage(lang); - } -} - i18n.use(backend) .use(LngDetector) .use(Cache) @@ -83,7 +75,7 @@ i18n.use(backend) // document.title = t('AppName'); // Accounts translation // https://github.com/softwarerero/meteor-accounts-t9n - setT9(i18n.language); + T9n.setLanguage(i18n.language); // console.log(T9n.get('error.accounts.User not found')); moment.locale(i18n.language); @@ -112,8 +104,7 @@ Meteor.subscribe('userData'); // lang is there i18n.on('languageChanged', (lng) => { moment.locale(lng); - // TODO fix this when gl is translated - setT9(lng); + T9n.setLanguage(lng); Tracker.autorun((computation) => { if (Meteor.userId()) { // logged diff --git a/imports/startup/common/i18n.js b/imports/startup/common/i18n.js index 218e46d..9be09fd 100644 --- a/imports/startup/common/i18n.js +++ b/imports/startup/common/i18n.js @@ -3,8 +3,6 @@ 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 -// import gl from 'meteor-accounts-t9n/build/gl'; const backOpts = { // path where resources get loaded from @@ -23,10 +21,7 @@ const shouldDebug = (forceDebug && !Meteor.isProduction); const i18nOpts = { backend: backOpts, // lng: 'es', - fallbackLng: { - gl: ['es'], - default: ['en'] - }, + fallbackLng: ['es', 'en'], sendMissingTo: 'fallback', interpolation: { escapeValue: false, // not needed for react!! @@ -40,7 +35,7 @@ const i18nOpts = { return value; } }, - whitelist: ['es', 'en', 'gl'], // allowed languages + whitelist: ['es', 'en'], // allowed languages load: 'languageOnly', // 'es' o 'en', previously: 'all', // es-ES -> es, en-US -> en debug: shouldDebug, ns: 'common', diff --git a/imports/startup/server/api.js b/imports/startup/server/api.js index a6044b4..73f1fa2 100644 --- a/imports/startup/server/api.js +++ b/imports/startup/server/api.js @@ -9,7 +9,6 @@ import '../../api/Users/server/publications'; import '../../api/Utility/server/methods'; import '../../api/ActiveFires/server/publications'; -import '../../api/ActiveFiresUnion/server/publications'; import '../../api/FireAlerts/server/publications'; import '../../api/Subscriptions/methods'; diff --git a/imports/startup/server/cron.js b/imports/startup/server/cron.js index 843e55b..9b0fa77 100644 --- a/imports/startup/server/cron.js +++ b/imports/startup/server/cron.js @@ -4,8 +4,6 @@ import { Meteor } from 'meteor/meteor'; import { tweetIberiaFires, tweetEuropeFires } from '/imports/api/ActiveFires/server/tweetFiresInZone'; import { isMailServerMaster, sendEmailsFromQueue } from '/imports/startup/server/email'; -import Notifications from '/imports/api/Notifications/Notifications'; -import processNotif from '/imports/modules/server/notificationsProcess.js'; // https://github.com/thesaucecode/meteor-synced-cron/ @@ -46,7 +44,7 @@ Meteor.startup(() => { timezone: 'Europe/Madrid', schedule: (parser) => { // http://bunkat.github.io/later/ - const sched = parser.text('every 10 min'); + const sched = parser.text('every 1 min'); if (sched.error !== -1) { console.error(`Mail cron 'when' field parsed with errors: ${sched.error}`); } @@ -54,30 +52,6 @@ Meteor.startup(() => { }, job: () => sendEmailsFromQueue() }); - - SyncedCron.add({ - name: 'Process pending notif', - timezone: 'Europe/Madrid', - schedule: (parser) => { - // http://bunkat.github.io/later/ - const sched = parser.text('every 15 min'); - if (sched.error !== -1) { - console.error(`Mail cron 'when' field parsed with errors: ${sched.error}`); - } - return sched; - }, - job: () => { - const mobileNotif = Notifications.find({ nofitied: null, type: 'mobile' }); - mobileNotif.forEach((notif) => { - processNotif(notif); - }); - const emailNotif = Notifications.find({ emailNofitied: null, type: 'web' }); - emailNotif.forEach((notif) => { - processNotif(notif); - }); - return { mobile: mobileNotif.count(), web: emailNotif.count() }; - } - }); } const esEn = Meteor.settings.private.twitter.es.enabled; diff --git a/imports/startup/server/index.js b/imports/startup/server/index.js index 7f84dde..dd4f913 100644 --- a/imports/startup/server/index.js +++ b/imports/startup/server/index.js @@ -17,4 +17,5 @@ import './prerender'; import './feedback'; import './cron'; import './isMaster'; +import './segfaults'; import './rest'; diff --git a/imports/startup/server/leaflet-workaround.js b/imports/startup/server/leaflet-workaround.js deleted file mode 100644 index 740aa3b..0000000 --- a/imports/startup/server/leaflet-workaround.js +++ /dev/null @@ -1,19 +0,0 @@ -// imports/startup/server/leaflet-workaround.js -global.window = { - screen: { - deviceXDPI: 1, - logicalXDPI: 1 - } -}; -global.document = { - documentElement: { - style: {} - }, - createElement: () => { - return {}; - } -}; -global.navigator = { - userAgent: 'foo', - platform: 'bar' -}; diff --git a/imports/startup/server/migrations.js b/imports/startup/server/migrations.js index e3bfac9..defc7c1 100644 --- a/imports/startup/server/migrations.js +++ b/imports/startup/server/migrations.js @@ -10,7 +10,6 @@ import FalsePositives from '/imports/api/FalsePositives/FalsePositives'; import Industries from '/imports/api/Industries/Industries'; import IndustryRegistries from '/imports/api/Industries/IndustryRegistries'; import Notifications from '/imports/api/Notifications/Notifications'; -import ActiveFiresUnion from '/imports/api/ActiveFiresUnion/ActiveFiresUnion'; import { Mongo } from 'meteor/mongo'; Meteor.startup(() => { @@ -241,18 +240,6 @@ Meteor.startup(() => { } }); - Migrations.add({ - version: 18, - up: () => { - const raw = ActiveFiresUnion.rawCollection(); - raw.createIndex({ centerid: '2dsphere' }); - raw.createIndex({ shape: '2dsphere' }); - raw.createIndex({ when: 1 }); - raw.createIndex({ createdAt: 1 }); - raw.createIndex({ updatedAt: 1 }); - } - }); - // Set createdAt in users & subs Migrations.migrateTo('latest'); diff --git a/imports/startup/server/notificationsObserver.js b/imports/startup/server/notificationsObserver.js index 70be8e0..0065e15 100644 --- a/imports/startup/server/notificationsObserver.js +++ b/imports/startup/server/notificationsObserver.js @@ -1,19 +1,145 @@ /* eslint-disable import/no-absolute-path */ +import i18n from 'i18next'; import { Meteor } from 'meteor/meteor'; +import moment from 'moment'; +import { dateLongFormat } from '/imports/api/Common/dates'; import Notifications from '/imports/api/Notifications/Notifications'; -import processNotif from '/imports/modules/server/notificationsProcess.js'; +// import sendMail from '/imports/startup/server/email'; +import sendEmail, { subjectTruncate } from '/imports/modules/server/send-email'; import { isMailServerMaster } from '/imports/startup/server/email'; +// import { hr } from '/imports/startup/server/email'; +import getEmailOf from '/imports/modules/get-email-of-user'; +import image from 'google-maps-image-api-url'; +import gcm from 'node-gcm'; +import { trim } from '/imports/ui/components/NotificationsObserver/util.js'; Meteor.startup(() => { - if (isMailServerMaster) { - Notifications.find().observe({ - added: function notifAdded(notif) { - processNotif(notif); - }, - changed: function notifChanged(updatedNotif) { // , oldNotif) { - processNotif(updatedNotif); - } + let validFcmSender = true; + + if (!(Meteor.settings.private.fcmApiToken && Meteor.settings.private.fcmApiToken.length > 0)) { + console.warn('Missing settings.private.fcmApiToken key, mobile notifications will not work'); + validFcmSender = false; + } + + const fcmSender = new gcm.Sender(Meteor.settings.private.fcmApiToken); + + // https://www.npmjs.com/package/google-maps-image-api-url + // https://stackoverflow.com/questions/24355007/is-there-no-way-to-embed-a-google-map-into-an-html-email + // https://developers.google.com/maps/documentation/static-maps/intro + function imgUrl(lat, lng) { + return image({ + key: Meteor.settings.gmaps.key, + type: 'staticmap', + center: `${lat},${lng}`, + size: '640x480', + zoom: 16, + maptype: 'hybrid', + language: 'es', + markers: `icon: ${Meteor.settings.private.fireIconUrl}|${lat},${lng}` }); } + + /* + function imgEl(lat, lng) { + return ``; + } */ + + function process(notif) { + if (isMailServerMaster && notif.type === 'mobile' && !notif.notified) { + const user = Meteor.users.findOne({ _id: notif.userId }); + moment.locale(user.lang); + // duplicate code below + const body = `${trim(notif.content)}`; + + // https://firebase.google.com/docs/cloud-messaging/concept-options + const msg = new gcm.Message(); + + msg.addNotification({ + title: i18n.t('Alerta de fuego'), + body, + click_action: 'FLUTTER_NOTIFICATION_CLICK', + tag: notif._id, // prevent duplication of fire notifications + sound: 'default', // Indicates sound to be played. Supports only default currently. + icon: 'launch_image' // 'ic_launcher' + }); + + msg.addData('id', notif._id._str); + msg.addData('description', body); + msg.addData('lat', notif.geo.coordinates[1]); + msg.addData('lon', notif.geo.coordinates[0]); + msg.addData('when', notif.when); + msg.addData('subsId', notif.subsId._str); + msg.addData('sealed', notif.sealed); + + const registrationTokens = []; + if (!user.fireBaseToken) { + console.warn('This mobile user doesn\'t have a firebase registration token'); + } else { + registrationTokens.push(user.fireBaseToken); + // FIXME: better join users + if (validFcmSender) { + fcmSender.send(msg, { registrationTokens }, Meteor.bindEnvironment(function processResult(err, response) { + if (err) { + console.error(`FCM error: ${err}`); + } else { + // console.log(`FCM response: ${response}`); + Notifications.update(notif._id, { $set: { notified: true, notifiedAt: new Date() } }); + } + })); + } + } + } else if (notif.type === 'web' && !notif.emailNotified) { + const user = Meteor.users.findOne({ _id: notif.userId }); + const { firstName, emailAddress } = getEmailOf(user); + + if (emailAddress) { + const img = imgUrl(notif.geo.coordinates[1], notif.geo.coordinates[0]); + // const url = imgUrl(notif.geo.coordinates[1], notif.geo.coordinates[0]); + const fireUrl = `${Meteor.absoluteUrl('fire/')}${notif.sealed}`; + // const fireHtmlUrl = `${i18n.t('Más información sobre este fuego')}`; + // TODO get _id of fire + // const fireTextUrl = `${i18n.t('Más información sobre este fuego')}:\n${fireUrl}`; + // FIXME use our map as url and static map as img + moment.locale(user.lang); + // moment user tz ? + const message = `${trim(notif.content)} (${i18n.t('fireDetectedAt', { when: dateLongFormat(notif.when) })}).`; + + // TODO Comunes Address + + const emailOpts = { + to: emailAddress, + // userName: firstName, + // sendAt: new Date(), + subject: subjectTruncate.apply(message), + // text: `${message}\n\n${fireTextUrl}\n\n`, + // template: '

{{appName}}

{{{html}}}', + lang: user.lang, + template: 'new-fire', + templateVars: { + applicationName: i18n.t('AppName'), + firstName, + message, + fireUrl, + img, + subsUrl: Meteor.absoluteUrl('subscriptions') + } + }; + sendEmail(emailOpts).catch((error) => { + throw new Meteor.Error('500', `${error}`); + }); + // sendMail(emailOpts, true); + Notifications.update(notif._id, { $set: { emailNotified: true, emailNotifiedAt: new Date() } }); + } + } + } + + Notifications.find().observe({ + added: function notifAdded(notif) { + process(notif); + }, + changed: function notifChanged(updatedNotif, oldNotif) { + process(updatedNotif); + } + }); }); diff --git a/imports/startup/server/segfaults.js b/imports/startup/server/segfaults.js new file mode 100644 index 0000000..b976f0b --- /dev/null +++ b/imports/startup/server/segfaults.js @@ -0,0 +1,25 @@ +import SegfaultHandler from 'segfault-handler'; +import moment from 'moment'; +// import ravenLogger from './ravenLogger'; + +// https://github.com/ddopson/node-segfault-handler + +const dateFileFormat = moment().format('YYYYMMDD_HH:mm:ss'); + +SegfaultHandler.registerHandler(`/var/tmp/tcef-stacktrace-${dateFileFormat}.log`); + + +// This callaback does not work: +// https://github.com/ddopson/node-segfault-handler/issues/49 + +/* SegfaultHandler.registerHandler('/var/tmp/tcef-crash.log', (signal, address, stack) => { + * // Do what you want with the signal, address, or stack (array) + * // This callback will execute before the signal is forwarded on. + * ravenLogger.log(stack, signal); + * }); */ + +// Only uncomment for segv tests +// SegfaultHandler.causeSegfault(); + +// Or this for normal error (should go to raven in configured) +// throw new Error('This is just a test of error in server'); diff --git a/imports/startup/server/sitemaps.js b/imports/startup/server/sitemaps.js index 8026449..c1fe573 100644 --- a/imports/startup/server/sitemaps.js +++ b/imports/startup/server/sitemaps.js @@ -4,8 +4,6 @@ import Fires from '/imports/api/Fires/Fires'; // import Comments from '/imports/api/Comments/Comments'; -const firesMapEnabled = false; - sitemaps.add('/sitemap.xml', () => { const today = new Date(); @@ -22,23 +20,21 @@ sitemaps.add('/sitemap.xml', () => { // When user has public page /* const users = Meteor.users.find().fetch(); - _.each(users, function(user) { - out.push({ - page: '/persona/' + user.username, - lastmod: user.updatedAt - }); + _.each(users, function(user) { + out.push({ + page: '/persona/' + user.username, + lastmod: user.updatedAt + }); }); */ - if (firesMapEnabled) { - Fires.find({}, { limit: 100, sort: { createdAt: -1 } }).fetch().forEach((fire) => { - // Search the last comment of tha fire - const lastComment = Comments.getCollection().findOne({ referenceId: `fire-${fire._id}` }, { sort: { createdAt: -1 } }); - out.push({ - page: `/fire/archive/${fire._id._str}`, - lastmod: lastComment ? lastComment.createdAt : fire.updatedAt - }); + Fires.find({}, { limit: 100, sort: { createdAt: -1 } }).fetch().forEach((fire) => { + // Search the last comment of tha fire + const lastComment = Comments.getCollection().findOne({ referenceId: `fire-${fire._id}` }, { sort: { createdAt: -1 } }); + out.push({ + page: `/fire/archive/${fire._id._str}`, + lastmod: lastComment ? lastComment.createdAt : fire.updatedAt }); - } + }); return out; }); diff --git a/imports/startup/server/subsUnion.js b/imports/startup/server/subsUnion.js index 2643359..96c6c20 100644 --- a/imports/startup/server/subsUnion.js +++ b/imports/startup/server/subsUnion.js @@ -4,8 +4,7 @@ import { Meteor } from 'meteor/meteor'; import Subscriptions from '/imports/api/Subscriptions/Subscriptions'; import SiteSettings from '/imports/api/SiteSettings/SiteSettings'; import Perlin from 'loms.perlin'; -import './leaflet-workaround'; -import L from 'leaflet'; +import L from 'leaflet-headless'; import calcUnion from '/imports/ui/components/Maps/SubsUnion/Unify'; import { isMailServerMaster } from '/imports/startup/server/email'; @@ -36,8 +35,9 @@ Meteor.startup(() => { const noNoisy = sub => sub; const process = (isPublic) => { + const group = new L.FeatureGroup(); const subscribers = Subscriptions.find().fetch(); - const result = calcUnion(L, subscribers, isPublic ? addNoisy : noNoisy, true); + const result = calcUnion(subscribers, group, isPublic ? addNoisy : noNoisy); const union = result[0]; const bounds = result[1]; diff --git a/imports/ui/components/Maps/DefMapLayers.js b/imports/ui/components/Maps/DefMapLayers.js index 6e3ed56..713b499 100644 --- a/imports/ui/components/Maps/DefMapLayers.js +++ b/imports/ui/components/Maps/DefMapLayers.js @@ -35,8 +35,7 @@ class DefMapLayers extends Component { ); const osmlayer = ( diff --git a/imports/ui/components/Maps/FireCircleMark.js b/imports/ui/components/Maps/FireCircleMark.js index 80b9826..e4a3460 100644 --- a/imports/ui/components/Maps/FireCircleMark.js +++ b/imports/ui/components/Maps/FireCircleMark.js @@ -1,10 +1,10 @@ import React, { Component } from 'react'; -import { GeoJSON } from 'react-leaflet'; +import { Circle } from 'react-leaflet'; import PropTypes from 'prop-types'; -import { rectangleAround } from 'map-common-utils'; import { onMarkClick } from './MarkListeners'; import FirePopup from './FirePopup'; + class FireCircleMark extends Component { constructor(props) { super(props); @@ -22,26 +22,22 @@ class FireCircleMark extends Component { lat, lon, scan, - track, nasa, id, history, when, t } = this.props; - const rect = rectangleAround({ lat, lon }, track, track); return ( - + - + ); - /* */ } } FireCircleMark.propTypes = { scan: PropTypes.number.isRequired, - track: PropTypes.number.isRequired, lat: PropTypes.number.isRequired, lon: PropTypes.number.isRequired, nasa: PropTypes.bool.isRequired, diff --git a/imports/ui/components/Maps/FireIconMark.js b/imports/ui/components/Maps/FireIconMark.js index ab24da4..94d93a0 100644 --- a/imports/ui/components/Maps/FireIconMark.js +++ b/imports/ui/components/Maps/FireIconMark.js @@ -3,8 +3,8 @@ import React, { Component, Fragment } from 'react'; import { CircleMarker, Marker, Tooltip } from 'react-leaflet'; import PropTypes from 'prop-types'; import { fireIconS, fireIconM, fireIconL, nFireIcon, industryIcon, regIndustryIcon } from '/imports/ui/components/Maps/Icons'; -import { translate } from 'react-i18next'; import { onMarkClick } from './MarkListeners'; +import { translate } from 'react-i18next'; import FirePopup from './FirePopup'; class FireIconMark extends Component { @@ -30,7 +30,17 @@ class FireIconMark extends Component { render() { const { - lat, lon, scan, track, nasa, id, history, falsePositives, industries, neighbour, when, t + lat, + lon, + scan, + nasa, + id, + history, + falsePositives, + industries, + neighbour, + when, + t } = this.props; return (
@@ -69,7 +79,6 @@ FireIconMark.propTypes = { lat: PropTypes.number.isRequired, lon: PropTypes.number.isRequired, scan: PropTypes.number, - track: PropTypes.number, nasa: PropTypes.bool, falsePositives: PropTypes.bool.isRequired, industries: PropTypes.bool.isRequired, diff --git a/imports/ui/components/Maps/FireListUnion.js b/imports/ui/components/Maps/FireListUnion.js deleted file mode 100644 index 3b094c0..0000000 --- a/imports/ui/components/Maps/FireListUnion.js +++ /dev/null @@ -1,22 +0,0 @@ -/* eslint-disable import/no-absolute-path */ -/* eslint-disable react/jsx-indent-props */ -/* eslint-disable react/jsx-indent */ -import React from 'react'; -import PropTypes from 'prop-types'; -import FirePolygonMark from '/imports/ui/components/Maps/FirePolygonMark'; - -export default function FireListUnion(props) { - const { - firesUnion, nasa, t, history - } = props; - /* console.log(`Using marks: ${useMarks}, using pixels: ${usePixel}`); */ - const items = firesUnion.map(({ _id, ...otherProps }) => ()); - return (
{items}
); -} - -FireListUnion.propTypes = { - firesUnion: PropTypes.array.isRequired, - nasa: PropTypes.bool.isRequired, - history: PropTypes.object.isRequired, - t: PropTypes.func.isRequired -}; diff --git a/imports/ui/components/Maps/FirePixel.js b/imports/ui/components/Maps/FirePixel.js index 4efcabe..c81b547 100644 --- a/imports/ui/components/Maps/FirePixel.js +++ b/imports/ui/components/Maps/FirePixel.js @@ -3,32 +3,31 @@ import React from 'react'; import { CircleMarker } from 'react-leaflet'; import PropTypes from 'prop-types'; -import { translate } from 'react-i18next'; import FirePopup from './FirePopup'; +import { translate } from 'react-i18next'; /* Less acurate (1 pixel per fire) but faster */ const FirePixel = ({ - lat, lon, nasa, id, when, t, history, track + lat, lon, nasa, id, when, t, history }) => ( ); + FirePixel.propTypes = { lat: PropTypes.number.isRequired, lon: PropTypes.number.isRequired, id: PropTypes.object.isRequired, history: PropTypes.object.isRequired, - scan: PropTypes.number, - track: PropTypes.number, when: PropTypes.instanceOf(Date), nasa: PropTypes.bool.isRequired, t: PropTypes.func.isRequired diff --git a/imports/ui/components/Maps/FirePolygonMark.js b/imports/ui/components/Maps/FirePolygonMark.js deleted file mode 100644 index 92e3640..0000000 --- a/imports/ui/components/Maps/FirePolygonMark.js +++ /dev/null @@ -1,48 +0,0 @@ -import React, { Component } from 'react'; -import { GeoJSON } from 'react-leaflet'; -import PropTypes from 'prop-types'; -import { onMarkClick } from './MarkListeners'; -import FirePopup from './FirePopup'; - -class FirePolygonMark extends Component { - constructor(props) { - super(props); - this.t = props.t; - this.onClick = this.onClick.bind(this); - } - - onClick() { - const { history, nasa, id } = this.props; - onMarkClick(history, nasa, id); - } - - render() { - const { - nasa, - id, - shape, - centerid, - history, - when, - t - } = this.props; - const lon = centerid.coordinates[0]; - const lat = centerid.coordinates[1]; - return ( - - ); - /* */ - } -} - -FirePolygonMark.propTypes = { - id: PropTypes.object.isRequired, - nasa: PropTypes.bool.isRequired, - shape: PropTypes.object.isRequired, - centerid: PropTypes.object.isRequired, - history: PropTypes.object.isRequired, - when: PropTypes.instanceOf(Date).isRequired, - t: PropTypes.func.isRequired -}; - -export default FirePolygonMark; diff --git a/imports/ui/components/Maps/SubsUnion/SubsUnion.js b/imports/ui/components/Maps/SubsUnion/SubsUnion.js index 41a4d81..dab127a 100644 --- a/imports/ui/components/Maps/SubsUnion/SubsUnion.js +++ b/imports/ui/components/Maps/SubsUnion/SubsUnion.js @@ -33,7 +33,8 @@ const subsUnion = (union, options) => { options.map.leafletElement.fitBounds(L.latLngBounds(bounds._northEast, bounds._southWest)); } } else if (options.subs.length > 0) { - const result = calcUnion(L, options.subs, sub => sub, true); + const unionGroup = new L.FeatureGroup(); + const result = calcUnion(options.subs, unionGroup, sub => sub); const unionJson = result[0]; const bounds = result[1]; diff --git a/imports/ui/components/Maps/SubsUnion/Unify.js b/imports/ui/components/Maps/SubsUnion/Unify.js index 873dcbf..46586b7 100644 --- a/imports/ui/components/Maps/SubsUnion/Unify.js +++ b/imports/ui/components/Maps/SubsUnion/Unify.js @@ -1,9 +1,48 @@ -import { calcUnion as calcUnionWoBounds } from 'map-common-utils'; +import { check } from 'meteor/check'; +import LGeo from 'leaflet-geodesy'; +import tunion from '@turf/union'; +import ttrunc from '@turf/truncate'; -const calcUnion = (L, subs, decorated, typeCircle) => { - const unionJson = calcUnionWoBounds(subs, decorated, typeCircle); - // return [unionJson, L.geoJSON(unionJson).getBounds()]; - return [unionJson, unionJson === null ? null : L.geoJSON(unionJson).getBounds()]; +// https://stackoverflow.com/questions/35394577/leaflet-js-union-merge-circles +const truncOptions = { precision: 6, coordinates: 2 }; + +const unify = (polyList) => { + let unionTemp; + for (let i = 0; i < polyList.length; i += 1) { + const pol = polyList[i].toGeoJSON(); + const cleanPol = ttrunc(pol, truncOptions); + if (i === 0) { + unionTemp = cleanPol; + } else { + unionTemp = ttrunc(tunion(unionTemp, cleanPol), truncOptions); + } + } + return unionTemp; +}; + +const calcUnion = (subs, group, decorated) => { + const unionGroup = group; + const copts = { + parts: 144 + }; + subs.forEach((osub) => { + try { + if (osub.location && osub.location.lat && osub.location.lon && osub.distance) { + check(osub.location.lon, Number); + check(osub.location.lat, Number); + check(osub.distance, Number); + const dsub = decorated(osub); + const circle = LGeo.circle([dsub.location.lat, dsub.location.lon], dsub.distance * 1000, copts); + circle.addTo(unionGroup); + } else { + console.info(`Wrong subscription ${JSON.stringify(osub)}`); + } + } catch (e) { + console.error(e, `Wrong subscription trying to make union ${JSON.stringify(osub)}`); + } + }); + const unionJson = unify(unionGroup.getLayers()); + return [unionJson, unionGroup.getBounds()]; }; export default calcUnion; diff --git a/imports/ui/layouts/App/App.js b/imports/ui/layouts/App/App.js index 4aeef3a..d991262 100644 --- a/imports/ui/layouts/App/App.js +++ b/imports/ui/layouts/App/App.js @@ -98,66 +98,66 @@ const App = props => ( /* https://react.i18next.com/components/i18nextprovider.html */
{props.i18nReady.get() && - - - - - - { !props.loading && -
- - - {i18n.t('AppName')} - - - - - - + + + + + + { !props.loading && +
+ + + {i18n.t('AppName')} + + + + + + - - - - - - - - - - - + + + + + + + + + + + - - - - - - - - + + + + + + + + - - - - - - - - + + + + + + + + - - - -
- - - {props.i18nReady.get() && } -
} -
-
-
-
-
} + + + +
+ + + {props.i18nReady.get() && } +
} +
+
+
+
+
}
); @@ -186,9 +186,7 @@ export default withTracker(() => { const loading = !Roles.subscription.ready() || !i18nReady.get(); const name = user && user.profile && user.profile.name && getUserName(user.profile.name); const emailAddress = user && user.emails && user.emails[0].address; - Meteor.autorun(() => { - console.log(`i18n ready?: ${i18nReady.get()}`); - }); + // console.log(`i18n ready?: ${i18nReady.get()}`); return { loading, loggingIn, diff --git a/imports/ui/pages/Fires/Fires.js b/imports/ui/pages/Fires/Fires.js index 2647992..0e4271a 100644 --- a/imports/ui/pages/Fires/Fires.js +++ b/imports/ui/pages/Fires/Fires.js @@ -10,8 +10,7 @@ import { Row, Col, Alert, FormGroup } from 'react-bootstrap'; import { Meteor } from 'meteor/meteor'; import { Bert } from 'meteor/themeteorchef:bert'; import { Helmet } from 'react-helmet'; -import { Map, GeoJSON } from 'react-leaflet'; -import { rectangleAround } from 'map-common-utils'; +import { Map, Circle } from 'react-leaflet'; import Blaze from 'meteor/gadicc:blaze-react-component'; import DefMapLayers from '/imports/ui/components/Maps/DefMapLayers'; import NotFound from '/imports/ui/pages/NotFound/NotFound'; @@ -116,9 +115,8 @@ class Fire extends React.Component { t('Información adicional sobre fuego detectado en {{where}} el {{when}}', { where: fire.address, when: this.dateLongFormat }) : t('Información adicional sobre fuego detectado el {{when}}', { when: this.dateLongFormat }); } - const ready = fire && !loading; - const rect = ready ? rectangleAround({ lat: fire.lat, lon: fire.lon }, fire.track, fire.track) : null; - return (ready ? ( + + return (fire && !loading ? (
{t('AppName')}: {t('Información adicional sobre fuego')} @@ -139,13 +137,14 @@ class Fire extends React.Component { zoom={13} > - { this.circle = circle; this.handleLeafletCircleLoad(circle); }} - data={rect} + { this.circle = circle; this.handleLeafletCircleLoad(circle); }} /> { (this.props.activefires.length + this.props.firealerts.length) === 0 ? No hay fuegos activos en esta zona del mapa. {{ countTotal: this.props.activefirestotal }} fuegos activos en el mundo. : - En rojo, {{ count: this.props.activefiresunion.length + this.props.firealerts.length }} fuegos activos. {{ countTotal: this.props.activefirestotal }} fuegos activos en el mundo. + En rojo, {{ count: this.props.activefires.length + this.props.firealerts.length }} fuegos activos. {{ countTotal: this.props.activefirestotal }} fuegos activos en el mundo. }

{isNotHomeAndMobile() && this.props.firealerts.length > 0 && @@ -297,14 +307,6 @@ class FiresMap extends React.Component { neighbour={false} industries={false} /> - { Meteor.isDevelopment && - - } { // console.log(`With industries: ${withIndustries}`); let subscription; - let subscriptionUnion; let alertSubscription; const centerStored = store.get('firesmap_center'); @@ -423,14 +422,6 @@ export default translate([], { wait: true })(withTracker(() => { mapSize.get()[1].lat, marks.get() && zoom.get() >= MAXZOOM ); - subscriptionUnion = Meteor.subscribe( - 'activefiresunionmyloc', - mapSize.get()[0].lng, - mapSize.get()[0].lat, - mapSize.get()[1].lng, - mapSize.get()[1].lat, - false - ); alertSubscription = Meteor.subscribe( 'fireAlerts', mapSize.get()[0].lng, @@ -451,7 +442,6 @@ export default translate([], { wait: true })(withTracker(() => { }); Meteor.subscribe('activefirestotal'); - Meteor.subscribe('activefiresuniontotal'); const settingsSubs = Meteor.subscribe('settings'); const lastCheck = SiteSettings.findOne({ name: 'last-fire-check' }); @@ -463,15 +453,13 @@ export default translate([], { wait: true })(withTracker(() => { const lastFireDetected = ActiveFiresCollection.findOne({}, { sort: { when: -1 } }); return { - loading: Meteor.status().status !== 'connected' || !subscription ? true : !(subscription.ready() && settingsSubs.ready() && alertSubscription.ready() && settingsSubs.ready() && subscriptionUnion.ready()), + loading: Meteor.status().status !== 'connected' || !subscription ? true : !(subscription.ready() && settingsSubs.ready() && alertSubscription.ready() && settingsSubs.ready()), userSubs: userSubs ? userSubs.value : null, userSubsBounds: userSubs ? userSubsBounds.value : null, subsready: settingsSubs.ready(), // Not reactive query depending on zoom level activefires: ActiveFiresCollection.find({}, { reactive: zoom.get() >= MAXZOOMREACTIVE }).fetch(), - activefiresunion: ActiveFiresUnion.find({}, { reactive: zoom.get() >= MAXZOOMREACTIVE }).fetch(), activefirestotal: Counter.get('countActiveFires') + fireAlerts.length, - activefiresuniontotal: Counter.get('countActiveFiresUnion') + fireAlerts.length, firealerts: fireAlerts, falsePositives, industries, diff --git a/imports/ui/pages/Index/Index.js b/imports/ui/pages/Index/Index.js index 9986fba..5d6b130 100644 --- a/imports/ui/pages/Index/Index.js +++ b/imports/ui/pages/Index/Index.js @@ -11,7 +11,6 @@ import PropTypes from 'prop-types'; // import { Link } from 'react-router-dom'; // https://www.npmjs.com/package/react-resize-detector import ReactResizeDetector from 'react-resize-detector'; -import MobileStoreButton from 'react-mobile-store-button'; import _ from 'lodash'; import 'html5-device-mockups/dist/device-mockups.min.css'; import 'bootstrap-carousel-swipe/carousel-swipe'; @@ -21,6 +20,7 @@ import SubscriptionsMap from '/imports/ui/pages/Subscriptions/SubscriptionsMap'; import ShareIt from '/imports/ui/components/ShareIt/ShareIt'; import getFallbackLang from '/imports/modules/lang-fallback'; import FiresMap from '../FiresMap/FiresMap'; + import './Index.scss'; import './Index-custom.scss'; @@ -102,7 +102,6 @@ class Index extends Component { render() { const { t } = this.props; const title = `${t('AppName')}: ${t('Inicio')}`; - const androidUrl = 'https://play.google.com/store/apps/details?id=org.comunes.fires'; return (
@@ -182,9 +181,6 @@ class Index extends Component {

Siempre alerta a los fuegos en nuestro vecindario

{/* {this.props.t('Participa')} */} -
- -
diff --git a/imports/ui/pages/Index/Index.scss b/imports/ui/pages/Index/Index.scss index 527aa93..3fb18b4 100644 --- a/imports/ui/pages/Index/Index.scss +++ b/imports/ui/pages/Index/Index.scss @@ -77,7 +77,3 @@ } } } - -.android-btn > div { - height: 50px !important; -} diff --git a/imports/ui/pages/Profile/Profile.js b/imports/ui/pages/Profile/Profile.js index 6d9b24b..665e9d7 100644 --- a/imports/ui/pages/Profile/Profile.js +++ b/imports/ui/pages/Profile/Profile.js @@ -9,17 +9,15 @@ import { Meteor } from 'meteor/meteor'; import { Accounts } from 'meteor/accounts-base'; import { Bert } from 'meteor/themeteorchef:bert'; import { testId } from '/imports/ui/components/Utils/TestUtils'; -import { translate } from 'react-i18next'; -import { T9n } from 'meteor-accounts-t9n'; -import { withTracker } from 'meteor/react-meteor-data'; import Col from '../../components/Col/Col'; +import { withTracker } from 'meteor/react-meteor-data'; import InputHint from '../../components/InputHint/InputHint'; import validate from '../../../modules/validate'; +import { translate } from 'react-i18next'; +import { T9n } from 'meteor-accounts-t9n'; import './Profile.scss'; -// List of languages: https://github.com/i18next/i18next/issues/1068 - class Profile extends React.Component { constructor(props) { super(props); @@ -157,7 +155,6 @@ class Profile extends React.Component { renderPasswordUser(loading, user) { const { t, i18n } = this.props; - const enabledLangs = ['en', 'es', 'gl']; const langName = { en: 'English', es: 'Español', gl: 'Galego', ast: 'Asturianu', ca: 'Català' }; @@ -205,7 +202,7 @@ class Profile extends React.Component { {langName[i18n.language]}
- {enabledLangs.map(lang => ( + {i18n.languages.map(lang => (