Compare commits

..

No commits in common. "tcef-master" and "1.8.3" have entirely different histories.

213 changed files with 2626 additions and 16886 deletions

8
.gitignore vendored
View file

@ -9,11 +9,3 @@ npm-debug.log
.deploy/ .deploy/
public/locales/undefined/ public/locales/undefined/
.deploy-staging .deploy-staging
.screenshots
.vscode
output.json
# fase-0 higiene: variantes de settings con secretos (no versionar)
settings-production-*.json
settings-tests.json
*.json~

View file

@ -4,33 +4,33 @@
# 'meteor add' and 'meteor remove' will edit this file for you, # 'meteor add' and 'meteor remove' will edit this file for you,
# but you can also edit it by hand. # 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 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 reactive-var@1.0.11 # Reactive variable for tracker
tracker@1.1.3 # Meteor's client-side reactive programming library 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-css@1.3.5 # CSS minifier run for production mode
standard-minifier-js@2.3.1 # JS minifier run for production mode standard-minifier-js@2.2.0 # JS minifier run for production mode
es5-shim@4.7.0 # ECMAScript 5 compatibility for older browsers. es5-shim@4.6.15 # ECMAScript 5 compatibility for older browsers.
ecmascript@0.10.6 # Enable ECMAScript2015+ syntax in app code ecmascript@0.9.0 # Enable ECMAScript2015+ syntax in app code
shell-server@0.3.1 # Server-side component of the `meteor shell` command shell-server@0.3.0 # Server-side component of the `meteor shell` command
react-meteor-data react-meteor-data
alanning:roles alanning:roles
fourseven:scss fourseven:scss
accounts-base@1.4.2 accounts-base@1.4.0
accounts-password@1.5.0 accounts-password@1.5.0
service-configuration@1.0.11 service-configuration@1.0.11
accounts-facebook@1.3.1 accounts-facebook@1.3.0
accounts-github@1.4.1 accounts-github@1.4.0
accounts-google@1.3.1 accounts-google@1.3.0
themeteorchef:bert themeteorchef:bert
fortawesome:fontawesome fortawesome:fontawesome
aldeed:collection2-core@2.0.1 aldeed:collection2-core@2.0.1
audit-argument-checks@1.0.7 audit-argument-checks@1.0.7
ddp-rate-limiter@1.0.7 ddp-rate-limiter@1.0.7
dynamic-import@0.3.0 dynamic-import@0.2.0
static-html static-html
alexwine:bootstrap-4 alexwine:bootstrap-4
@ -45,6 +45,7 @@ natestrauser:publish-performant-counts
maximum:server-transform maximum:server-transform
mys:fonts # fonst in npm mys:fonts # fonst in npm
ostrio:mailer # mailer ostrio:mailer # mailer
mizzao:user-status # user status (online, etc)
# babrahams:constellation # dev utility # babrahams:constellation # dev utility
percolate:migrations # db migrations percolate:migrations # db migrations
ostrio:meteor-root ostrio:meteor-root
@ -53,17 +54,10 @@ practicalmeteor:chai
aldeed:schema-deny aldeed:schema-deny
aldeed:template-extension aldeed:template-extension
dburles:collection-helpers dburles:collection-helpers
less@2.7.11 less
markdown@1.0.12 markdown
mongo-livedata@1.0.12 mongo-livedata
reywood:publish-composite reywood:publish-composite
barbatus:stars-rating barbatus:stars-rating
arkham:comments-ui arkham:comments-ui
facts@1.0.9 facts
gadicohen:sitemaps
nspangler:autoreconnect
saucecode:timezoned-synced-cron
# lmachens:kadira
meteorhacks:zones
nimble:restivus
xolvio:cleaner

View file

@ -1 +1 @@
METEOR@1.6.1.1 METEOR@1.6

View file

@ -1,110 +1,102 @@
255kb:meteor-status@1.5.0 255kb:meteor-status@1.5.0
accounts-base@1.4.2 accounts-base@1.4.0
accounts-facebook@1.3.1 accounts-facebook@1.3.0
accounts-github@1.4.1 accounts-github@1.4.0
accounts-google@1.3.1 accounts-google@1.3.0
accounts-oauth@1.1.15 accounts-oauth@1.1.15
accounts-password@1.5.1 accounts-password@1.5.0
alanning:roles@1.2.16 alanning:roles@1.2.16
aldeed:collection2-core@2.1.2 aldeed:collection2-core@2.0.1
aldeed:schema-deny@2.0.1 aldeed:schema-deny@2.0.1
aldeed:template-extension@4.1.0 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 allow-deny@1.1.0
arkham:comments-ui@1.4.4 arkham:comments-ui@1.4.4
audit-argument-checks@1.0.7 audit-argument-checks@1.0.7
autoupdate@1.4.0 autoupdate@1.3.12
babel-compiler@7.0.7 babel-compiler@6.24.7
babel-runtime@1.2.2 babel-runtime@1.1.1
barbatus:stars-rating@1.1.1 barbatus:stars-rating@1.1.1
base64@1.0.11 base64@1.0.10
binary-heap@1.0.10 binary-heap@1.0.10
blaze@2.3.2 blaze@2.3.2
blaze-tools@1.0.10 blaze-tools@1.0.10
boilerplate-generator@1.4.0 boilerplate-generator@1.3.0
browser-policy@1.1.0 caching-compiler@1.1.9
browser-policy-common@1.0.11
browser-policy-content@1.1.0
browser-policy-framing@1.1.0
caching-compiler@1.1.11
caching-html-compiler@1.1.2 caching-html-compiler@1.1.2
callback-hook@1.1.0 callback-hook@1.0.10
check@1.3.1 check@1.2.5
coffeescript@1.0.17 coffeescript@1.0.17
dburles:collection-helpers@1.1.0 dburles:collection-helpers@1.1.0
dburles:mongo-collection-instances@0.3.5 dburles:mongo-collection-instances@0.3.5
ddp@1.4.0 ddp@1.4.0
ddp-client@2.3.2 ddp-client@2.2.0
ddp-common@1.4.0 ddp-common@1.3.0
ddp-rate-limiter@1.0.7 ddp-rate-limiter@1.0.7
ddp-server@2.1.2 ddp-server@2.1.0
deps@1.0.12 deps@1.0.12
diff-sequence@1.1.0 diff-sequence@1.0.7
dynamic-import@0.3.0 dynamic-import@0.2.0
ecmascript@0.10.7 ecmascript@0.9.0
ecmascript-runtime@0.5.0 ecmascript-runtime@0.5.0
ecmascript-runtime-client@0.6.2 ecmascript-runtime-client@0.5.0
ecmascript-runtime-server@0.5.0 ecmascript-runtime-server@0.5.0
ejson@1.1.0 ejson@1.1.0
email@1.2.3 email@1.2.3
es5-shim@4.7.3 es5-shim@4.6.15
facebook-oauth@1.4.0 facebook-oauth@1.4.0
facts@1.0.9 facts@1.0.9
flowkey:raven@1.1.0 flowkey:raven@1.0.0
fortawesome:fontawesome@4.7.0 fortawesome:fontawesome@4.7.0
fourseven:scss@4.5.4 fourseven:scss@4.5.4
gadicc:blaze-react-component@1.4.0 gadicc:blaze-react-component@1.4.0
gadicohen:robots-txt@0.0.10
gadicohen:sitemaps@0.0.26
geojson-utils@1.0.10 geojson-utils@1.0.10
github-oauth@1.2.0 github-oauth@1.2.0
google-oauth@1.2.5 google-oauth@1.2.4
hot-code-push@1.0.4 hot-code-push@1.0.4
html-tools@1.0.11 html-tools@1.0.11
htmljs@1.0.11 htmljs@1.0.11
http@1.4.0 http@1.3.0
id-map@1.1.0 id-map@1.0.9
jquery@1.11.11 jquery@1.11.10
lai:collection-extensions@0.2.1_1 lai:collection-extensions@0.2.1_1
launch-screen@1.1.1 launch-screen@1.1.1
less@2.7.12 less@2.7.11
livedata@1.0.18 livedata@1.0.18
localstorage@1.2.0 localstorage@1.2.0
logging@1.1.20 logging@1.1.19
markdown@1.0.12 markdown@1.0.12
maximum:package-base@1.1.2 maximum:package-base@1.1.2
maximum:server-transform@0.5.0 maximum:server-transform@0.5.0
mdg:geolocation@1.3.0 mdg:geolocation@1.3.0
meteor@1.8.6 meteor@1.8.0
meteor-base@1.3.0 meteor-base@1.2.0
meteorhacks:inject-initial@1.0.4 meteortesting:browser-tests@0.1.2
meteorhacks:zones@1.6.0 meteortesting:mocha@0.5.0
meteortesting:browser-tests@0.2.0 minifier-css@1.2.16
meteortesting:mocha@0.5.1 minifier-js@2.2.1
minifier-css@1.3.1 minimongo@1.4.0
minifier-js@2.3.4 mizzao:timesync@0.3.4
minimongo@1.4.4 mizzao:user-status@0.6.7
mobile-experience@1.0.5 mobile-experience@1.0.5
mobile-status-bar@1.0.14 mobile-status-bar@1.0.14
modules@0.11.6 modules@0.11.0
modules-runtime@0.9.2 modules-runtime@0.9.0
mongo@1.4.7 mongo@1.3.0
mongo-dev-server@1.1.0 mongo-dev-server@1.1.0
mongo-id@1.0.7 mongo-id@1.0.6
mongo-livedata@1.0.12 mongo-livedata@1.0.12
mys:fonts@0.0.2 mys:fonts@0.0.2
natestrauser:publish-performant-counts@0.1.2 natestrauser:publish-performant-counts@0.1.2
nimble:restivus@0.8.12
npm-bcrypt@0.9.3 npm-bcrypt@0.9.3
npm-mongo@2.2.34 npm-mongo@2.2.34
nspangler:autoreconnect@0.0.1 oauth@1.2.0
oauth@1.2.3
oauth2@1.2.0 oauth2@1.2.0
observe-sequence@1.0.16 observe-sequence@1.0.16
ordered-dict@1.1.0 ordered-dict@1.0.9
ostrio:cookies@2.1.3 ostrio:cookies@2.1.3
ostrio:mailer@2.1.0 ostrio:mailer@2.1.0
ostrio:meteor-root@1.0.7 ostrio:meteor-root@1.0.6
peerlibrary:assert@0.2.5 peerlibrary:assert@0.2.5
peerlibrary:fiber-utils@0.6.0 peerlibrary:fiber-utils@0.6.0
peerlibrary:reactive-mongo@0.1.1 peerlibrary:reactive-mongo@0.1.1
@ -112,44 +104,37 @@ peerlibrary:server-autorun@0.5.2
percolate:migrations@1.0.2 percolate:migrations@1.0.2
practicalmeteor:chai@2.1.0_1 practicalmeteor:chai@2.1.0_1
practicalmeteor:mocha-core@1.0.1 practicalmeteor:mocha-core@1.0.1
promise@0.10.2 promise@0.10.0
raix:eventemitter@0.1.3 raix:eventemitter@0.1.3
random@1.1.0 random@1.0.10
rate-limit@1.0.9 rate-limit@1.0.8
react-meteor-data@0.2.16 react-meteor-data@0.2.15
reactive-dict@1.2.0 reactive-dict@1.2.0
reactive-var@1.0.11 reactive-var@1.0.11
reload@1.2.0 reload@1.1.11
retry@1.1.0 retry@1.0.9
reywood:publish-composite@1.6.0 reywood:publish-composite@1.5.2
routepolicy@1.0.13 routepolicy@1.0.12
saucecode:timezoned-synced-cron@1.2.11
selaias:cookie-consent@0.4.0 selaias:cookie-consent@0.4.0
server-render@0.3.0
service-configuration@1.0.11 service-configuration@1.0.11
session@1.1.7 session@1.1.7
sha@1.0.9 sha@1.0.9
shell-server@0.3.1 shell-server@0.3.0
shim-common@0.1.0
simple:json-routes@2.1.0
socket-stream-client@0.1.0
spacebars@1.0.15 spacebars@1.0.15
spacebars-compiler@1.1.3 spacebars-compiler@1.1.3
srp@1.0.10 srp@1.0.10
standard-minifier-css@1.4.1 standard-minifier-css@1.3.5
standard-minifier-js@2.3.3 standard-minifier-js@2.2.1
static-html@1.2.2 static-html@1.2.2
templating@1.3.2 templating@1.3.2
templating-compiler@1.3.3 templating-compiler@1.3.3
templating-runtime@1.3.2 templating-runtime@1.3.2
templating-tools@1.1.2 templating-tools@1.1.2
themeteorchef:bert@2.1.3 themeteorchef:bert@2.1.3
tmeasday:check-npm-versions@0.3.2 tmeasday:check-npm-versions@0.3.1
tracker@1.1.3 tracker@1.1.3
ui@1.0.13
underscore@1.0.10 underscore@1.0.10
url@1.2.0 url@1.1.0
vjrj:piwik@0.3.1 vjrj:piwik@0.3.1
webapp@1.5.0 webapp@1.4.0
webapp-hashing@1.0.9 webapp-hashing@1.0.9
xolvio:cleaner@0.3.3

View file

@ -1,3 +0,0 @@
# Don't reload meteor server when we modify or do test
cucumber
output.json

7
API.md
View file

@ -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
```

View file

@ -1,19 +1,10 @@
# Tod@s contra el Fuego (All Against Fire) # Tod@s contra el Fuego (All Against Fire)
[![Build Status](http://ci.comunes.org/buildStatus/icon?job=todos-contra-el-fuego-web)](http://ci.comunes.org/job/todos-contra-el-fuego-web/)
This is web service that notifies about fires detected in an area of your interest. It helps the early detection of fires and facilitates local mobilization, while the professional extinction services arrive. This is web service that notifies about fires detected in an area of your interest. It helps the early detection of fires and facilitates local mobilization, while the professional extinction services arrive.
## Install ## Install
Prerrequisites: a running meteor (we share the `fuegos` database with the telegram bot). Install `meteor` and run `npm start`
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
```
### GeoIP ### GeoIP
@ -29,23 +20,9 @@ More platforms and services in the future...
We do tests via: 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
# and
node_modules/.bin/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 ## 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*. *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 +33,4 @@ GNU APLv3. See our [LICENSE](https://github.com/comunes/todos-contra-el-fuego-we
## Thanks & other acknowlegments ## Thanks & other acknowlegments
Thanks indeed to: (...)
- Lupe Bao Reixa for Galician translation

View file

@ -1,16 +0,0 @@
# cat file.csv| ./au-dee-npi.awk | mongoimport -d "fuegos" -c "industries"
gawk -F',' 'NR>1 { \
gsub(/"/,"",$5);
gsub(/"/,"",$6);
gsub(/\t/,"",$13);
gsub(/\t/,"",$14);
gsub(/\x1a/,"",$13);
gsub(/\x1a/,"",$14);
lon = $14+0;
lat = $13+0;
name= $5", "$6
gsub(/^, /,"",name);
if (lon != 0 && lat != 0 && lon > -180 && lon < 180 && lat >= -90 && lat <= 90)
printf("{name:\"%s\",geo:{type:\"Point\",coordinates:[%s,%s]},registry:\"4\"}\n" \
,name,lon,lat)}'

View file

@ -1,19 +0,0 @@
# cat file.csv| ./ca-godp-npri.awk | uniq | mongoimport -d "fuegos" -c "industries"
gawk -F',' 'NR>1 { \
gsub(/"/,"",$3);
gsub(/"/,"",$4);
gsub(/\t/,"",$3);
gsub(/\t/,"",$4);
gsub(/\x1a/,"",$3);
gsub(/\x1a/,"",$4);
lon = $16+0;
lat = $15+0;
if ($3 != $4)
name= $3", "$4
else
name=$3
gsub(/^, /,"",name);
# if (lon != 0 && lat != 0 && lon > -180 && lon < 180 && lat >= -90 && lat <= 90)
printf("{name:\"%s\",geo:{type:\"Point\",coordinates:[%s,%s]},registry:\"3\"}\n" \
,name,lon,lat)}'

View file

@ -1,14 +0,0 @@
# cat dbo.PUBLISH_FACILITYREPORT.csv| ./eu-eea-e-prtr.awk | mongoimport -d "fuegos" -c "industries"
gawk -F',' 'NR>1 { \
gsub(/"/,"",$6);
gsub(/"/,"",$5);
gsub(/\t/,"",$6);
gsub(/\t/,"",$5);
gsub(/\x1a/,"",$5);
gsub(/\x1a/,"",$6);
lon = $14+0;
lat = $13+0;
if (lon != 0 && lat != 0 && lon > -180 && lon < 180 && lat >= -90 && lat <= 90)
printf("{name:\"%s, %s\",geo:{type:\"Point\",coordinates:[%s,%s]},registry:\"1\"}\n" \
,$6,$5,lon,lat)}'

View file

@ -1,10 +0,0 @@
# cat NATIONAL_SINGLE.CSV| ./us-epa-frs.awk | mongoimport -d "fuegos" -c "industries"
gawk -F',' 'NR>1{ \
gsub(/"/,"",$33);
gsub(/"/,"",$32);
gsub(/"/,"",$3);
gsub(/\t/,"",$3);
lon = $33+0;
lat = $32+0;
if (lon != 0 && lat != 0 && lon > -180 && lon < 180 && lat >= -90 && lat <= 90) { printf("{name:\"%s\",geo:{type:\"Point\",coordinates:[%s,%s]},registry:\"2\"}\n",name,lon,lat)}}'

View file

@ -1,8 +1,7 @@
<head> <head>
<meta charset="utf-8"> <meta charset="utf-8">
<meta name="fragment" content="!"> <title>All agaist Fire | Tod@s contra el Fuego</title>
<title>All Against the Fire | Tod@s contra el Fuego</title> <meta name="description" content="Corwdsourcing against wildfires | Colaboración masiva contra los incendios">
<meta name="description" content="Crowdsourcing against wildfires | Colaboración masiva contra los incendios">
<meta name="viewport" content="initial-scale=1, minimal-ui, maximum-scale=1, minimum-scale=1" /> <meta name="viewport" content="initial-scale=1, minimal-ui, maximum-scale=1, minimum-scale=1" />
<link rel="stylesheet" href="https://fonts.googleapis.com/css?family=Roboto:300,400,500,700|Material+Icons"> <link rel="stylesheet" href="https://fonts.googleapis.com/css?family=Roboto:300,400,500,700|Material+Icons">

View file

@ -1,4 +1 @@
import '../imports/startup/client'; import '../imports/startup/client';
// https://github.com/GoogleChrome/rendertron#rendering-budget-timeout
window.renderComplete = false;

View file

@ -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
}
}
};

