diff --git a/.gitignore b/.gitignore index 603fb12..0f4ef0a 100644 --- a/.gitignore +++ b/.gitignore @@ -9,11 +9,3 @@ npm-debug.log .deploy/ public/locales/undefined/ .deploy-staging -.screenshots -.vscode -output.json - -# fase-0 higiene: variantes de settings con secretos (no versionar) -settings-production-*.json -settings-tests.json -*.json~ diff --git a/.meteor/packages b/.meteor/packages index 5cca156..f49fd6a 100644 --- a/.meteor/packages +++ b/.meteor/packages @@ -4,33 +4,33 @@ # 'meteor add' and 'meteor remove' will edit this file for you, # but you can also edit it by hand. -meteor-base@1.3.0 # Packages every Meteor app needs to have +meteor-base@1.2.0 # Packages every Meteor app needs to have mobile-experience@1.0.5 # Packages for a great mobile UX -mongo@1.4.7 # The database Meteor supports right now +mongo@1.3.0 # The database Meteor supports right now reactive-var@1.0.11 # Reactive variable for tracker tracker@1.1.3 # Meteor's client-side reactive programming library -standard-minifier-css@1.4.0 # CSS minifier run for production mode -standard-minifier-js@2.3.1 # JS minifier run for production mode -es5-shim@4.7.0 # ECMAScript 5 compatibility for older browsers. -ecmascript@0.10.6 # Enable ECMAScript2015+ syntax in app code -shell-server@0.3.1 # Server-side component of the `meteor shell` command +standard-minifier-css@1.3.5 # CSS minifier run for production mode +standard-minifier-js@2.2.0 # JS minifier run for production mode +es5-shim@4.6.15 # ECMAScript 5 compatibility for older browsers. +ecmascript@0.9.0 # Enable ECMAScript2015+ syntax in app code +shell-server@0.3.0 # Server-side component of the `meteor shell` command react-meteor-data alanning:roles fourseven:scss -accounts-base@1.4.2 +accounts-base@1.4.0 accounts-password@1.5.0 service-configuration@1.0.11 -accounts-facebook@1.3.1 -accounts-github@1.4.1 -accounts-google@1.3.1 +accounts-facebook@1.3.0 +accounts-github@1.4.0 +accounts-google@1.3.0 themeteorchef:bert fortawesome:fontawesome aldeed:collection2-core@2.0.1 audit-argument-checks@1.0.7 ddp-rate-limiter@1.0.7 -dynamic-import@0.3.0 +dynamic-import@0.2.0 static-html alexwine:bootstrap-4 @@ -53,17 +53,15 @@ practicalmeteor:chai aldeed:schema-deny aldeed:template-extension dburles:collection-helpers -less@2.7.11 -markdown@1.0.12 -mongo-livedata@1.0.12 +less +markdown +mongo-livedata reywood:publish-composite barbatus:stars-rating arkham:comments-ui -facts@1.0.9 +facts gadicohen:sitemaps nspangler:autoreconnect saucecode:timezoned-synced-cron -# lmachens:kadira +meteorhacks:kadira meteorhacks:zones -nimble:restivus -xolvio:cleaner diff --git a/.meteor/release b/.meteor/release index 011385b..0fa8d22 100644 --- a/.meteor/release +++ b/.meteor/release @@ -1 +1 @@ -METEOR@1.6.1.1 +METEOR@1.6 diff --git a/.meteor/versions b/.meteor/versions index 9cbf1c3..c76cf8b 100644 --- a/.meteor/versions +++ b/.meteor/versions @@ -1,56 +1,52 @@ 255kb:meteor-status@1.5.0 -accounts-base@1.4.2 -accounts-facebook@1.3.1 -accounts-github@1.4.1 -accounts-google@1.3.1 +accounts-base@1.4.0 +accounts-facebook@1.3.0 +accounts-github@1.4.0 +accounts-google@1.3.0 accounts-oauth@1.1.15 -accounts-password@1.5.1 +accounts-password@1.5.0 alanning:roles@1.2.16 -aldeed:collection2-core@2.1.2 +aldeed:collection2-core@2.0.1 aldeed:schema-deny@2.0.1 aldeed:template-extension@4.1.0 -alexwine:bootstrap-4@4.1.0 +alexwine:bootstrap-4@4.0.0-beta.2 allow-deny@1.1.0 arkham:comments-ui@1.4.4 audit-argument-checks@1.0.7 -autoupdate@1.4.0 -babel-compiler@7.0.7 -babel-runtime@1.2.2 +autoupdate@1.3.12 +babel-compiler@6.24.7 +babel-runtime@1.1.1 barbatus:stars-rating@1.1.1 -base64@1.0.11 +base64@1.0.10 binary-heap@1.0.10 blaze@2.3.2 blaze-tools@1.0.10 -boilerplate-generator@1.4.0 -browser-policy@1.1.0 -browser-policy-common@1.0.11 -browser-policy-content@1.1.0 -browser-policy-framing@1.1.0 -caching-compiler@1.1.11 +boilerplate-generator@1.3.0 +caching-compiler@1.1.9 caching-html-compiler@1.1.2 -callback-hook@1.1.0 -check@1.3.1 +callback-hook@1.0.10 +check@1.2.5 coffeescript@1.0.17 dburles:collection-helpers@1.1.0 dburles:mongo-collection-instances@0.3.5 ddp@1.4.0 -ddp-client@2.3.2 -ddp-common@1.4.0 +ddp-client@2.2.0 +ddp-common@1.3.0 ddp-rate-limiter@1.0.7 -ddp-server@2.1.2 +ddp-server@2.1.0 deps@1.0.12 -diff-sequence@1.1.0 -dynamic-import@0.3.0 -ecmascript@0.10.7 +diff-sequence@1.0.7 +dynamic-import@0.2.0 +ecmascript@0.9.0 ecmascript-runtime@0.5.0 -ecmascript-runtime-client@0.6.2 +ecmascript-runtime-client@0.5.0 ecmascript-runtime-server@0.5.0 ejson@1.1.0 email@1.2.3 -es5-shim@4.7.3 +es5-shim@4.6.15 facebook-oauth@1.4.0 facts@1.0.9 -flowkey:raven@1.1.0 +flowkey:raven@1.0.0 fortawesome:fontawesome@4.7.0 fourseven:scss@4.5.4 gadicc:blaze-react-component@1.4.0 @@ -58,53 +54,54 @@ gadicohen:robots-txt@0.0.10 gadicohen:sitemaps@0.0.26 geojson-utils@1.0.10 github-oauth@1.2.0 -google-oauth@1.2.5 +google-oauth@1.2.4 hot-code-push@1.0.4 html-tools@1.0.11 htmljs@1.0.11 -http@1.4.0 -id-map@1.1.0 -jquery@1.11.11 +http@1.3.0 +id-map@1.0.9 +jquery@1.11.10 lai:collection-extensions@0.2.1_1 launch-screen@1.1.1 -less@2.7.12 +less@2.7.11 livedata@1.0.18 localstorage@1.2.0 -logging@1.1.20 +logging@1.1.19 markdown@1.0.12 maximum:package-base@1.1.2 maximum:server-transform@0.5.0 mdg:geolocation@1.3.0 -meteor@1.8.6 -meteor-base@1.3.0 +meteor@1.8.0 +meteor-base@1.2.0 meteorhacks:inject-initial@1.0.4 +meteorhacks:kadira@2.30.4 +meteorhacks:meteorx@1.4.1 meteorhacks:zones@1.6.0 -meteortesting:browser-tests@0.2.0 -meteortesting:mocha@0.5.1 -minifier-css@1.3.1 -minifier-js@2.3.4 -minimongo@1.4.4 +meteortesting:browser-tests@0.1.2 +meteortesting:mocha@0.5.0 +minifier-css@1.2.16 +minifier-js@2.2.1 +minimongo@1.4.0 mobile-experience@1.0.5 mobile-status-bar@1.0.14 -modules@0.11.6 -modules-runtime@0.9.2 -mongo@1.4.7 +modules@0.11.0 +modules-runtime@0.9.0 +mongo@1.3.0 mongo-dev-server@1.1.0 -mongo-id@1.0.7 +mongo-id@1.0.6 mongo-livedata@1.0.12 mys:fonts@0.0.2 natestrauser:publish-performant-counts@0.1.2 -nimble:restivus@0.8.12 npm-bcrypt@0.9.3 npm-mongo@2.2.34 nspangler:autoreconnect@0.0.1 -oauth@1.2.3 +oauth@1.2.0 oauth2@1.2.0 observe-sequence@1.0.16 -ordered-dict@1.1.0 +ordered-dict@1.0.9 ostrio:cookies@2.1.3 ostrio:mailer@2.1.0 -ostrio:meteor-root@1.0.7 +ostrio:meteor-root@1.0.6 peerlibrary:assert@0.2.5 peerlibrary:fiber-utils@0.6.0 peerlibrary:reactive-mongo@0.1.1 @@ -112,44 +109,39 @@ peerlibrary:server-autorun@0.5.2 percolate:migrations@1.0.2 practicalmeteor:chai@2.1.0_1 practicalmeteor:mocha-core@1.0.1 -promise@0.10.2 +promise@0.10.0 raix:eventemitter@0.1.3 -random@1.1.0 -rate-limit@1.0.9 -react-meteor-data@0.2.16 +random@1.0.10 +rate-limit@1.0.8 +react-meteor-data@0.2.15 reactive-dict@1.2.0 reactive-var@1.0.11 -reload@1.2.0 -retry@1.1.0 -reywood:publish-composite@1.6.0 -routepolicy@1.0.13 +reload@1.1.11 +retry@1.0.9 +reywood:publish-composite@1.5.2 +routepolicy@1.0.12 saucecode:timezoned-synced-cron@1.2.11 selaias:cookie-consent@0.4.0 -server-render@0.3.0 service-configuration@1.0.11 session@1.1.7 sha@1.0.9 -shell-server@0.3.1 -shim-common@0.1.0 -simple:json-routes@2.1.0 -socket-stream-client@0.1.0 +shell-server@0.3.0 spacebars@1.0.15 spacebars-compiler@1.1.3 srp@1.0.10 -standard-minifier-css@1.4.1 -standard-minifier-js@2.3.3 +standard-minifier-css@1.3.5 +standard-minifier-js@2.2.1 static-html@1.2.2 templating@1.3.2 templating-compiler@1.3.3 templating-runtime@1.3.2 templating-tools@1.1.2 themeteorchef:bert@2.1.3 -tmeasday:check-npm-versions@0.3.2 +tmeasday:check-npm-versions@0.3.1 tracker@1.1.3 ui@1.0.13 underscore@1.0.10 -url@1.2.0 +url@1.1.0 vjrj:piwik@0.3.1 -webapp@1.5.0 +webapp@1.4.0 webapp-hashing@1.0.9 -xolvio:cleaner@0.3.3 diff --git a/.meteorignore b/.meteorignore deleted file mode 100644 index b25101c..0000000 --- a/.meteorignore +++ /dev/null @@ -1,3 +0,0 @@ -# Don't reload meteor server when we modify or do test -cucumber -output.json diff --git a/API.md b/API.md deleted file mode 100644 index ed2561b..0000000 --- a/API.md +++ /dev/null @@ -1,7 +0,0 @@ -Basic API: - -```bash -curl -X GET http://localhost:3000/api/v1/status/last-fire-detected -curl -X GET http://localhost:3000/api/v1/status/last-fire-check -curl -X GET http://localhost:3000/api/v1/status/active-fires-count -``` diff --git a/README.md b/README.md index 4417536..7e5be67 100644 --- a/README.md +++ b/README.md @@ -6,14 +6,7 @@ This is web service that notifies about fires detected in an area of your intere ## Install -Prerrequisites: a running meteor (we share the `fuegos` database with the telegram bot). - -Install `meteor` and run `meteor npm install` and `npm start` - -Some other development deps (in debian & ubuntu): -``` -apt-get install libcairo2-dev libjpeg-dev libgif-dev pkg-config -``` +Install `meteor` and run `npm start` ### GeoIP @@ -29,23 +22,18 @@ More platforms and services in the future... We do tests via: ``` -TEST_PORT=3000 TEST_WATCH=1 TEST_CLIENT=0 MONGO_URL=mongodb://localhost:27017/fuegostest meteor --settings settings-development.json test --driver-package meteortesting:mocha --port 3010 +TEST_WATCH=1 MONGO_URL=mongodb://localhost:27017/fuegos meteor --settings settings-development.json test --driver-package meteortesting:mocha --port 3010 # 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 +44,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/client/main.js b/client/main.js index f139266..1412f30 100644 --- a/client/main.js +++ b/client/main.js @@ -1,4 +1 @@ import '../imports/startup/client'; - -// https://github.com/GoogleChrome/rendertron#rendering-budget-timeout -window.renderComplete = false; 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/feedback.feature b/cucumber/features/feedback.feature index 3bbb113..d1ffed5 100644 --- a/cucumber/features/feedback.feature +++ b/cucumber/features/feedback.feature @@ -1,3 +1,4 @@ +@watch Feature: Allow users to give feedback As a user (authenticated or not) @@ -9,7 +10,6 @@ Feature: Allow users to give feedback # Because is not verified Then I can send feedback with email When I sign out - Given I'm not logged Then I can send feedback with email Scenario: A anon user can give feedback diff --git a/cucumber/features/pages.feature b/cucumber/features/pages.feature index 0807c2c..1c17a30 100644 --- a/cucumber/features/pages.feature +++ b/cucumber/features/pages.feature @@ -1,8 +1,5 @@ Feature: Test all secundary pages - Background: - Given I am on the home page - Scenario: Check that all secondary pages work well Given a list of page ids and contents | license | License | @@ -13,9 +10,8 @@ Feature: Test all secundary pages | about | About | Then I check that all pages works properly - Scenario: Check that all secondary pages work well when not logged - Given I logout if logged - And a list of page urls and contents + Scenario: Check that all secondary pages work well + Given a list of page urls and contents | login | Login | true | | signup | Sign Up | true | | fires | Fires | true | @@ -25,27 +21,10 @@ Feature: Test all secundary pages | fire/inexistent | This page doesn't exist | false | | 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 - # And they are spiderable - @watch - Scenario: Check that all secondary pages work well when logged - Given I have an account and I logged in - And a list of page urls and contents - | status | Status | false | - | fires | Fires | true | - | zones | Monitored | true | - | verify-email/something | Verify | true | - | fire/inexistent | This page doesn't exist | false | - | 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 - Scenario: Check that other non visible pages work well - Given I logout if logged - And a list of non visible pages ids and contents - | error | Upppps | false | - # FIXME: find a way to monitor json pages - # | api/v1/status/last-fire-detected | updateAt | - # | api/v1/status/last-fire-check | description | - # | api/v1/status/active-fires-count | total | + Given a list of non visible pages ids and contents + | status | Status | + | error | Upps | Then I check that all non visible pages works properly diff --git a/cucumber/features/steps_definitions/feedback.js b/cucumber/features/steps_definitions/feedback.js index ffd78be..f25a3b3 100644 --- a/cucumber/features/steps_definitions/feedback.js +++ b/cucumber/features/steps_definitions/feedback.js @@ -24,7 +24,6 @@ module.exports = function doSteps() { client.waitForVisible('#feedback-tab', 10000); client.click('#feedback-tab'); client.waitForVisible('#feedback-form', 10000); - client.waitForEnabled('input[name="email"]'); client.setValue('input[name="email"]', randomEmail()); client.setValue('#feedbackTextarea', randomText(500)); client.click('#sendFeedbackBtn'); diff --git a/cucumber/features/steps_definitions/hooks.js b/cucumber/features/steps_definitions/hooks.js index 64b405a..9e3e611 100644 --- a/cucumber/features/steps_definitions/hooks.js +++ b/cucumber/features/steps_definitions/hooks.js @@ -2,50 +2,17 @@ (function () { module.exports = function () { - const printLog = (type) => { - client.log(type).value.forEach((logItem) => { - const { source, message } = logItem; - if (type === 'browser' && source === 'console-api') { - // Clean messages of type: - // http://localhost:3000/app/app.js?hash=34da4258bd2caa4e14c7c0c20ba6396f309e7236 13325:18 "GMaps script just loaded" - // to get the last part - const cleanMessage = message.replace(/[^ ]{1,} [^ ]{1,} "(.*)"$/g, '$1'); - console.log(`${logItem.level}: ${cleanMessage}`); - } else { - // Right now we don't need logs like: - // %cDownload the React DevTools for a better development experience: https://fb.me/react-devtools - // console.log(`${logItem.level}: ${message}`); - } - }); - }; - - this.StepResult((stepResult) => { - if (stepResult.getStatus() === 'failed') { - // http://webdriver.io/api/protocol/log.html - console.log(`Step result: ${stepResult.getStatus()}`); - printLog('browser'); - } - }); - this.Before(() => { // global.expect = require('@xolvio/jasmine-expect').expect; - // https://github.com/webdriverio/webdriverio/issues/1145 + /* Not used if (!this.initMyCmds) { client.addCommand('waitForClickable', function elementClickable(selector, timeout) { this.waitForVisible(selector, timeout); this.waitForEnabled(selector, timeout); }); - - client.addCommand('waitUntilText', function waitUntilText(selector, text, ms) { - ms = ms || 5000; - this.waitForVisible(selector); - const self = this; - this.waitUntil(() => self.getText(selector).includes(text), ms, `expected text in ${selector} be different after ${ms / 1000}s. Obtained: '${self.getText(selector)}' expected: '${text}'`); - }); - this.initMyCmds = true; - } + } */ client.windowHandleSize({ width: 1500, height: 1000 }); diff --git a/cucumber/features/steps_definitions/login.js b/cucumber/features/steps_definitions/login.js index b255b96..03f11f8 100644 --- a/cucumber/features/steps_definitions/login.js +++ b/cucumber/features/steps_definitions/login.js @@ -10,7 +10,7 @@ let pass; let email; const setUserValues = (isEdit) => { - client.waitForVisible('input[name="firstName"]', 10000); + client.waitForVisible('input[name="firstName"]', 5000); firstName = randomUsername(); lastName = randomUsername(); email = randomEmail(); @@ -32,54 +32,31 @@ module.exports = function doSteps(notos) { goHome(client); }); - function waitNoBert() { - client.pause(2000); - if (client.isVisible('.bert-container')) { - // console.log('Removing bert alert'); - client.click('.bert-container'); - client.waitForVisible('.bert-container', 10000, true); - } else { - // console.log('No bert alert'); - } - } - - function logoutIfLogged() { + function register() { if (client.isVisible('#logout')) { client.click('#logout'); } - client.waitForVisible('#signup', 10000); - } - - function register() { - logoutIfLogged(); - client.waitForVisible('#signup', 10000); + client.waitForVisible('#signup', 5000); client.click('#signup'); setUserValues(); if (!notos) { client.click('input[name="tos"]'); } client.click('#signUpSubmit'); - client.waitForVisible('#logout', 10000); } - this.Given(/^I logout if logged$/, () => { - waitNoBert(); - logoutIfLogged(); - }); - this.Given(/^I have an account and I logged in$/, () => { register(); }); function checkName() { client.waitForVisible('#profile', 5000); - client.waitUntilText('#profile', firstName); - client.waitUntilText('#profile', lastName); + client.waitForText('#profile', firstName); + client.waitForText('#profile', lastName); } this.Then(/^I should be logged in$/, () => { client.waitUntil(() => client.isVisible('#logout') === true, 10000); - client.waitUntil(() => client.isVisible('#profile') === true, 10000); }); /* this.Given(/^I am signed out$/, () => { @@ -87,24 +64,29 @@ module.exports = function doSteps(notos) { * }); */ + function closeAlert() { + // client.waitForVisible('.bert-alert', 5000); + // client.click('.bert-content'); + client.waitUntil(() => client.isVisible('.bert-alert') === false, 10000); + } this.When(/^I sign out$/, () => { - waitNoBert(); - client.waitForClickable('#logout', 10000); - // client.waitForVisible('#logout', 10000); + if (client.isVisible('.bert-alert')) { + client.waitUntil(() => client.isVisible('.bert-alert') === false, 10000); + } + client.waitForVisible('#logout', 5000); client.click('#logout'); - client.waitForVisible('#login', 10000); }); function login(badpass) { - waitNoBert(); client.waitForVisible('#login', 10000); + client.waitUntil(() => client.isVisible('.bert-alert') === false, 10000); client.click('#login'); client.waitForVisible('#loginSubmit', 5000); client.setValue('input[name="emailAddress"]', email); client.setValue('input[name="password"]', badpass ? randomPassword() : pass); client.click('#loginSubmit'); - waitNoBert(); + closeAlert(); } this.When(/^I enter my email and password$/, () => { @@ -112,13 +94,11 @@ module.exports = function doSteps(notos) { }); this.Then(/^I can edit my profile$/, () => { - waitNoBert(); - client.waitForClickable('#profile', 10000); + client.waitForVisible('#profile', 5000); client.click('#profile'); - client.pause(2000); setUserValues(true); client.click('#profileSubmit'); - waitNoBert(); + closeAlert(); checkName(); }); @@ -136,7 +116,7 @@ module.exports = function doSteps(notos) { this.Then(/^I should be registered$/, () => { client.waitForVisible('.bert-alert', 10000, true); - waitNoBert(); + closeAlert(); }); this.Given(/^I register with some name, password and email but without accept conditions$/, () => { diff --git a/cucumber/features/steps_definitions/pages.js b/cucumber/features/steps_definitions/pages.js index 29f6aab..38e61b0 100644 --- a/cucumber/features/steps_definitions/pages.js +++ b/cucumber/features/steps_definitions/pages.js @@ -16,7 +16,7 @@ module.exports = function () { const content = pages[i][1]; client.waitForVisible(id, 10000); client.click(id); - client.waitUntilText('#react-root', content); + client.waitForText('#react-root', content); // https://jasmine.github.io/2.3/introduction.html#section-Expectations expect(client.getTitle()).toContain(pages[i][1]); } @@ -26,7 +26,7 @@ module.exports = function () { const content = pages[i][1]; client.url(`${process.env.ROOT_URL}/${url}`); client.waitForVisible('#react-root', 10000); - client.waitUntilText('#react-root', content); + client.waitForText('#react-root', content); const checkTitle = pages[i][2] === 'true'; // https://jasmine.github.io/2.3/introduction.html#section-Expectations if (checkTitle) { @@ -41,7 +41,10 @@ module.exports = function () { this.Then(/^I check that all non visible pages works properly$/, (callback) => { for (let i = 0; i < pages.length; i += 1) { - processPageUrl(i); + client.url(`${process.env.ROOT_URL}/${pages[i][0]}`); + const content = pages[i][1]; + client.waitForVisible('#react-root', 10000); + client.waitForText('#react-root', content); } callback(); }); @@ -74,7 +77,7 @@ module.exports = function () { // Write code here that turns the phrase above into concrete actions for (let i = 0; i < pages.length; i += 1) { client.url(`${process.env.ROOT_URL}/${pages[i][0]}?_escaped_fragment_=`); - client.waitUntilText('#react-root', pages[i][1]); + client.waitForText('#react-root', pages[i][1]); } callback(); }); diff --git a/designs/icons-tcef.svg b/designs/icons-tcef.svg index 0835ab8..a35fdfa 100644 --- a/designs/icons-tcef.svg +++ b/designs/icons-tcef.svg @@ -247,7 +247,7 @@ inkscape:pageopacity="0.0" inkscape:pageshadow="2" inkscape:zoom="0.23559314" - inkscape:cx="-2890.4884" + inkscape:cx="-1825.0924" inkscape:cy="-486.23766" inkscape:document-units="mm" inkscape:current-layer="layer1" @@ -621,39 +621,5 @@ y="0" x="0" /> - - - - - - - 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..2be6e68 100644 --- a/imports/api/ActiveFires/server/countFires.js +++ b/imports/api/ActiveFires/server/countFires.js @@ -2,7 +2,6 @@ import { whichAreFalsePositives, firesUnion } from '/imports/api/FalsePositives/server/publications'; import FalsePositives from '/imports/api/FalsePositives/FalsePositives'; import Industries from '/imports/api/Industries/Industries'; -import ravenLogger from '/imports/startup/server/ravenLogger'; import ActiveFires from '../ActiveFires'; const debug = 0; @@ -29,51 +28,46 @@ const findFiresInRegion = (zone) => { return result; }; -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) { - realFires.push(fire); - } - }); - // group fires - const realUnion = firesUnion(realFires); - const unionCount = realUnion[0] && - realUnion[0].geometry && - realUnion[0].geometry.coordinates ? realUnion[0].geometry.coordinates.length : 0; - return unionCount; -}; - -const countFiresInRegions = (regions, stringsToRemove) => { +const countFires = (regions, stringsToRemove) => { let total = 0; const fireStats = {}; regions.features.forEach((region) => { const regionName = cleanProv(region.properties.name, stringsToRemove); - const regionCode = region.properties.iso_a2; if (debug) console.log(`${regionName} -----`); try { const fires = findFiresInRegion(region); // TODO Also check neighbour fires (when better implementation of that part) const initialCount = fires.count(); - if (initialCount > 0) { - const unionCount = countRealFires(fires); - if (debug) console.log(`${regionName} initial: ${initialCount}, union calc: ${unionCount}`); + let count = initialCount; + if (count > 0) { + const realFires = []; + fires.forEach((fire) => { + const union = firesUnion([fire]); + const falsePos = whichAreFalsePositives(FalsePositives, union); + const industries = whichAreFalsePositives(Industries, union); + if (falsePos.count() === 0 && industries.count() === 0) { + realFires.push(fire); + } else { + count -= 1; + } + }); + // group fires + const realUnion = firesUnion(realFires); + const unionCount = realUnion[0] && + realUnion[0].geometry && + realUnion[0].geometry.coordinates ? realUnion[0].geometry.coordinates.length : 0; + if (debug) console.log(`${regionName} initial: ${initialCount}, first calc: ${count} union calc: ${unionCount}`); if (unionCount > 0) { total += unionCount; - fireStats[regionName] = { count: unionCount, code: regionCode }; + fireStats[regionName] = unionCount; } } } catch (e) { - ravenLogger.log(e); + console.log(e); } }); return { total, fires: fireStats }; }; -export default countFiresInRegions; +export default countFires; diff --git a/imports/api/ActiveFires/server/publications.js b/imports/api/ActiveFires/server/publications.js index 03878b3..9aee452 100644 --- a/imports/api/ActiveFires/server/publications.js +++ b/imports/api/ActiveFires/server/publications.js @@ -31,12 +31,12 @@ const activefires = (northEastLng, northEastLat, southWestLng, southWestLat, wit lat: 1, lon: 1, when: 1, - scan: 1, - track: 1 + scan: 1 } }); - if (Meteor.isDevelopment) console.log(`Active fires total: ${fires.count()}`); + // console.log(JSON.stringify(result)); + // console.log(`Fires total: ${fires.count()}`); if (withMarks && fires.fetch().length > 0) { const union = firesUnion(fires); @@ -59,7 +59,6 @@ Meteor.publish('activefiresmyloc', function activeInMyLoc(northEastLng, northEas return activefires(northEastLng, northEastLat, southWestLng, southWestLat, withMarks); }); -// Warning: this increase always by one the fire stats Meteor.publish('lastFireDetected', function lastFireDetected() { return ActiveFires.find({}, { limit: 1, sort: { when: -1 } }); }); diff --git a/imports/api/ActiveFires/server/tweetFiresInZone.js b/imports/api/ActiveFires/server/tweetFiresInZone.js index 74d40fb..7c9bc4d 100644 --- a/imports/api/ActiveFires/server/tweetFiresInZone.js +++ b/imports/api/ActiveFires/server/tweetFiresInZone.js @@ -2,11 +2,8 @@ import getPrivateFile from '/imports/modules/server/get-private-file'; import { Meteor } from 'meteor/meteor'; import tbuffer from '@turf/buffer'; -import emojiFlags from 'emoji-flags'; import tweet from '/imports/modules/server/twitter'; -import countFiresInRegions from './countFires'; - -const debug = false; +import countFires from './countFires'; const autonomies = tbuffer(JSON.parse(getPrivateFile('data/es-atlas-autonomies.json')), 0); const portugal = tbuffer(JSON.parse(getPrivateFile('data/pt-atlas.json')), 0); @@ -33,16 +30,7 @@ const stringsToRemoveIberia = [ [' ', ''] ]; -const emoji = (key, name, fires) => { - const emo = emojiFlags.countryCode(key.code); - if (!emo) { - if (Meteor.isDevelopment) console.log(`Cannot find flag for country ${name}: ${JSON.stringify(key)}`); - return ''; - } - return `${emo.emoji} ${fires}`; -}; - -const composeEuropeTweet = (num, stats, abrev) => { +const composeEuropeTweet = (num, stats) => { if (num <= 0) { return ''; } @@ -50,18 +38,11 @@ const composeEuropeTweet = (num, stats, abrev) => { const numZones = keys.length; let text = ''; keys.map((key, index) => { // , index - const zoneFires = stats[key].count; + const zoneFires = stats[key]; const zoneFiresText = zoneFires > 1 ? index === 0 ? zoneFires : `${zoneFires}` : index === 0 ? 'One' : '1'; const what = index > 0 ? ' ' : zoneFires > 1 ? ' active #wildfires ' : ' active #wildfire '; - const subText = abrev ? emoji(stats[key], key, zoneFires) : - `${zoneFiresText}${what}in #{key}`; - let delimiter; - if (!abrev) { - delimiter = index === numZones - 1 ? ' and' : ','; - } else { - delimiter = ','; - } - + const subText = `${zoneFiresText}${what}in #${key}`; + const delimiter = index === numZones - 1 ? ' and' : ','; const subTexts = index === 0 ? subText : `${delimiter} ${subText}`; text += subTexts; return ''; @@ -77,7 +58,7 @@ const composeIberiaTweet = (num, stats) => { const numZones = keys.length; let text = ''; keys.map((key, index) => { // , index - const zoneFires = stats[key].count; + const zoneFires = stats[key]; const zoneFiresText = zoneFires > 1 ? index === 0 ? zoneFires : `otros ${zoneFires}` : index === 0 ? 'Un' : 'otro'; const what = index > 0 ? ' ' : zoneFires > 1 ? ' #fuegos activos ' : ' #fuego activo '; const subText = `${zoneFiresText}${what}en #${key}`; @@ -92,30 +73,11 @@ const composeIberiaTweet = (num, stats) => { const tweetHeaders = ['🔥']; const tweetFooters = ['Más info en: https://fuegos.comunes.org/fires']; - -const correctTweetSize = (tweetText) => { - const length = tweetText.length; - if (length > 280) { - if (Meteor.isDevelopment) console.log(`Tweet is too big (${length}): ${tweetText}`); - return false; - } - return true; -}; - const tweetEuropeFires = () => { - const resultEu = countFiresInRegions(europe, [[' ', '']]); + const resultEu = countFires(europe, [[' ', '']]); if (resultEu.total > 0) { - let tweetText = `${tweetHeaders[0].trim()} ${composeEuropeTweet(resultEu.total, resultEu.fires, false)}. More info: https://fires.comunes.org/fires`; - if (!correctTweetSize(tweetText)) { - tweetText = `${tweetHeaders[0].trim()}Active #wildfires in #Europe:\n${composeEuropeTweet(resultEu.total, resultEu.fires, true)}. More info: https://fires.comunes.org/fires`; - } - if (!correctTweetSize(tweetText)) { - tweetText = `${tweetHeaders[0].trim()}Active #wildfires in #Europe:\n${composeEuropeTweet(resultEu.total, resultEu.fires, true)}`; - } - if (!correctTweetSize(tweetText)) { - tweetText = `${tweetHeaders[0].trim()} There are ${resultEu.total} active #wildfires in #Europe. More info: https://fires.comunes.org/fires`; - } + const tweetText = `${tweetHeaders[0].trim()} ${composeEuropeTweet(resultEu.total, resultEu.fires)}. More info: https://fires.comunes.org/fires`; if (Meteor.isDevelopment) { console.log(tweetText); } else { @@ -126,8 +88,8 @@ const tweetEuropeFires = () => { }; const tweetIberiaFires = () => { - const resultEs = countFiresInRegions(autonomies, stringsToRemoveIberia); - const resultPt = countFiresInRegions(portugal, stringsToRemoveIberia); + const resultEs = countFires(autonomies, stringsToRemoveIberia); + const resultPt = countFires(portugal, stringsToRemoveIberia); const fires = Object.assign(resultEs.fires, resultPt.fires); const total = resultEs.total + resultPt.total; @@ -135,19 +97,17 @@ const tweetIberiaFires = () => { // console.log(`Total fires ${total}, ${JSON.stringify(fires)}`); if (total > 0) { const tweetText = `${tweetHeaders[0].trim()} ${composeIberiaTweet(total, fires)}. ${tweetFooters[0]}`; - if (correctTweetSize(tweetText)) { - if (Meteor.isDevelopment) { - console.log(tweetText); - } else { - tweet(tweetText, 'es'); - } + if (Meteor.isDevelopment) { + console.log(tweetText); + } else { + tweet(tweetText, 'es'); } return tweetText; } return ''; }; -if (Meteor.isDevelopment && debug) { +if (Meteor.isDevelopment) { tweetIberiaFires(); tweetEuropeFires(); } 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/methods.js b/imports/api/FalsePositives/methods.js index 85005ba..7aa4ef1 100644 --- a/imports/api/FalsePositives/methods.js +++ b/imports/api/FalsePositives/methods.js @@ -6,29 +6,6 @@ import FalsePositiveTypes from './FalsePositiveTypes'; import Fires from '../Fires/Fires'; import rateLimit from '../../modules/rate-limit'; -export function upsertFalsePositive(keyType, owner, fire) { - const fireType = fire.type; - if (fireType === 'vecinal') { - throw new Meteor.Error('500', 'No se puede marcar este tipo de fuego'); - } - const date = new Date(); - const formated = moment(date).format('YYYYMMDD'); - try { - const set = { - owner, - type: keyType, - when: date, - whendateformat: formated, - fireId: fire._id, - geo: fire.ourid - }; - return FalsePositives.upsert({ geo: fire.ourid, owner }, { $set: set }, { multi: false, upsert: true }); - } catch (exception) { - console.log(exception); - throw new Meteor.Error('500', exception); - } -} - Meteor.methods({ 'falsePositives.insert': function falsePositivesInsert(fireId, keyType) { check(fireId, Meteor.Collection.ObjectID); @@ -40,9 +17,27 @@ Meteor.methods({ } check(this.userId, String); const fire = Fires.findOne(fireId); - const owner = this.userId; if (fire) { - upsertFalsePositive(keyType, owner, fire); + const fireType = fire.type; + if (fireType === 'vecinal') { + throw new Meteor.Error('500', 'No se puede marcar este tipo de fuego'); + } + const date = new Date(); + const formated = moment(date).format('YYYYMMDD'); + try { + const set = { + owner: this.userId, + type: keyType, + when: date, + whendateformat: formated, + fireId, + geo: fire.ourid + }; + return FalsePositives.upsert({ geo: fire.ourid, owner: this.userId }, { $set: set }, { multi: false, upsert: true }); + } catch (exception) { + console.log(exception); + throw new Meteor.Error('500', exception); + } } else { throw new Meteor.Error('500', 'Fuego no encontrado'); } diff --git a/imports/api/FalsePositives/server/publications.js b/imports/api/FalsePositives/server/publications.js index a79ae1a..ca66105 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,13 @@ 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); - return union; -}; - -export const zoneToUnion = (lat, lon, distance) => { - 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..dca2c5d 100644 --- a/imports/api/FireAlerts/server/publications.js +++ b/imports/api/FireAlerts/server/publications.js @@ -33,10 +33,9 @@ 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()}`); + // console.log(`Fires total: ${fires.count()}`); return fires; }); diff --git a/imports/api/Fires/server/publications.js b/imports/api/Fires/server/publications.js index 3862beb..96d6a85 100644 --- a/imports/api/Fires/server/publications.js +++ b/imports/api/Fires/server/publications.js @@ -12,7 +12,6 @@ import FireAlertsCollection from '/imports/api/FireAlerts/FireAlerts'; import { whichAreFalsePositives, firesUnion } from '/imports/api/FalsePositives/server/publications'; import FalsePositives from '/imports/api/FalsePositives/FalsePositives'; import Industries from '/imports/api/Industries/Industries'; -import ravenLogger from '/imports/startup/server/ravenLogger'; import FiresCollection from '../Fires'; @@ -133,46 +132,32 @@ Meteor.publish('fireFromId', function fireFromId(_id) { } }); -function logUrl(fireEnc, params) { - const message = `Wrong fire: ${fireEnc}. Params received in url: ${JSON.stringify(params)}`; - console.log(message); - ravenLogger.log(message); -} - -export function fireFromHash(fireEnc, params) { +Meteor.publish('fireFromHash', function fireFromHash(fireEnc) { check(fireEnc, String); - check(params, Object); - - // console.log(fireEnc); - // const unsealed = Promise.await(urlEnc.decrypt(fireEnc)); - const unsealed = Promise.await(unsealW(fireEnc)); - if (unsealed === undefined) { - throw Error(`Fail to unseal '${fireEnc}'`); - } - const w = unsealed.when; - unsealed.when = new Date(w); - const c = unsealed.createdAt; - unsealed.createdAt = !c ? new Date() : new Date(c); - const u = unsealed.updatedAt; - unsealed.updatedAt = !u ? new Date() : new Date(u); - // console.log(unsealed); - // FIXME: - const unsealedFix = fixConfidence(unsealed); - FiresCollection.schema.validate(unsealedFix); - return findOrCreateFire(unsealedFix); - /* console.log(`fires: ${fire.count()}`); - * return fire; */ -} - -Meteor.publish('fireFromHash', function fireFromHashFunc(fireEnc, params) { - check(fireEnc, String); - check(params, Object); try { - return fireFromHash(fireEnc, params); + // console.log(fireEnc); + // const unsealed = Promise.await(urlEnc.decrypt(fireEnc)); + const unsealed = Promise.await(unsealW(fireEnc)); + if (unsealed === undefined) { + console.info(`Wrong fire: ${fireEnc}`); + // https://guide.meteor.com/data-loading.html + return this.ready(); + } + const w = unsealed.when; + unsealed.when = new Date(w); + const c = unsealed.createdAt; + unsealed.createdAt = !c ? new Date() : new Date(c); + const u = unsealed.updatedAt; + unsealed.updatedAt = !u ? new Date() : new Date(u); + // console.log(unsealed); + // FIXME: + const unsealedFix = fixConfidence(unsealed); + FiresCollection.schema.validate(unsealedFix); + return findOrCreateFire(unsealedFix); + /* console.log(`fires: ${fire.count()}`); + * return fire; */ } catch (e) { console.warn(e); - logUrl(fireEnc, params); - // https://guide.meteor.com/data-loading.html return this.ready(); } }); diff --git a/imports/api/Notifications/Notifications.js b/imports/api/Notifications/Notifications.js index 0355540..0c89c55 100644 --- a/imports/api/Notifications/Notifications.js +++ b/imports/api/Notifications/Notifications.js @@ -2,7 +2,6 @@ /* eslint-disable import/no-absolute-path */ import { Mongo } from 'meteor/mongo'; -import { Meteor } from 'meteor/meteor'; import SimpleSchema from 'simpl-schema'; import { defaultCreatedAt, defaultUpdateAt } from '/imports/api/Utility/Utils.js'; import LocationSchema from '/imports/api/Utility/LocationSchema.js'; @@ -23,12 +22,11 @@ Notifications.deny({ Notifications.schema = new SimpleSchema({ userId: String, - subsId: { type: Meteor.Collection.ObjectID, optional: true, blackbox: true }, content: String, geo: LocationSchema, type: String, - notified: { type: Boolean, optional: true }, - notifiedAt: { type: Date, optional: true }, + webNotified: { type: Boolean, optional: true }, + webNotifiedAt: { type: Date, optional: true }, emailNotified: { type: Boolean, optional: true }, emailNotifiedAt: { type: Date, optional: true }, when: Date, diff --git a/imports/api/Notifications/methods.js b/imports/api/Notifications/methods.js index 05d7d08..604c6e0 100644 --- a/imports/api/Notifications/methods.js +++ b/imports/api/Notifications/methods.js @@ -8,7 +8,7 @@ Meteor.methods({ check(notifId, Meteor.Collection.ObjectID); try { - Notifications.update(notifId, { $set: { notified: true, notifiedAt: new Date() } }); + Notifications.update(notifId, { $set: { webNotified: true, webNotifiedAt: new Date() } }); return notifId; } catch (exception) { throw new Meteor.Error('500', exception); diff --git a/imports/api/Notifications/server/publications.js b/imports/api/Notifications/server/publications.js index d868692..83ceaaf 100644 --- a/imports/api/Notifications/server/publications.js +++ b/imports/api/Notifications/server/publications.js @@ -4,7 +4,7 @@ import { Meteor } from 'meteor/meteor'; import Notifications from '../Notifications'; Meteor.publish('mynotifications', function notifications() { - const notif = Notifications.find({ userId: this.userId, type: 'web', notified: null }); + const notif = Notifications.find({ userId: this.userId, type: 'web', webNotified: null }); // console.log(`Notifications for user ${this.userId}: ${notif.count()}`); return notif; }); diff --git a/imports/api/Rest/Rest.js b/imports/api/Rest/Rest.js deleted file mode 100644 index ae5e383..0000000 --- a/imports/api/Rest/Rest.js +++ /dev/null @@ -1,457 +0,0 @@ -/* 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, zoneToUnion } from '/imports/api/FalsePositives/server/publications'; -import { fireFromHash } from '/imports/api/Fires/server/publications.js'; -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'; -import { Mongo } from 'meteor/mongo'; -import { upsertFalsePositive } from '/imports/api/FalsePositives/methods.js'; - -const debug = false; - -const uptime = new Date(); - -const restivusError = (code, message) => ({ status: 'error', statusCode: code, body: message }); - - -function failMsg(msg) { - return restivusError(500, `Unexpected error in REST call: ${msg}`); -} - -function fail(e) { - return restivusError(500, `Unexpected error in REST call: ${e}`); -} - -function defaultFailParams(e) { - return restivusError(400, `Wrong REST params: ${e}`); -} - -function checkAuthToken(token) { - if (!Meteor.settings.private.internalApiToken || token !== Meteor.settings.private.internalApiToken) { - const message = 'Unauthorized auth token in REST API'; - console.warn(message); - return restivusError(401, 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)); -} - -if (!Meteor.settings.private.internalApiToken) { - console.warn('Meteor.settings.private.internalApiToken is not configured so we don\'t enable our REST API'); -} else { -// 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, - type: 1, // modis, viirs, vecinal - scan: 1, - track: 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; - } - - let union; - - // TODO only get real - const firesA = fires.fetch(); - - if (firesA.length > 0) { - union = firesUnion(fires); - } else { - union = zoneToUnion(lat, lng, km); - } - const falsePos = whichAreFalsePositives(FalsePositives, union); - const industries = whichAreFalsePositives(Industries, union); - result.fires = firesA; - result.industries = industries.fetch(); - result.falsePos = falsePos.fetch(); - return result; - } - - // 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; - - const already = Meteor.users.find({ fireBaseToken: mobileToken }); - - if (already.count() > 1) { - return restivusError(500, 'Unexpected error in REST call: several users with that mobile token?'); - } else if (already.count() === 1) { - username = already.fetch()[0].username; - } else { - do { - username = Random.id(15); - } while (Meteor.users.find({ username }).count() !== 0); - } - - // FIXME check valid lang - - const now = new Date(); - - 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 upsertUser = Meteor.users.findOne({ username }); - - if (debug) console.log(upsertUser); - - return jsend.success({ - upsertResult: result, - username, - userId: upsertUser._id, - lang: upsertUser.lang, - mobileToken: upsertUser.fireBaseToken - }); - } - - }); - - // max sitance: 100km - apiV1.addRoute('mobile/subscriptions', { authRequired: false }, { - post: function post() { - const { - token, - mobileToken, - id, - lat, - lon, - distance - } = this.bodyParams; - let oid; - try { - check(token, String); - check(mobileToken, String); - check(id, String); - oid = new Mongo.ObjectID(id); - check(oid, Meteor.Collection.ObjectID); - check(lat, Number); - check(lon, Number); - check(distance, Number); - checkLatLonDist(distance, lat, lon); - } catch (e) { - if (debug) console.log(e); - return defaultFailParams(e); - } - - const failed = checkAuthToken(token); - if (failed) return failed; - - const user = Meteor.users.findOne({ fireBaseToken: mobileToken }); - if (!user) return failMsg('User not found'); - - const newSubs = {}; - newSubs._id = new Meteor.Collection.ObjectID(id); - 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 }); - } - }); - - apiV1.addRoute('mobile/subscriptions/:token/:mobileToken/:subsId', { authRequired: false }, { - delete: function del() { - const { - token, - mobileToken, - subsId - } = this.urlParams; - 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 failMsg('User not found'); - - try { - Subscriptions.remove({ owner: user._id, _id: new Meteor.Collection.ObjectID(subsId) }); - } catch (e) { - return fail(e); - } - - return jsend.success({}); - } - }); - - apiV1.addRoute('mobile/subscriptions/all/:token/:mobileToken', { authRequired: false }, { - get: function get() { - const { token, mobileToken } = this.urlParams; - try { - check(token, String); - check(mobileToken, String); - } catch (e) { - return defaultFailParams(e); - } - - const failed = checkAuthToken(token); - if (failed) return failed; - - const user = Meteor.users.findOne({ fireBaseToken: mobileToken }); - if (!user) return failMsg('User not found'); - - const result = Subscriptions.find({ owner: user._id }); - - return jsend.success({ subscriptions: result.fetch(), count: result.count() }); - }, - delete: function delAll() { - const { token, mobileToken } = this.urlParams; - try { - check(token, String); - check(mobileToken, String); - } catch (e) { - return defaultFailParams(e); - } - - const failed = checkAuthToken(token); - if (failed) return failed('Auth api check failed'); - - if (Meteor.users.find({ fireBaseToken: mobileToken }).count() !== 1) return fail; - - const user = Meteor.users.findOne({ fireBaseToken: mobileToken }); - if (!user) return failMsg('User not found'); - - const toRemove = Subscriptions.find({ owner: user._id }).count(); - Subscriptions.remove({ owner: user._id }); - - return jsend.success({ count: toRemove }); - } - }); - - apiV1.addRoute('status/subs-public-union/:token', { authRequired: false }, { - get: function get() { - const { token } = this.urlParams; - try { - check(token, String); - } catch (e) { - return defaultFailParams(e); - } - - const failed = checkAuthToken(token); - if (failed) return failed; - - const currentUnion = SiteSettings.findOne({ name: 'subs-public-union' }); - const userSubsBounds = SiteSettings.findOne({ name: 'subs-public-union-bounds' }); - - return jsend.success({ union: currentUnion, bounds: userSubsBounds }); - } - }); - - apiV1.addRoute('mobile/falsepositive', { authRequired: false }, { - post: function post() { - const { - token, - mobileToken, - sealed, - type - } = this.bodyParams; - try { - check(token, String); - check(mobileToken, String); - check(sealed, String); - check(type, String); - } catch (e) { - if (debug) console.log(e); - return defaultFailParams(e); - } - - const failed = checkAuthToken(token); - if (failed) return failed; - - 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'); - } - }); -} diff --git a/imports/api/Subscriptions/Subscriptions.js b/imports/api/Subscriptions/Subscriptions.js index c69e20b..eb4be16 100644 --- a/imports/api/Subscriptions/Subscriptions.js +++ b/imports/api/Subscriptions/Subscriptions.js @@ -1,7 +1,6 @@ /* eslint-disable consistent-return */ /* eslint-disable import/no-absolute-path */ -import { Meteor } from 'meteor/meteor'; import { Mongo } from 'meteor/mongo'; import SimpleSchema from 'simpl-schema'; import { defaultCreatedAt, defaultUpdateAt } from '/imports/api/Utility/Utils.js'; @@ -43,7 +42,6 @@ Subscriptions.deny({ Subscriptions.schema = new SimpleSchema({ - _id: { type: Meteor.Collection.ObjectID, optional: true, blackbox: true }, location: Object, 'location.lat': Number, 'location.lon': Number, diff --git a/imports/api/Subscriptions/methods.js b/imports/api/Subscriptions/methods.js index cf927e0..618ed21 100644 --- a/imports/api/Subscriptions/methods.js +++ b/imports/api/Subscriptions/methods.js @@ -10,49 +10,30 @@ function geo(doc) { }; } -export function subscriptionsInsert(doc, userId, type) { - check(doc, { - _id: Match.Maybe(Meteor.Collection.ObjectID), - 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({ - 'subscriptions.insert': function subscriptionInsertMethod(doc) { + 'subscriptions.insert': function subscriptionsInsert(doc) { check(doc, { location: Match.ObjectIncluding({ lat: Number, lon: Number }), distance: Number }); - return subscriptionsInsert(doc, this.userId, 'web'); + const type = '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) { check(doc, { @@ -71,9 +52,15 @@ Meteor.methods({ throw new Meteor.Error('500', exception); } }, - 'subscriptions.remove': function subscriptionsRemoveMethod(subscriptionId) { + 'subscriptions.remove': function subscriptionsRemove(subscriptionId) { check(subscriptionId, Meteor.Collection.ObjectID); - return subscriptionsRemove(subscriptionId); + + try { + return Subscriptions.remove(subscriptionId); + } catch (exception) { + console.error(exception); + throw new Meteor.Error('500', exception); + } } }); diff --git a/imports/api/Users/Users.js b/imports/api/Users/Users.js index 8911a47..34ade38 100644 --- a/imports/api/Users/Users.js +++ b/imports/api/Users/Users.js @@ -78,8 +78,11 @@ const schemaUser = new SimpleSchema({ // Option 2: [String] type // If you are sure you will never need to use role groups, then // you can specify [String] as the type - roles: { type: Array, optional: true }, - 'roles.$': { type: String, optional: true }, + roles: { + type: Array, + // autoform: { type: 'hidden' }, + optional: true + }, // https://github.com/todda00/meteor-friendly-slugs/issues/1 friendlySlugs: { type: Object, @@ -107,7 +110,6 @@ const schemaUser = new SimpleSchema({ telegramUsername: { type: String, optional: true }, telegramFirstName: { type: String, optional: true }, telegramLanguageCode: { type: String, optional: true }, - fireBaseToken: { type: String, optional: true }, createdAt: defaultCreatedAt, updatedAt: defaultUpdateAt }); diff --git a/imports/api/Utility/LocationSchema.js b/imports/api/Utility/LocationSchema.js index 6aaf812..9cb603b 100644 --- a/imports/api/Utility/LocationSchema.js +++ b/imports/api/Utility/LocationSchema.js @@ -12,10 +12,10 @@ const LocationSchema = new SimpleSchema({ minCount: 2, maxCount: 2, custom: function custom() { - if (!(this.value[0] >= -180 && this.value[0] <= 180)) { + if (!(this.value[0] >= -90 && this.value[0] <= 90)) { return 'lngOutOfRange'; } - if (!(this.value[1] >= -90 && this.value[1] <= 90)) { + if (!(this.value[1] >= -180 && this.value[1] <= 180)) { return 'latOutOfRange'; } return true; @@ -27,8 +27,8 @@ const LocationSchema = new SimpleSchema({ }); LocationSchema.messageBox.messages({ - latOutOfRange: '[label] latitude should be between -90 and 90', - lngOutOfRange: '[label] longitude should be between -180 and 180' + lonOutOfRange: '[label] longitude should be between -90 and 90', + latOutOfRange: '[label] latitude should be between -180 and 180' }); export default LocationSchema; diff --git a/imports/api/Utility/server/files.js b/imports/api/Utility/server/files.js index 6f02058..717f19f 100644 --- a/imports/api/Utility/server/files.js +++ b/imports/api/Utility/server/files.js @@ -1,7 +1,5 @@ -/* eslint-disable import/no-absolute-path */ - import fs from 'fs'; -import getFallbackLang from '/imports/modules/lang-fallback'; +import getFallbackLang from '/imports/startup/server/accounts/lang-fallback'; const as = f => `assets/app/${f}`; 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/client/index.js b/imports/startup/client/index.js index 147cf4f..6cff432 100644 --- a/imports/startup/client/index.js +++ b/imports/startup/client/index.js @@ -1,8 +1,6 @@ -/* global */ import React from 'react'; import { render } from 'react-dom'; import { Meteor } from 'meteor/meteor'; -import '/imports/startup/client/modernizr'; import App from '../../ui/layouts/App/App'; import '../../ui/stylesheets/app.scss'; @@ -11,9 +9,4 @@ import '../../ui/stylesheets/app.scss'; const isIE9OrBelow = () => /MSIE\s/.test(navigator.userAgent) && parseFloat(navigator.appVersion.split('MSIE')[1]) < 10; if (isIE9OrBelow()) window.alert('You are using an outdated browser. Please use Chrome or Firefox to display this site.'); -else { - Modernizr.on('webp', (result) => { - if (Meteor.isDevelopment) console.log(`webp ${result ? '' : 'not '}supported`); - }); - Meteor.startup(() => render(, document.getElementById('react-root'))); -} +else Meteor.startup(() => render(, document.getElementById('react-root'))); diff --git a/imports/startup/client/modernizr.js b/imports/startup/client/modernizr.js deleted file mode 100644 index f6ac835..0000000 --- a/imports/startup/client/modernizr.js +++ /dev/null @@ -1,3 +0,0 @@ -/*! modernizr 3.6.0 (Custom Build) | MIT * - * https://modernizr.com/download/?-webp-prefixedcss-setclasses !*/ -!function(e,n,t){function r(e,n){return typeof e===n}function o(){var e,n,t,o,i,a,s;for(var l in w)if(w.hasOwnProperty(l)){if(e=[],n=w[l],n.name&&(e.push(n.name.toLowerCase()),n.options&&n.options.aliases&&n.options.aliases.length))for(t=0;tp;p++)if(d=e[p],m=E.style[d],f(d,"-")&&(d=s(d)),E.style[d]!==t){if(i||r(o,"undefined"))return a(),"pfx"==n?d:!0;try{E.style[d]=o}catch(v){}if(E.style[d]!=m)return a(),"pfx"==n?d:!0}return a(),!1}function v(e,n,t,o,i){var a=e.charAt(0).toUpperCase()+e.slice(1),s=(e+" "+B.join(a+" ")+a).split(" ");return r(n,"string")||r(n,"undefined")?h(s,n,o,i):(s=(e+" "+U.join(a+" ")+a).split(" "),p(s,n,t))}var y=[],w=[],C={_version:"3.6.0",_config:{classPrefix:"",enableClasses:!0,enableJSClass:!0,usePrefixes:!0},_q:[],on:function(e,n){var t=this;setTimeout(function(){n(t[e])},0)},addTest:function(e,n,t){w.push({name:e,fn:n,options:t})},addAsyncTest:function(e){w.push({name:null,fn:e})}},Modernizr=function(){};Modernizr.prototype=C,Modernizr=new Modernizr;var _,b=n.documentElement,S="svg"===b.nodeName.toLowerCase();!function(){var e={}.hasOwnProperty;_=r(e,"undefined")||r(e.call,"undefined")?function(e,n){return n in e&&r(e.constructor.prototype[n],"undefined")}:function(n,t){return e.call(n,t)}}(),C._l={},C.on=function(e,n){this._l[e]||(this._l[e]=[]),this._l[e].push(n),Modernizr.hasOwnProperty(e)&&setTimeout(function(){Modernizr._trigger(e,Modernizr[e])},0)},C._trigger=function(e,n){if(this._l[e]){var t=this._l[e];setTimeout(function(){var e,r;for(e=0;ea;a++){var s=prefixes[a],l=s.toUpperCase()+"_"+r;if(l in i)return"@-"+s.toLowerCase()+"-"+n}return!1};C.atRule=Q;var U=C._config.usePrefixes?x.toLowerCase().split(" "):[];C._domPrefixes=U;var R={elem:u("modernizr")};Modernizr._q.push(function(){delete R.elem});var E={style:R.elem.style};Modernizr._q.unshift(function(){delete E.style}),C.testAllProps=v;var T=C.prefixed=function(e,n,t){return 0===e.indexOf("@")?Q(e):(-1!=e.indexOf("-")&&(e=s(e)),n?v(e,n,t):v(e,"pfx"))};C.prefixedCSS=function(e){var n=T(e);return n&&a(n)};o(),i(y),delete C.addTest,delete C.addAsyncTest;for(var P=0;P { console.log(error); } }; -console.log(`ravenLogger ${enabled ? 'enabled' : 'disabled'}`); - export default ravenLogger; 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/modules/lang-fallback.js b/imports/startup/server/accounts/lang-fallback.js similarity index 69% rename from imports/modules/lang-fallback.js rename to imports/startup/server/accounts/lang-fallback.js index d912347..67fbb66 100644 --- a/imports/modules/lang-fallback.js +++ b/imports/startup/server/accounts/lang-fallback.js @@ -1,6 +1,5 @@ const getFallbackLang = (lang) => { - if (lang && (lang === 'ast' || lang === 'gl' || lang === 'eu' || - lang === 'es' || lang === 'ca' || lang.match(/^es-/))) { + if (lang && (lang === 'ast' || lang === 'gl' || lang === 'eu' || lang === 'ca' || lang.match(/^es-/))) { return 'es'; } return 'en'; diff --git a/imports/startup/server/accounts/on-create-user.js b/imports/startup/server/accounts/on-create-user.js index 566acc6..383b8c0 100644 --- a/imports/startup/server/accounts/on-create-user.js +++ b/imports/startup/server/accounts/on-create-user.js @@ -1,9 +1,7 @@ -/* eslint-disable import/no-absolute-path */ - import { Accounts } from 'meteor/accounts-base'; -import getFallbackLang from '/imports/modules/lang-fallback'; -import sendWelcomeEmail from '/imports/api/Users/server/send-welcome-email'; -import getOAuthProfile from '/imports/modules/get-oauth-profile'; +import sendWelcomeEmail from '../../../api/Users/server/send-welcome-email'; +import getOAuthProfile from '../../../modules/get-oauth-profile'; +import getFallbackLang from './lang-fallback'; Accounts.onCreateUser((options, user) => { const userToCreate = user; 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/catchExceptions.js b/imports/startup/server/catchExceptions.js deleted file mode 100644 index 179ab8b..0000000 --- a/imports/startup/server/catchExceptions.js +++ /dev/null @@ -1,19 +0,0 @@ -/* eslint-disable import/no-absolute-path */ - -import { Meteor } from 'meteor/meteor'; -import ravenLogger from '/imports/startup/server/ravenLogger'; - -Meteor.startup(() => { - const bound = Meteor.bindEnvironment((callback) => { callback(); }); - process.on('uncaughtException', (err) => { - bound(() => { - console.error(err); - - // https://github.com/flowkey/meteor-raven - ravenLogger.log(err); - - // https://github.com/nodejs/node-v0.x-archive/blob/master/doc/api/process.markdown#exit-codes - process.exit(7); - }); - }); -}); 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/fibers.js b/imports/startup/server/fibers.js deleted file mode 100644 index ff96265..0000000 --- a/imports/startup/server/fibers.js +++ /dev/null @@ -1,7 +0,0 @@ -// Workaround for: https://github.com/meteor/meteor/issues/9796 -// https://github.com/meteor/meteor/issues/9796#issuecomment-381676326 -// https://github.com/sandstorm-io/sandstorm/blob/0f1fec013fe7208ed0fd97eb88b31b77e3c61f42/shell/server/00-startup.js#L99-L129 - -import Fiber from 'fibers'; - -Fiber.poolSize = 1e9; diff --git a/imports/startup/server/index.js b/imports/startup/server/index.js index 7f84dde..cb0d461 100644 --- a/imports/startup/server/index.js +++ b/imports/startup/server/index.js @@ -1,6 +1,4 @@ import './ravenLogger'; -import './catchExceptions'; -import './fibers'; import './i18n'; import './accounts'; import './api'; @@ -17,4 +15,4 @@ import './prerender'; import './feedback'; import './cron'; import './isMaster'; -import './rest'; +import './segfaults'; 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..158a75c 100644 --- a/imports/startup/server/migrations.js +++ b/imports/startup/server/migrations.js @@ -9,8 +9,6 @@ import SiteSettings from '/imports/api/SiteSettings/SiteSettings'; 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(() => { @@ -229,30 +227,6 @@ Meteor.startup(() => { } }); - Migrations.add({ - version: 17, - up: function renameWebNotifiedField() { - Notifications.update({ webNotified: { $exists: true } }, { - $rename: { webNotifiedAt: 'notifiedAt' } - }, { upsert: false, multi: true }); - Notifications.update({ webNotified: { $exists: true } }, { - $rename: { webNotified: 'notified' } - }, { upsert: false, multi: true }); - } - }); - - 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..85cd531 100644 --- a/imports/startup/server/notificationsObserver.js +++ b/imports/startup/server/notificationsObserver.js @@ -1,19 +1,92 @@ /* 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 { isMailServerMaster } from '/imports/startup/server/email'; +// import sendMail from '/imports/startup/server/email'; +import sendEmail, { subjectTruncate } from '/imports/modules/server/send-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 { 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); - } + // 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 (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/ravenLogger.js b/imports/startup/server/ravenLogger.js index b3b83a3..ac242f9 100644 --- a/imports/startup/server/ravenLogger.js +++ b/imports/startup/server/ravenLogger.js @@ -2,17 +2,11 @@ import RavenLogger from 'meteor/flowkey:raven'; import { Meteor } from 'meteor/meteor'; const ravenOptions = {}; -const publicDSN = Meteor.settings.public.sentryPublicDSN; -const privateDSN = Meteor.settings.sentryPrivateDSN; -const enabled = publicDSN && privateDSN; -const ravenLogger = enabled ? new RavenLogger({ - publicDSN, - privateDSN, +const ravenLogger = new RavenLogger({ + privateDSN: Meteor.settings.sentryPrivateDSN, shouldCatchConsoleError: true, // default true trackUser: true // default false -}, ravenOptions) : { log: (error) => { console.log(error); } }; - -console.log(`ravenLogger ${enabled ? 'enabled' : 'disabled'}`); +}, ravenOptions); export default ravenLogger; diff --git a/imports/startup/server/rest.js b/imports/startup/server/rest.js deleted file mode 100644 index 64e8172..0000000 --- a/imports/startup/server/rest.js +++ /dev/null @@ -1 +0,0 @@ -import '../../api/Rest/Rest.js'; diff --git a/imports/startup/server/segfaults.js b/imports/startup/server/segfaults.js new file mode 100644 index 0000000..bb1b798 --- /dev/null +++ b/imports/startup/server/segfaults.js @@ -0,0 +1,22 @@ +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(); 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..11b8d8b 100644 --- a/imports/startup/server/subsUnion.js +++ b/imports/startup/server/subsUnion.js @@ -4,18 +4,12 @@ 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'; // sudo apt-get install libcairo2-dev libjpeg-dev libgif-dev Meteor.startup(() => { - if (!isMailServerMaster) { - console.log('We only process subsUnion in master'); - return; - } const debug = true; // Meteor.isDevelopment; Perlin.seed(Math.random()); @@ -36,8 +30,8 @@ Meteor.startup(() => { const noNoisy = sub => sub; const process = (isPublic) => { - const subscribers = Subscriptions.find().fetch(); - const result = calcUnion(L, subscribers, isPublic ? addNoisy : noNoisy, true); + const group = new L.FeatureGroup(); + const result = calcUnion(Subscriptions.find().fetch(), group, isPublic ? addNoisy : noNoisy); const union = result[0]; const bounds = result[1]; @@ -63,42 +57,19 @@ Meteor.startup(() => { type: 'string' } }; - const sizeSet = { - $set: { - name: 'subs-union-count', - value: subscribers.length, - isPublic: false, - description: 'Subscriptions count', - type: 'number' - } - }; // FIXME, take care of object size: // https://stackoverflow.com/questions/10827812/what-is-the-length-maximum-for-a-string-data-type-in-mongodb-used-with-ruby SiteSettings.upsert({ name: `subs-${publicl}-union` }, unionSet, { multi: false }); SiteSettings.upsert({ name: `subs-${publicl}-union-bounds` }, boundsSet, { multi: false }); - SiteSettings.upsert({ name: 'subs-union-count' }, sizeSet, { multi: false }); if (debug) console.log(`${Publicl} subscription union calculated`); } else { console.log('Subscription union failed!'); } }; - // At startup, we check if it's necessary to calc subscriptions union again - const currentUnion = SiteSettings.findOne({ name: 'subs-public-union' }); - const lastSubs = Subscriptions.findOne({}, { sort: { updatedAt: -1 } }); - const countUnionSubs = SiteSettings.findOne({ name: 'subs-union-count' }); - const countSubs = Subscriptions.find({}).count(); - if (currentUnion && lastSubs) { - const lastUnionUpdated = currentUnion.updatedAt; - const lastSubsUpdated = lastSubs.updatedAt; - if (lastUnionUpdated > lastSubsUpdated || !countUnionSubs || countSubs !== countUnionSubs.value) { - console.log('Subs union outdated'); - process(true); - process(false); - } else { - console.log('Subs union up-to-date'); - } - } + // At startup + process(true); + process(false); Subscriptions.find({ createdAt: { $gt: new Date() } }).observe({ added: function newSubAdded() { // doc) { diff --git a/imports/ui/components/Loading/LoadingBar.js b/imports/ui/components/Loading/LoadingBar.js index 5dab00c..943e9a5 100644 --- a/imports/ui/components/Loading/LoadingBar.js +++ b/imports/ui/components/Loading/LoadingBar.js @@ -2,8 +2,7 @@ import React from 'react'; import PropTypes from 'prop-types'; -import { Meteor } from 'meteor/meteor'; -import { Line } from 'react-progress-bar.js'; +import { Line } from 'react-progressbar.js'; import './LoadingBar.scss'; @@ -11,7 +10,7 @@ import './LoadingBar.scss'; const LoadingBar = ({ progress }) => (
diff --git a/imports/ui/components/Maps/DefMapLayers.js b/imports/ui/components/Maps/DefMapLayers.js index 6e3ed56..0db329c 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 = ( @@ -57,9 +56,9 @@ class DefMapLayers extends Component { } { this.state.gkey && - - - } + + + } { this.state.gkey && @@ -73,7 +72,6 @@ DefMapLayers.propTypes = { t: PropTypes.func.isRequired, gray: PropTypes.bool, osmcolor: PropTypes.bool, - terrain: PropTypes.bool, satellite: PropTypes.bool }; @@ -81,7 +79,6 @@ DefMapLayers.propTypes = { DefMapLayers.defaultProps = { gray: false, osmcolor: false, - terrain: false, satellite: false }; 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/components/NotificationsObserver/NotificationsObserver.js b/imports/ui/components/NotificationsObserver/NotificationsObserver.js index ac374b5..e68b1ef 100644 --- a/imports/ui/components/NotificationsObserver/NotificationsObserver.js +++ b/imports/ui/components/NotificationsObserver/NotificationsObserver.js @@ -12,7 +12,7 @@ import { trim } from './util.js'; function process(notif) { // No already notified if (Push.Permission.has()) { - if (!notif.notified && notif.type === 'web') { + if (!notif.webNotified) { Push.create(i18n.t('AppName'), { body: `${trim(notif.content)} (${i18n.t('fireDetected', { when: dateFromNow(notif.when) })})`, icon: '/n-fire-marker.png', @@ -47,7 +47,7 @@ Meteor.startup(() => { if (Meteor.userId()) { Meteor.subscribe('mynotifications'); // Check for notifications not processed at startup - Notifications.find({ notified: null, type: 'web' }).forEach((notif) => { + Notifications.find({ webNotified: null }).forEach((notif) => { process(notif); }); } diff --git a/imports/ui/components/Reconnect/Reconnect.js b/imports/ui/components/Reconnect/Reconnect.js index 6c12778..4b60590 100644 --- a/imports/ui/components/Reconnect/Reconnect.js +++ b/imports/ui/components/Reconnect/Reconnect.js @@ -10,7 +10,7 @@ import Blaze from 'meteor/gadicc:blaze-react-component'; const Reconnect = ({ t, authenticated }) => (
- { true && /* !authenticated && */ + { /* !authenticated && */ true && ( /* 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 +184,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..9862933 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); }} /> { } else { console.log('Seems a fire from enc hash'); fromHash = true; - subscription = Meteor.subscribe('fireFromHash', id, match.params); + subscription = Meteor.subscribe('fireFromHash', id); } // console.log(`Type of '${fireType}' fire, active: ${active}, archive: ${archive}, fromHash: ${fromHash}`); diff --git a/imports/ui/pages/FiresMap/FireStats.js b/imports/ui/pages/FiresMap/FireStats.js deleted file mode 100644 index a4742ef..0000000 --- a/imports/ui/pages/FiresMap/FireStats.js +++ /dev/null @@ -1,60 +0,0 @@ -/* eslint-disable react/jsx-indent-props */ -/* eslint-disable import/no-absolute-path */ -/* eslint-disable import/no-absolute-path */ - -import React, { Component, Fragment } from 'react'; -import PropTypes from 'prop-types'; -import { translate, Trans } from 'react-i18next'; -import FromNow from '/imports/ui/components/FromNow/FromNow'; - -class FireStats extends Component { - constructor(props) { - super(props); - this.state = { - lastFireDetected: props.lastFireDetected, - lastCheck: props.lastCheck, - loadingAll: props.loadingAll - }; - } - - componentWillReceiveProps(nextProps) { - if (this.props.lastCheck !== nextProps.lastCheck || - this.props.lastFireDetected !== nextProps.lastFireDetected || - this.props.loadingAll !== nextProps.loadingAll - ) { - this.setState({ - lastFireDetected: nextProps.lastFireDetected, - lastCheck: nextProps.lastCheck, - loadingAll: nextProps.loadingAll - }); - } - } - - shouldComponentUpdate(nextProps, nextState) { - return !(nextState.lastCheck === this.state.lastCheck && - nextState.lastFireDetected === this.state.lastFireDetected && - nextState.loadingAll === this.state.loadingAll); - } - - render() { - if (this.state.loadingAll) return (
Actualizando...
); - if (this.state.lastCheck && !this.state.lastFireDetected) { - return (
Actualizado .
); - } - if (this.state.lastCheck && this.state.lastFireDetected) { - return (
Actualizado , último fuego detectado .
); - } - return null; - } -} - -FireStats.propTypes = { - lastFireDetected: PropTypes.object, - loadingAll: PropTypes.bool.isRequired, - lastCheck: PropTypes.instanceOf(Date) -}; - -FireStats.defaultProps = { -}; - -export default translate([], { wait: true })(FireStats); diff --git a/imports/ui/pages/FiresMap/FiresMap.js b/imports/ui/pages/FiresMap/FiresMap.js index e797427..f1fe788 100644 --- a/imports/ui/pages/FiresMap/FiresMap.js +++ b/imports/ui/pages/FiresMap/FiresMap.js @@ -23,11 +23,10 @@ import 'leaflet-sleep/Leaflet.Sleep.js'; import geolocation from '/imports/startup/client/geolocation'; import CenterInMyPosition from '/imports/ui/components/CenterInMyPosition/CenterInMyPosition'; import FireList from '/imports/ui/components/Maps/FireList'; -import FireListUnion from '/imports/ui/components/Maps/FireListUnion'; import subsUnion from '/imports/ui/components/Maps/SubsUnion/SubsUnion'; import DefMapLayers from '/imports/ui/components/Maps/DefMapLayers'; +import FromNow from '/imports/ui/components/FromNow/FromNow'; import ActiveFiresCollection from '/imports/api/ActiveFires/ActiveFires'; -import ActiveFiresUnion from '/imports/api/ActiveFiresUnion/ActiveFiresUnion'; import FireAlertsCollection from '/imports/api/FireAlerts/FireAlerts'; import IndustriesCollection, { industriesRemap } from '/imports/api/Industries/Industries'; import FalsePositivesCollection, { falsePositivesRemap } from '/imports/api/FalsePositives/FalsePositives'; @@ -38,7 +37,7 @@ import ShareIt from '/imports/ui/components/ShareIt/ShareIt'; import FullScreenMap from '/imports/ui/components/Maps/FullScreenMap'; import LocationAutocomplete from '/imports/ui/components/LocationAutocomplete/LocationAutocomplete'; import Gkeys from '/imports/startup/client/Gkeys'; -import FireStats from './FireStats'; + import './FiresMap.scss'; const MAXZOOM = 6; @@ -75,6 +74,7 @@ class FiresMap extends React.Component { this.onViewportChanged = this.onViewportChanged.bind(this); this.onMoveEnd = this.onMoveEnd.bind(this); this.onMoveStart = this.onMoveStart.bind(this); + this.fireStats = this.fireStats.bind(this); } componentDidMount() { @@ -84,6 +84,19 @@ class FiresMap extends React.Component { }); } + /* shouldComponentUpdate(nextProps, nextState) { + * const notMoving = !nextState.moving; + * const markersChanged = this.state.useMarkers !== nextState.useMarkers; + * const unionChanged = this.state.showSubsUnion !== nextState.showSubsUnion; + * const otherViewport = this.state.viewport !== nextState.viewport; + * // const init = nextState.viewport.center === [0, 0]; + * // console.log(notMoving ? 'Not moving map' : 'Moving map'); + * // console.log(otherViewport ? 'Other viewport' : 'Not other viewport'); + * console.log(`${otherViewport ? 'OTHER' : 'Not other'} viewport ${nextState.viewport.center} zoom: ${nextState.viewport.zoom}`); + * return this.state.init || (notMoving && otherViewport && this.state.moved) || unionChanged || markersChanged; + * } + */ + shouldComponentUpdate(nextProps, nextState) { const notMoving = !nextState.moving; return notMoving; @@ -183,17 +196,22 @@ class FiresMap extends React.Component { } } + fireStats() { + return ( + + { this.props.lastCheck && this.props.lastFireDetected && + Actualizado , último fuego detectado .} + + ); + } + render() { const { t } = this.props; - const loading = this.props.loading || !this.props.subsready; - console.log(`Rendering ${loading ? 'loading' : 'LOADED'}, zoom ${this.state.viewport.zoom}, map ${this.props.activefires.length + this.props.firealerts.length} of ${this.props.activefirestotal} total. False positives: ${this.props.falsePositives.length}. Reactive ${this.state.viewport.zoom >= MAXZOOMREACTIVE}`); + console.log(`Rendering ${this.props.loading ? 'loading' : 'LOADED'}, zoom ${this.state.viewport.zoom}, map ${this.props.activefires.length + this.props.firealerts.length} of ${this.props.activefirestotal} total. False positives: ${this.props.falsePositives.length}. Reactive ${this.state.viewport.zoom >= MAXZOOMREACTIVE}`); const title = `${t('AppName')}: ${t('Fuegos activos')}`; - if (Meteor.isDevelopment && this.props.activefires.length === 1) console.log(`Active fire: ${JSON.stringify(this.props.activefires[0])}`); - if (Meteor.isDevelopment) console.log(`Active fires total: ${this.props.activefires.length}`); - if (Meteor.isDevelopment) console.log(`Active fires union total: ${this.props.activefiresunion.length}`); if (Meteor.isDevelopment) console.log(`False positives total: ${this.props.falsePositives.length}`); - if (Meteor.isDevelopment) console.log(`Fire alerts total: ${this.props.firealerts.length}`); + if (Meteor.isDevelopment) console.log(`Industries total: ${this.props.industries.length}`); if (!this.state.init) { @@ -235,8 +253,8 @@ class FiresMap extends React.Component {

{ (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. + No hay fuegos activos en esta zona del mapa. {{ countTotal: this.props.activefirestotal }} fuegos activos en el mundo. {this.fireStats()} : + En rojo, {{ count: this.props.activefires.length + this.props.firealerts.length }} fuegos activos. {{ countTotal: this.props.activefirestotal }} fuegos activos en el mundo. {this.fireStats()} }

{isNotHomeAndMobile() && this.props.firealerts.length > 0 && @@ -253,7 +271,7 @@ class FiresMap extends React.Component {

- {loading ? + {this.props.loading || !this.props.subsready ? : ''} {/* https://github.com/CliffCloud/Leaflet.Sleep */} @@ -297,14 +315,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'); @@ -406,8 +413,7 @@ export default translate([], { wait: true })(withTracker(() => { if (typeof showUnionStored === 'boolean') { showUnion.set(showUnionStored); } - // Disable, because this increase the number of fires by one - // Meteor.subscribe('lastFireDetected'); + Meteor.subscribe('lastFireDetected'); Meteor.autorun(() => { if ((centerStored !== [0, 0] || geolocation.get()) && geoInit) { center.set(centerStored || geolocation.get()); @@ -423,14 +429,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 +449,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' }); @@ -460,23 +457,20 @@ export default translate([], { wait: true })(withTracker(() => { const fireAlerts = FireAlertsCollection.find().fetch(); const falsePositives = FalsePositivesCollection.find().fetch().map(falsePositivesRemap); const industries = IndustriesCollection.find().fetch().map(industriesRemap); - 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: !subscription ? true : !(subscription.ready() && settingsSubs.ready() && alertSubscription.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, lastCheck: lastCheck ? lastCheck.value : null, - lastFireDetected, + lastFireDetected: ActiveFiresCollection.findOne({}, { sort: { when: -1 } }), center: center.get(), marks: marks.get(), showUnion: showUnion.get(), diff --git a/imports/ui/pages/Index/Index-custom.scss b/imports/ui/pages/Index/Index-custom.scss index 8e089c1..7273007 100644 --- a/imports/ui/pages/Index/Index-custom.scss +++ b/imports/ui/pages/Index/Index-custom.scss @@ -187,72 +187,28 @@ transform: translate3d(0, 0, 0); } -.no-webp .carousel-item-1 { - background-image: url('/images/home-slide1.jpg') +.carousel-item-1 { + background-image: url('/home-slide1.jpg') } -.no-webp .carousel-item-2.lazy { - background-image: url('/images/home-slide2.jpg') +.carousel-item-2 { + background-image: url('/home-slide2.jpg') } -.no-webp .carousel-item-3.lazy { - background-image: url('/images/home-slide3.jpg') +.carousel-item-3 { + background-image: url('/home-slide3.jpg') } -.no-webp .carousel-item-4.lazy { - background-image: url('/images/home-slide4.jpg') +.carousel-item-4 { + background-image: url('/home-slide4.jpg') } -.no-webp .carousel-snd-item-1 { - background-image: url('/images/home-slide-snd-1.jpg') +.carousel-snd-item-1 { + background-image: url('/home-slide-snd-1.jpg') } -.no-webp .carousel-snd-item-2.lazy { - background-image: url('/images/home-slide-snd-2.jpg') -} - -.webp .carousel-item-1 { - background-image: url('/images/home-slide1.webp') -} - -.webp .carousel-item-2.lazy { - background-image: url('/images/home-slide2.webp') -} - -.webp .carousel-item-3.lazy { - background-image: url('/images/home-slide3.webp') -} - -.webp .carousel-item-4.lazy { - background-image: url('/images/home-slide4.webp') -} - -/* .webp .carousel-item-1 { - background-image: url('/images/home-slide1-blur.webp') -} */ - -.webp .carousel-item-2 { - background-image: url('/images/home-slide2-blur.webp') -} - -.webp .carousel-item-3 { - background-image: url('/images/home-slide3-blur.webp') -} - -.webp .carousel-item-4 { - background-image: url('/images/home-slide4-blur.webp') -} - -.webp .carousel-snd-item-1 { - background-image: url('/images/home-slide-snd-1.webp') -} - -.webp .carousel-snd-item-2 { - background-image: url('/images/home-slide-snd-2-blur.webp') -} - -.webp .carousel-snd-item-2.lazy { - background-image: url('/images/home-slide-snd-2.webp') +.carousel-snd-item-2 { + background-image: url('/home-slide-snd-2.jpg') } .participe-btn { @@ -269,52 +225,28 @@ -moz-background-size: cover; background-size: cover; -o-background-size: cover; -} - -.no-webp .bg-image-full { - background-image: url('/images/home-fires.jpg'); -} - -.webp .bg-image-full { - background-image: url('/images/home-fires.webp'); + background-image: url('/home-fires.jpg'); } @media only screen and (max-width: 767px) { - .no-webp .bg-image-full { - background-image: url('/images/home-fires-800.jpg'); - } - - .webp .bg-image-full { - background-image: url('/images/home-fires-800.webp'); + .bg-image-full { + background-image: url('/home-fires-800.jpg'); } } @media only screen and (max-width: 400px) { - .no-webp .bg-image-full { - background-image: url('/images/home-fires-400.jpg'); - } - - .webp .bg-image-full { - background-image: url('/images/home-fires-400.webp'); + .bg-image-full { + background-image: url('/home-fires-400.jpg'); } } @media only screen and (max-width: 1000px) { - .no-webp .bg-image-full { - background-image: url('/images/home-fires-1000.jpg'); - } - - .webp .bg-image-full { - background-image: url('/images/home-fires-1000.webp'); - } + .bg-image-full { + background-image: url('/home-fires-1000.jpg'); } } -.nowebp .device[data-device=iPhone6][data-orientation=portrait][data-color=white] { - background-image: url(/images/mobile.png); -} - -.webp .device[data-device=iPhone6][data-orientation=portrait][data-color=white] { - background-image: url(/images/mobile.webp); +.device[data-device=iPhone6][data-orientation=portrait][data-color=white] { + background-image: url(/mobile.png); } .full-width .page-header { // section titles diff --git a/imports/ui/pages/Index/Index.js b/imports/ui/pages/Index/Index.js index 9986fba..10e50c8 100644 --- a/imports/ui/pages/Index/Index.js +++ b/imports/ui/pages/Index/Index.js @@ -2,7 +2,6 @@ /* eslint-disable react/jsx-indent-props */ /* eslint-disable key-spacing */ /* eslint-env jquery */ -import 'simple-line-icons/css/simple-line-icons.css'; import React, { Component } from 'react'; import { translate, Trans } from 'react-i18next'; import { Helmet } from 'react-helmet'; @@ -11,7 +10,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'; @@ -19,8 +17,9 @@ import { isAnyMobile } from '/imports/ui/components/Utils/isMobile'; import SubscriptionEditor from '/imports/ui/components/SubscriptionEditor/SubscriptionEditor'; 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 'simple-line-icons/css/simple-line-icons.css'; import './Index.scss'; import './Index-custom.scss'; @@ -35,22 +34,11 @@ class Index extends Component { self.scaleHeader(); }, 250); this.onResize = this.onResize.bind(this); - this.getBotUrl = this.getBotUrl.bind(this); } componentDidMount() { - const car1 = $('#carouselMainIndicators'); - const car2 = $('#carouselSndIndicators'); - car1.carousel(); - car2.carousel(); - /* _.defer(() => { - $('#firsthomeslide').addClass('lazy'); - }); */ - const loadLazy = (ev) => { - ev.relatedTarget.classList.add('lazy'); - }; - car1.on('slide.bs.carousel', loadLazy); - car2.on('slide.bs.carousel', loadLazy); + $('#carouselMainIndicators').carousel(); + $('#carouselSndIndicators').carousel(); } onResize() { @@ -69,14 +57,6 @@ class Index extends Component { return (
); } - getBotUrl() { - const fallbackLng = getFallbackLang(this.props.i18n.language); - console.log(`language: ${this.props.i18n.language}, fallback: ${fallbackLng}`); - return fallbackLng === 'es' ? - 'https://t.me/TodosContraElFuego_bot' : - 'https://t.me/AllAgainstTheFire_bot'; - } - scaleHeader() { const scalable = document.getElementById('tcefh1'); const margin = 10; @@ -89,7 +69,7 @@ class Index extends Component { } handleBtnClick() { - window.open(this.getBotUrl(), '_blank'); + window.open('https://t.me/TodosContraElFuego_bot', '_blank'); } gotoParticipe() { @@ -102,7 +82,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 (
@@ -127,7 +106,7 @@ class Index extends Component {
-
+

Siempre alerta a los fuegos en nuestro vecindario

{/* {this.props.t('Participa')} */} -
- -
@@ -274,7 +250,7 @@ class Index extends Component {
{/* Demo image for screen mockup, you can put an image here, some HTML, an animation, video, or anything else! */} - +
{}} role="button" onClick={() => this.handleBtnClick()} />
@@ -284,7 +260,7 @@ class Index extends Component {
- +

Telegram

@@ -332,7 +308,6 @@ class Index extends Component { Index.propTypes = { history: PropTypes.object.isRequired, - i18n: PropTypes.object.isRequired, t: PropTypes.func.isRequired }; 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 => (