View file

@ -1,22 +0,0 @@
Feature: Allow users to give feedback
As a user (authenticated or not)
I want to give feedback about this site
Scenario: A user can login and give feedback
Given I am on the zones page
And I have an account and I logged in
# 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
Given I am on the fires page
Given I'm not logged
Then I can send feedback with email
Scenario: A user cannot give feedback in home
Given I am on the home page
Then I cannot send feedback

View file

@ -1,12 +0,0 @@
Feature: This app should generate a correct sitemap, etc
Scenario: I verify that a sitemap is generated correctly
Given the page sitemap.xml
Then I check that exist this list of pages in the sitemap
| license | License |
| terms | Terms of Service |
| credits | Credits |
| privacy | Privacy Policy |
| fires | Active Fires |
| login | Login |
| signup | Signup |

View file

@ -1,8 +1,6 @@
@watch
Feature: Test all secundary pages Feature: Test all secundary pages
Background:
Given I am on the home page
Scenario: Check that all secondary pages work well Scenario: Check that all secondary pages work well
Given a list of page ids and contents Given a list of page ids and contents
| license | License | | license | License |
@ -10,42 +8,20 @@ Feature: Test all secundary pages
| credits | Credits | | credits | Credits |
| privacy | Privacy Policy | | privacy | Privacy Policy |
| activeFires | Active Fires | | activeFires | Active Fires |
| about | About |
Then I check that all pages works properly Then I check that all pages works properly
Scenario: Check that all secondary pages work well when not logged Scenario: Check that all secondary pages work well
Given I logout if logged Given a list of page urls and contents
And a list of page urls and contents
| login | Login | true | | login | Login | true |
| signup | Sign Up | true | | signup | Sign Up | true |
| fires | Fires | true |
| zones | Monitored | true |
| recover-password | Recover Password | true | | recover-password | Recover Password | true |
| verify-email/something | Verify | true | | verify-email/something | Verify | true |
| fire/inexistent | This page doesn't exist | false | | 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 | | 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 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 Scenario: Check that other non visible pages work well
Given I logout if logged Given a list of non visible pages ids and contents
And a list of non visible pages ids and contents | status | Status |
| error | Upppps | false | | error | Upps |
# 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 |
Then I check that all non visible pages works properly Then I check that all non visible pages works properly

View file

@ -1,50 +0,0 @@
/* global module expect require process client */
const { randomText, randomEmail } = require('./helper.js');
module.exports = function doSteps() {
this.Given(/^I am on the zones page$/, () => {
client.url(`${process.env.ROOT_URL}/zones`);
if (client.isVisible('#logout')) {
client.click('#logout');
}
});
this.Then(/^I can send feedback without email$/, () => {
client.waitForVisible('#feedback-tab', 10000);
client.click('#feedback-tab');
client.waitForVisible('#feedback-form', 10000);
client.setValue('#feedbackTextarea', randomText(500));
client.click('#sendFeedbackBtn');
client.waitForVisible('.bert-alert', 10000, true);
client.waitUntil(() => client.isVisible('.bert-alert') === false, 10000);
});
this.Then(/^I can send feedback with email$/, () => {
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');
client.waitForVisible('.bert-alert', 10000, true);
client.waitUntil(() => client.isVisible('.bert-alert') === false, 10000);
});
this.Given(/^I'm not logged$/, () => {
client.waitForVisible('#react-root', 5000);
if (client.isVisible('#logout')) {
client.click('#logout');
}
});
this.Given(/^I am on the fires page$/, () => {
client.url(`${process.env.ROOT_URL}/fires`);
});
this.Then(/^I cannot send feedback$/, () => {
client.waitForVisible('#react-root', 5000);
client.waitUntil(() => client.isVisible('#feedback-tab') === false, 10000);
});
};

View file

@ -1,20 +0,0 @@
/* global module expect require process client */
module.exports = function doSteps() {
let pages;
this.Then(/^I check that exist this list of pages in the sitemap$/, (table, callback) => {
// Write code here that turns the phrase above into concrete actions
pages = table.raw();
for (let i = 0; i < pages.length; i += 1) {
// TODO download sitemap and parse
// client.waitForText('.text', pages[i][0]);
}
callback();
});
this.Given(/^the page sitemap\.xml$/, (callback) => {
client.url(`${process.env.ROOT_URL}/sitemap.xml`);
callback();
});
};

View file

@ -8,8 +8,6 @@ function makeid(size) {
return text; return text;
} }
export const randomText = n => makeid(n);
export const randomUsername = () => makeid(7); export const randomUsername = () => makeid(7);
export const randomPassword = () => randomUsername(); export const randomPassword = () => randomUsername();

View file

@ -2,52 +2,17 @@
(function () { (function () {
module.exports = 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(() => { this.Before(() => {
// global.expect = require('@xolvio/jasmine-expect').expect; // global.expect = require('@xolvio/jasmine-expect').expect;
// https://github.com/webdriverio/webdriverio/issues/1145 /* Not used
if (!this.initMyCmds) { if (!this.initMyCmds) {
client.addCommand('waitForClickable', function elementClickable(selector, timeout) { client.addCommand('waitForClickable', function elementClickable(selector, timeout) {
this.waitForVisible(selector, timeout); this.waitForVisible(selector, timeout);
this.waitForEnabled(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; this.initMyCmds = true;
} } */
client.windowHandleSize({ width: 1500, height: 1000 });
// server.call('fixtures/reset'); // server.call('fixtures/reset');
// server.call('emailStub/reset'); // server.call('emailStub/reset');

View file

@ -10,7 +10,7 @@ let pass;
let email; let email;
const setUserValues = (isEdit) => { const setUserValues = (isEdit) => {
client.waitForVisible('input[name="firstName"]', 10000); client.waitForVisible('input[name="firstName"]', 5000);
firstName = randomUsername(); firstName = randomUsername();
lastName = randomUsername(); lastName = randomUsername();
email = randomEmail(); email = randomEmail();
@ -32,54 +32,31 @@ module.exports = function doSteps(notos) {
goHome(client); goHome(client);
}); });
function waitNoBert() { function register() {
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() {
if (client.isVisible('#logout')) { if (client.isVisible('#logout')) {
client.click('#logout'); client.click('#logout');
} }
client.waitForVisible('#signup', 10000); client.waitForVisible('#signup', 5000);
}
function register() {
logoutIfLogged();
client.waitForVisible('#signup', 10000);
client.click('#signup'); client.click('#signup');
setUserValues(); setUserValues();
if (!notos) { if (!notos) {
client.click('input[name="tos"]'); client.click('input[name="tos"]');
} }
client.click('#signUpSubmit'); 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$/, () => { this.Given(/^I have an account and I logged in$/, () => {
register(); register();
}); });
function checkName() { function checkName() {
client.waitForVisible('#profile', 5000); client.waitForVisible('#profile', 5000);
client.waitUntilText('#profile', firstName); client.waitForText('#profile', firstName);
client.waitUntilText('#profile', lastName); client.waitForText('#profile', lastName);
} }
this.Then(/^I should be logged in$/, () => { this.Then(/^I should be logged in$/, () => {
client.waitUntil(() => client.isVisible('#logout') === true, 10000); client.waitUntil(() => client.isVisible('#logout') === true, 10000);
client.waitUntil(() => client.isVisible('#profile') === true, 10000);
}); });
/* this.Given(/^I am signed out$/, () => { /* 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$/, () => { this.When(/^I sign out$/, () => {
waitNoBert(); if (client.isVisible('.bert-alert')) {
client.waitForClickable('#logout', 10000); client.waitUntil(() => client.isVisible('.bert-alert') === false, 10000);
// client.waitForVisible('#logout', 10000); }
client.waitForVisible('#logout', 5000);
client.click('#logout'); client.click('#logout');
client.waitForVisible('#login', 10000);
}); });
function login(badpass) { function login(badpass) {
waitNoBert();
client.waitForVisible('#login', 10000); client.waitForVisible('#login', 10000);
client.waitUntil(() => client.isVisible('.bert-alert') === false, 10000);
client.click('#login'); client.click('#login');
client.waitForVisible('#loginSubmit', 5000); client.waitForVisible('#loginSubmit', 5000);
client.setValue('input[name="emailAddress"]', email); client.setValue('input[name="emailAddress"]', email);
client.setValue('input[name="password"]', badpass ? randomPassword() : pass); client.setValue('input[name="password"]', badpass ? randomPassword() : pass);
client.click('#loginSubmit'); client.click('#loginSubmit');
waitNoBert(); closeAlert();
} }
this.When(/^I enter my email and password$/, () => { this.When(/^I enter my email and password$/, () => {
@ -112,13 +94,11 @@ module.exports = function doSteps(notos) {
}); });
this.Then(/^I can edit my profile$/, () => { this.Then(/^I can edit my profile$/, () => {
waitNoBert(); client.waitForVisible('#profile', 5000);
client.waitForClickable('#profile', 10000);
client.click('#profile'); client.click('#profile');
client.pause(2000);
setUserValues(true); setUserValues(true);
client.click('#profileSubmit'); client.click('#profileSubmit');
waitNoBert(); closeAlert();
checkName(); checkName();
}); });
@ -136,7 +116,7 @@ module.exports = function doSteps(notos) {
this.Then(/^I should be registered$/, () => { this.Then(/^I should be registered$/, () => {
client.waitForVisible('.bert-alert', 10000, true); client.waitForVisible('.bert-alert', 10000, true);
waitNoBert(); closeAlert();
}); });
this.Given(/^I register with some name, password and email but without accept conditions$/, () => { this.Given(/^I register with some name, password and email but without accept conditions$/, () => {

View file

@ -16,7 +16,7 @@ module.exports = function () {
const content = pages[i][1]; const content = pages[i][1];
client.waitForVisible(id, 10000); client.waitForVisible(id, 10000);
client.click(id); client.click(id);
client.waitUntilText('#react-root', content); client.waitForText('#react-root', content);
// https://jasmine.github.io/2.3/introduction.html#section-Expectations // https://jasmine.github.io/2.3/introduction.html#section-Expectations
expect(client.getTitle()).toContain(pages[i][1]); expect(client.getTitle()).toContain(pages[i][1]);
} }
@ -26,7 +26,7 @@ module.exports = function () {
const content = pages[i][1]; const content = pages[i][1];
client.url(`${process.env.ROOT_URL}/${url}`); client.url(`${process.env.ROOT_URL}/${url}`);
client.waitForVisible('#react-root', 10000); client.waitForVisible('#react-root', 10000);
client.waitUntilText('#react-root', content); client.waitForText('#react-root', content);
const checkTitle = pages[i][2] === 'true'; const checkTitle = pages[i][2] === 'true';
// https://jasmine.github.io/2.3/introduction.html#section-Expectations // https://jasmine.github.io/2.3/introduction.html#section-Expectations
if (checkTitle) { if (checkTitle) {
@ -41,7 +41,9 @@ module.exports = function () {
this.Then(/^I check that all non visible pages works properly$/, (callback) => { this.Then(/^I check that all non visible pages works properly$/, (callback) => {
for (let i = 0; i < pages.length; i += 1) { 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.waitForText('#react-root', content);
} }
callback(); callback();
}); });
@ -69,13 +71,4 @@ module.exports = function () {
} }
callback(); callback();
}); });
this.Then(/^they are spiderable$/, (callback) => {
// 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]);
}
callback();
});
}; };

View file

@ -7,7 +7,6 @@
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:svg="http://www.w3.org/2000/svg" xmlns:svg="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg" xmlns="http://www.w3.org/2000/svg"
xmlns:xlink="http://www.w3.org/1999/xlink"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd" xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
width="210mm" width="210mm"
@ -246,9 +245,9 @@
borderopacity="1.0" borderopacity="1.0"
inkscape:pageopacity="0.0" inkscape:pageopacity="0.0"
inkscape:pageshadow="2" inkscape:pageshadow="2"
inkscape:zoom="0.23559314" inkscape:zoom="0.25"
inkscape:cx="-2890.4884" inkscape:cx="-324.9908"
inkscape:cy="-486.23766" inkscape:cy="997.3585"
inkscape:document-units="mm" inkscape:document-units="mm"
inkscape:current-layer="layer1" inkscape:current-layer="layer1"
showgrid="false" showgrid="false"
@ -339,9 +338,9 @@
<g <g
id="g22554" id="g22554"
transform="translate(103.16604,61.471991)" transform="translate(103.16604,61.471991)"
inkscape:export-filename="/mnt/md1/proyectos/dev/todos-contra-el-fuego-web/public/fire-marker-s.png" inkscape:export-filename="/mnt/md1/proyectos/dev/todos-contra-el-fuego-web/public/fire-marker.png"
inkscape:export-xdpi="2" inkscape:export-xdpi="4.0856881"
inkscape:export-ydpi="2"> inkscape:export-ydpi="4.0856881">
<path <path
style="fill:#d41300;fill-opacity:1;stroke:none;stroke-width:10.58333302;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" style="fill:#d41300;fill-opacity:1;stroke:none;stroke-width:10.58333302;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
d="m -25.638382,38.048346 c 11.566313,13.887908 17.1215844,22.244854 13.751242,33.868326 -2.465387,8.50265 -8.895424,18.626534 -18.100172,25.532106 -9.204776,6.905572 -20.549951,13.517692 -24.263404,17.728802 -3.71348,4.21108 -9.835779,11.6046 -10.976769,21.82688 -2.725208,24.7073 15.038044,45.63926 39.702238,48.35952 11.289242,1.2311 26.6324564,-5.2645 34.2331684,-10.31973 C 16.308659,169.98901 28.520768,152.76263 31.570884,138.81099 35.854436,119.21732 35.065926,96.385127 23.626002,79.16853 12.946123,63.095807 3.8673424,50.775037 -25.638382,38.048346 Z" d="m -25.638382,38.048346 c 11.566313,13.887908 17.1215844,22.244854 13.751242,33.868326 -2.465387,8.50265 -8.895424,18.626534 -18.100172,25.532106 -9.204776,6.905572 -20.549951,13.517692 -24.263404,17.728802 -3.71348,4.21108 -9.835779,11.6046 -10.976769,21.82688 -2.725208,24.7073 15.038044,45.63926 39.702238,48.35952 11.289242,1.2311 26.6324564,-5.2645 34.2331684,-10.31973 C 16.308659,169.98901 28.520768,152.76263 31.570884,138.81099 35.854436,119.21732 35.065926,96.385127 23.626002,79.16853 12.946123,63.095807 3.8673424,50.775037 -25.638382,38.048346 Z"
@ -449,18 +448,11 @@
inkscape:connector-curvature="0" /> inkscape:connector-curvature="0" />
</g> </g>
</g> </g>
<rect
style="opacity:1;vector-effect:none;fill:#c8b7b7;fill-opacity:1;stroke:#c8b7b7;stroke-width:0.26458332;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
id="rect8537"
width="368.82184"
height="657.66956"
x="-971.73767"
y="74.954208" />
<g <g
id="g8543" id="g17717"
inkscape:export-filename="/mnt/md1/proyectos/dev/todos-contra-el-fuego-web/public/fire-marker-l.png" inkscape:export-filename="/mnt/md1/proyectos/dev/todos-contra-el-fuego-web/public/favicon-200.png"
inkscape:export-xdpi="4" inkscape:export-xdpi="28.790001"
inkscape:export-ydpi="4"> inkscape:export-ydpi="28.790001">
<rect <rect
y="108.84225" y="108.84225"
x="-419.07861" x="-419.07861"
@ -507,66 +499,61 @@
style="font-size:192px;fill:#ff0000" /></flowRegion><flowPara style="font-size:192px;fill:#ff0000" /></flowRegion><flowPara
id="flowPara8488"></flowPara></flowRoot> <g id="flowPara8488"></flowPara></flowRoot> <g
id="g22554-45" id="g22554-45"
transform="matrix(-0.04534342,-0.17182024,0.17182024,-0.04534342,-93.960693,-204.49561)" transform="rotate(-104.78334,-231.82318,51.6182)"
inkscape:export-filename="/mnt/md1/proyectos/dev/todos-contra-el-fuego-web/public/fire-marker.png" inkscape:export-filename="/mnt/md1/proyectos/dev/todos-contra-el-fuego-web/public/fire-marker.png"
inkscape:export-xdpi="4.0856881" inkscape:export-xdpi="4.0856881"
inkscape:export-ydpi="4.0856881" inkscape:export-ydpi="4.0856881">
style="stroke-width:5.62737894">
<g <g
id="g8559" id="g8559">
style="stroke-width:5.62737894">
<g <g
inkscape:export-ydpi="4.0856881" inkscape:export-ydpi="4.0856881"
inkscape:export-xdpi="4.0856881" inkscape:export-xdpi="4.0856881"
inkscape:export-filename="/mnt/md1/proyectos/dev/todos-contra-el-fuego-web/public/fire-marker.png" inkscape:export-filename="/mnt/md1/proyectos/dev/todos-contra-el-fuego-web/public/fire-marker.png"
transform="rotate(-144.74008,-66.772616,119.34544)" transform="rotate(-144.74008,-66.772616,119.34544)"
id="g22554-2" id="g22554-2">
style="stroke-width:5.62737894">
<path <path
sodipodi:nodetypes="cszzcczasc" sodipodi:nodetypes="cszzcczasc"
inkscape:connector-curvature="0" inkscape:connector-curvature="0"
id="path5329-8" id="path5329-8"
d="m -25.638382,38.048346 c 11.566313,13.887908 17.1215844,22.244854 13.751242,33.868326 -2.465387,8.50265 -8.895424,18.626534 -18.100172,25.532106 -9.204776,6.905572 -20.549951,13.517692 -24.263404,17.728802 -3.71348,4.21108 -9.835779,11.6046 -10.976769,21.82688 -2.725208,24.7073 15.038044,45.63926 39.702238,48.35952 11.289242,1.2311 26.6324564,-5.2645 34.2331684,-10.31973 C 16.308659,169.98901 28.520768,152.76263 31.570884,138.81099 35.854436,119.21732 35.065926,96.385127 23.626002,79.16853 12.946123,63.095807 3.8673424,50.775037 -25.638382,38.048346 Z" d="m -25.638382,38.048346 c 11.566313,13.887908 17.1215844,22.244854 13.751242,33.868326 -2.465387,8.50265 -8.895424,18.626534 -18.100172,25.532106 -9.204776,6.905572 -20.549951,13.517692 -24.263404,17.728802 -3.71348,4.21108 -9.835779,11.6046 -10.976769,21.82688 -2.725208,24.7073 15.038044,45.63926 39.702238,48.35952 11.289242,1.2311 26.6324564,-5.2645 34.2331684,-10.31973 C 16.308659,169.98901 28.520768,152.76263 31.570884,138.81099 35.854436,119.21732 35.065926,96.385127 23.626002,79.16853 12.946123,63.095807 3.8673424,50.775037 -25.638382,38.048346 Z"
style="fill:#d41300;fill-opacity:1;stroke:none;stroke-width:59.556427;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> style="fill:#d41300;fill-opacity:1;stroke:none;stroke-width:10.58333302;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" />
<path <path
sodipodi:nodetypes="cszzcczcsc" sodipodi:nodetypes="cszzcczcsc"
inkscape:connector-curvature="0" inkscape:connector-curvature="0"
id="path5331-30" id="path5331-30"
d="m -7.5198746,101.09834 c 4.018254,8.70077 3.821166,14.69136 0.49964,20.18233 -2.429696,4.01667 -7.1125824,8.31964 -12.7749834,10.59336 -5.662401,2.27373 -12.38086,4.1034 -14.860826,5.75067 -2.479966,1.64727 -6.630512,4.59399 -8.617877,9.67322 -4.786736,12.28103 1.27127,26.11038 13.530846,30.8887 5.613373,2.17993 11.515354,2.13146 17.442629,-0.0849 5.9272234,-2.21636 14.0405914,-9.44377 18.0234704,-15.42084 6.3490206,-9.59829 8.9531296,-23.76395 5.4565016,-34.15229 -3.2643496,-9.69812 -4.6224286,-12.3603 -18.6994006,-27.43025 z" d="m -7.5198746,101.09834 c 4.018254,8.70077 3.821166,14.69136 0.49964,20.18233 -2.429696,4.01667 -7.1125824,8.31964 -12.7749834,10.59336 -5.662401,2.27373 -12.38086,4.1034 -14.860826,5.75067 -2.479966,1.64727 -6.630512,4.59399 -8.617877,9.67322 -4.786736,12.28103 1.27127,26.11038 13.530846,30.8887 5.613373,2.17993 11.515354,2.13146 17.442629,-0.0849 5.9272234,-2.21636 14.0405914,-9.44377 18.0234704,-15.42084 6.3490206,-9.59829 8.9531296,-23.76395 5.4565016,-34.15229 -3.2643496,-9.69812 -4.6224286,-12.3603 -18.6994006,-27.43025 z"
style="fill:#ffffff;fill-opacity:1;stroke:none;stroke-width:59.556427;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> style="fill:#ffffff;fill-opacity:1;stroke:none;stroke-width:10.58333302;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" />
</g> </g>
<g <g
inkscape:export-ydpi="4.0856881" inkscape:export-ydpi="4.0856881"
inkscape:export-xdpi="4.0856881" inkscape:export-xdpi="4.0856881"
inkscape:export-filename="/mnt/md1/proyectos/dev/todos-contra-el-fuego-web/public/fire-marker.png" inkscape:export-filename="/mnt/md1/proyectos/dev/todos-contra-el-fuego-web/public/fire-marker.png"
transform="rotate(96.689458,-75.070103,121.00438)" transform="rotate(96.689458,-75.070103,121.00438)"
id="g22554-5" id="g22554-5">
style="stroke-width:5.62737894">
<path <path
sodipodi:nodetypes="cszzcczasc" sodipodi:nodetypes="cszzcczasc"
inkscape:connector-curvature="0" inkscape:connector-curvature="0"
id="path5329-4" id="path5329-4"
d="m -25.638382,38.048346 c 11.566313,13.887908 17.1215844,22.244854 13.751242,33.868326 -2.465387,8.50265 -8.895424,18.626534 -18.100172,25.532106 -9.204776,6.905572 -20.549951,13.517692 -24.263404,17.728802 -3.71348,4.21108 -9.835779,11.6046 -10.976769,21.82688 -2.725208,24.7073 15.038044,45.63926 39.702238,48.35952 11.289242,1.2311 26.6324564,-5.2645 34.2331684,-10.31973 C 16.308659,169.98901 28.520768,152.76263 31.570884,138.81099 35.854436,119.21732 35.065926,96.385127 23.626002,79.16853 12.946123,63.095807 3.8673424,50.775037 -25.638382,38.048346 Z" d="m -25.638382,38.048346 c 11.566313,13.887908 17.1215844,22.244854 13.751242,33.868326 -2.465387,8.50265 -8.895424,18.626534 -18.100172,25.532106 -9.204776,6.905572 -20.549951,13.517692 -24.263404,17.728802 -3.71348,4.21108 -9.835779,11.6046 -10.976769,21.82688 -2.725208,24.7073 15.038044,45.63926 39.702238,48.35952 11.289242,1.2311 26.6324564,-5.2645 34.2331684,-10.31973 C 16.308659,169.98901 28.520768,152.76263 31.570884,138.81099 35.854436,119.21732 35.065926,96.385127 23.626002,79.16853 12.946123,63.095807 3.8673424,50.775037 -25.638382,38.048346 Z"
style="fill:#d41300;fill-opacity:1;stroke:none;stroke-width:59.556427;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> style="fill:#d41300;fill-opacity:1;stroke:none;stroke-width:10.58333302;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" />
<path <path
sodipodi:nodetypes="cszzcczcsc" sodipodi:nodetypes="cszzcczcsc"
inkscape:connector-curvature="0" inkscape:connector-curvature="0"
id="path5331-9" id="path5331-9"
d="m -7.5198746,101.09834 c 4.018254,8.70077 3.821166,14.69136 0.49964,20.18233 -2.429696,4.01667 -7.1125824,8.31964 -12.7749834,10.59336 -5.662401,2.27373 -12.38086,4.1034 -14.860826,5.75067 -2.479966,1.64727 -6.630512,4.59399 -8.617877,9.67322 -4.786736,12.28103 1.27127,26.11038 13.530846,30.8887 5.613373,2.17993 11.515354,2.13146 17.442629,-0.0849 5.9272234,-2.21636 14.0405914,-9.44377 18.0234704,-15.42084 6.3490206,-9.59829 8.9531296,-23.76395 5.4565016,-34.15229 -3.2643496,-9.69812 -4.6224286,-12.3603 -18.6994006,-27.43025 z" d="m -7.5198746,101.09834 c 4.018254,8.70077 3.821166,14.69136 0.49964,20.18233 -2.429696,4.01667 -7.1125824,8.31964 -12.7749834,10.59336 -5.662401,2.27373 -12.38086,4.1034 -14.860826,5.75067 -2.479966,1.64727 -6.630512,4.59399 -8.617877,9.67322 -4.786736,12.28103 1.27127,26.11038 13.530846,30.8887 5.613373,2.17993 11.515354,2.13146 17.442629,-0.0849 5.9272234,-2.21636 14.0405914,-9.44377 18.0234704,-15.42084 6.3490206,-9.59829 8.9531296,-23.76395 5.4565016,-34.15229 -3.2643496,-9.69812 -4.6224286,-12.3603 -18.6994006,-27.43025 z"
style="fill:#ffffff;fill-opacity:1;stroke:none;stroke-width:59.556427;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> style="fill:#ffffff;fill-opacity:1;stroke:none;stroke-width:10.58333302;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" />
</g> </g>
<g <g
transform="rotate(-22.690691,-35.704234,120.074)" transform="rotate(-22.690691,-35.704234,120.074)"
id="g8542" id="g8542">
style="stroke-width:5.62737894">
<path <path
style="fill:#d41300;fill-opacity:1;stroke:none;stroke-width:59.556427;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" style="fill:#d41300;fill-opacity:1;stroke:none;stroke-width:10.58333302;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
d="m -25.638382,38.048346 c 11.566313,13.887908 17.1215844,22.244854 13.751242,33.868326 -2.465387,8.50265 -8.895424,18.626534 -18.100172,25.532106 -9.204776,6.905572 -20.549951,13.517692 -24.263404,17.728802 -3.71348,4.21108 -9.835779,11.6046 -10.976769,21.82688 -2.725208,24.7073 15.038044,45.63926 39.702238,48.35952 11.289242,1.2311 26.6324564,-5.2645 34.2331684,-10.31973 C 16.308659,169.98901 28.520768,152.76263 31.570884,138.81099 35.854436,119.21732 35.065926,96.385127 23.626002,79.16853 12.946123,63.095807 3.8673424,50.775037 -25.638382,38.048346 Z" d="m -25.638382,38.048346 c 11.566313,13.887908 17.1215844,22.244854 13.751242,33.868326 -2.465387,8.50265 -8.895424,18.626534 -18.100172,25.532106 -9.204776,6.905572 -20.549951,13.517692 -24.263404,17.728802 -3.71348,4.21108 -9.835779,11.6046 -10.976769,21.82688 -2.725208,24.7073 15.038044,45.63926 39.702238,48.35952 11.289242,1.2311 26.6324564,-5.2645 34.2331684,-10.31973 C 16.308659,169.98901 28.520768,152.76263 31.570884,138.81099 35.854436,119.21732 35.065926,96.385127 23.626002,79.16853 12.946123,63.095807 3.8673424,50.775037 -25.638382,38.048346 Z"
id="path5329-9" id="path5329-9"
inkscape:connector-curvature="0" inkscape:connector-curvature="0"
sodipodi:nodetypes="cszzcczasc" /> sodipodi:nodetypes="cszzcczasc" />
<path <path
style="fill:#ffffff;fill-opacity:1;stroke:none;stroke-width:59.556427;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" style="fill:#ffffff;fill-opacity:1;stroke:none;stroke-width:10.58333302;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
d="m -7.5198746,101.09834 c 4.018254,8.70077 3.821166,14.69136 0.49964,20.18233 -2.429696,4.01667 -7.1125824,8.31964 -12.7749834,10.59336 -5.662401,2.27373 -12.38086,4.1034 -14.860826,5.75067 -2.479966,1.64727 -6.630512,4.59399 -8.617877,9.67322 -4.786736,12.28103 1.27127,26.11038 13.530846,30.8887 5.613373,2.17993 11.515354,2.13146 17.442629,-0.0849 5.9272234,-2.21636 14.0405914,-9.44377 18.0234704,-15.42084 6.3490206,-9.59829 8.9531296,-23.76395 5.4565016,-34.15229 -3.2643496,-9.69812 -4.6224286,-12.3603 -18.6994006,-27.43025 z" d="m -7.5198746,101.09834 c 4.018254,8.70077 3.821166,14.69136 0.49964,20.18233 -2.429696,4.01667 -7.1125824,8.31964 -12.7749834,10.59336 -5.662401,2.27373 -12.38086,4.1034 -14.860826,5.75067 -2.479966,1.64727 -6.630512,4.59399 -8.617877,9.67322 -4.786736,12.28103 1.27127,26.11038 13.530846,30.8887 5.613373,2.17993 11.515354,2.13146 17.442629,-0.0849 5.9272234,-2.21636 14.0405914,-9.44377 18.0234704,-15.42084 6.3490206,-9.59829 8.9531296,-23.76395 5.4565016,-34.15229 -3.2643496,-9.69812 -4.6224286,-12.3603 -18.6994006,-27.43025 z"
id="path5331-35" id="path5331-35"
inkscape:connector-curvature="0" inkscape:connector-curvature="0"
@ -574,86 +561,5 @@
</g> </g>
</g> </g>
</g> </g>
<g
id="g8551"
inkscape:export-filename="/mnt/md1/proyectos/dev/todos-contra-el-fuego-web/public/fire-marker-s.png"
inkscape:export-xdpi="4"
inkscape:export-ydpi="4">
<use
transform="translate(0,404.8127)"
height="100%"
width="100%"
id="use8529"
xlink:href="#rect17701"
y="0"
x="0" />
<use
style="stroke-width:2"
transform="matrix(0.5,0,0,0.5,-165.41924,545.68726)"
height="100%"
width="100%"
id="use8531"
xlink:href="#g22554-4"
y="0"
x="0" />
</g>
<g
id="g8547"
transform="translate(0,-14.019767)"
inkscape:export-filename="/mnt/md1/proyectos/dev/todos-contra-el-fuego-web/public/fire-marker-m.png"
inkscape:export-xdpi="4"
inkscape:export-ydpi="4">
<use
transform="translate(0,190.50006)"
height="100%"
width="100%"
id="use8533"
xlink:href="#rect17701"
y="0"
x="0" />
<use
style="stroke-width:1.33333337"
transform="matrix(0.75,0,0,0.75,-82.709618,260.93734)"
height="100%"
width="100%"
id="use8535"
xlink:href="#g22554-4"
y="0"
x="0" />
</g>
<g
transform="translate(176.48029,228.33241)"
id="g8543-0"
inkscape:export-filename="/home/vjrj/proyectos/dev/fires_flutter/images/logo-1024.png"
inkscape:export-xdpi="147.38"
inkscape:export-ydpi="147.38">
<rect
y="108.84225"
x="-419.07861"
height="176.48029"
width="176.48029"
id="rect17701-2"
style="opacity:1;vector-effect:none;fill:none;fill-opacity:1;stroke:none;stroke-width:0.26458332;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" />
<g
style="stroke-width:0.88457036"
inkscape:export-ydpi="4.0856881"
inkscape:export-xdpi="4.0856881"
inkscape:export-filename="/mnt/md1/proyectos/dev/todos-contra-el-fuego-web/public/fire-marker.png"
transform="matrix(1.1304923,0,0,1.1304923,-313.01185,70.713452)"
id="g22554-4-3">
<path
sodipodi:nodetypes="cszzcczasc"
inkscape:connector-curvature="0"
id="path5329-5-7"
d="m -25.638382,38.048346 c 11.566313,13.887908 17.1215844,22.244854 13.751242,33.868326 -2.465387,8.50265 -8.895424,18.626534 -18.100172,25.532106 -9.204776,6.905572 -20.549951,13.517692 -24.263404,17.728802 -3.71348,4.21108 -9.835779,11.6046 -10.976769,21.82688 -2.725208,24.7073 15.038044,45.63926 39.702238,48.35952 11.289242,1.2311 26.6324564,-5.2645 34.2331684,-10.31973 C 16.308659,169.98901 28.520768,152.76263 31.570884,138.81099 35.854436,119.21732 35.065926,96.385127 23.626002,79.16853 12.946123,63.095807 3.8673424,50.775037 -25.638382,38.048346 Z"
style="fill:#d41300;fill-opacity:1;stroke:none;stroke-width:9.36170292;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" />
<path
sodipodi:nodetypes="cszzcczcsc"
inkscape:connector-curvature="0"
id="path5331-3-5"
d="m -7.5198746,101.09834 c 4.018254,8.70077 3.821166,14.69136 0.49964,20.18233 -2.429696,4.01667 -7.1125824,8.31964 -12.7749834,10.59336 -5.662401,2.27373 -12.38086,4.1034 -14.860826,5.75067 -2.479966,1.64727 -6.630512,4.59399 -8.617877,9.67322 -4.786736,12.28103 1.27127,26.11038 13.530846,30.8887 5.613373,2.17993 11.515354,2.13146 17.442629,-0.0849 5.9272234,-2.21636 14.0405914,-9.44377 18.0234704,-15.42084 6.3490206,-9.59829 8.9531296,-23.76395 5.4565016,-34.15229 -3.2643496,-9.69812 -4.6224286,-12.3603 -18.6994006,-27.43025 z"
style="fill:#ffffff;fill-opacity:1;stroke:none;stroke-width:9.36170292;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" />
</g>
</g>
</g> </g>
</svg> </svg>

Before

Width:  |  Height:  |  Size: 51 KiB

After

Width:  |  Height:  |  Size: 46 KiB

Before After
Before After

View file

@ -1,9 +1,10 @@
{ {
"extensions": [ "ttf", "woff", "woff2" ], "extensions": [ "ttf", "woff", "woff2" ],
"map": { "map": {
"node_modules/simple-line-icons/fonts/Simple-Line-Icons.woff": "/fonts/Simple-Line-Icons.woff?v=2.4.1", "node_modules/simple-line-icons/fonts/Simple-Line-Icons.woff": "fonts/Simple-Line-Icons.woff",
"node_modules/simple-line-icons/fonts/Simple-Line-Icons.woff2": "/fonts/Simple-Line-Icons.woff2?v=2.4.1", "node_modules/simple-line-icons/fonts/Simple-Line-Icons.woff": "fonts/Simple-Line-Icons.woff?v=2.4.0",
"node_modules/simple-line-icons/fonts/Simple-Line-Icons.ttf": "/fonts/Simple-Line-Icons.ttf?v=2.4.1" "node_modules/simple-line-icons/fonts/Simple-Line-Icons.woff2": "fonts/Simple-Line-Icons.woff2?v=2.4.0",
"node_modules/simple-line-icons/fonts/Simple-Line-Icons.ttf": "fonts/Simple-Line-Icons.ttf?v=2.4.0"
}, },
"verbose": false "verbose": false
} }

View file

@ -1,6 +1,5 @@
/* eslint-disable consistent-return */ /* eslint-disable consistent-return */
import { Meteor } from 'meteor/meteor';
import { Mongo } from 'meteor/mongo'; import { Mongo } from 'meteor/mongo';
import SimpleSchema from 'simpl-schema'; import SimpleSchema from 'simpl-schema';
import firesCommonSchema from '../Common/FiresSchema'; import firesCommonSchema from '../Common/FiresSchema';
@ -19,11 +18,8 @@ ActiveFires.deny({
remove: () => true remove: () => true
}); });
const activeFiresSchema = firesCommonSchema;
activeFiresSchema.fireUnion = { type: Meteor.Collection.ObjectID, optional: true, blackbox: true }; ActiveFires.schema = new SimpleSchema(firesCommonSchema);
ActiveFires.schema = new SimpleSchema(activeFiresSchema);
ActiveFires.attachSchema(ActiveFires.schema); ActiveFires.attachSchema(ActiveFires.schema);

View file

@ -1,79 +0,0 @@
/* eslint-disable import/no-absolute-path */
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;
const cleanProv = (prov, stringsToRemove) => {
let lprov = prov;
stringsToRemove.forEach((st) => {
lprov = lprov.replace(st[0], st[1]);
});
return lprov;
};
const findFiresInRegion = (zone) => {
const result = ActiveFires.find({
ourid: {
$geoWithin: {
$geometry: zone.geometry
}
}
});
/* console.log(`False positive total: ${falsePos.count()}`);
console.log(`False positives: ${JSON.stringify(falsePos.fetch())}`); */
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) => {
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}`);
if (unionCount > 0) {
total += unionCount;
fireStats[regionName] = { count: unionCount, code: regionCode };
}
}
} catch (e) {
ravenLogger.log(e);
}
});
return { total, fires: fireStats };
};
export default countFiresInRegions;

View file

@ -5,9 +5,6 @@
import { Meteor } from 'meteor/meteor'; import { Meteor } from 'meteor/meteor';
import { check } from 'meteor/check'; import { check } from 'meteor/check';
import { NumberBetween } from '/imports/modules/server/other-checks'; 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 ActiveFires from '../ActiveFires'; import ActiveFires from '../ActiveFires';
const counter = new Counter('countActiveFires', ActiveFires.find({})); const counter = new Counter('countActiveFires', ActiveFires.find({}));
@ -16,7 +13,7 @@ Meteor.publish('activefirestotal', function total() {
return counter; return counter;
}); });
const activefires = (northEastLng, northEastLat, southWestLng, southWestLat, withMarks) => { const activefires = (northEastLng, northEastLat, southWestLng, southWestLat) => {
const fires = ActiveFires.find({ const fires = ActiveFires.find({
ourid: { ourid: {
$geoWithin: { $geoWithin: {
@ -31,35 +28,19 @@ const activefires = (northEastLng, northEastLat, southWestLng, southWestLat, wit
lat: 1, lat: 1,
lon: 1, lon: 1,
when: 1, when: 1,
scan: 1, scan: 1
track: 1
} }
}); });
// console.log(`Fires total: ${fires.count()}`);
if (Meteor.isDevelopment) console.log(`Active fires 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; return fires;
}; };
Meteor.publish('activefiresmyloc', function activeInMyLoc(northEastLng, northEastLat, southWestLng, southWestLat, withMarks) { Meteor.publish('activefiresmyloc', function activeInMyLoc(northEastLng, northEastLat, southWestLng, southWestLat) {
// latitude -90 and 90 and the longitude between -180 and 180 // latitude -90 and 90 and the longitude between -180 and 180
check(northEastLng, NumberBetween(-180, 180)); check(northEastLng, NumberBetween(-180, 180));
check(southWestLat, NumberBetween(-90, 90)); check(southWestLat, NumberBetween(-90, 90));
check(southWestLng, NumberBetween(-180, 180)); check(southWestLng, NumberBetween(-180, 180));
check(northEastLat, NumberBetween(-90, 90)); check(northEastLat, NumberBetween(-90, 90));
check(withMarks, Boolean);
return activefires(northEastLng, northEastLat, southWestLng, southWestLat, withMarks); return activefires(northEastLng, northEastLat, southWestLng, southWestLat);
});
// Warning: this increase always by one the fire stats
Meteor.publish('lastFireDetected', function lastFireDetected() {
return ActiveFires.find({}, { limit: 1, sort: { when: -1 } });
}); });

View file

@ -1,155 +0,0 @@
/* eslint-disable import/no-absolute-path */
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;
const autonomies = tbuffer(JSON.parse(getPrivateFile('data/es-atlas-autonomies.json')), 0);
const portugal = tbuffer(JSON.parse(getPrivateFile('data/pt-atlas.json')), 0);
const europe = tbuffer(JSON.parse(getPrivateFile('data/europe-atlas.json')), 0);
const stringsToRemoveIberia = [
['Ciudad Autónoma de', ''],
['País Vasco/', ''],
['Pais Vasco', 'Euskadi'],
['Comunidad Foral de', ''],
['Región de', ''],
['Valencia', 'CValenciana'],
['Comunidad de Madrid', 'CMadrid'],
['León', 'CyLeón'],
['Leon', 'CyLeón'],
['Castilla-', ''],
['Cataluña/', ''],
['Cataluña', 'Catalunya'],
['La Mancha', 'CLMancha'],
['Baleares', 'IllesBalears'],
['Galicia', 'Galiza'],
['Principado de', ''],
[' ', '']
];
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) => {
if (num <= 0) {
return '';
}
const keys = Object.keys(stats);
const numZones = keys.length;
let text = '';
keys.map((key, index) => { // , index
const zoneFires = stats[key].count;
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 subTexts = index === 0 ? subText : `${delimiter} ${subText}`;
text += subTexts;
return '';
});
return `${text}`;
};
const composeIberiaTweet = (num, stats) => {
if (num <= 0) {
return '';
}
const keys = Object.keys(stats);
const numZones = keys.length;
let text = '';
keys.map((key, index) => { // , index
const zoneFires = stats[key].count;
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}`;
const delimiter = index === numZones - 1 ? ' y' : ',';
const subTexts = index === 0 ? subText : `${delimiter} ${subText}`;
text += subTexts;
return '';
});
return `${text}`;
};
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, [[' ', '']]);
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`;
}
if (Meteor.isDevelopment) {
console.log(tweetText);
} else {
tweet(tweetText, 'en');
}
return tweetText;
}
};
const tweetIberiaFires = () => {
const resultEs = countFiresInRegions(autonomies, stringsToRemoveIberia);
const resultPt = countFiresInRegions(portugal, stringsToRemoveIberia);
const fires = Object.assign(resultEs.fires, resultPt.fires);
const total = resultEs.total + resultPt.total;
// 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');
}
}
return tweetText;
}
return '';
};
if (Meteor.isDevelopment && debug) {
tweetIberiaFires();
tweetEuropeFires();
}
export { composeIberiaTweet, tweetHeaders, tweetFooters, tweetIberiaFires, tweetEuropeFires };

View file

@ -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/
// <field>: { type: <GeoJSON type> , coordinates: <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;

View file

@ -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 } });
});

View file

@ -3,5 +3,4 @@ import moment from 'moment-timezone';
export const momentTz = date => moment.tz(date, moment.tz.guess()); export const momentTz = date => moment.tz(date, moment.tz.guess());
export const dateLongFormat = date => momentTz(date).format('LLLL (z)'); export const dateLongFormat = date => momentTz(date).format('LLLL (z)');
export const dateParseShortFormat = date => moment(date, 'YYYY-MM-DD').format('LL'); export const dateParseShortFormat = date => moment(date, 'YYYY-MM-DD').format('LL');
export const dateYYYYMMDD = date => moment(date).format('YYYY-MM-DD');
export const dateFromNow = date => momentTz(date).fromNow(); export const dateFromNow = date => momentTz(date).fromNow();

View file

@ -2,7 +2,6 @@
const types = { const types = {
industry: 'Es una industria', industry: 'Es una industria',
// TODO volcano: 'Es un volcán',
controled: 'Es una quema controlada', controled: 'Es una quema controlada',
falsealarm: 'Parece una falsa alarma' falsealarm: 'Parece una falsa alarma'
}; };

View file

@ -34,15 +34,4 @@ FalsePositives.schema = new SimpleSchema({
FalsePositives.attachSchema(FalsePositives.schema); FalsePositives.attachSchema(FalsePositives.schema);
export const falsePositivesRemap = (odoc) => {
const doc = odoc;
const geo = doc.geo;
doc.lat = geo.coordinates[1];
doc.lon = geo.coordinates[0];
doc._id = doc.fireId;
doc.id = doc.fireId;
delete doc.geo;
return doc;
};
export default FalsePositives; export default FalsePositives;

View file

@ -6,29 +6,6 @@ import FalsePositiveTypes from './FalsePositiveTypes';
import Fires from '../Fires/Fires'; import Fires from '../Fires/Fires';
import rateLimit from '../../modules/rate-limit'; 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({ Meteor.methods({
'falsePositives.insert': function falsePositivesInsert(fireId, keyType) { 'falsePositives.insert': function falsePositivesInsert(fireId, keyType) {
check(fireId, Meteor.Collection.ObjectID); check(fireId, Meteor.Collection.ObjectID);
@ -40,9 +17,27 @@ Meteor.methods({
} }
check(this.userId, String); check(this.userId, String);
const fire = Fires.findOne(fireId); const fire = Fires.findOne(fireId);
const owner = this.userId;
if (fire) { 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 { } else {
throw new Meteor.Error('500', 'Fuego no encontrado'); throw new Meteor.Error('500', 'Fuego no encontrado');
} }

View file

@ -5,10 +5,6 @@
import { Meteor } from 'meteor/meteor'; import { Meteor } from 'meteor/meteor';
import { check } from 'meteor/check'; import { check } from 'meteor/check';
import { NumberBetween } from '/imports/modules/server/other-checks'; import { NumberBetween } from '/imports/modules/server/other-checks';
import '/imports/startup/server/leaflet-workaround.js';
import L from 'leaflet';
import calcUnion from '/imports/ui/components/Maps/SubsUnion/Unify';
import Industries from '/imports/api/Industries/Industries';
import FalsePositives from '../FalsePositives'; import FalsePositives from '../FalsePositives';
const counter = new Counter('countFalsePositives', FalsePositives.find({})); const counter = new Counter('countFalsePositives', FalsePositives.find({}));
@ -17,53 +13,8 @@ Meteor.publish('falsePositivesTotal', function total() {
return counter; return counter;
}); });
export const firesUnion = (fires) => { const falsePositives = (northEastLng, northEastLat, southWestLng, southWestLat) => {
const firesArray = Array.isArray(fires) ? fires : fires.fetch(); // if not is a cursor const fires = FalsePositives.find({
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;
});
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);
return union;
};
export const whichAreFalsePositives = (collection, union) => {
const result = collection.find({
geo: {
$geoWithin: {
$geometry: union[0].geometry
}
}
}, {
fields: {
geo: 1,
// type: 1,
// when: 1,
fireId: 1
}
});
/* console.log(`False positive total: ${falsePos.count()}`);
console.log(`False positives: ${JSON.stringify(falsePos.fetch())}`); */
return result;
};
const find = (collection, northEastLng, northEastLat, southWestLng, southWestLat) => {
const fires = collection.find({
geo: { geo: {
$geoWithin: { $geoWithin: {
$box: [ $box: [
@ -79,6 +30,19 @@ const find = (collection, northEastLng, northEastLat, southWestLng, southWestLat
// when: 1, // when: 1,
fireId: 1 fireId: 1
} }
}).serverTransform(function transformDoc(odoc) {
const doc = odoc;
// Destructuring gives me an error: "Cannot destructure property `geo` of 'undefined'"
const geo = doc.geo;
if (geo) {
doc.lat = geo.coordinates[1];
doc.lon = geo.coordinates[0];
}
doc._id = doc.fireId;
doc.id = doc.fireId;
delete doc.geo;
// console.log(doc);
return doc;
}); });
// console.log(`Fires total: ${fires.count()}`); // console.log(`Fires total: ${fires.count()}`);
return fires; return fires;
@ -91,15 +55,5 @@ Meteor.publishTransformed('falsePositivesMyloc', function falsePositivesInMyLoc(
check(southWestLng, NumberBetween(-180, 180)); check(southWestLng, NumberBetween(-180, 180));
check(northEastLat, NumberBetween(-90, 90)); check(northEastLat, NumberBetween(-90, 90));
return find(FalsePositives, northEastLng, northEastLat, southWestLng, southWestLat); return falsePositives(northEastLng, northEastLat, southWestLng, southWestLat);
});
Meteor.publishTransformed('industriesMyloc', function industriesInMyLoc(northEastLng, northEastLat, southWestLng, southWestLat) {
// 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));
return find(Industries, northEastLng, northEastLat, southWestLng, southWestLat);
}); });

View file

@ -33,10 +33,9 @@ Meteor.publish('fireAlerts', function fireAlerts(northEastLng, northEastLat, sou
fields: { fields: {
lat: 1, lat: 1,
lon: 1, lon: 1,
scan: 1, scan: 1
track: 1
} }
}); });
if (Meteor.isDevelopment) console.log(`Neighbour alerts total: ${fires.count()}`); // console.log(`Fires total: ${fires.count()}`);
return fires; return fires;
}); });

View file

@ -8,12 +8,6 @@ import { Promise } from 'meteor/promise';
import NodeGeocoder from 'node-geocoder'; import NodeGeocoder from 'node-geocoder';
import { gmapServerKey } from '/imports/startup/server/IPGeocoder'; import { gmapServerKey } from '/imports/startup/server/IPGeocoder';
import ActiveFiresCollection from '/imports/api/ActiveFires/ActiveFires'; import ActiveFiresCollection from '/imports/api/ActiveFires/ActiveFires';
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'; import FiresCollection from '../Fires';
function findFire(unsealed) { function findFire(unsealed) {
@ -76,24 +70,6 @@ const findOrCreateFire = (obj) => {
return findFire(obj); return findFire(obj);
}; };
Meteor.publish('fireFromAlertId', function fireFromAlertId(_id) {
try {
check(_id, String);
// console.log(`Looking for alert fire ${_id}`);
const fire = FireAlertsCollection.findOne(new Meteor.Collection.ObjectID(_id));
if (fire) {
// console.info(`Active fire found: ${_id}`);
return findOrCreateFire(fixConfidence(fire));
}
console.info(`Alert fire not found: ${_id}`);
// Not found in active fires!
return this.ready();
} catch (e) {
console.info(`Alert fire not found (with error): ${_id}`);
return this.ready();
}
});
Meteor.publish('fireFromActiveId', function fireFromActiveId(_id) { Meteor.publish('fireFromActiveId', function fireFromActiveId(_id) {
try { try {
check(_id, String); check(_id, String);
@ -119,60 +95,43 @@ Meteor.publish('fireFromId', function fireFromId(_id) {
const fire = FiresCollection.find(new Meteor.Collection.ObjectID(_id)); const fire = FiresCollection.find(new Meteor.Collection.ObjectID(_id));
if (fire.count() !== 0) { if (fire.count() !== 0) {
// console.info(`Archive fire found: ${_id}`); // console.info(`Archive fire found: ${_id}`);
const union = firesUnion(fire); return fire;
const falsePos = whichAreFalsePositives(FalsePositives, union);
const industries = whichAreFalsePositives(Industries, union);
return [fire, falsePos, industries];
} }
console.info(`Fire not found: ${_id}`); console.info(`Fire not found: ${_id}`);
// Not found in active fires! // Not found in active fires!
return this.ready(); return this.ready();
} catch (e) { } catch (e) {
console.info(`Archive fire not found (with error): ${_id}`); console.info(`Active fire not found (with error): ${_id}`);
return this.ready(); return this.ready();
} }
}); });
function logUrl(fireEnc, params) { Meteor.publish('fireFromHash', function fireFromHash(fireEnc) {
const message = `Wrong fire: ${fireEnc}. Params received in url: ${JSON.stringify(params)}`;
console.log(message);
ravenLogger.log(message);
}
export function fireFromHash(fireEnc, params) {
check(fireEnc, String); 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 { 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) { } catch (e) {
console.warn(e); console.warn(e);
logUrl(fireEnc, params);
// https://guide.meteor.com/data-loading.html
return this.ready(); return this.ready();
} }
}); });

View file

@ -1,58 +0,0 @@
/* eslint-disable consistent-return */
import { Mongo } from 'meteor/mongo';
import SimpleSchema from 'simpl-schema';
import LocationSchema from '/imports/api/Utility/LocationSchema.js';
const Industries = new Mongo.Collection('industries');
// https://prtr.unece.org/prtr-global-map
// For now using:
// AUSTRALIA
// https://data.gov.au/dataset/npi#
//
// EUROPE
// https://www.eea.europa.eu/data-and-maps/data/member-states-reporting-art-7-under-the-european-pollutant-release-and-transfer-register-e-prtr-regulation-16
//
// CANADA
// https://www.canada.ca/en/environment-climate-change/services/national-pollutant-release-inventory/tools-resources-data/access.html
//
// USA
// https://www.epa.gov/enviro/epa-frs-facilities-state-single-file-csv-download
// TODO:
// http://www.nite.go.jp/
// http://www.retc.cl/datos-retc/
Industries.allow({
insert: () => false,
update: () => false,
remove: () => false
});
Industries.deny({
insert: () => true,
update: () => true,
remove: () => true
});
Industries.schema = new SimpleSchema({
geo: LocationSchema,
name: { type: String, optional: true },
registry: String
});
Industries.attachSchema(Industries.schema);
export const industriesRemap = (odoc) => {
const doc = odoc;
const geo = doc.geo;
doc.lat = geo.coordinates[1];
doc.lon = geo.coordinates[0];
doc.id = doc._id;
delete doc.geo;
return doc;
};
export default Industries;

View file

@ -1,29 +0,0 @@
/* eslint-disable consistent-return */
import { Mongo } from 'meteor/mongo';
import SimpleSchema from 'simpl-schema';
const IndustryRegistries = new Mongo.Collection('industryregistries', { idGeneration: 'MONGO' });
IndustryRegistries.allow({
insert: () => false,
update: () => false,
remove: () => false
});
IndustryRegistries.deny({
insert: () => true,
update: () => true,
remove: () => true
});
IndustryRegistries.schema = new SimpleSchema({
name: String,
agency: String,
region: String
});
IndustryRegistries.attachSchema(IndustryRegistries.schema);
export default IndustryRegistries;

View file

@ -2,7 +2,6 @@
/* eslint-disable import/no-absolute-path */ /* eslint-disable import/no-absolute-path */
import { Mongo } from 'meteor/mongo'; import { Mongo } from 'meteor/mongo';
import { Meteor } from 'meteor/meteor';
import SimpleSchema from 'simpl-schema'; import SimpleSchema from 'simpl-schema';
import { defaultCreatedAt, defaultUpdateAt } from '/imports/api/Utility/Utils.js'; import { defaultCreatedAt, defaultUpdateAt } from '/imports/api/Utility/Utils.js';
import LocationSchema from '/imports/api/Utility/LocationSchema.js'; import LocationSchema from '/imports/api/Utility/LocationSchema.js';
@ -23,12 +22,11 @@ Notifications.deny({
Notifications.schema = new SimpleSchema({ Notifications.schema = new SimpleSchema({
userId: String, userId: String,
subsId: { type: Meteor.Collection.ObjectID, optional: true, blackbox: true },
content: String, content: String,
geo: LocationSchema, geo: LocationSchema,
type: String, type: String,
notified: { type: Boolean, optional: true }, webNotified: { type: Boolean, optional: true },
notifiedAt: { type: Date, optional: true }, webNotifiedAt: { type: Date, optional: true },
emailNotified: { type: Boolean, optional: true }, emailNotified: { type: Boolean, optional: true },
emailNotifiedAt: { type: Date, optional: true }, emailNotifiedAt: { type: Date, optional: true },
when: Date, when: Date,

View file

@ -8,7 +8,7 @@ Meteor.methods({
check(notifId, Meteor.Collection.ObjectID); check(notifId, Meteor.Collection.ObjectID);
try { try {
Notifications.update(notifId, { $set: { notified: true, notifiedAt: new Date() } }); Notifications.update(notifId, { $set: { webNotified: true, webNotifiedAt: new Date() } });
return notifId; return notifId;
} catch (exception) { } catch (exception) {
throw new Meteor.Error('500', exception); throw new Meteor.Error('500', exception);

View file

@ -4,7 +4,7 @@ import { Meteor } from 'meteor/meteor';
import Notifications from '../Notifications'; import Notifications from '../Notifications';
Meteor.publish('mynotifications', function 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()}`); // console.log(`Notifications for user ${this.userId}: ${notif.count()}`);
return notif; return notif;
}); });

View file

@ -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');
}
});
}

View file

@ -40,7 +40,6 @@ SiteSettings.getSchema = type => new SimpleSchema({
type: String, type: String,
description: String, description: String,
value: SiteSettingsTypes[type].value, value: SiteSettingsTypes[type].value,
isPublic: Boolean,
createdAt: defaultCreatedAt, createdAt: defaultCreatedAt,
updatedAt: defaultUpdateAt updatedAt: defaultUpdateAt
}); });

View file

@ -1,11 +1,11 @@
import { Meteor } from 'meteor/meteor'; import { Meteor } from 'meteor/meteor';
/* import { check } from 'meteor/check'; import { check } from 'meteor/check';
import SiteSettings from './SiteSettings'; import SiteSettings from './SiteSettings';
import SiteSettingsTypes from './SiteSettingsTypes'; */ import SiteSettingsTypes from './SiteSettingsTypes';
import rateLimit from '../../modules/rate-limit'; import rateLimit from '../../modules/rate-limit';
Meteor.methods({ Meteor.methods({
/* 'siteSettings.insert': function siteSettingsInsert(setting) { 'siteSettings.insert': function siteSettingsInsert(setting) {
check(setting, { check(setting, {
name: String, name: String,
type: String, type: String,
@ -46,14 +46,14 @@ Meteor.methods({
} catch (exception) { } catch (exception) {
throw new Meteor.Error('500', exception); throw new Meteor.Error('500', exception);
} }
} */ }
}); });
rateLimit({ rateLimit({
methods: [ methods: [
/* 'siteSettings.insert', 'siteSettings.insert',
'siteSettings.update', 'siteSettings.update',
'siteSettings.remove' */ 'siteSettings.remove'
], ],
limit: 5, limit: 5,
timeRange: 1000 timeRange: 1000

View file

@ -3,4 +3,4 @@
import { Meteor } from 'meteor/meteor'; import { Meteor } from 'meteor/meteor';
import SiteSettings from '../SiteSettings'; import SiteSettings from '../SiteSettings';
Meteor.publish('settings', () => SiteSettings.find({ isPublic: true })); Meteor.publish('settings', () => SiteSettings.find());

View file

@ -1,7 +1,6 @@
/* eslint-disable consistent-return */ /* eslint-disable consistent-return */
/* eslint-disable import/no-absolute-path */ /* eslint-disable import/no-absolute-path */
import { Meteor } from 'meteor/meteor';
import { Mongo } from 'meteor/mongo'; import { Mongo } from 'meteor/mongo';
import SimpleSchema from 'simpl-schema'; import SimpleSchema from 'simpl-schema';
import { defaultCreatedAt, defaultUpdateAt } from '/imports/api/Utility/Utils.js'; import { defaultCreatedAt, defaultUpdateAt } from '/imports/api/Utility/Utils.js';
@ -43,14 +42,12 @@ Subscriptions.deny({
Subscriptions.schema = new SimpleSchema({ Subscriptions.schema = new SimpleSchema({
_id: { type: Meteor.Collection.ObjectID, optional: true, blackbox: true },
location: Object, location: Object,
'location.lat': Number, 'location.lat': Number,
'location.lon': Number, 'location.lon': Number,
geo: LocationSchema, geo: LocationSchema,
distance: Number, distance: Number,
chatId: { type: Number, optional: true }, // only in 'telegram' type chatId: { type: Number, optional: true }, // only in 'telegram' type
telegramBot: { type: String, optional: true }, // only in 'telegram' type
owner: String, owner: String,
type: String, type: String,
createdAt: defaultCreatedAt, createdAt: defaultCreatedAt,

View file

@ -10,49 +10,26 @@ 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({ Meteor.methods({
'subscriptions.insert': function subscriptionInsertMethod(doc) { 'subscriptions.insert': function subscriptionsInsert(doc) {
check(doc, { check(doc, {
location: Match.ObjectIncluding({ lat: Number, lon: Number }), location: Match.ObjectIncluding({ lat: Number, lon: Number }),
distance: Number distance: Number
}); });
return subscriptionsInsert(doc, this.userId, 'web'); const type = 'web';
try {
const newDoc = {
owner: this.userId,
type,
geo: geo(doc),
...doc
};
// console.log(newDoc);
return Subscriptions.insert(newDoc);
} catch (exception) {
console.error(exception);
throw new Meteor.Error('500', exception);
}
}, },
'subscriptions.update': function subscriptionsUpdate(doc) { 'subscriptions.update': function subscriptionsUpdate(doc) {
check(doc, { check(doc, {
@ -71,9 +48,15 @@ Meteor.methods({
throw new Meteor.Error('500', exception); throw new Meteor.Error('500', exception);
} }
}, },
'subscriptions.remove': function subscriptionsRemoveMethod(subscriptionId) { 'subscriptions.remove': function subscriptionsRemove(subscriptionId) {
check(subscriptionId, Meteor.Collection.ObjectID); check(subscriptionId, Meteor.Collection.ObjectID);
return subscriptionsRemove(subscriptionId);
try {
return Subscriptions.remove(subscriptionId);
} catch (exception) {
console.error(exception);
throw new Meteor.Error('500', exception);
}
} }
}); });

View file

@ -3,8 +3,43 @@
import { Meteor } from 'meteor/meteor'; import { Meteor } from 'meteor/meteor';
import { Mongo } from 'meteor/mongo'; import { Mongo } from 'meteor/mongo';
import { check } from 'meteor/check'; import { check } from 'meteor/check';
import Perlin from 'loms.perlin';
import Subscriptions from '../Subscriptions'; import Subscriptions from '../Subscriptions';
Perlin.seed(Math.random());
Meteor.publishTransformed('userSubsToFires', function transform() {
// https://en.wikipedia.org/wiki/Location_obfuscation
// https://en.wikipedia.org/wiki/Decimal_degrees#Precision
// https://gis.stackexchange.com/questions/27792/what-simple-effective-techniques-for-obfuscating-points-are-available
return Subscriptions.find().serverTransform(function transformDoc(odoc) {
const doc = odoc;
// Destructuring gives me an error: "Cannot destructure property `location` of 'undefined'"
const location = doc.location;
/* doc.lat = location.lat;
* doc.lon = location.lon; */
let lat;
let lon;
if (location) {
lat = Math.round(location.lat * 10) / 10;
lon = Math.round(location.lon * 10) / 10;
// console.log(`[${lat}, ${lon}]`);
const noiseBase = Perlin.perlin2(lat, lon);
const noise = Math.abs(noiseBase / 3);
// console.log(`Noise ${noise}, abs: ${Math.abs(noise)}`);
lat += noise;
lon += noise;
doc.location.lat = lat;
doc.location.lon = lon;
doc.distance += noiseBase;
}
// console.log(`with noise: [${doc.lat}, ${doc.lon}]`);
delete doc.chatId;
delete doc.geo;
return doc;
});
});
Meteor.publish('mysubscriptions', function subscriptions() { Meteor.publish('mysubscriptions', function subscriptions() {
return Subscriptions.find({ owner: this.userId }); // type: 'web' return Subscriptions.find({ owner: this.userId }); // type: 'web'
}); });

View file

@ -78,8 +78,11 @@ const schemaUser = new SimpleSchema({
// Option 2: [String] type // Option 2: [String] type
// If you are sure you will never need to use role groups, then // If you are sure you will never need to use role groups, then
// you can specify [String] as the type // you can specify [String] as the type
roles: { type: Array, optional: true }, roles: {
'roles.$': { type: String, optional: true }, type: Array,
// autoform: { type: 'hidden' },
optional: true
},
// https://github.com/todda00/meteor-friendly-slugs/issues/1 // https://github.com/todda00/meteor-friendly-slugs/issues/1
friendlySlugs: { friendlySlugs: {
type: Object, type: Object,
@ -107,7 +110,6 @@ const schemaUser = new SimpleSchema({
telegramUsername: { type: String, optional: true }, telegramUsername: { type: String, optional: true },
telegramFirstName: { type: String, optional: true }, telegramFirstName: { type: String, optional: true },
telegramLanguageCode: { type: String, optional: true }, telegramLanguageCode: { type: String, optional: true },
fireBaseToken: { type: String, optional: true },
createdAt: defaultCreatedAt, createdAt: defaultCreatedAt,
updatedAt: defaultUpdateAt updatedAt: defaultUpdateAt
}); });

View file

@ -10,12 +10,11 @@ export default (options, user, lang) => {
const applicationName = i18n.t('AppName'); const applicationName = i18n.t('AppName');
const firstName = OAuthProfile ? OAuthProfile.name.first : options.profile.name.first; const firstName = OAuthProfile ? OAuthProfile.name.first : options.profile.name.first;
const emailAddress = OAuthProfile ? OAuthProfile.email : options.email; const emailAddress = OAuthProfile ? OAuthProfile.email : options.email;
const welcome = i18n.t('welcome');
if (emailAddress) { if (emailAddress) {
sendEmail({ sendEmail({
to: emailAddress, to: emailAddress,
from: `${applicationName} <noreply@comunes.org>`, from: `${applicationName} <noreply@comunes.org>`,
subject: `[${applicationName}] ${welcome} ${firstName}!`, subject: `[${applicationName}] Welcome, ${firstName}!`,
lang, lang,
template: 'welcome', template: 'welcome',
templateVars: { templateVars: {

View file

@ -12,10 +12,10 @@ const LocationSchema = new SimpleSchema({
minCount: 2, minCount: 2,
maxCount: 2, maxCount: 2,
custom: function custom() { custom: function custom() {
if (!(this.value[0] >= -180 && this.value[0] <= 180)) { if (!(this.value[0] >= -90 && this.value[0] <= 90)) {
return 'lngOutOfRange'; return 'lngOutOfRange';
} }
if (!(this.value[1] >= -90 && this.value[1] <= 90)) { if (!(this.value[1] >= -180 && this.value[1] <= 180)) {
return 'latOutOfRange'; return 'latOutOfRange';
} }
return true; return true;
@ -27,8 +27,8 @@ const LocationSchema = new SimpleSchema({
}); });
LocationSchema.messageBox.messages({ LocationSchema.messageBox.messages({
latOutOfRange: '[label] latitude should be between -90 and 90', lonOutOfRange: '[label] longitude should be between -90 and 90',
lngOutOfRange: '[label] longitude should be between -180 and 180' latOutOfRange: '[label] latitude should be between -180 and 180'
}); });
export default LocationSchema; export default LocationSchema;

View file

@ -1,10 +1,15 @@
/* eslint-disable import/no-absolute-path */
import fs from 'fs'; import fs from 'fs';
import getFallbackLang from '/imports/modules/lang-fallback';
const as = f => `assets/app/${f}`; const as = f => `assets/app/${f}`;
export const getFallbackLang = (lang) => {
if (lang === 'ast' || lang === 'gl' || lang === 'eu' || lang === 'ca') {
return 'es';
}
return 'en';
};
export const getFileNameOfLang = (dir, fileName, ext, lang) => { export const getFileNameOfLang = (dir, fileName, ext, lang) => {
const base = `${dir}/${fileName}`; const base = `${dir}/${fileName}`;
const fallback = getFallbackLang(lang); const fallback = getFallbackLang(lang);

View file

@ -1,19 +0,0 @@
/* eslint-disable import/no-absolute-path */
import getOAuthProfile from '/imports/modules/get-oauth-profile';
const getEmailOf = (user) => {
const hasPassword = user.services && user.services.password && user.services.password.bcrypt;
// console.log(`Has password: ${hasPassword}`);
const OAuthProfile = getOAuthProfile({
password: hasPassword,
profile: user.profile
}, user);
const firstName = OAuthProfile ? OAuthProfile.name.first : user.profile.name.first;
const emailAddress = OAuthProfile ? OAuthProfile.email :
user && user.emails[0] && user.emails[0].verified ? user.emails[0].address : null;
return { emailAddress, firstName };
};
export default getEmailOf;

View file

@ -1,11 +1,12 @@
const parseGoogleData = service => ({ const parseGoogleData = service => {
email: service.email, return {
name: { email: service.email,
first: service.given_name, name: {
last: service.family_name first: service.given_name,
}, last: service.family_name,
lang: service.locale },
}); };
};
const parseGithubData = (profile, service) => { const parseGithubData = (profile, service) => {
const name = profile.name.split(' '); const name = profile.name.split(' ');
@ -13,18 +14,20 @@ const parseGithubData = (profile, service) => {
email: service.email, email: service.email,
name: { name: {
first: name[0], first: name[0],
last: name[1] last: name[1],
} },
}; };
}; };
const parseFacebookData = service => ({ const parseFacebookData = service => {
email: service.email, return {
name: { email: service.email,
first: service.first_name, name: {
last: service.last_name first: service.first_name,
} last: service.last_name,
}); },
};
};
const getDataForService = (profile, services) => { const getDataForService = (profile, services) => {
if (services.facebook) return parseFacebookData(services.facebook); if (services.facebook) return parseFacebookData(services.facebook);

View file

@ -1,9 +0,0 @@
const getFallbackLang = (lang) => {
if (lang && (lang === 'ast' || lang === 'gl' || lang === 'eu' ||
lang === 'es' || lang === 'ca' || lang.match(/^es-/))) {
return 'es';
}
return 'en';
};
export default getFallbackLang;

View file

@ -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 `<img src="${imgUrl(lat, lng)}" width="640" height="480"/>`;
} */
// 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 = `<a href="${fireUrl}">${i18n.t('Más información sobre este fuego')}</a>`;
// 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: '<body><h2>{{appName}}</h2>{{{html}}}</body>',
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;

View file

@ -1,21 +1,11 @@
/* eslint-disable import/no-absolute-path */
import { Meteor } from 'meteor/meteor'; import { Meteor } from 'meteor/meteor';
import i18n from 'i18next'; // import { Email } from 'meteor/email';
import { getFileNameOfLang } from '/imports/api/Utility/server/files.js';
import sendMail from '/imports/startup/server/email';
import getPrivateFile from './get-private-file'; import getPrivateFile from './get-private-file';
import templateToText from './handlebars-email-to-text'; import templateToText from './handlebars-email-to-text';
import templateToHTML from './handlebars-email-to-html'; import templateToHTML from './handlebars-email-to-html';
import { getFileNameOfLang } from '/imports/api/Utility/server/files.js';
export function subjectTruncate(n = 70, useWordBoundary = true) { import sendMail from '/imports/startup/server/email';
// https://stackoverflow.com/questions/1199352/smart-way-to-shorten-long-strings-with-javascript import i18n from 'i18next';
if (this.length <= n) {
return `${this}`;
}
const subString = this.substr(0, n - 1);
return `${useWordBoundary ? subString.substr(0, subString.lastIndexOf(' ')) : subString}...`;
}
const sendEmail = (options, { resolve, reject }) => { const sendEmail = (options, { resolve, reject }) => {
try { try {

View file

@ -1,27 +0,0 @@
import { Meteor } from 'meteor/meteor';
import Twit from 'twit';
// https://www.npmjs.com/package/twit
const confTwitter = (lang) => {
const tConf = Meteor.settings.private.twitter[lang];
const T = new Twit({
consumer_key: tConf.consumer_key,
consumer_secret: tConf.consumer_secret,
access_token: tConf.access_token,
access_token_secret: tConf.access_token_secret
// timeout_ms: 60*1000, // optional HTTP request timeout to apply to all requests.
});
return T;
};
const tweet = (message, lang) => {
const T = confTwitter(lang);
T.post('statuses/update', { status: message }, (err, data, response) => {
// console.log(data);
// console.log(response);
});
};
export default tweet;

View file

@ -37,6 +37,6 @@ i18n.init((err, t) => {
template: 'bootstrap', // default 'semantic-ui' template: 'bootstrap', // default 'semantic-ui'
// default 'http://s3.amazonaws.com/37assets/svn/765-default-avatar.png' // default 'http://s3.amazonaws.com/37assets/svn/765-default-avatar.png'
defaultAvatar: '/default-avatar.png', defaultAvatar: '/default-avatar.png',
markdown: true markdown: false
}); });
}); });

View file

@ -17,7 +17,7 @@ import i18nOpts from '../common/i18n';
const detectorOptions = { const detectorOptions = {
// order and from where user language should be detected // 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 // keys or params to lookup language from
lookupQuerystring: 'lng', lookupQuerystring: 'lng',
@ -53,22 +53,15 @@ i18nOpts.react = {
nsMode: 'default' */ nsMode: 'default' */
}; };
const sendMissing = true; // Meteor.isDevelopment; const sendMissing = Meteor.isDevelopment;
if (sendMissing && Meteor.isDevelopment) { if (sendMissing && !Meteor.isProduction) {
i18nOpts.sendMissing = true; i18nOpts.sendMissing = true;
i18nOpts.missingKeyHandler = function miss(lng, ns, key, defaultValue) { i18nOpts.sendMissingTo = 'fallback';
i18nOpts.missingKeyHandler = function miss(lng, ns, key, defaultValue, lngs) {
Meteor.call('utility.saveMissingI18n', key, defaultValue); Meteor.call('utility.saveMissingI18n', key, defaultValue);
}; };
} }
function setT9(lang) {
if (lang === 'gl') {
T9n.setLanguage('es');
} else {
T9n.setLanguage(lang);
}
}
i18n.use(backend) i18n.use(backend)
.use(LngDetector) .use(LngDetector)
.use(Cache) .use(Cache)
@ -78,16 +71,15 @@ i18n.use(backend)
console.error(err); console.error(err);
return; return;
} }
console.log(`Language initialized: ${i18n.language}`); i18nReady.set(true);
// document.title = t('AppName'); // document.title = t('AppName');
// Accounts translation // Accounts translation
// https://github.com/softwarerero/meteor-accounts-t9n // https://github.com/softwarerero/meteor-accounts-t9n
setT9(i18n.language); // console.log("Language: " + i18n.language);
T9n.setLanguage(i18n.language);
// console.log(T9n.get('error.accounts.User not found')); // console.log(T9n.get('error.accounts.User not found'));
moment.locale(i18n.language);
i18nReady.set(true); moment.locale(i18n.language);
// cookies eu consent // cookies eu consent
const cookiesOpt = { const cookiesOpt = {
@ -99,7 +91,7 @@ i18n.use(backend)
position: 'bottom', position: 'bottom',
linkText: 'Lee más', linkText: 'Lee más',
linkRouteName: '/privacy', linkRouteName: '/privacy',
acceptButtonText: t('Aceptar'), acceptButtonText: 'Aceptar',
html: false, html: false,
expirationInDays: 70, expirationInDays: 70,
forceShow: false forceShow: false
@ -112,8 +104,7 @@ Meteor.subscribe('userData'); // lang is there
i18n.on('languageChanged', (lng) => { i18n.on('languageChanged', (lng) => {
moment.locale(lng); moment.locale(lng);
// TODO fix this when gl is translated T9n.setLanguage(lng);
setT9(lng);
Tracker.autorun((computation) => { Tracker.autorun((computation) => {
if (Meteor.userId()) { if (Meteor.userId()) {
// logged // logged

View file

@ -1,19 +1,8 @@
/* global */
import React from 'react'; import React from 'react';
import { render } from 'react-dom'; import { render } from 'react-dom';
import { Meteor } from 'meteor/meteor'; import { Meteor } from 'meteor/meteor';
import '/imports/startup/client/modernizr';
import App from '../../ui/layouts/App/App'; import App from '../../ui/layouts/App/App';
import '../../ui/stylesheets/app.scss'; import '../../ui/stylesheets/app.scss';
// https://stackoverflow.com/questions/19562207/jquery-detect-browser-ie9-and-below-and-throw-up-a-modal-to-upgrade Meteor.startup(() => render(<App />, document.getElementById('react-root')));
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(<App />, document.getElementById('react-root')));
}

File diff suppressed because one or more lines are too long

View file

@ -1,7 +1,8 @@
import { Meteor } from 'meteor/meteor'; Meteor.startup(function() {
import { Tracker } from 'meteor/tracker'; return Tracker.autorun(function() {
var userId;
Meteor.startup(() => Tracker.autorun(() => { userId = Meteor.userId();
const userId = Meteor.userId(); // console.log(userId);
Meteor.Piwik.setUserInfo(userId); Meteor.Piwik.setUserInfo(userId);
})); });
});

View file

@ -2,15 +2,12 @@ import RavenLogger from 'meteor/flowkey:raven';
import { Meteor } from 'meteor/meteor'; import { Meteor } from 'meteor/meteor';
const ravenOptions = {}; const ravenOptions = {};
const publicDSN = Meteor.settings.public.sentryPublicDSN;
const enabled = !!publicDSN;
const ravenLogger = enabled ? new RavenLogger({ const ravenLogger = new RavenLogger({
publicDSN, // will be used on the client publicDSN: Meteor.settings.public.sentryPublicDSN, // will be used on the client
privateDSN: Meteor.settings.sentryPrivateDSN, // will be used on the server
shouldCatchConsoleError: true, // default shouldCatchConsoleError: true, // default
trackUser: true // default trackUser: false // default
}, ravenOptions) : { log: (error) => { console.log(error); } }; }, ravenOptions);
console.log(`ravenLogger ${enabled ? 'enabled' : 'disabled'}`);
export default ravenLogger; export default ravenLogger;

View file

@ -1,8 +0,0 @@
/* global reconnecToServer */
import { Meteor } from 'meteor/meteor';
Meteor.startup(() => {
// Reconnect Function
reconnectToServer(5000, true);
});

View file

@ -3,16 +3,11 @@
// Client and Server // Client and Server
Comments.config({ Comments.config({
rating: 'likes-and-dislikes', rating: 'likes-and-dislikes',
allowAnonymous: () => false, allowAnonymous: () => true,
allowReplies: () => false, // disabled right now: I don't know hot to get referenceId of replies
anonymousSalt: 'klasddl3lala0l3lasdlas0ol3lasdlao3lasdoaslaldal3lasdclasdlal3lasdladlaq', anonymousSalt: 'klasddl3lala0l3lasdlas0ol3lasdlao3lasdoaslaldal3lasdclasdlal3lasdladlaq',
publishUserFields: { publishUserFields: {
profile: 1 profile: 1
}, },
mediaAnalyzers: [
Comments.analyzers.image,
Comments.analyzers.youtube
],
generateUsername: function genUser(user) { generateUsername: function genUser(user) {
// console.log(JSON.stringify(user)); // console.log(JSON.stringify(user));
// FIXME // FIXME

View file

@ -3,8 +3,6 @@ import moment from 'moment';
// Load the js langs // Load the js langs
import es from 'meteor-accounts-t9n/build/es'; import es from 'meteor-accounts-t9n/build/es';
import en from 'meteor-accounts-t9n/build/en'; import en from 'meteor-accounts-t9n/build/en';
// TODO ask for translation of this
// import gl from 'meteor-accounts-t9n/build/gl';
const backOpts = { const backOpts = {
// path where resources get loaded from // path where resources get loaded from
@ -23,11 +21,7 @@ const shouldDebug = (forceDebug && !Meteor.isProduction);
const i18nOpts = { const i18nOpts = {
backend: backOpts, backend: backOpts,
// lng: 'es', // lng: 'es',
fallbackLng: { fallbackLng: ['es', 'en'],
gl: ['es'],
default: ['en']
},
sendMissingTo: 'fallback',
interpolation: { interpolation: {
escapeValue: false, // not needed for react!! escapeValue: false, // not needed for react!!
formatSeparator: ',', formatSeparator: ',',
@ -40,7 +34,8 @@ const i18nOpts = {
return value; return value;
} }
}, },
whitelist: ['es', 'en', 'gl'], // allowed languages whitelist: false,
// whitelist: ['es', 'en'], // allowed languages
load: 'languageOnly', // 'es' o 'en', previously: 'all', // es-ES -> es, en-US -> en load: 'languageOnly', // 'es' o 'en', previously: 'all', // es-ES -> es, en-US -> en
debug: shouldDebug, debug: shouldDebug,
ns: 'common', ns: 'common',

View file

@ -1,27 +1,18 @@
/* eslint-disable import/no-absolute-path */
import { Accounts } from 'meteor/accounts-base'; import { Accounts } from 'meteor/accounts-base';
import getFallbackLang from '/imports/modules/lang-fallback'; import sendWelcomeEmail from '../../../api/Users/server/send-welcome-email';
import sendWelcomeEmail from '/imports/api/Users/server/send-welcome-email';
import getOAuthProfile from '/imports/modules/get-oauth-profile';
Accounts.onCreateUser((options, user) => { Accounts.onCreateUser((options, user) => {
const userToCreate = user; const userToCreate = user;
// console.log(JSON.stringify(user)); // console.log(JSON.stringify(user));
// console.log(JSON.stringify(options)); // console.log(JSON.stringify(options));
let lang = 'en'; // fallback let lang = 'en'; // fallback
if (options.profile) { if (options.profile) {
userToCreate.profile = options.profile; userToCreate.profile = options.profile;
userToCreate.lang = options.profile.lang; userToCreate.lang = options.profile.lang;
// console.log(JSON.stringify(user)); lang = options.profile.lang;
lang = userToCreate.lang;
delete options.profile.lang; delete options.profile.lang;
} } else {
const OAuthProfile = getOAuthProfile(options, user); // TODO others (google, etc) ?
if (OAuthProfile) {
userToCreate.lang = getFallbackLang(OAuthProfile && OAuthProfile.lang ? OAuthProfile.lang : lang);
lang = userToCreate.lang;
} }
sendWelcomeEmail(options, userToCreate, lang); sendWelcomeEmail(options, userToCreate, lang);
return userToCreate; return userToCreate;

View file

@ -9,7 +9,6 @@ import '../../api/Users/server/publications';
import '../../api/Utility/server/methods'; import '../../api/Utility/server/methods';
import '../../api/ActiveFires/server/publications'; import '../../api/ActiveFires/server/publications';
import '../../api/ActiveFiresUnion/server/publications';
import '../../api/FireAlerts/server/publications'; import '../../api/FireAlerts/server/publications';
import '../../api/Subscriptions/methods'; import '../../api/Subscriptions/methods';

View file

@ -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);
});
});
});

View file

@ -1,80 +0,0 @@
/* global Comments */
/* eslint-disable import/no-absolute-path */
import { Meteor } from 'meteor/meteor';
import i18n from 'i18next';
import sendEmail, { subjectTruncate } from '/imports/modules/server/send-email';
import getEmailOf from '/imports/modules/get-email-of-user';
import '../common/comments';
/* import i18n from 'i18next';
import moment from 'moment';
import { dateLongFormat } from '/imports/api/Common/dates';
import Notifications from '/imports/api/Notifications/Notifications';
// import sendMail from '/imports/startup/server/email';
// import { hr } from '/imports/startup/server/email';
import getOAuthProfile from '/imports/modules/get-oauth-profile';
import image from 'google-maps-image-api-url';
import { trim } from '/imports/ui/components/NotificationsObserver/util.js'; */
Meteor.startup(() => {
// On Server or Client (preferably on Server)
Comments.config({
onEvent: (name, action, payload) => {
// e.g send a mail
// console.log(`name: ${name}, action: ${action}, payload: ${JSON.stringify(payload)}`);
// Samples:
// name: comment, action: add, payload: {"referenceId":"fire-56a9cc5a5586879eb9cc6dd7","content":"asdfasdf asd fasd fas","userId":"tdK4e43ikjDXct7Gj","isAnonymous":false,"createdAt":"2018-03-04T14:53:40.319Z","likes":[],"dislikes":[],"replies":[],"media":{},"status":"approved","starRatings":[],"ratingScore":0,"_id":"mfAe3HL9qNofhiAnh"}
// name: reply, action: add, payload: {"replyId":"teSLaz4f8RxHjptBm","content":"sas fasdfasdasdf","userId":"tdK4e43ikjDXct7Gj","createdAt":"2018-03-04T14:54:56.965Z","replies":[],"likes":[],"lastUpdatedAt":"2018-03-04T14:54:56.965Z","isAnonymous":false,"status":"approved","media":{},"ratingScore":0,"_id":"DyHt28rF6rTDnC5fP","rootUserId":"tdK4e43ikjDXct7Gj"}
// name: reply, action: like, payload: {"replyId":"teSLaz4f8RxHjptBm","content":"sas fasdfasdasdf","userId":"tdK4e43ikjDXct7Gj","createdAt":"2018-03-04T14:54:56.965Z","replies":[],"likes":["tdK4e43ikjDXct7Gj"],"lastUpdatedAt":"2018-03-04T14:54:56.965Z","isAnonymous":false,"status":"approved","media":{},"ratingScore":1,"_id":"DyHt28rF6rTDnC5fP","ratedUserId":"tdK4e43ikjDXct7Gj","rootUserId":"tdK4e43ikjDXct7Gj"}
// name: comment, action: like, payload: {"_id":"mfAe3HL9qNofhiAnh","ratedUserId":"tdK4e43ikjDXct7Gj"}
// name: reply, action: edit, payload: {"_id":"DyHt28rF6rTDnC5fP","replyId":"teSLaz4f8RxHjptBm","content":"sas fasdfasdasdf dasd fadsfa","userId":"tdK4e43ikjDXct7Gj","createdAt":"2018-03-04T14:54:56.965Z","replies":[],"likes":[],"lastUpdatedAt":"2018-03-04T14:54:56.965Z","isAnonymous":false,"status":"approved","media":{},"ratingScore":0,"starRatings":[],"ratedUserId":"tdK4e43ikjDXct7Gj","rootUserId":"tdK4e43ikjDXct7Gj"}
// name: reply, action: remove, payload: {"_id":"DyHt28rF6rTDnC5fP","rootUserId":"tdK4e43ikjDXct7Gj"}
// name: (comment|reply)
// action: (add|like|edit|remove)
if (name === 'comment' && action.match(/add|like|edit/)) {
if (action === 'add') {
// Check for other users that did comments and send an email (uniq users, and not this user)
// console.log(payload.referenceId);
const query = payload.isAnonymous ?
{ referenceId: payload.referenceId } :
{ referenceId: payload.referenceId, userId: { $ne: payload.userId } };
Comments.getCollection().rawCollection().distinct('userId', query).then((users) => {
// console.log(users);
const path = payload.referenceId.replace(/fire-/, 'fire/archive/');
const fireUrl = Meteor.absoluteUrl(path);
// console.log(fireUrl);
Meteor.users.find({ _id: { $in: users } }).forEach((user) => {
const { firstName, emailAddress } = getEmailOf(user);
// console.log(JSON.stringify(user));
if (emailAddress) {
const emailOpts = {
to: emailAddress,
subject: subjectTruncate.apply(i18n.t('Hay más información sobre un fuego')),
lang: user.lang,
template: 'new-fire-comment',
templateVars: {
applicationName: i18n.t('AppName'),
firstName,
fireUrl
}
};
sendEmail(emailOpts).catch((error) => {
throw new Meteor.Error('500', `${error}`);
});
}
});
});
}
}
if (name === 'reply' && action.match(/add|like|edit/)) {
// Check for other previous users to this thread and send an email
// console.log('Reply op');
}
}
});
});

View file

@ -1,135 +0,0 @@
/* global SyncedCron */
/* eslint-disable import/no-absolute-path */
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/
Meteor.startup(() => {
SyncedCron.config({
// Log job run details to console
log: Meteor.settings.private.cron.debug || false,
// Use a custom logger function (defaults to Meteor's logging package)
// workaround to disable logging succesfully
logger: Meteor.settings.private.cron.debug ? null : () => {}
// Name of collection to use for synchronisation and logging
// collectionName: 'cronHistory',
// Default to localTime
// Options: 'utc', 'localtime', or specific timezones 'America/New_York'
// Will be applied to jobs with no timezone defined
// timezone: 'utc',
/*
TTL in seconds for history records in collection to expire
NOTE: Unset to remove expiry but ensure you remove the index from
mongo by hand
ALSO: SyncedCron can't use the `_ensureIndex` command to modify
the TTL index. The best way to modify the default value of
`collectionTTL` is to remove the index by hand (in the mongo shell
run `db.cronHistory.dropIndex({startedAt: 1})`) and re-run your
project. SyncedCron will recreate the index with the updated TTL.
*/
// collectionTTL: 172800
});
if (isMailServerMaster) {
SyncedCron.add({
name: 'Send emails',
timezone: 'Europe/Madrid',
schedule: (parser) => {
// http://bunkat.github.io/later/
const sched = parser.text('every 10 min');
if (sched.error !== -1) {
console.error(`Mail cron 'when' field parsed with errors: ${sched.error}`);
}
return sched;
},
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;
const enEn = Meteor.settings.private.twitter.en.enabled;
if (esEn) {
SyncedCron.add({
name: 'Check for fires stats in Spain and tweet about',
timezone: 'Europe/Madrid',
// Optionally set a positive offset if you wish to 'snooze' a schedule
// offset: 30 * 60 * 100,
// context: {
// userID: 'xyz'
// },
schedule: (parser) => {
// this.magic = true; // Context is accesible here as this context.
// parser is a later.parse object
// return parser.text('every 2 minutes');
// http://bunkat.github.io/later/
const sched = parser.text(Meteor.settings.private.twitter.es.when);
if (sched.error !== -1) {
console.error(`Twitter cron 'when' field parsed with errors: ${sched.error}`);
}
return sched;
},
job: () => tweetIberiaFires()
/* console.log('cron is working');
* console.log(this.userID) // Context Object becomes this argument
* console.log(this.magic) /
* var numbersCrunched = CrushSomeNumbers();
* return numbersCrunched;
return tweetFires(); */
});
}
if (enEn) {
SyncedCron.add({
name: 'Check for fires stats in Europe and tweet about',
timezone: 'Europe/Madrid',
schedule: (parser) => {
const sched = parser.text(Meteor.settings.private.twitter.en.when);
if (sched.error !== -1) {
console.error(`Twitter cron 'when' field parsed with errors: ${sched.error}`);
}
return sched;
},
job: () => tweetEuropeFires()
});
}
if (isMailServerMaster || esEn || enEn) {
SyncedCron.start();
}
});

View file

@ -1,8 +1,8 @@
import { Meteor } from 'meteor/meteor'; import { Meteor } from 'meteor/meteor';
import { Mongo } from 'meteor/mongo';
import nodemailer from 'nodemailer'; import nodemailer from 'nodemailer';
import { MailTime } from 'meteor/ostrio:mailer'; import { MailTime } from 'meteor/ostrio:mailer';
import i18n from 'i18next'; import i18n from 'i18next';
import isMaster from './isMaster';
const transports = []; const transports = [];
@ -20,46 +20,38 @@ export const hr = `<table cellspacing="0" cellpadding="0" border="0" width="100%
</tr> </tr>
</table>`; </table>`;
let MailQueue; const MailQueue = new MailTime({
db,
type: 'server',
strategy: 'balancer', // Transports will be used in round robin chain
transports,
from(transport) {
// To pass spam-filters `from` field should be correctly set
// for each transport, check `transport` object for more options
if (!Meteor.isProduction) {
// only for test purposes
return `${i18n.t('AppName')} <notify@example.org>`;
}
return `${i18n.t('AppName')} <${transport.options.auth.user}>`;
},
debug: Meteor.settings.private.debugMailer,
concatEmails: true, // Concatenate emails to the same addressee
concatSubject: `${i18n.t('Nuevas notificaciones de {{app}}', { app: i18n.t('AppName') })}`,
/* eslint-disable */
concatDelimiter: hr + '<h2>{{{subject}}}</h2>', // Start each concatenated email with it's own subject
/* eslint-enable */
// concatThrottling: 30,
template: MailTime.Template // Use default template
});
export const isMailServerMaster = Meteor.settings.private.isMailServer && isMaster(); // A Client (not used yet)
// const MailQueueClient = new MailTime({
if (isMailServerMaster) { // db,
console.log('I\'m the mail server'); // type: 'client',
MailQueue = new MailTime({ // debug: true,
db, // strategy: 'balancer', // Transports will be used in round robin chain
type: 'client', // instead of 'server' for now to avoid use the JosK task manager at 250ms, we use our cron // concatEmails: true // Concatenate emails to the same address
// see https://github.com/VeliovGroup/Mail-Time/issues/5 // });
strategy: 'balancer', // Transports will be used in round robin chain
transports,
from(transport) {
// To pass spam-filters `from` field should be correctly set
// for each transport, check `transport` object for more options
if (!Meteor.isProduction) {
// only for test purposes
return `${i18n.t('AppName')} <notify@example.org>`;
}
return `${i18n.t('AppName')} <${transport.options.auth.user}>`;
},
debug: Meteor.settings.private.debugMailer,
concatEmails: true, // Concatenate emails to the same addressee
concatSubject: `${i18n.t('Nuevas notificaciones de {{app}}', { app: i18n.t('AppName') })}`,
/* eslint-disable */
concatDelimiter: hr + '<h2>{{{subject}}}</h2>', // Start each concatenated email with it's own subject
/* eslint-enable */
// concatThrottling: 30,
template: MailTime.Template // Use default template
});
} else {
console.log('I\'m a mail client');
MailQueue = new MailTime({
db,
type: 'client',
debug: Meteor.settings.private.debugMailer,
strategy: 'balancer', // Transports will be used in round robin chain
concatEmails: true // Concatenate emails to the same address
});
}
export default function sendMail(opts, debug) { export default function sendMail(opts, debug) {
if (debug) { if (debug) {
@ -86,6 +78,27 @@ if (Meteor.settings.private.testMailer) {
sendMail(emailOpts, true); sendMail(emailOpts, true);
} }
export const sendEmailsFromQueue = () => {
MailQueue.___send(() => { }); // Set interval to greater than 256
// https://github.com/VeliovGroup/Mail-Time/issues/5
const MailJobs = new Mongo.Collection('__JobTasks__mailTimeQueue', { idGeneration: 'MONGO' });
const updateJob = (mailJob) => {
MailJobs.update(mailJob._id, { $set: { delay: 30000 } });
// console.log(MailJobs.findOne());
}; };
const mailJob = MailJobs.findOne();
if (mailJob) {
updateJob(mailJob);
}
MailJobs.find().observe({
added: function notifAdded(item) {
updateJob(item);
},
changed: function notifChanged(item) {
updateJob(item);
}
});

View file

@ -1,25 +0,0 @@
/* eslint-disable prefer-arrow-callback */
/* eslint-disable import/no-absolute-path */
import { Meteor } from 'meteor/meteor';
import i18n from 'i18next';
import { check } from 'meteor/check';
import sendEmail from '/imports/startup/server/email';
Meteor.methods({
'send-feedback': function sendFeedback(email, feedback) {
check(email, String);
check(feedback, String);
const appName = i18n.t('AppName');
sendEmail({
to: 'fires@comunes.org',
from: `${appName} <noreply@comunes.org>`,
subject: `Feedback de ${email}!`,
sendAt: new Date(),
text: `Feedback de ${email}\n\n${feedback}`,
html: `<p>${feedback}</p>`,
template: '<body>{{appName}}<h2>{{{subject}}}</h2>{{{html}}}</body>',
appName
}, true);
}
});

View file

@ -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;

View file

@ -1,6 +1,3 @@
import './ravenLogger';
import './catchExceptions';
import './fibers';
import './i18n'; import './i18n';
import './accounts'; import './accounts';
import './api'; import './api';
@ -10,11 +7,4 @@ import './IPGeocoder';
import './migrations'; import './migrations';
import './notificationsObserver'; import './notificationsObserver';
import './facts'; import './facts';
import './comments'; import '../common/comments';
import './sitemaps';
import './subsUnion';
import './prerender';
import './feedback';
import './cron';
import './isMaster';
import './rest';

View file

@ -1,16 +0,0 @@
/* eslint-disable import/no-absolute-path */
import pm2Master from 'pm2-master';
import { Meteor } from 'meteor/meteor';
Meteor.startup(() => {
if (pm2Master.isMaster()) {
console.log('This is the master process');
} else {
console.log('This is a worker process');
}
});
const isMaster = () => pm2Master.isMaster();
export default isMaster;

View file

@ -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'
};

View file

@ -1,4 +1,4 @@
/* global Migrations, Comments */ /* global Migrations */
/* eslint-disable import/no-absolute-path */ /* eslint-disable import/no-absolute-path */
import { Meteor } from 'meteor/meteor'; import { Meteor } from 'meteor/meteor';
import { Accounts } from 'meteor/accounts-base'; import { Accounts } from 'meteor/accounts-base';
@ -7,18 +7,13 @@ import UserSubsToFiresCollection from '/imports/api/Subscriptions/Subscriptions'
import FireAlertsCollection from '/imports/api/FireAlerts/FireAlerts'; import FireAlertsCollection from '/imports/api/FireAlerts/FireAlerts';
import SiteSettings from '/imports/api/SiteSettings/SiteSettings'; import SiteSettings from '/imports/api/SiteSettings/SiteSettings';
import FalsePositives from '/imports/api/FalsePositives/FalsePositives'; 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(() => { Meteor.startup(() => {
// https://github.com/percolatestudio/meteor-migrations // https://github.com/percolatestudio/meteor-migrations
Migrations.config({ Migrations.config({
// Log job run details to console // Log job run details to console
log: Meteor.isProduction log: true
}); });
Migrations.add({ Migrations.add({
@ -109,152 +104,8 @@ Meteor.startup(() => {
} }
}); });
Migrations.add({
version: 7,
up: function defLangIfNull() {
Meteor.users.find({ lang: null }).forEach((user) => {
Meteor.users.update({ _id: user._id }, {
$set: {
lang: 'es'
}
});
});
}
});
Migrations.add({
version: 8,
up: function siteSettingsAddIndex() {
SiteSettings._ensureIndex({ isPublic: 1 });
SiteSettings.find({ isPublic: null }).forEach((setting) => {
SiteSettings.update({ _id: setting._id }, { $set: { isPublic: true } });
});
}
});
Migrations.add({
version: 9,
up: function siteSettingsAddIndex() {
Industries._ensureIndex({ registry: 1 });
Industries._ensureIndex({ geo: '2dsphere' });
// https://www.eea.europa.eu/data-and-maps/data/member-states-reporting-art-7-under-the-european-pollutant-release-and-transfer-register-e-prtr-regulation-16
IndustryRegistries.insert({
_id: '1', name: 'E-PRTR', agency: 'EEA', region: 'EU'
});
// https://www.epa.gov/enviro/epa-frs-facilities-state-single-file-csv-download
IndustryRegistries.insert({
_id: '2', name: 'FRS', agency: 'EPA', region: 'US'
});
}
});
Migrations.add({
version: 10,
up: function siteSettingsAddIndex() {
IndustryRegistries.insert({
_id: '3', name: 'NPRI', agency: 'GCODP', region: 'Canada'
});
IndustryRegistries.insert({
_id: '4', name: 'NPI', agency: 'DEE', region: 'Australia'
});
}
});
Migrations.add({
version: 11,
up: function noAnonComments() {
Comments.getCollection().remove({ isAnonymous: true });
}
});
Migrations.add({
version: 12,
up: function setTelegramUsersBotId() {
Meteor.users.update({ telegramChatId: { $ne: null }, telegramBot: null }, {
$set: {
telegramBot: 'es'
}
}, { multi: true });
}
});
Migrations.add({
version: 13,
up: function removeTelegramBotFromUsersId() {
Meteor.users.update({}, {
$unset: {
telegramBot: ''
}
}, { multi: true });
}
});
Migrations.add({
version: 14,
up: function setTelegramUsersBotId() {
UserSubsToFiresCollection.update({ chatId: { $ne: null }, telegramBot: null }, {
$set: {
telegramBot: 'es'
}
}, { multi: true });
}
});
Migrations.add({
version: 15,
up: function moveToFalsePositivesUppercase() {
/* const falsepositiveslower = new Mongo.Collection('falsepositives', { idGeneration: 'MONGO' });
* falsepositiveslower.find({}).forEach((falseDoc) => {
* FalsePositives.insert(falseDoc);
* });*/
// TODO remove falsepositives lowercase collection
}
});
Migrations.add({
version: 16,
up: function moveToFalsePositivesUppercaseWithUser() {
const falsepositiveslower = new Mongo.Collection('falsepositives', { idGeneration: 'MONGO' });
const now = new Date();
falsepositiveslower.find({}).forEach((falseDoc) => {
const user = Meteor.users.findOne({ telegramChatId: falseDoc.chatId });
if (user) {
falseDoc.owner= user._id;
}
falseDoc.type = 'industry';
falseDoc.createdAt = now;
falseDoc.updatedAt = now;
FalsePositives.insert(falseDoc);
});
}
});
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 // Set createdAt in users & subs
Migrations.migrateTo('latest'); Migrations.migrateTo('latest');
// Migrations.migrateTo('14,rerun'); // Migrations.migrateTo('5,rerun');
}); });

View file

@ -1,19 +1,111 @@
/* eslint-disable import/no-absolute-path */ /* eslint-disable import/no-absolute-path */
import i18n from 'i18next';
import { Meteor } from 'meteor/meteor'; import { Meteor } from 'meteor/meteor';
import moment from 'moment';
import { dateLongFormat } from '/imports/api/Common/dates';
import Notifications from '/imports/api/Notifications/Notifications'; import Notifications from '/imports/api/Notifications/Notifications';
import processNotif from '/imports/modules/server/notificationsProcess.js'; // import sendMail from '/imports/startup/server/email';
import { isMailServerMaster } from '/imports/startup/server/email'; import sendEmail from '/imports/modules/server/send-email';
// import { hr } from '/imports/startup/server/email';
import getOAuthProfile from '/imports/modules/get-oauth-profile';
import image from 'google-maps-image-api-url';
import { trim } from '/imports/ui/components/NotificationsObserver/util.js';
Meteor.startup(() => { Meteor.startup(() => {
if (isMailServerMaster) { // https://stackoverflow.com/questions/1199352/smart-way-to-shorten-long-strings-with-javascript
Notifications.find().observe({ function truncate(n, useWordBoundary) {
added: function notifAdded(notif) { if (this.length <= n) {
processNotif(notif); return this;
}, }
changed: function notifChanged(updatedNotif) { // , oldNotif) { const subString = this.substr(0, n - 1);
processNotif(updatedNotif); return `${useWordBoundary ?
} subString.substr(0, subString.lastIndexOf(' ')) :
subString}...`;
}
// 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 `<img src="${imgUrl(lat, lng)}" width="640" height="480"/>`;
} */
function process(notif) {
if (notif.type === 'web' && !notif.emailNotified) {
const user = Meteor.users.findOne({ _id: notif.userId });
const hasPassword = user.services && user.services.password && user.services.password.bcrypt;
// console.log(`Has password: ${hasPassword}`);
const OAuthProfile = getOAuthProfile({
password: hasPassword,
profile: user.profile
}, user);
const firstName = OAuthProfile ? OAuthProfile.name.first : user.profile.name.first;
const emailAddress = OAuthProfile ? OAuthProfile.email :
user && user.emails[0] && user.emails[0].verified ? user.emails[0].address : null;
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 = `<a href="${fireUrl}">${i18n.t('Más información sobre este fuego')}</a>`;
// 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: truncate.apply(message, [50, true]),
// text: `${message}\n\n${fireTextUrl}\n\n`,
// template: '<body><h2>{{appName}}</h2>{{{html}}}</body>',
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);
}
});
}); });

View file

@ -1,46 +0,0 @@
/* global WebApp */
import { Meteor } from 'meteor/meteor';
import prerenderIO from 'prerender-node';
// NOTE: Fetch as Google needs the /?_escaped_fragment_=
// https://webmasters.stackexchange.com/questions/72819/fetch-as-google-doesnt-find-the-html-snapshot-for-my-ajax-content
// https://forums.meteor.com/t/any-guide-for-prerender-node-and-your-own-server-on-meteor/22054/9
const settings = Meteor.settings.PrerenderIO;
// https://github.com/dferber90/meteor-prerender/blob/master/server/prerender.js
const token = process.env.PRERENDERIO_TOKEN || (settings && settings.token);
const protocol = process.env.PRERENDERIO_PROTOCOL || (settings && settings.protocol);
// service url (support `prerenderServiceUrl` (for historical reasons) and `serviceUrl`)
let serviceUrl = settings && (settings.prerenderServiceUrl || settings.serviceUrl);
serviceUrl = process.env.PRERENDERIO_SERVICE_URL || serviceUrl;
Meteor.startup(() => {
/* TODO if (!__meteor_runtime_config__.ROOT_URL.match(/www|stg|app/)) return; */
prerenderIO.set('prerenderToken', token);
if (serviceUrl) prerenderIO.set('prerenderServiceUrl', serviceUrl);
prerenderIO.set('prerenderToken', token);
if (protocol) prerenderIO.set('protocol', protocol);
prerenderIO.set('beforeRender', (req, done) => {
if (Meteor.isDevelopment) console.log('\nprerender before', req.headers, '\n\n');
/* This method is intended to be used for caching, but could be used to save analytics or anything else you need to do for each crawler request. If you return a string from beforeRender, the middleware will serve that to the crawler (with status 200) instead of making a request to the prerender service. If you return an object the middleware will look for a status and body property (defaulting to 200 and "" respectively) and serve those instead. */
done();
});
prerenderIO.set('afterRender', (err, req, prerender_res) => {
/* This method is intended to be used for caching, but could be used to save analytics or anything else you need to do for each crawler request. This method is a noop and is called after the prerender service returns HTML. */
if (err) {
console.log('prerenderio error', err);
return;
}
if (Meteor.isDevelopment) console.log('prerender after', req.url, '\nheaders:', req.headers, '\nres complete:', prerender_res.complete, prerender_res.statusCode, prerender_res.statusMessage, '\nres headers:', prerender_res.headers, '\nres body', prerender_res);
});
WebApp.rawConnectHandlers.use(prerenderIO);
// console.log('\nprerender service:', settings);
});

View file

@ -1,18 +0,0 @@
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,
shouldCatchConsoleError: true, // default true
trackUser: true // default false
}, ravenOptions) : { log: (error) => { console.log(error); } };
console.log(`ravenLogger ${enabled ? 'enabled' : 'disabled'}`);
export default ravenLogger;

View file

@ -1 +0,0 @@
import '../../api/Rest/Rest.js';

View file

@ -1,44 +0,0 @@
/* global sitemaps Comments */
/* eslint-disable import/no-absolute-path */
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();
const out = [];
out.push({ page: '/fires', lastmod: today });
out.push({ page: '/login', lastmod: today });
out.push({ page: '/signup', lastmod: today });
out.push({ page: '/recover-password', lastmod: today });
out.push({ page: '/credits', lastmod: today });
out.push({ page: '/terms', lastmod: today });
out.push({ page: '/license', lastmod: today });
out.push({ page: '/privacy', lastmod: today });
out.push({ page: '/about', lastmod: today });
// When user has public page
/* const users = Meteor.users.find().fetch();
_.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
});
});
}
return out;
});

View file

@ -1,123 +0,0 @@
/* eslint-disable import/no-absolute-path */
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 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());
const addNoisy = (osub) => {
const sub = osub;
let lat = Math.round(sub.location.lat * 10) / 10;
let lon = Math.round(sub.location.lon * 10) / 10;
const noiseBase = Perlin.perlin2(lat, lon);
const noise = Math.abs(noiseBase / 3);
lat += noise;
lon += noise;
sub.location.lat = lat;
sub.location.lon = lon;
sub.distance += noiseBase;
return sub;
};
const noNoisy = sub => sub;
const process = (isPublic) => {
const subscribers = Subscriptions.find().fetch();
const result = calcUnion(L, subscribers, isPublic ? addNoisy : noNoisy, true);
const union = result[0];
const bounds = result[1];
const publicl = isPublic ? 'public' : 'private';
const Publicl = publicl.replace(/\b\w/g, l => l.toUpperCase());
if (typeof union === 'object') {
const unionSet = {
$set: {
name: `subs-${publicl}-union`,
value: JSON.stringify(union),
isPublic,
description: `${Publicl} subscriptions union`,
type: 'string'
}
};
const boundsSet = {
$set: {
name: `subs-${publicl}-union-bounds`,
value: JSON.stringify(bounds),
isPublic,
description: `${Publicl} subscriptions union bounds`,
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');
}
}
Subscriptions.find({ createdAt: { $gt: new Date() } }).observe({
added: function newSubAdded() { // doc) {
if (debug) console.log('Subs added so recreate union');
process(true);
process(false);
}
});
Subscriptions.find().observe({
changed: function subsChanged() { // updatedDoc, oldDoc) {
if (debug) console.log('Subs changed so recreate union');
process(true);
process(false);
},
removed: function subsRemoved() { // oldDoc) {
if (debug) console.log('Subs removed so recreate union');
process(true);
process(false);
}
});
});

View file

@ -5,8 +5,6 @@ import { Tracker } from 'meteor/tracker';
import { ReactiveVar } from 'meteor/reactive-var'; import { ReactiveVar } from 'meteor/reactive-var';
import { Button } from 'react-bootstrap'; import { Button } from 'react-bootstrap';
import { translate } from 'react-i18next'; import { translate } from 'react-i18next';
import { Bert } from 'meteor/themeteorchef:bert';
import './CenterInMyPosition.scss'; import './CenterInMyPosition.scss';
class CenterInMyPosition extends React.Component { class CenterInMyPosition extends React.Component {
@ -16,6 +14,7 @@ class CenterInMyPosition extends React.Component {
// only with SSL: // only with SSL:
// https://developer.mozilla.org/en-US/docs/Web/API/Geolocation/getCurrentPosition // https://developer.mozilla.org/en-US/docs/Web/API/Geolocation/getCurrentPosition
console.log('Center');
// https://stackoverflow.com/questions/31608579/somethings-wrong-with-my-meteor-geolocation-functions // https://stackoverflow.com/questions/31608579/somethings-wrong-with-my-meteor-geolocation-functions
const userGeoLocation = new ReactiveVar(null); const userGeoLocation = new ReactiveVar(null);
const self = this; const self = this;
@ -28,17 +27,8 @@ class CenterInMyPosition extends React.Component {
zoom: 11 zoom: 11
}; };
self.props.onClick(viewport); self.props.onClick(viewport);
computation.stop(); // console.log(viewport);
} // self.onViewportChanged(viewport);
// https://developer.mozilla.org/en-US/docs/Web/API/PositionError)
const error = Geolocation.error();
if (error) {
const cod = error.code;
// window.alert(cod);
if (cod === 1) Bert.alert(self.props.t('geo-not-perms-error'), 'danger');
else if (cod === 2) Bert.alert(self.props.t('geo-not-avail-error'), 'danger');
else if (cod === 3) Bert.alert(self.props.t('geo-not-timeout-error'), 'danger');
else Bert.alert(error.message, 'danger');
computation.stop(); computation.stop();
} }
}); });
@ -48,8 +38,8 @@ class CenterInMyPosition extends React.Component {
const { onlyIcon, t } = this.props; const { onlyIcon, t } = this.props;
const msg = t('Centrar en tu ubicación'); const msg = t('Centrar en tu ubicación');
return ( return (
<Button bsStyle="default" title={msg} onClick={() => this.onClick()}> <Button bsStyle="default" onClick={() => this.onClick()}>
<i className="fa fa-crosshairs" />{!onlyIcon ? ` ${msg}` : ''} <i className="icons icon-target" title={!onlyIcon ? msg : ''} />{!onlyIcon ? msg : ''}
</Button>); </Button>);
} }
} }

View file

@ -54,7 +54,7 @@ class DistanceSlider extends React.Component {
render() { render() {
return ( return (
<div className="dist-slider"> <div className="dist-slider">
<p><Trans parent="span">¿A que distancia a la redonda quieres recibir notificaciones de fuegos?</Trans></p> <p><Trans parent="span">¿A que distancia a la redonda quieres recibir notificaciones?</Trans></p>
<Slider <Slider
min={1} min={1}
max={105} max={105}

View file

@ -4,7 +4,9 @@
import React, { Component } from 'react'; import React, { Component } from 'react';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import { translate } from 'react-i18next';
import { Row, Col } from 'react-bootstrap'; import { Row, Col } from 'react-bootstrap';
import { withTracker } from 'meteor/react-meteor-data';
import ravenLogger from '/imports/startup/client/ravenLogger'; import ravenLogger from '/imports/startup/client/ravenLogger';
import './ErrorBoundary.scss'; import './ErrorBoundary.scss';
@ -12,6 +14,7 @@ import './ErrorBoundary.scss';
class ErrorBoundary extends Component { class ErrorBoundary extends Component {
constructor(props) { constructor(props) {
super(props); super(props);
this.t = props.t;
this.state = { hasError: false }; this.state = { hasError: false };
} }
@ -19,48 +22,38 @@ class ErrorBoundary extends Component {
// Display fallback UI // Display fallback UI
this.setState({ hasError: true }); this.setState({ hasError: true });
// You can also log the error to an error reporting service // You can also log the error to an error reporting service
// console.log(`typeof error ${typeof error}`); ravenLogger.log(error);
// console.log(`typeof info ${typeof info}`); ravenLogger.log(info);
ravenLogger.log(error, info);
// ravenLogger.log(info);
} }
render() { render() {
const {
appName, title, subTitle, children
} = this.props;
if (this.state.hasError) { if (this.state.hasError) {
// You can render any custom fallback UI // You can render any custom fallback UI
return ( return (
<div className="error-boundary"> <div className="error-boundary">
<Row className="align-items-center justify-content-center"> <Row className="align-items-center justify-content-center">
<Col xs={12} sm={6} md={5} lg={4}> <Col xs={12} sm={6} md={5} lg={4}>
<h1 className="page-header">{appName}</h1> <h1 className="page-header">{this.t('AppNameFull')}</h1>
<h4 className="page-header">{title}</h4> <h4 className="page-header">{this.t('general-error-title')}</h4>
<p>{subTitle}</p> <Row>
{this.t('general-error-description')}
</Row>
</Col> </Col>
</Row> </Row>
</div> </div>
); );
} }
return children; return this.props.children;
} }
} }
ErrorBoundary.propTypes = { ErrorBoundary.propTypes = {
appName: PropTypes.string, t: PropTypes.func.isRequired
title: PropTypes.string,
subTitle: PropTypes.string,
children: PropTypes.oneOfType([
PropTypes.arrayOf(PropTypes.node),
PropTypes.node
]).isRequired
}; };
ErrorBoundary.defaultProps = { ErrorBoundary.defaultProps = {
appName: 'All Against the Fire!',
title: 'Upppps: Something has gone wrong',
subTitle: 'We are investigating the problem, try again in a while'
}; };
export default ErrorBoundary; export default translate([], { wait: true })(withTracker(props => ({
// props.something
}))(ErrorBoundary));

View file

@ -2,9 +2,9 @@
background-image: url('/error-background.png'); background-image: url('/error-background.png');
height: 100%; height: 100%;
min-height: 55vh; min-height: 55vh;
margin: 40px 10px 10px 10px; margin-top: 40px;
} }
.error-boundary > div > div > h4 { .error-boundary > div > div > h4 {
line-height: 1.3em; line-height: 1.5em;
} }

View file

@ -1,141 +0,0 @@
/* eslint-disable react/jsx-indent-props */
/* eslint-disable import/no-absolute-path */
/* eslint-disable import/no-absolute-path */
import React, { Component } from 'react';
import PropTypes from 'prop-types';
import { Meteor } from 'meteor/meteor';
import { translate } from 'react-i18next';
import { FormGroup, Button, FormControl } from 'react-bootstrap';
import { Bert } from 'meteor/themeteorchef:bert';
import { withTracker } from 'meteor/react-meteor-data';
import { isHome } from '/imports/ui/components/Utils/location';
import { testId } from '/imports/ui/components/Utils/TestUtils';
import validate from '/imports/modules/validate';
import './Feedback.scss';
class Feedback extends Component {
constructor(props) {
super(props);
this.handleSubmit = this.handleSubmit.bind(this);
this.onTabClick = this.onTabClick.bind(this);
}
componentDidMount() {
const component = this;
const { t } = this.props;
validate(component.form, {
rules: {
email: {
required: true,
email: true
},
feedbackText: {
required: true
}
},
messages: {
feedbackText: {
required: t('Por favor, escribe aquí tu feedback...')
},
email: {
required: t('Tu correo'),
email: t('¿Es correcto este correo?')
}
},
submitHandler() { component.handleSubmit(); }
});
}
onTabClick() {
$('#feedback-form').toggle('slide');
}
handleSubmit() {
const email = this.email.value.trim();
const feedbackText = this.feedbackText.value.trim();
Meteor.call('send-feedback', email, feedbackText, (error) => {
if (error) {
Bert.alert(error.reason, 'danger');
} else {
this.form.reset();
Bert.alert('Feedback recibido, gracias...', 'success');
this.onTabClick();
}
});
}
render() {
// console.log(`Render Feedback because isHome ${this.props.isHome}, email: '${this.props.emailAddress}'`);
const disabled = this.props.emailVerified && this.props.emailAddress;
const { t } = this.props;
return (
<div>
{ !this.props.isHome &&
<div id="feedback">
<div id="feedback-form" ref={formdiv => (this.formdiv = formdiv)} style={{ display: 'none' }} className="card">
<form
ref={form => (this.form = form)}
className="form card-body"
onSubmit={event => event.preventDefault()}
>
<FormGroup controlId="formEmail">
<input
id={testId('emailInput')}
onChange={this.handleChange}
name="email"
className="form-control"
ref={email => (this.email = email)}
placeholder={t('Tu correo')}
key={disabled ? 'disabledEmail' : 'enabledEmail'}
disabled={disabled}
defaultValue={disabled ? this.props.emailAddress : ''}
type="email"
/>
</FormGroup>
<FormGroup
controlId="formFeedback"
>
<textarea
id={testId('feedbackTextarea')}
className="form-control"
name="feedbackText"
ref={feedbackText => (this.feedbackText = feedbackText)}
placeholder={t('Por favor, escribe aquí tu feedback...')}
rows="6"
/>
<FormControl.Feedback />
</FormGroup>
<Button type="submit" bsStyle="success" className="float-right" id={testId('sendFeedbackBtn')} >
{t('Enviar')}
</Button>
</form>
</div>
<div
id="feedback-tab"
onClick={(event) => { this.onTabClick(event); }}
>
{t('Feedback')}
</div>
</div>
}
</div>
);
}
}
Feedback.propTypes = {
t: PropTypes.func.isRequired,
emailAddress: PropTypes.string,
emailVerified: PropTypes.bool.isRequired,
isHome: PropTypes.bool.isRequired
};
export default translate()(withTracker(props => ({
emailAddress: props.emailAddress,
emailVerified: props.emailVerified,
isHome: isHome()
}))(Feedback));

View file

@ -1,71 +0,0 @@
/* From: https://github.com/spektom/bootstrap-feedback-form */
@import '../../stylesheets/mixins';
#feedback {
position: fixed;
z-index: 1000;
left: 0;
bottom: 0;
height: 350px;
margin-left: -3px;
margin-bottom: 10px;
font-size: 12px;
}
@include breakpoint(tablet) {
#feedback {
height: 400px;
}
}
#feedback-form {
float: left;
width: 400px;
height: 100%;
z-index: 1000;
padding-left: 5px;
padding-right: 10px;
background-clip: padding-box;
border: 1px solid rgba(0,0,0,.2);
border-radius: 0px;
box-shadow: 0 5px 10px rgba(0,0,0,.2);
}
@include breakpoint(mobile) {
#feedback-form {
width: 270px;
}
}
#feedback-tab {
float: right;
color: #fff;
font-size: 12px;
cursor: pointer;
text-align: center;
width: 80px;
height: 24px;
background-color: rgba(0,0,0,0.5);
margin-top: 60px;
margin-left: -28px;
padding-top: 3px;
-webkit-border-top-left-radius: 3px;
-webkit-border-top-right-radius: 3px;
-moz-border-radius-topleft: 3px;
-moz-border-radius-topright: 3px;
border-top-left-radius: 3px;
border-top-right-radius: 3px;
-webkit-transform: rotate(90deg);
-moz-transform: rotate(90deg);
-ms-transform: rotate(90deg);
-o-transform: rotate(90deg);
transform: rotate(90deg);
}
#feedback-tab:hover {
background-color: rgba(0,0,0,0.4);
}
#feedback-form textarea {
resize: none;
}

View file

@ -23,16 +23,10 @@ const Footer = (props) => {
<p className="pull-left"><span className="reverse">&copy;</span><span className="d-none d-md-inline"> Copyleft</span> {copyrightYear()} <a href="https://comunes.org/"><span className="d-none d-md-inline">{t('OrgName')}</span><span className="d-inline d-md-none">{t('OrgName')}</span></a></p> <p className="pull-left"><span className="reverse">&copy;</span><span className="d-none d-md-inline"> Copyleft</span> {copyrightYear()} <a href="https://comunes.org/"><span className="d-none d-md-inline">{t('OrgName')}</span><span className="d-inline d-md-none">{t('OrgName')}</span></a></p>
<ul className="pull-right"> <ul className="pull-right">
<li>
<Link id={testId('about')} to="/about">
<span className="d-none d-lg-inline">{t('Sobre nosotr@s')}</span>
<span className="d-lg-none">{t('Nosotr@s')}</span>
</Link>
</li>
<li> <li>
<Link id={testId('tos')} to="/terms"> <Link id={testId('tos')} to="/terms">
<span className="d-none d-lg-inline">{t('Términos de Servicio')}</span> <span className="d-none d-lg-inline">{t('Términos de Servicio')}</span>
<span className="d-lg-none" /> <span className="d-lg-none">{t('Términos')}</span>
</Link> </Link>
</li> </li>
<li> <li>

View file

@ -63,10 +63,7 @@ body {
} }
@include breakpoint(tablet) { @include breakpoint(tablet) {
.Footer ul li:first-child, .Footer ul li:first-child, .Footer ul li:nth-child(2) {
.Footer ul li:nth-child(2),
.Footer ul li:nth-child(3),
.Footer ul li:nth-child(4){
margin-right: 15px; margin-right: 15px;
} }
} }
@ -82,14 +79,4 @@ body {
.Footer, .Footer p { .Footer, .Footer p {
font-size: 13px; font-size: 13px;
} }
.Footer {
line-height: 1.8em;
}
.Footer ul li:first-child,
.Footer ul li:nth-child(2),
.Footer ul li:nth-child(3) {
margin-right: 7px;
}
} }

View file

@ -5,10 +5,6 @@ import React, { Component } from 'react';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import { withTracker } from 'meteor/react-meteor-data'; import { withTracker } from 'meteor/react-meteor-data';
import Chronos from '/imports/ui/components/Chronos/Chronos'; import Chronos from '/imports/ui/components/Chronos/Chronos';
import { translate } from 'react-i18next';
import { dateLongFormat } from '/imports/api/Common/dates';
import './FromNow.scss';
class FromNow extends Component { class FromNow extends Component {
constructor(props) { constructor(props) {
@ -31,39 +27,26 @@ class FromNow extends Component {
return !(nextState.when === this.state.when); return !(nextState.when === this.state.when);
} }
thatIs() {
return this.props.t('esDecirTalDia', { date: dateLongFormat(this.props.whenDate) });
}
render() { render() {
console.log(`Render from now ${this.state.when}`); console.log(`Render from now ${this.state.when}`);
return ( return (
<span data-toggle="tooltip" className="from-now" title={this.thatIs()}>{this.state.when}</span> <span>{this.state.when}</span>
); );
} }
} }
FromNow.propTypes = { FromNow.propTypes = {
t: PropTypes.func.isRequired, when: PropTypes.string
when: PropTypes.string,
whenDate: PropTypes.instanceOf(Date)
}; };
FromNow.defaultProps = { FromNow.defaultProps = {
}; };
/* const FromNowContainer = withTracker((props) => {
* const FromNowContainer = withTracker((props) => {
*
* })(FromNow);
*
* export default FromNowContainer;
* */
export default translate([], { wait: true })(withTracker((props) => {
const whenDate = props.when; const whenDate = props.when;
return { return {
when: whenDate ? Chronos.moment(whenDate).fromNow() : null, when: whenDate ? Chronos.moment(whenDate).fromNow() : null
whenDate
}; };
})(FromNow)); })(FromNow);
export default FromNowContainer;

View file

@ -1,4 +0,0 @@
.from-now {
border-bottom: 1px gray dashed;
cursor: pointer;
}

View file

@ -1,5 +1,12 @@
import { Meteor } from 'meteor/meteor';
import createHistory from 'history/createBrowserHistory'; import createHistory from 'history/createBrowserHistory';
const history = createHistory(); const history = createHistory();
history.listen((location) => { // , action ) => {
// console.log(location.pathname);
// console.log(action); // PUSH, etc
Meteor.Piwik.trackPage(location.pathname);
});
export default history; export default history;

View file

@ -1,25 +0,0 @@
/* eslint-disable react/jsx-indent */
import React from 'react';
import PropTypes from 'prop-types';
import { Meteor } from 'meteor/meteor';
import { Line } from 'react-progress-bar.js';
import './LoadingBar.scss';
// Check: https://github.com/kimmobrunfeldt/react-progressbar.js/pull/26
const LoadingBar = ({ progress }) => (
<div className="loading-bar">
<Line
progress={Meteor.status().status !== 'connected' ? Meteor.status().retryCount / 10 : progress}
options={{ strokeWidth: 2, color: '#5A7636' }}
initialAnimate
/>
</div>
);
export default LoadingBar;
LoadingBar.propTypes = {
progress: PropTypes.number.isRequired
};

View file

@ -1,5 +0,0 @@
.loading-bar {
height: 3px;
overflow: hidden;
margin-top: -3px;
}

Some files were not shown because too many files have changed in this diff Show more