Merge remote-tracking branch 'origin/tcef-master' into tcef-master
This commit is contained in:
commit
6f683ebaa2
39 changed files with 2076 additions and 873 deletions
3
.gitignore
vendored
3
.gitignore
vendored
|
|
@ -9,3 +9,6 @@ npm-debug.log
|
||||||
.deploy/
|
.deploy/
|
||||||
public/locales/undefined/
|
public/locales/undefined/
|
||||||
.deploy-staging
|
.deploy-staging
|
||||||
|
.screenshots
|
||||||
|
.vscode
|
||||||
|
output.json
|
||||||
|
|
|
||||||
3
.meteorignore
Normal file
3
.meteorignore
Normal file
|
|
@ -0,0 +1,3 @@
|
||||||
|
# Don't reload meteor server when we modify or do test
|
||||||
|
cucumber
|
||||||
|
output.json
|
||||||
|
|
@ -8,7 +8,12 @@ This is web service that notifies about fires detected in an area of your intere
|
||||||
|
|
||||||
Prerrequisites: a running meteor (we share the `fuegos` database with the telegram bot).
|
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
|
||||||
|
|
||||||
|
|
@ -24,7 +29,7 @@ More platforms and services in the future...
|
||||||
|
|
||||||
We do tests via:
|
We do tests via:
|
||||||
```
|
```
|
||||||
TEST_WATCH=1 MONGO_URL=mongodb://localhost:27017/fuegostest meteor --settings settings-development.json test --driver-package meteortesting:mocha --port 3010
|
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
|
||||||
|
|
||||||
# and
|
# and
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,3 @@
|
||||||
@watch
|
|
||||||
Feature: Allow users to give feedback
|
Feature: Allow users to give feedback
|
||||||
|
|
||||||
As a user (authenticated or not)
|
As a user (authenticated or not)
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,8 @@
|
||||||
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,8 +13,9 @@ Feature: Test all secundary pages
|
||||||
| about | About |
|
| 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
|
Scenario: Check that all secondary pages work well when not logged
|
||||||
Given a list of page urls and contents
|
Given I logout if logged
|
||||||
|
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 |
|
| fires | Fires | true |
|
||||||
|
|
@ -21,12 +25,26 @@ Feature: Test all secundary pages
|
||||||
| 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
|
# 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
|
||||||
|
|
||||||
|
@watch
|
||||||
Scenario: Check that other non visible pages work well
|
Scenario: Check that other non visible pages work well
|
||||||
Given a list of non visible pages ids and contents
|
Given I logout if logged
|
||||||
| status | Status |
|
And a list of non visible pages ids and contents
|
||||||
| error | Upps |
|
| error | Upppps | false |
|
||||||
# FIXME: find a way to monitor json pages
|
# FIXME: find a way to monitor json pages
|
||||||
# | api/v1/status/last-fire-detected | updateAt |
|
# | api/v1/status/last-fire-detected | updateAt |
|
||||||
# | api/v1/status/last-fire-check | description |
|
# | api/v1/status/last-fire-check | description |
|
||||||
|
|
|
||||||
|
|
@ -24,6 +24,7 @@ module.exports = function doSteps() {
|
||||||
client.waitForVisible('#feedback-tab', 10000);
|
client.waitForVisible('#feedback-tab', 10000);
|
||||||
client.click('#feedback-tab');
|
client.click('#feedback-tab');
|
||||||
client.waitForVisible('#feedback-form', 10000);
|
client.waitForVisible('#feedback-form', 10000);
|
||||||
|
client.waitForEnabled('input[name="email"]');
|
||||||
client.setValue('input[name="email"]', randomEmail());
|
client.setValue('input[name="email"]', randomEmail());
|
||||||
client.setValue('#feedbackTextarea', randomText(500));
|
client.setValue('#feedbackTextarea', randomText(500));
|
||||||
client.click('#sendFeedbackBtn');
|
client.click('#sendFeedbackBtn');
|
||||||
|
|
|
||||||
|
|
@ -2,17 +2,50 @@
|
||||||
|
|
||||||
(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;
|
||||||
|
|
||||||
/* Not used
|
// https://github.com/webdriverio/webdriverio/issues/1145
|
||||||
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 });
|
client.windowHandleSize({ width: 1500, height: 1000 });
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -10,7 +10,7 @@ let pass;
|
||||||
let email;
|
let email;
|
||||||
|
|
||||||
const setUserValues = (isEdit) => {
|
const setUserValues = (isEdit) => {
|
||||||
client.waitForVisible('input[name="firstName"]', 5000);
|
client.waitForVisible('input[name="firstName"]', 10000);
|
||||||
firstName = randomUsername();
|
firstName = randomUsername();
|
||||||
lastName = randomUsername();
|
lastName = randomUsername();
|
||||||
email = randomEmail();
|
email = randomEmail();
|
||||||
|
|
@ -32,31 +32,54 @@ module.exports = function doSteps(notos) {
|
||||||
goHome(client);
|
goHome(client);
|
||||||
});
|
});
|
||||||
|
|
||||||
function register() {
|
function waitNoBert() {
|
||||||
|
client.pause(2000);
|
||||||
|
if (client.isVisible('.bert-container')) {
|
||||||
|
// console.log('Removing bert alert');
|
||||||
|
client.click('.bert-container');
|
||||||
|
client.waitForVisible('.bert-container', 10000, true);
|
||||||
|
} else {
|
||||||
|
// console.log('No bert alert');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function logoutIfLogged() {
|
||||||
if (client.isVisible('#logout')) {
|
if (client.isVisible('#logout')) {
|
||||||
client.click('#logout');
|
client.click('#logout');
|
||||||
}
|
}
|
||||||
client.waitForVisible('#signup', 5000);
|
client.waitForVisible('#signup', 10000);
|
||||||
|
}
|
||||||
|
|
||||||
|
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.waitForText('#profile', firstName);
|
client.waitUntilText('#profile', firstName);
|
||||||
client.waitForText('#profile', lastName);
|
client.waitUntilText('#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$/, () => {
|
||||||
|
|
@ -64,30 +87,24 @@ 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$/, () => {
|
||||||
if (client.isVisible('.bert-alert')) {
|
waitNoBert();
|
||||||
client.waitUntil(() => client.isVisible('.bert-alert') === false, 10000);
|
client.waitForClickable('#logout', 10000);
|
||||||
}
|
// client.waitForVisible('#logout', 10000);
|
||||||
client.waitForVisible('#logout', 10000);
|
|
||||||
client.click('#logout');
|
client.click('#logout');
|
||||||
client.waitForVisible('#login', 10000);
|
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');
|
||||||
closeAlert();
|
waitNoBert();
|
||||||
}
|
}
|
||||||
|
|
||||||
this.When(/^I enter my email and password$/, () => {
|
this.When(/^I enter my email and password$/, () => {
|
||||||
|
|
@ -95,11 +112,13 @@ module.exports = function doSteps(notos) {
|
||||||
});
|
});
|
||||||
|
|
||||||
this.Then(/^I can edit my profile$/, () => {
|
this.Then(/^I can edit my profile$/, () => {
|
||||||
client.waitForVisible('#profile', 5000);
|
waitNoBert();
|
||||||
|
client.waitForClickable('#profile', 10000);
|
||||||
client.click('#profile');
|
client.click('#profile');
|
||||||
|
client.pause(2000);
|
||||||
setUserValues(true);
|
setUserValues(true);
|
||||||
client.click('#profileSubmit');
|
client.click('#profileSubmit');
|
||||||
closeAlert();
|
waitNoBert();
|
||||||
checkName();
|
checkName();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
@ -117,7 +136,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);
|
||||||
closeAlert();
|
waitNoBert();
|
||||||
});
|
});
|
||||||
|
|
||||||
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$/, () => {
|
||||||
|
|
|
||||||
|
|
@ -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.waitForText('#react-root', content);
|
client.waitUntilText('#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.waitForText('#react-root', content);
|
client.waitUntilText('#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,10 +41,7 @@ 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) {
|
||||||
client.url(`${process.env.ROOT_URL}/${pages[i][0]}`);
|
processPageUrl(i);
|
||||||
const content = pages[i][1];
|
|
||||||
client.waitForVisible('#react-root', 10000);
|
|
||||||
client.waitForText('#react-root', content);
|
|
||||||
}
|
}
|
||||||
callback();
|
callback();
|
||||||
});
|
});
|
||||||
|
|
@ -77,7 +74,7 @@ module.exports = function () {
|
||||||
// Write code here that turns the phrase above into concrete actions
|
// Write code here that turns the phrase above into concrete actions
|
||||||
for (let i = 0; i < pages.length; i += 1) {
|
for (let i = 0; i < pages.length; i += 1) {
|
||||||
client.url(`${process.env.ROOT_URL}/${pages[i][0]}?_escaped_fragment_=`);
|
client.url(`${process.env.ROOT_URL}/${pages[i][0]}?_escaped_fragment_=`);
|
||||||
client.waitForText('#react-root', pages[i][1]);
|
client.waitUntilText('#react-root', pages[i][1]);
|
||||||
}
|
}
|
||||||
callback();
|
callback();
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -247,7 +247,7 @@
|
||||||
inkscape:pageopacity="0.0"
|
inkscape:pageopacity="0.0"
|
||||||
inkscape:pageshadow="2"
|
inkscape:pageshadow="2"
|
||||||
inkscape:zoom="0.23559314"
|
inkscape:zoom="0.23559314"
|
||||||
inkscape:cx="-1825.0924"
|
inkscape:cx="-2890.4884"
|
||||||
inkscape:cy="-486.23766"
|
inkscape:cy="-486.23766"
|
||||||
inkscape:document-units="mm"
|
inkscape:document-units="mm"
|
||||||
inkscape:current-layer="layer1"
|
inkscape:current-layer="layer1"
|
||||||
|
|
@ -621,5 +621,39 @@
|
||||||
y="0"
|
y="0"
|
||||||
x="0" />
|
x="0" />
|
||||||
</g>
|
</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: 48 KiB After Width: | Height: | Size: 51 KiB |
|
|
@ -6,6 +6,29 @@ 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);
|
||||||
|
|
@ -17,27 +40,9 @@ 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) {
|
||||||
const fireType = fire.type;
|
upsertFalsePositive(keyType, owner, fire);
|
||||||
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');
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -27,6 +27,13 @@ export const firesUnion = (fires) => {
|
||||||
return union;
|
return union;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const zoneToUnion = (lat, lon, distance) => {
|
||||||
|
const group = new L.FeatureGroup();
|
||||||
|
const remap = [{ location: { lat, lon }, distance }];
|
||||||
|
const union = calcUnion(remap, group, sub => sub);
|
||||||
|
return union;
|
||||||
|
};
|
||||||
|
|
||||||
export const whichAreFalsePositives = (collection, union) => {
|
export const whichAreFalsePositives = (collection, union) => {
|
||||||
const result = collection.find({
|
const result = collection.find({
|
||||||
geo: {
|
geo: {
|
||||||
|
|
|
||||||
|
|
@ -139,35 +139,40 @@ function logUrl(fireEnc, params) {
|
||||||
ravenLogger.log(message);
|
ravenLogger.log(message);
|
||||||
}
|
}
|
||||||
|
|
||||||
Meteor.publish('fireFromHash', function fireFromHash(fireEnc, params) {
|
export function fireFromHash(fireEnc, params) {
|
||||||
|
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('Undefined unsealed');
|
||||||
|
}
|
||||||
|
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(fireEnc, String);
|
||||||
check(params, Object);
|
check(params, Object);
|
||||||
try {
|
try {
|
||||||
// console.log(fireEnc);
|
return fireFromHash(fireEnc, params);
|
||||||
// const unsealed = Promise.await(urlEnc.decrypt(fireEnc));
|
|
||||||
const unsealed = Promise.await(unsealW(fireEnc));
|
|
||||||
if (unsealed === undefined) {
|
|
||||||
logUrl(fireEnc, params);
|
|
||||||
// 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);
|
logUrl(fireEnc, params);
|
||||||
|
// https://guide.meteor.com/data-loading.html
|
||||||
return this.ready();
|
return this.ready();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -2,6 +2,7 @@
|
||||||
/* 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';
|
||||||
|
|
@ -22,11 +23,12 @@ 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,
|
||||||
webNotified: { type: Boolean, optional: true },
|
notified: { type: Boolean, optional: true },
|
||||||
webNotifiedAt: { type: Date, optional: true },
|
notifiedAt: { 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,
|
||||||
|
|
|
||||||
|
|
@ -8,7 +8,7 @@ Meteor.methods({
|
||||||
check(notifId, Meteor.Collection.ObjectID);
|
check(notifId, Meteor.Collection.ObjectID);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
Notifications.update(notifId, { $set: { webNotified: true, webNotifiedAt: new Date() } });
|
Notifications.update(notifId, { $set: { notified: true, notifiedAt: new Date() } });
|
||||||
return notifId;
|
return notifId;
|
||||||
} catch (exception) {
|
} catch (exception) {
|
||||||
throw new Meteor.Error('500', exception);
|
throw new Meteor.Error('500', exception);
|
||||||
|
|
|
||||||
|
|
@ -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', webNotified: null });
|
const notif = Notifications.find({ userId: this.userId, type: 'web', notified: null });
|
||||||
// console.log(`Notifications for user ${this.userId}: ${notif.count()}`);
|
// console.log(`Notifications for user ${this.userId}: ${notif.count()}`);
|
||||||
return notif;
|
return notif;
|
||||||
});
|
});
|
||||||
|
|
|
||||||
453
imports/api/Rest/Rest.js
Normal file
453
imports/api/Rest/Rest.js
Normal file
|
|
@ -0,0 +1,453 @@
|
||||||
|
/* 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,
|
||||||
|
scan: 1
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
const result = { total: fires.count(), real: countRealFires(fires) };
|
||||||
|
|
||||||
|
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');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
@ -1,6 +1,7 @@
|
||||||
/* 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';
|
||||||
|
|
@ -42,6 +43,7 @@ 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,
|
||||||
|
|
|
||||||
|
|
@ -10,30 +10,49 @@ 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 subscriptionsInsert(doc) {
|
'subscriptions.insert': function subscriptionInsertMethod(doc) {
|
||||||
check(doc, {
|
check(doc, {
|
||||||
location: Match.ObjectIncluding({ lat: Number, lon: Number }),
|
location: Match.ObjectIncluding({ lat: Number, lon: Number }),
|
||||||
distance: Number
|
distance: Number
|
||||||
});
|
});
|
||||||
const type = 'web';
|
return subscriptionsInsert(doc, this.userId, 'web');
|
||||||
const newDoc = {
|
|
||||||
owner: this.userId,
|
|
||||||
type,
|
|
||||||
geo: geo(doc),
|
|
||||||
...doc
|
|
||||||
};
|
|
||||||
// console.log(newDoc);
|
|
||||||
const already = Subscriptions.findOne(newDoc);
|
|
||||||
if (already) {
|
|
||||||
throw new Meteor.Error('on-already-subscribed', 'The user is already subscribed to this area');
|
|
||||||
}
|
|
||||||
try {
|
|
||||||
return Subscriptions.insert(newDoc);
|
|
||||||
} catch (exception) {
|
|
||||||
// console.error(exception);
|
|
||||||
throw new Meteor.Error('500', exception);
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
'subscriptions.update': function subscriptionsUpdate(doc) {
|
'subscriptions.update': function subscriptionsUpdate(doc) {
|
||||||
check(doc, {
|
check(doc, {
|
||||||
|
|
@ -52,15 +71,9 @@ Meteor.methods({
|
||||||
throw new Meteor.Error('500', exception);
|
throw new Meteor.Error('500', exception);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
'subscriptions.remove': function subscriptionsRemove(subscriptionId) {
|
'subscriptions.remove': function subscriptionsRemoveMethod(subscriptionId) {
|
||||||
check(subscriptionId, Meteor.Collection.ObjectID);
|
check(subscriptionId, Meteor.Collection.ObjectID);
|
||||||
|
return subscriptionsRemove(subscriptionId);
|
||||||
try {
|
|
||||||
return Subscriptions.remove(subscriptionId);
|
|
||||||
} catch (exception) {
|
|
||||||
console.error(exception);
|
|
||||||
throw new Meteor.Error('500', exception);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -107,6 +107,7 @@ const schemaUser = new SimpleSchema({
|
||||||
telegramUsername: { type: String, optional: true },
|
telegramUsername: { type: String, optional: true },
|
||||||
telegramFirstName: { type: String, optional: true },
|
telegramFirstName: { type: String, optional: true },
|
||||||
telegramLanguageCode: { type: String, optional: true },
|
telegramLanguageCode: { type: String, optional: true },
|
||||||
|
fireBaseToken: { type: String, optional: true },
|
||||||
createdAt: defaultCreatedAt,
|
createdAt: defaultCreatedAt,
|
||||||
updatedAt: defaultUpdateAt
|
updatedAt: defaultUpdateAt
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -2,13 +2,15 @@ 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 = true && !Meteor.isDevelopment;
|
const enabled = !!publicDSN;
|
||||||
|
|
||||||
const ravenLogger = enabled ? new RavenLogger({
|
const ravenLogger = enabled ? new RavenLogger({
|
||||||
publicDSN: Meteor.settings.public.sentryPublicDSN, // will be used on the client
|
publicDSN, // will be used on the client
|
||||||
shouldCatchConsoleError: true, // default
|
shouldCatchConsoleError: true, // default
|
||||||
trackUser: true // default
|
trackUser: true // default
|
||||||
}, ravenOptions) : { log: (error) => { console.log(error); } };
|
}, ravenOptions) : { log: (error) => { console.log(error); } };
|
||||||
|
|
||||||
|
console.log(`ravenLogger ${enabled ? 'enabled' : 'disabled'}`);
|
||||||
|
|
||||||
export default ravenLogger;
|
export default ravenLogger;
|
||||||
|
|
|
||||||
|
|
@ -9,6 +9,7 @@ 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 Industries from '/imports/api/Industries/Industries';
|
||||||
import IndustryRegistries from '/imports/api/Industries/IndustryRegistries';
|
import IndustryRegistries from '/imports/api/Industries/IndustryRegistries';
|
||||||
|
import Notifications from '/imports/api/Notifications/Notifications';
|
||||||
import { Mongo } from 'meteor/mongo';
|
import { Mongo } from 'meteor/mongo';
|
||||||
|
|
||||||
Meteor.startup(() => {
|
Meteor.startup(() => {
|
||||||
|
|
@ -227,6 +228,18 @@ Meteor.startup(() => {
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
Migrations.add({
|
||||||
|
version: 17,
|
||||||
|
up: function renameWebNotifiedField() {
|
||||||
|
Notifications.update({ webNotified: { $exists: true } }, {
|
||||||
|
$rename: { webNotifiedAt: 'notifiedAt' }
|
||||||
|
}, { upsert: false, multi: true });
|
||||||
|
Notifications.update({ webNotified: { $exists: true } }, {
|
||||||
|
$rename: { webNotified: 'notified' }
|
||||||
|
}, { upsert: false, multi: true });
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
// Set createdAt in users & subs
|
// Set createdAt in users & subs
|
||||||
Migrations.migrateTo('latest');
|
Migrations.migrateTo('latest');
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -7,16 +7,26 @@ import { dateLongFormat } from '/imports/api/Common/dates';
|
||||||
import Notifications from '/imports/api/Notifications/Notifications';
|
import Notifications from '/imports/api/Notifications/Notifications';
|
||||||
// import sendMail from '/imports/startup/server/email';
|
// import sendMail from '/imports/startup/server/email';
|
||||||
import sendEmail, { subjectTruncate } from '/imports/modules/server/send-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 { hr } from '/imports/startup/server/email';
|
||||||
import getEmailOf from '/imports/modules/get-email-of-user';
|
import getEmailOf from '/imports/modules/get-email-of-user';
|
||||||
import image from 'google-maps-image-api-url';
|
import image from 'google-maps-image-api-url';
|
||||||
|
import gcm from 'node-gcm';
|
||||||
import { trim } from '/imports/ui/components/NotificationsObserver/util.js';
|
import { trim } from '/imports/ui/components/NotificationsObserver/util.js';
|
||||||
|
|
||||||
Meteor.startup(() => {
|
Meteor.startup(() => {
|
||||||
|
let validFcmSender = true;
|
||||||
|
|
||||||
|
if (!(Meteor.settings.private.fcmApiToken && Meteor.settings.private.fcmApiToken.length > 0)) {
|
||||||
|
console.warn('Missing settings.private.fcmApiToken key, mobile notifications will not work');
|
||||||
|
validFcmSender = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
const fcmSender = new gcm.Sender(Meteor.settings.private.fcmApiToken);
|
||||||
|
|
||||||
// https://www.npmjs.com/package/google-maps-image-api-url
|
// https://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://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
|
// https://developers.google.com/maps/documentation/static-maps/intro
|
||||||
|
|
||||||
function imgUrl(lat, lng) {
|
function imgUrl(lat, lng) {
|
||||||
return image({
|
return image({
|
||||||
key: Meteor.settings.gmaps.key,
|
key: Meteor.settings.gmaps.key,
|
||||||
|
|
@ -31,12 +41,55 @@ Meteor.startup(() => {
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
function imgEl(lat, lng) {
|
function imgEl(lat, lng) {
|
||||||
return `<img src="${imgUrl(lat, lng)}" width="640" height="480"/>`;
|
return `<img src="${imgUrl(lat, lng)}" width="640" height="480"/>`;
|
||||||
} */
|
} */
|
||||||
|
|
||||||
function process(notif) {
|
function process(notif) {
|
||||||
if (notif.type === 'web' && !notif.emailNotified) {
|
if (isMailServerMaster && notif.type === 'mobile' && !notif.notified) {
|
||||||
|
const user = Meteor.users.findOne({ _id: notif.userId });
|
||||||
|
moment.locale(user.lang);
|
||||||
|
// duplicate code below
|
||||||
|
const body = `${trim(notif.content)}`;
|
||||||
|
|
||||||
|
// https://firebase.google.com/docs/cloud-messaging/concept-options
|
||||||
|
const msg = new gcm.Message();
|
||||||
|
|
||||||
|
msg.addNotification({
|
||||||
|
title: i18n.t('Alerta de fuego'),
|
||||||
|
body,
|
||||||
|
click_action: 'FLUTTER_NOTIFICATION_CLICK',
|
||||||
|
tag: notif._id, // prevent duplication of fire notifications
|
||||||
|
sound: 'default', // Indicates sound to be played. Supports only default currently.
|
||||||
|
icon: 'launch_image' // 'ic_launcher'
|
||||||
|
});
|
||||||
|
|
||||||
|
msg.addData('id', notif._id._str);
|
||||||
|
msg.addData('description', body);
|
||||||
|
msg.addData('lat', notif.geo.coordinates[1]);
|
||||||
|
msg.addData('lon', notif.geo.coordinates[0]);
|
||||||
|
msg.addData('when', notif.when);
|
||||||
|
msg.addData('subsId', notif.subsId._str);
|
||||||
|
msg.addData('sealed', notif.sealed);
|
||||||
|
|
||||||
|
const registrationTokens = [];
|
||||||
|
if (!user.fireBaseToken) {
|
||||||
|
console.warn('This mobile user doesn\'t have a firebase registration token');
|
||||||
|
} else {
|
||||||
|
registrationTokens.push(user.fireBaseToken);
|
||||||
|
// FIXME: better join users
|
||||||
|
if (validFcmSender) {
|
||||||
|
fcmSender.send(msg, { registrationTokens }, Meteor.bindEnvironment(function processResult(err, response) {
|
||||||
|
if (err) {
|
||||||
|
console.error(`FCM error: ${err}`);
|
||||||
|
} else {
|
||||||
|
// console.log(`FCM response: ${response}`);
|
||||||
|
Notifications.update(notif._id, { $set: { notified: true, notifiedAt: new Date() } });
|
||||||
|
}
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else if (notif.type === 'web' && !notif.emailNotified) {
|
||||||
const user = Meteor.users.findOne({ _id: notif.userId });
|
const user = Meteor.users.findOne({ _id: notif.userId });
|
||||||
const { firstName, emailAddress } = getEmailOf(user);
|
const { firstName, emailAddress } = getEmailOf(user);
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -2,12 +2,17 @@ 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 privateDSN = Meteor.settings.sentryPrivateDSN;
|
||||||
|
const enabled = publicDSN && privateDSN;
|
||||||
|
|
||||||
const ravenLogger = new RavenLogger({
|
const ravenLogger = enabled ? new RavenLogger({
|
||||||
publicDSN: Meteor.settings.public.sentryPublicDSN,
|
publicDSN,
|
||||||
privateDSN: Meteor.settings.sentryPrivateDSN,
|
privateDSN,
|
||||||
shouldCatchConsoleError: true, // default true
|
shouldCatchConsoleError: true, // default true
|
||||||
trackUser: true // default false
|
trackUser: true // default false
|
||||||
}, ravenOptions);
|
}, ravenOptions) : { log: (error) => { console.log(error); } };
|
||||||
|
|
||||||
|
console.log(`ravenLogger ${enabled ? 'enabled' : 'disabled'}`);
|
||||||
|
|
||||||
export default ravenLogger;
|
export default ravenLogger;
|
||||||
|
|
|
||||||
|
|
@ -1,117 +1 @@
|
||||||
/* global Restivus */
|
import '../../api/Rest/Rest.js';
|
||||||
/* eslint-disable import/no-absolute-path */
|
|
||||||
|
|
||||||
import { Meteor } from 'meteor/meteor';
|
|
||||||
import { NumberBetween } from '/imports/modules/server/other-checks';
|
|
||||||
import Fires from '/imports/api/Fires/Fires';
|
|
||||||
import { check } from 'meteor/check';
|
|
||||||
import SiteSettings from '/imports/api/SiteSettings/SiteSettings';
|
|
||||||
import ActiveFiresCollection from '/imports/api/ActiveFires/ActiveFires';
|
|
||||||
import { countRealFires } from '/imports/api/ActiveFires/server/countFires';
|
|
||||||
|
|
||||||
Meteor.startup(() => {
|
|
||||||
const uptime = new Date();
|
|
||||||
|
|
||||||
const apiV1 = new Restivus({
|
|
||||||
useDefaultAuth: true,
|
|
||||||
apiPath: 'api',
|
|
||||||
version: 'v1',
|
|
||||||
prettyJson: true
|
|
||||||
});
|
|
||||||
|
|
||||||
// Generates: POST on /api/users and GET, DELETE /api/users/:id for
|
|
||||||
// Meteor.users collection
|
|
||||||
/* apiV1.addCollection(Meteor.users, {
|
|
||||||
* excludedEndpoints: ['getAll', 'put', 'delete', 'patch', 'get'],
|
|
||||||
* routeOptions: {
|
|
||||||
* authRequired: true
|
|
||||||
* },
|
|
||||||
* endpoints: {
|
|
||||||
* post: {
|
|
||||||
* authRequired: false
|
|
||||||
* },
|
|
||||||
* delete: {
|
|
||||||
* roleRequired: 'admin'
|
|
||||||
* }
|
|
||||||
* }
|
|
||||||
* }); */
|
|
||||||
|
|
||||||
apiV1.addCollection(Fires, {
|
|
||||||
excludedEndpoints: ['put', 'post', 'patch', 'delete'],
|
|
||||||
// excludedEndpoints: ['getAll', 'put', 'post', 'patch', 'delete'],
|
|
||||||
routeOptions: {
|
|
||||||
authRequired: false
|
|
||||||
},
|
|
||||||
endpoints: {
|
|
||||||
get: {
|
|
||||||
action: function getFire() {
|
|
||||||
return Fires.findOne(new Meteor.Collection.ObjectID(this.urlParams.id));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
// Maps to: /api/v1/status/last-fire-check
|
|
||||||
apiV1.addRoute('status/last-fire-check', { authRequired: false }, {
|
|
||||||
get: function get() {
|
|
||||||
return SiteSettings.findOne({ name: 'last-fire-check' });
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
// Maps to: /api/v1/status/last-fire-detected
|
|
||||||
apiV1.addRoute('status/last-fire-detected', { authRequired: false }, {
|
|
||||||
get: function get() {
|
|
||||||
return ActiveFiresCollection.findOne({}, { sort: { when: -1 } });
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
// Maps to: /api/v1/status/last-fire-detected
|
|
||||||
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 };
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
// Maps to: /api/v1/fires-in/:lat/:lng/:km
|
|
||||||
// Ex: http://127.0.0.1:3000/api/v1/fires-in/38.736946/-9.142685/100
|
|
||||||
apiV1.addRoute('fires-in/:token/:lat/:lng/:km', { authRequired: false }, {
|
|
||||||
get: function get() {
|
|
||||||
const lat = Number(this.urlParams.lat);
|
|
||||||
const lng = Number(this.urlParams.lng);
|
|
||||||
const km = Number(this.urlParams.km);
|
|
||||||
const { token } = this.urlParams;
|
|
||||||
check(lng, NumberBetween(-180, 180));
|
|
||||||
check(lat, NumberBetween(-90, 90));
|
|
||||||
check(km, NumberBetween(0, 100));
|
|
||||||
check(token, String);
|
|
||||||
|
|
||||||
console.log(`Query for fires in ${lat}, ${lng} in ${km} km radius`);
|
|
||||||
|
|
||||||
if (token !== Meteor.settings.private.internalApiToken) {
|
|
||||||
return {};
|
|
||||||
}
|
|
||||||
|
|
||||||
const fires = ActiveFiresCollection.find({
|
|
||||||
ourid: {
|
|
||||||
$near: {
|
|
||||||
$geometry: {
|
|
||||||
type: 'Point',
|
|
||||||
coordinates: [lng, lat]
|
|
||||||
},
|
|
||||||
$maxDistance: km * 1000,
|
|
||||||
$minDistance: 0
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
return { total: fires.count(), real: countRealFires(fires) };
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
|
||||||
|
|
@ -3,7 +3,7 @@
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import { Meteor } from 'meteor/meteor';
|
import { Meteor } from 'meteor/meteor';
|
||||||
import { Line } from 'react-progressbar.js';
|
import { Line } from 'react-progress-bar.js';
|
||||||
|
|
||||||
import './LoadingBar.scss';
|
import './LoadingBar.scss';
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -12,7 +12,7 @@ import { trim } from './util.js';
|
||||||
function process(notif) {
|
function process(notif) {
|
||||||
// No already notified
|
// No already notified
|
||||||
if (Push.Permission.has()) {
|
if (Push.Permission.has()) {
|
||||||
if (!notif.webNotified) {
|
if (!notif.notified && notif.type === 'web') {
|
||||||
Push.create(i18n.t('AppName'), {
|
Push.create(i18n.t('AppName'), {
|
||||||
body: `${trim(notif.content)} (${i18n.t('fireDetected', { when: dateFromNow(notif.when) })})`,
|
body: `${trim(notif.content)} (${i18n.t('fireDetected', { when: dateFromNow(notif.when) })})`,
|
||||||
icon: '/n-fire-marker.png',
|
icon: '/n-fire-marker.png',
|
||||||
|
|
@ -47,7 +47,7 @@ Meteor.startup(() => {
|
||||||
if (Meteor.userId()) {
|
if (Meteor.userId()) {
|
||||||
Meteor.subscribe('mynotifications');
|
Meteor.subscribe('mynotifications');
|
||||||
// Check for notifications not processed at startup
|
// Check for notifications not processed at startup
|
||||||
Notifications.find({ webNotified: null }).forEach((notif) => {
|
Notifications.find({ notified: null, type: 'web' }).forEach((notif) => {
|
||||||
process(notif);
|
process(notif);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -8,19 +8,49 @@ import { translate, Trans } from 'react-i18next';
|
||||||
import FromNow from '/imports/ui/components/FromNow/FromNow';
|
import FromNow from '/imports/ui/components/FromNow/FromNow';
|
||||||
|
|
||||||
class FireStats extends Component {
|
class FireStats extends Component {
|
||||||
|
constructor(props) {
|
||||||
|
super(props);
|
||||||
|
this.state = {
|
||||||
|
lastFireDetected: props.lastFireDetected,
|
||||||
|
lastCheck: props.lastCheck,
|
||||||
|
loadingAll: props.loadingAll
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
componentWillReceiveProps(nextProps) {
|
||||||
|
if (this.props.lastCheck !== nextProps.lastCheck ||
|
||||||
|
this.props.lastFireDetected !== nextProps.lastFireDetected ||
|
||||||
|
this.props.loadingAll !== nextProps.loadingAll
|
||||||
|
) {
|
||||||
|
this.setState({
|
||||||
|
lastFireDetected: nextProps.lastFireDetected,
|
||||||
|
lastCheck: nextProps.lastCheck,
|
||||||
|
loadingAll: nextProps.loadingAll
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
shouldComponentUpdate(nextProps, nextState) {
|
||||||
|
return !(nextState.lastCheck === this.state.lastCheck &&
|
||||||
|
nextState.lastFireDetected === this.state.lastFireDetected &&
|
||||||
|
nextState.loadingAll === this.state.loadingAll);
|
||||||
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
return (
|
if (this.state.loadingAll) return (<Fragment><br /><em><Trans>Actualizando...</Trans></em></Fragment>);
|
||||||
<Fragment>
|
if (this.state.lastCheck && !this.state.lastFireDetected) {
|
||||||
{ this.props.lastCheck && this.props.lastFireDetected ?
|
return (<Fragment><br /><Trans>Actualizado <FromNow when={this.state.lastCheck} />.</Trans></Fragment>);
|
||||||
<Trans>Actualizado <FromNow when={this.props.lastCheck} />, último fuego detectado <FromNow when={this.props.lastFireDetected.when} />.</Trans>
|
}
|
||||||
: '' }
|
if (this.state.lastCheck && this.state.lastFireDetected) {
|
||||||
</Fragment>
|
return (<Fragment><br /><Trans>Actualizado <FromNow when={this.state.lastCheck} />, último fuego detectado <FromNow when={this.state.lastFireDetected.when} />.</Trans></Fragment>);
|
||||||
);
|
}
|
||||||
|
return null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
FireStats.propTypes = {
|
FireStats.propTypes = {
|
||||||
lastFireDetected: PropTypes.object,
|
lastFireDetected: PropTypes.object,
|
||||||
|
loadingAll: PropTypes.bool.isRequired,
|
||||||
lastCheck: PropTypes.instanceOf(Date)
|
lastCheck: PropTypes.instanceOf(Date)
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -245,8 +245,8 @@ class FiresMap extends React.Component {
|
||||||
<Col xs={12} sm={6} md={6} lg={6} >
|
<Col xs={12} sm={6} md={6} lg={6} >
|
||||||
<p className="firesmap-legend">
|
<p className="firesmap-legend">
|
||||||
{ (this.props.activefires.length + this.props.firealerts.length) === 0 ?
|
{ (this.props.activefires.length + this.props.firealerts.length) === 0 ?
|
||||||
<Fragment><Trans parent="span" i18nKey="noActiveFireInMapCount">No hay fuegos activos en esta zona del mapa. <strong>{{ countTotal: this.props.activefirestotal }}</strong> fuegos activos en el mundo.</Trans> <FireStats {... this.props} /></Fragment> :
|
<Fragment><Trans parent="span" i18nKey="noActiveFireInMapCount">No hay fuegos activos en esta zona del mapa. <strong>{{ countTotal: this.props.activefirestotal }}</strong> fuegos activos en el mundo.</Trans> <FireStats loadingAll={loading} {... this.props} /></Fragment> :
|
||||||
<Fragment><Trans parent="span" i18nKey="activeFireInMapCount">En rojo, <strong>{{ count: this.props.activefires.length + this.props.firealerts.length }}</strong> fuegos activos. <strong>{{ countTotal: this.props.activefirestotal }}</strong> fuegos activos en el mundo.</Trans> <FireStats {... this.props} /></Fragment>
|
<Fragment><Trans parent="span" i18nKey="activeFireInMapCount">En rojo, <strong>{{ count: this.props.activefires.length + this.props.firealerts.length }}</strong> fuegos activos. <strong>{{ countTotal: this.props.activefirestotal }}</strong> fuegos activos en el mundo.</Trans> <FireStats loadingAll={loading} {... this.props} /></Fragment>
|
||||||
}
|
}
|
||||||
</p>
|
</p>
|
||||||
{isNotHomeAndMobile() && this.props.firealerts.length > 0 &&
|
{isNotHomeAndMobile() && this.props.firealerts.length > 0 &&
|
||||||
|
|
@ -453,7 +453,7 @@ export default translate([], { wait: true })(withTracker(() => {
|
||||||
const lastFireDetected = ActiveFiresCollection.findOne({}, { sort: { when: -1 } });
|
const lastFireDetected = ActiveFiresCollection.findOne({}, { sort: { when: -1 } });
|
||||||
|
|
||||||
return {
|
return {
|
||||||
loading: Meteor.status().status !== 'connected' || !subscription ? true : !(subscription.ready() && settingsSubs.ready() && alertSubscription.ready()),
|
loading: Meteor.status().status !== 'connected' || !subscription ? true : !(subscription.ready() && settingsSubs.ready() && alertSubscription.ready() && settingsSubs.ready()),
|
||||||
userSubs: userSubs ? userSubs.value : null,
|
userSubs: userSubs ? userSubs.value : null,
|
||||||
userSubsBounds: userSubs ? userSubsBounds.value : null,
|
userSubsBounds: userSubs ? userSubsBounds.value : null,
|
||||||
subsready: settingsSubs.ready(),
|
subsready: settingsSubs.ready(),
|
||||||
|
|
|
||||||
|
|
@ -11,6 +11,7 @@ import PropTypes from 'prop-types';
|
||||||
// import { Link } from 'react-router-dom';
|
// import { Link } from 'react-router-dom';
|
||||||
// https://www.npmjs.com/package/react-resize-detector
|
// https://www.npmjs.com/package/react-resize-detector
|
||||||
import ReactResizeDetector from 'react-resize-detector';
|
import ReactResizeDetector from 'react-resize-detector';
|
||||||
|
import MobileStoreButton from 'react-mobile-store-button';
|
||||||
import _ from 'lodash';
|
import _ from 'lodash';
|
||||||
import 'html5-device-mockups/dist/device-mockups.min.css';
|
import 'html5-device-mockups/dist/device-mockups.min.css';
|
||||||
import 'bootstrap-carousel-swipe/carousel-swipe';
|
import 'bootstrap-carousel-swipe/carousel-swipe';
|
||||||
|
|
@ -20,7 +21,6 @@ import SubscriptionsMap from '/imports/ui/pages/Subscriptions/SubscriptionsMap';
|
||||||
import ShareIt from '/imports/ui/components/ShareIt/ShareIt';
|
import ShareIt from '/imports/ui/components/ShareIt/ShareIt';
|
||||||
import getFallbackLang from '/imports/modules/lang-fallback';
|
import getFallbackLang from '/imports/modules/lang-fallback';
|
||||||
import FiresMap from '../FiresMap/FiresMap';
|
import FiresMap from '../FiresMap/FiresMap';
|
||||||
|
|
||||||
import './Index.scss';
|
import './Index.scss';
|
||||||
import './Index-custom.scss';
|
import './Index-custom.scss';
|
||||||
|
|
||||||
|
|
@ -102,6 +102,7 @@ class Index extends Component {
|
||||||
render() {
|
render() {
|
||||||
const { t } = this.props;
|
const { t } = this.props;
|
||||||
const title = `${t('AppName')}: ${t('Inicio')}`;
|
const title = `${t('AppName')}: ${t('Inicio')}`;
|
||||||
|
const androidUrl = 'https://play.google.com/store/apps/details?id=org.comunes.fires';
|
||||||
return (
|
return (
|
||||||
<div className="IndexDisabled full-width">
|
<div className="IndexDisabled full-width">
|
||||||
<Helmet>
|
<Helmet>
|
||||||
|
|
@ -181,6 +182,9 @@ class Index extends Component {
|
||||||
</div>
|
</div>
|
||||||
<p className="moto"><Trans>Siempre alerta a los fuegos en nuestro vecindario</Trans></p>
|
<p className="moto"><Trans>Siempre alerta a los fuegos en nuestro vecindario</Trans></p>
|
||||||
{/* <Link className="participe-btn btn btn-lg btn-warning" role="button" to="/#platforms">{this.props.t('Participa')}</Link> */}
|
{/* <Link className="participe-btn btn btn-lg btn-warning" role="button" to="/#platforms">{this.props.t('Participa')}</Link> */}
|
||||||
|
<p>
|
||||||
|
<MobileStoreButton store="android" url={androidUrl} />
|
||||||
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
</section>
|
</section>
|
||||||
|
|
|
||||||
1201
package-lock.json
generated
1201
package-lock.json
generated
File diff suppressed because it is too large
Load diff
|
|
@ -37,6 +37,7 @@
|
||||||
"ismobilejs": "^0.4.1",
|
"ismobilejs": "^0.4.1",
|
||||||
"jquery": "^2.2.4",
|
"jquery": "^2.2.4",
|
||||||
"jquery-validation": "^1.17.0",
|
"jquery-validation": "^1.17.0",
|
||||||
|
"jsend": "^1.0.2",
|
||||||
"juice": "^4.2.2",
|
"juice": "^4.2.2",
|
||||||
"leaflet": "^1.3.1",
|
"leaflet": "^1.3.1",
|
||||||
"leaflet-geodesy": "^0.2.1",
|
"leaflet-geodesy": "^0.2.1",
|
||||||
|
|
@ -51,7 +52,9 @@
|
||||||
"modernizr": "^3.6.0",
|
"modernizr": "^3.6.0",
|
||||||
"moment": "^2.19.1",
|
"moment": "^2.19.1",
|
||||||
"moment-timezone": "^0.5.14",
|
"moment-timezone": "^0.5.14",
|
||||||
|
"node-gcm": "^1.0.2",
|
||||||
"node-geocoder": "^3.21.1",
|
"node-geocoder": "^3.21.1",
|
||||||
|
"node-gyp": "^3.7.0",
|
||||||
"nodemailer": "^4.4.2",
|
"nodemailer": "^4.4.2",
|
||||||
"npm": "^5.8.0",
|
"npm": "^5.8.0",
|
||||||
"pm2-master": "^1.1.3",
|
"pm2-master": "^1.1.3",
|
||||||
|
|
@ -73,8 +76,9 @@
|
||||||
"react-leaflet-fullscreen": "0.0.6",
|
"react-leaflet-fullscreen": "0.0.6",
|
||||||
"react-leaflet-google": "^3.2.1",
|
"react-leaflet-google": "^3.2.1",
|
||||||
"react-leaflet-sidebarv2": "^0.5.1",
|
"react-leaflet-sidebarv2": "^0.5.1",
|
||||||
|
"react-mobile-store-button": "0.0.3",
|
||||||
"react-places-autocomplete": "^5.4.3",
|
"react-places-autocomplete": "^5.4.3",
|
||||||
"react-progressbar.js": "git://github.com/squarecat/react-progressbar.js.git",
|
"react-progress-bar.js": "^0.2.3",
|
||||||
"react-resize-detector": "^1.1.0",
|
"react-resize-detector": "^1.1.0",
|
||||||
"react-router-bootstrap": "^0.24.4",
|
"react-router-bootstrap": "^0.24.4",
|
||||||
"react-router-dom": "^4.2.2",
|
"react-router-dom": "^4.2.2",
|
||||||
|
|
|
||||||
|
|
@ -1,97 +1,156 @@
|
||||||
Your privacy is important to us.
|
# PRIVACY POLICY #
|
||||||
|
|
||||||
Comunes Association built the 'All Against the Fire' app and this SERVICE is provided by Comunes Association at no cost and is intended for use as is.
|
*Last updated July 12, 2018*
|
||||||
|
|
||||||
This page is used to inform website visitors regarding our policies with the collection, use, and disclosure of Personal Information if anyone decided to use our Service.
|
**In Short**: *Your privacy is important to us. We do not do business with your data, we do not share it with third parties and we only worry about the fires and how to strengthen neighborhoods against them.*
|
||||||
|
|
||||||
If you choose to use our Service, then you agree to the collection and use of information in relation to this policy. The Personal Information that we collect is used for providing and improving the Service. We will not use or share your information with anyone except as described in this Privacy Policy.
|
Thank you for choosing to be part of our community 'All Against the Fires' at Comunes Association (“we”, “us”, or “our”). We are committed to protecting your personal information and your right to privacy. If you have any questions or concerns about our policy, or our practices with regards to your personal information, please contact us at info@comunes.org.
|
||||||
|
|
||||||
The terms used in this Privacy Policy have the same meanings as in our [Terms and Conditions](/terms), which is accessible at 'All Against the Fire' unless otherwise defined in this Privacy Policy.
|
When you visit our website [fires.comunes.org](https://fires.comunes.org), mobile application, and use our services, you trust us with your personal information. We take your privacy very seriously. In this privacy notice, we describe our privacy policy. We seek to explain to you in the clearest way possible what information we collect, how we use it and what rights you have in relation to it. We hope you take some time to read through it carefully, as it is important. If there are any terms in this privacy policy that you do not agree with, please discontinue use of our Sites or Apps and our services.
|
||||||
|
|
||||||
**Information Collection and Use**
|
This privacy policy applies to all information collected through our website (such as [fires.comunes.org](https://fires.comunes.org)), mobile application (the "**Apps**"), and/or any related services, or events (we refer to them collectively in this privacy policy as the "**Sites**").
|
||||||
|
|
||||||
For a better experience, while using our Service, we may require you to provide us with certain personally identifiable information, including but not limited to name, email, language, and the telegram username (of our Telegram users). The information that we request is will be retained by us and used as described in this privacy policy.
|
**Please read this privacy policy carefully as it will help you make informed decisions about sharing your personal information with us.**
|
||||||
|
|
||||||
In addition, the app may collect certain information automatically, including, but not limited to, the type of device you use, the IP address of your device, your mobile operating system, the type of Internet browsers you use, and information about the way you use the app.
|
|
||||||
|
|
||||||
We may use the information we collect in the following ways:
|
## 1. WHAT INFORMATION DO WE COLLECT? ##
|
||||||
* To personalize your experience and to allow us to deliver the type of content in which you are most interested.
|
|
||||||
* To send periodic emails regarding our services.
|
|
||||||
|
|
||||||
The app does use third party services that may collect information used to identify you.
|
### Information automatically collected ###
|
||||||
|
|
||||||
Links to privacy policy of third party service providers used by the app:
|
**In Short:** *Some information – such as IP address and/or browser and device characteristics – is collected
|
||||||
|
automatically when you visit our Sites or Apps.*
|
||||||
|
|
||||||
* [OpenStreetMap](https://wiki.openstreetmap.org/wiki/Privacy_Policy/)
|
We automatically collect certain information when you visit, use or navigate the Sites or Apps. This information does not reveal your specific identity (like your name or contact information) but may include device and usage information, such as your IP address, browser and device characteristics, operating system, language preferences, referring URLs, device name, country, location, information about how and when you use our Sites or Apps and other technical information. This information is primarily needed to maintain the security and operation of our Sites or Apps, and for our internal analytics and reporting purposes. Like many businesses, we also collect information through cookies and similar technologies.
|
||||||
* [Google Maps](https://developers.google.com/maps/terms?hl=es#3-privacy-and-personal-information)
|
|
||||||
|
|
||||||
We have not enabled Google AdSense.
|
### Information collected through our Apps ###
|
||||||
|
|
||||||
**Does the app collect precise real time location information of the device?**
|
**In Short:** *We may collect information regarding your geo-location, push notifications, when you use our apps.*
|
||||||
|
|
||||||
When you visit the app, we may use GPS technology (or other similar technology) to determine your current location in order to determine the city you are located within and/or display a location map of local fires and/or provide alerts of future fires in your zone. We **will not** share your current location with other users or partners.
|
If you use our Apps, we may also collect the following information:
|
||||||
|
|
||||||
If you do not want us to use your location for the purposes set forth above, you should turn off the location services for the mobile app located in your account settings or in your mobile phone settings and/or within the app and/or when asked by your browser.
|
- *Geo-Location Information.* We may request at some point access or permission to location-based information from your mobile device to provide location-based services. If you wish to change our access or permissions, you may do so in your device's settings.
|
||||||
|
- *Push Notifications.* We may request to send you push notifications regarding your account or the mobile application. If you wish to opt-out from receiving these types of communications, you may turn them off in your device's settings.
|
||||||
|
|
||||||
**How do we protect your information?**
|
|
||||||
|
|
||||||
We do not use vulnerability scanning and/or scanning to PCI standards. We only provide articles and information. We never ask for credit card numbers. We do not use Malware Scanning.
|
## 2. HOW DO WE USE YOUR INFORMATION? ##
|
||||||
|
|
||||||
Your personal information is contained behind secured networks and is only accessible by a limited number of persons who have special access rights to such systems, and are required to keep the information confidential. In addition, all sensitive information you supply is encrypted via Secure Socket Layer (SSL) technology.
|
**In Short:** *We process your information for purposes based on legitimate interests, the fulfillment of our contract with you, compliance with our legal obligations, and/or your consent.*
|
||||||
|
|
||||||
We implement a variety of security measures when a user enters, submits, or accesses their information to maintain the safety of your personal information.
|
We use personal information collected via our Sites or Apps for a variety of purposes described below. We process your personal information for these purposes in order to enter into or perform a contract with you ("Contractual"), with your consent ("Consent"), and/or for compliance with our legal obligations ("Legal Reasons"). We indicate the specific processing grounds we rely on next to each purpose listed below.
|
||||||
|
|
||||||
**Log Data**
|
We use the information we collect or receive:
|
||||||
|
|
||||||
We want to inform you that whenever you use our Service, in a case of an error in the app we collect data and information (through third party products) on your phone called Log Data. This Log Data may include information such as your device Internet Protocol (“IP”) address, device name, operating system version, the configuration of the app when utilizing our Service, the time and date of your use of the Service, and other statistics.
|
- **To facilitate account creation and logon process** with your Consent. If you choose to link your account with us to a third party account \*(such as your Google or Telegram account), we use the information you allowed us to collect from those third parties to facilitate account creation and logon process.
|
||||||
|
- **Request Feedback**. We may use your information to request feedback and to contact you about your use of our Sites or Apps.
|
||||||
|
- **To enable user-to-user communications** with your Consent. We may use your information in order to enable user-to-user communications with each user's consent.
|
||||||
|
- **To enforce our terms, conditions and policies**.
|
||||||
|
- **To respond to legal requests and prevent harm** for Legal Reasons. If we receive a subpoena or other legal request, we may need to inspect the data we hold to determine how to respond.
|
||||||
|
|
||||||
**Do we use 'cookies'?**
|
|
||||||
|
|
||||||
Yes. Cookies are small files that a site or its service provider transfers to your computer's hard drive through your Web browser (if you allow) that enables the site's or service provider's systems to recognize your browser and capture and remember certain information. For instance, we use cookies to help us remember and process your prefered language. They are also used to help us understand your preferences based on previous or current site activity, which enables us to provide you with improved services. We also use cookies to help us compile aggregate data about site traffic and site interaction so that we can offer better site experiences and tools in the future.
|
## 3. WILL YOUR INFORMATION BE SHARED WITH ANYONE? ##
|
||||||
|
|
||||||
We use cookies to understand and save user's preferences for future visits.
|
**In Short:** *We only share information with your consent, to comply with laws or to protect your rights.*
|
||||||
|
|
||||||
You can choose to have your computer warn you each time a cookie is being sent, or you can choose to turn off all cookies. You do this through your browser settings. Since browser is a little different, look at your browser's Help Menu to learn the correct way to modify your cookies.
|
We only share and disclose your information in the following situations:
|
||||||
|
|
||||||
If you turn cookies off, some of the features that make your site experience more efficient may not function properly.
|
- **Compliance with Laws**. We may disclose your information where we are legally required to do so in order to comply with applicable law, governmental requests, a judicial proceeding, court order, or legal process, such as in response to a court order or a subpoena (including in response to public authorities to meet national security or law enforcement requirements).
|
||||||
|
- **Vital Interests and Legal Rights**. We may disclose your information where we believe it is necessary to investigate, prevent, or take action regarding potential violations of our policies, suspected fraud, situations involving potential threats to the safety of any person and illegal activities, or as evidence in litigation in which we are involved.
|
||||||
|
- **With your Consent**. We may disclose your personal information for any other purpose with your consent.
|
||||||
|
|
||||||
**Service Providers**
|
|
||||||
|
|
||||||
We may employ third-party companies and individuals to facilitate our Service.
|
## 4. DO WE USE COOKIES AND OTHER TRACKING TECHNOLOGIES? ##
|
||||||
|
|
||||||
**Third-party disclosure**
|
**In Short:** *We may use cookies and other tracking technologies to collect and store your information.*
|
||||||
|
|
||||||
We do not sell, trade, or otherwise transfer to outside parties your Personally Identifiable Information.
|
We may use cookies to access or store information about account login and your user preferences (like your prefered language).
|
||||||
|
|
||||||
**Security**
|
|
||||||
|
|
||||||
We value your trust in providing us your Personal Information, thus we are striving to use commercially acceptable means of protecting it. But remember that no method of transmission over the internet, or method of electronic storage is 100% secure and reliable, and we cannot guarantee its absolute security.
|
## 5. DO WE USE GOOGLE MAPS? ##
|
||||||
|
|
||||||
**Links to Other Sites**
|
**In Short:** *Yes, we use Google Maps for the purpose of providing better service.*
|
||||||
|
|
||||||
We do not include or offer third-party products or services on our website but this Service may contain links to other sites in user comments. If you click on a third-party link, you will be directed to that site. Note that these external sites are not operated by us. Therefore, we strongly advise you to review the Privacy Policy of these websites. We have no control over and assume no responsibility for the content, privacy policies, or practices of any third-party sites or services.
|
This website, mobile application, or Telegram application uses Google Maps APIs. You may find the Google Maps APIs Terms of Service [here](https://developers.google.com/maps/terms). To better understand Google's Privacy Policy, please refer to this [link](https://policies.google.com/privacy).
|
||||||
|
|
||||||
**Children’s Privacy**
|
By using our Maps API Implementation, you agree to be bound by Google's Terms of Service. You agree to allow us to obtain or cache your location. You may revoke your consent at anytime. We use information about location in conjunction with data from other data providers.
|
||||||
|
|
||||||
These Services do not address anyone under the age of 13. We do not knowingly collect personally identifiable information from children under 13. In the case we discover that a child under 13 has provided us with personal information, we immediately delete this from our servers. If you are a parent or guardian and you are aware that your child has provided us with personal information, please contact us so that we will be able to do necessary actions.
|
|
||||||
|
|
||||||
**Fair Information Practices**
|
## 6. HOW LONG DO WE KEEP YOUR INFORMATION? ##
|
||||||
|
|
||||||
The Fair Information Practices Principles form the backbone of privacy law in the United States and the concepts they include have played a significant role in the development of data protection laws around the globe. Understanding the Fair Information Practice Principles and how they should be implemented is critical to comply with the various privacy laws that protect personal information.
|
**In Short:** *We keep your information for as long as necessary to fulfill the purposes outlined in this privacy policy unless otherwise required by law.*
|
||||||
|
|
||||||
In order to be in line with Fair Information Practices we will take the following responsive action, should a data breach occur:
|
We will only keep your personal information for as long as it is necessary for the purposes set out in this privacy policy, unless a longer retention period is required or permitted by law (such as tax, accounting or other legal requirements). No purpose in this policy will require us keeping your personal information for longer than the period of time in which users have an account with us.
|
||||||
* We will notify you via email, within 7 business days
|
|
||||||
|
|
||||||
We also agree to the Individual Redress Principle which requires that individuals have the right to legally pursue enforceable rights against data collectors and processors who fail to adhere to the law. This principle requires not only that individuals have enforceable rights against data users, but also that individuals have recourse to courts or government agencies to investigate and/or prosecute non-compliance by data processors.
|
When we have no ongoing legitimate need to process your personal information, we will either delete or anonymize it, or, if this is not possible (for example, because your personal information has been stored in backup archives), then we will securely store your personal information and isolate it from any further processing until deletion is possible.
|
||||||
|
|
||||||
**Changes to This Privacy Policy**
|
|
||||||
|
|
||||||
We may update our Privacy Policy from time to time. Thus, you are advised to review this page periodically for any changes. We will notify you of any changes by posting the new Privacy Policy on this page. These changes are effective immediately after they are posted on this page.
|
## 7.HOW DO WE KEEP YOUR INFORMATION SAFE? ##
|
||||||
|
|
||||||
**Your Consent**
|
**In Short:** *We aim to protect your personal information through a system of organizational and technical security measures.*
|
||||||
|
|
||||||
By using the app, you are consenting to our processing of your information as set forth in this Privacy Policy now and as amended by us. "Processing," means using cookies on a computer/hand held device or using or touching information in any way, including, but not limited to, collecting, storing, deleting, using, combining and disclosing information, all of which activities will take place in Spain. If you reside outside Spain your information will be transferred, processed and stored there under Spain privacy standards.
|
We have implemented appropriate technical and organizational security measures designed to protect the security of any personal information we process. However, please also remember that we cannot guarantee that the internet itself is 100% secure. Although we will do our best to protect your personal information, transmission of personal information to and from our Sites or Apps is at your own risk. You should only access the services within a secure environment.
|
||||||
|
|
||||||
**Contact Us**
|
|
||||||
|
|
||||||
If you have any questions or suggestions about our Privacy Policy, do not hesitate to contact us: info AT comunes DOT org.
|
## 8. DO WE COLLECT INFORMATION FROM MINORS? ##
|
||||||
|
|
||||||
|
**In Short:** *We do not knowingly collect data from or market to children under 18 years of age.*
|
||||||
|
|
||||||
|
We do not knowingly solicit data from or market to children under 18 years of age. By using the Sites or Apps, you represent that you are at least 18 or that you are the parent or guardian of such a minor and consent to such minor dependent's use of the Sites or Apps. If we learn that personal information from users less than 18 years of age has been collected, we will deactivate the account and take reasonable measures to promptly delete such data from our records. If you become aware of any data we have collected from children under age 18, please contact us at info@comunes.org.
|
||||||
|
|
||||||
|
|
||||||
|
## 9. WHAT ARE YOUR PRIVACY RIGHTS? ##
|
||||||
|
|
||||||
|
**In Short:** *In some regions, such as the European Economic Area, you have rights that allow you greater access to and control over your personal information. You may review, change, or terminate your account at any time.*
|
||||||
|
|
||||||
|
In some regions (like the European Economic Area), you have certain rights under applicable data protection laws. These may include the right (i) to request access and obtain a copy of your personal information, (ii) to request rectification or erasure; (iii) to restrict the processing of your personal information; and (iv) if applicable, to data portability. In certain circumstances, you may also have the right to object to the processing of your personal information. To make such a request, please use the contact details provided below. We will consider and act upon any request in accordance with applicable data protection laws.
|
||||||
|
|
||||||
|
If we are relying on your consent to process your personal information, you have the right to withdraw your consent at any time. Please note however that this will not affect the lawfulness of the processing before its withdrawal.
|
||||||
|
|
||||||
|
If you are resident in the European Economic Area and you believe we are unlawfully processing your personal information, you also have the right to complain to your local data protection supervisory authority. You can find their contact details here: [http://ec.europa.eu/justice/data-protection/bodies/authorities/index_en.htm](http://ec.europa.eu/justice/data-protection/bodies/authorities/index_en.htm)
|
||||||
|
|
||||||
|
### Account Information ###
|
||||||
|
|
||||||
|
If you would at any time like to review or change the information in your account or terminate your account, you can:
|
||||||
|
|
||||||
|
- Log into your account settings and update your user account.
|
||||||
|
|
||||||
|
Upon your request to terminate your account, we will deactivate or delete your account and information from our active databases. However, some information may be retained in our files to prevent fraud, troubleshoot problems, assist with any investigations, enforce our Terms of Use and/or comply with legal requirements.
|
||||||
|
|
||||||
|
**Cookies and similar technologies:** Most Web browsers are set to accept cookies by default. If you prefer, you can usually choose to set your browser to remove cookies and to reject cookies. If you choose to remove cookies or reject cookies, this could affect certain features or services of our Sites or Apps.
|
||||||
|
|
||||||
|
**Opting out of email notifications:** You can unsubscribe from our notification emails at any time by clicking on the unsubscribe link in the emails that we send or by contacting us using the details provided below. You will then be removed from these emails – however, we will still need to send you service-related emails that are necessary for the administration and use of your account. To otherwise opt-out, you may:
|
||||||
|
|
||||||
|
- Note your preferences when you register an account with the site.
|
||||||
|
- Access your account settings and update preferences.
|
||||||
|
|
||||||
|
|
||||||
|
## 10. DO CALIFORNIA RESIDENTS HAVE SPECIFIC PRIVACY RIGHTS? ##
|
||||||
|
|
||||||
|
**In Short:** *Yes, if you are a resident of California, you are granted specific rights regarding access to your personal information.*
|
||||||
|
|
||||||
|
California Civil Code Section 1798.83, also known as the "Shine The Light" law, permits our users who are California residents to request and obtain from us, once a year and free of charge, information about categories of personal information (if any) we disclosed to third parties for direct marketing purposes and the names and addresses of all third parties with which we shared personal information in the immediately preceding calendar year. If you are a California resident and would like to make such a request, please submit your request in writing to us using the contact information provided below.
|
||||||
|
|
||||||
|
If you are under 18 years of age, reside in California, and have a registered account with the Sites or Apps, you have the right to request removal of unwanted data that you publicly post on the Sites or Apps. To request removal of such data, please contact us using the contact information provided below, and include the email address associated with your account and a statement that you reside in California. We will make sure the data is not publicly displayed on the Sites or Apps, but please be aware that the data may not be completely or comprehensively removed from our systems.
|
||||||
|
|
||||||
|
|
||||||
|
## 11. DO WE MAKE UPDATES TO THIS POLICY? ##
|
||||||
|
|
||||||
|
**In Short:** *Yes, we will update this policy as necessary to stay compliant with relevant laws.*
|
||||||
|
|
||||||
|
We may update this privacy policy from time to time. The updated version will be indicated by an updated "Revised" date and the updated version will be effective as soon as it is accessible. If we make material changes to this privacy policy, we may notify you either by prominently posting a notice of such changes or by directly sending you a notification. We encourage you to review this privacy policy frequently to be informed of how we are protecting your information.
|
||||||
|
|
||||||
|
|
||||||
|
## 12. HOW CAN YOU CONTACT US ABOUT THIS POLICY? ##
|
||||||
|
|
||||||
|
If you have questions or comments about this policy, you may email us at info@comunes.org or by post to:
|
||||||
|
|
||||||
|
ASOCIACIÓN COMUNES
|
||||||
|
C/ COLÓN 21, 5º DCHA, ESC DCHA
|
||||||
|
03001 ALICANTE
|
||||||
|
SPAIN
|
||||||
|
|
||||||
|
If you have any further questions or comments about us or our policies, email us at info@comunes.org or by post to:
|
||||||
|
|
||||||
|
ASOCIACIÓN COMUNES
|
||||||
|
C/ COLÓN 21, 5º DCHA, ESC DCHA
|
||||||
|
03001 ALICANTE
|
||||||
|
SPAIN
|
||||||
|
|
|
||||||
|
|
@ -1,97 +1,155 @@
|
||||||
Su privacidad es importante para nosotros.
|
# POLÍTICA DE PRIVACIDAD #
|
||||||
|
|
||||||
La Asociación Comunes ha creado esta aplicación 'Tod@s contra el fuego' y este SERVICIO que se provee por la Asociación de Comunes sin costo y está destinado a ser usado tal cual.
|
*Última actualización 12 de julio de 2018*
|
||||||
|
|
||||||
Esta página se utiliza para informar a los visitantes del sitio web sobre nuestras políticas con la recopilación, el uso y la divulgación de información personal si alguien decide utilizar nuestro Servicio.
|
**En resumen**: *Su privacidad es importante para nosotros/as. No hacemos negocios con sus datos, no los compartimos con terceros y solo nos preocupan los incendios y cómo fortalecer vecindarios contra ellos.*
|
||||||
|
|
||||||
Si elige usar nuestro Servicio, acepta la recopilación y el uso de la información en relación con esta política. La información personal que recopilamos se usa para proporcionar y mejorar el servicio. No usaremos ni compartiremos su información con nadie, excepto según se describe en esta Política de privacidad.
|
Gracias por elegir ser parte de nuestra comunidad 'Tod@s contra los Fuegos' en la Asociación de Comunes ("nosotras/os" o "nos"). Nos comprometemos a proteger su información personal y su derecho a la privacidad. Si tiene alguna pregunta o inquietud sobre nuestra política o nuestras prácticas con respecto a su información personal, contáctenos a info@comunes.org.
|
||||||
|
|
||||||
Los términos utilizados en esta Política de privacidad tienen los mismos significados que en nuestros [Términos y condiciones](/terms), a los que se puede acceder en "Tod@s contra el fuego" a menos que se defina lo contrario en esta Política de privacidad.
|
Cuando visita nuestro sitio web [fuegos.comunes.org](https://fuegos.comunes.org), la aplicación móvil y utiliza nuestros servicios, confía en nosotras/os con su información personal. Nos tomamos su privacidad muy en serio. En este aviso de privacidad, describimos nuestra política de privacidad. Buscamos explicarte de la manera más clara posible qué información recopilamos, cómo la utilizamos y qué derechos tienes en relación con ella. Esperamos que se tome un tiempo para leer detenidamente, ya que es importante. Si hay términos en esta política de privacidad con los que no está de acuerdo, interrumpa el uso de nuestros Sitios o Aplicaciones y nuestros servicios.
|
||||||
|
|
||||||
**Recopilación y uso de información**
|
Esta política de privacidad se aplica a toda la información recopilada a través de nuestro sitio web (como [fuegos.comunes.org](https://fuegos.comunes.org)), la aplicación móvil (las "**Aplicaciones**") y/o cualquier servicio relacionado o eventos (nos referimos a ellos colectivamente en esta política de privacidad como los "**Sitios**").
|
||||||
|
|
||||||
Para una mejor experiencia, al utilizar nuestro Servicio, podemos solicitarle que nos brinde cierta información de identificación personal, que incluye, entre otros, nombre, correo electrónico, idioma y el nombre de usuario del telegrama (de nuestros usuarios de Telegram). La información que solicitamos será retenida por nosotros y utilizada como se describe en esta política de privacidad.
|
**Lea atentamente esta política de privacidad, ya que le ayudará a tomar decisiones informadas sobre cómo compartir su información personal con nosotras/os.**
|
||||||
|
|
||||||
Además, la aplicación puede recopilar cierta información automáticamente, que incluye, entre otros, el tipo de dispositivo que usa, la dirección IP de su dispositivo, su sistema operativo móvil, el tipo de navegador de Internet que utiliza e información sobre la forma que usas la aplicación.
|
|
||||||
|
|
||||||
Podemos usar la información que recopilamos de las siguientes maneras:
|
## 1. ¿QUÉ INFORMACIÓN RECOPILAMOS? ##
|
||||||
* Para personalizar su experiencia y permitirnos ofrecer el tipo de contenido que más le interesa.
|
|
||||||
* Para enviar correos electrónicos periódicos con respecto a nuestros servicios.
|
|
||||||
|
|
||||||
La aplicación utiliza servicios de terceros que pueden recopilar información utilizada para identificarlo.
|
### Información recopilada automáticamente ###
|
||||||
|
|
||||||
Enlaces a la política de privacidad de proveedores de servicios de terceros utilizados por la aplicación:
|
**En resumen:** *Recopilamos información automáticamente, como la dirección IP y/o las características del navegador y del dispositivo, cuando visita nuestros Sitios o Aplicaciones.*
|
||||||
|
|
||||||
* [OpenStreetMap](https://wiki.openstreetmap.org/wiki/Privacy_Policy/)
|
Recopilamos cierta información automáticamente cuando visita, utiliza o navega por nuestros Sitios o Aplicaciones. Esta información no revela su identidad específica (como su nombre o información de contacto) pero puede incluir información de uso y dispositivo, como su dirección IP, características del navegador y del dispositivo, sistema operativo, preferencias de idioma, URL de referencia, nombre del dispositivo, país, ubicación , información sobre cómo y cuándo utiliza nuestros sitios o aplicaciones y otra información técnica. Esta información es principalmente necesaria para mantener la seguridad y el funcionamiento de nuestros sitios o aplicaciones, y para nuestros fines de análisis internos e informes. Al igual que muchas empresas, también recopilamos información a través de cookies y tecnologías similares.
|
||||||
* [Google Maps](https://developers.google.com/maps/terms?hl=es#3-privacy-and-personal-information)
|
|
||||||
|
|
||||||
No hemos habilitado Google AdSense.
|
### Información recopilada a través de nuestras aplicaciones ###
|
||||||
|
|
||||||
**¿La aplicación recopila información precisa sobre la ubicación en tiempo real del dispositivo?**
|
**En resumen:** *Podemos recopilar información sobre su ubicación geográfica, notificaciones push, cuando usa nuestras aplicaciones.*
|
||||||
|
|
||||||
Cuando visita la aplicación, podemos usar tecnología GPS (u otra tecnología similar) para determinar su ubicación actual a fin de determinar la ciudad en la que se encuentra y/o mostrar un mapa de ubicación de incendios locales y/o proporcionar alertas de incendios futuros en tu zona. **No** compartiremos su ubicación actual con otros usuarios o socios.
|
Si usa nuestras aplicaciones, también podemos recopilar la siguiente información:
|
||||||
|
|
||||||
Si no desea que usemos su ubicación para los fines establecidos anteriormente, debe desactivar los servicios de ubicación para la aplicación móvil ubicados en la configuración de su cuenta o en la configuración de su teléfono móvil y/o dentro de la aplicación y/o cuando se le solicite por tu navegador.
|
- *Información de geolocalización.* Podemos solicitar en algún momento acceso o permiso a la información basada en la ubicación desde su dispositivo móvil para proporcionar servicios basados en la ubicación. Si desea cambiar nuestro acceso o permisos, puede hacerlo en la configuración de su dispositivo.
|
||||||
|
- *Notificaciones Push.* Podemos solicitar el envío de notificaciones push con respecto a su cuenta o la aplicación móvil. Si desea optar por no recibir este tipo de comunicaciones, puede desactivarlas en la configuración de su dispositivo.
|
||||||
|
|
||||||
**¿Cómo protegemos tu información?**
|
|
||||||
|
|
||||||
No utilizamos el escaneo y/o escaneo de vulnerabilidades a estándares PCI. Solo proporcionamos artículos e información. Nunca pedimos números de tarjetas de crédito. No utilizamos el Malware Scanning.
|
## 2. ¿CÓMO USAMOS SU INFORMACIÓN? ##
|
||||||
|
|
||||||
Su información personal está almacenada detrás de redes seguras y solo es accesible para un número limitado de personas que tienen derechos especiales de acceso a dichos sistemas, y se les exige mantener la confidencialidad de la información. Además, toda la información sensible que usted suministra se cifra a través de la tecnología Secure Socket Layer (SSL).
|
**En resumen:** *Procesamos su información para fines basados en intereses legítimos, el cumplimiento de nuestro contrato con usted, el cumplimiento de nuestras obligaciones legales y/o su consentimiento.*
|
||||||
|
|
||||||
Implementamos una variedad de medidas de seguridad cuando un usuario ingresa, envía o accede a su información para mantener la seguridad de su información personal.
|
Usamos información personal recopilada a través de nuestros Sitios o Aplicaciones para una variedad de propósitos que se describen a continuación. Procesamos su información personal para estos fines a fin de celebrar o realizar un contrato con usted ("Contractual"), con su consentimiento ("Consentimiento"), y/o para cumplir con nuestras obligaciones legales ("Razones legales"). Indicamos los motivos de procesamiento específicos en los que confiamos junto a cada uno de los propósitos enumerados a continuación.
|
||||||
|
|
||||||
**Dato de registro**
|
Usamos la información que recopilamos o recibimos:
|
||||||
|
|
||||||
Queremos informarle que cada vez que utiliza nuestro Servicio, en caso de error en la aplicación, recopilamos datos e información (a través de productos de terceros) en su teléfono, llamados Datos de registro. Este registro de datos puede incluir información como la dirección del protocolo de Internet ("IP"), el nombre del dispositivo, la versión del sistema operativo, la configuración de la aplicación al utilizar nuestro servicio, la hora y fecha de uso del servicio y otras estadísticas.
|
- **Para facilitar la creación de la cuenta y el proceso de inicio de sesión** con su consentimiento. Si elige vincular su cuenta con nosotras/os a una cuenta de terceros \*(como su cuenta de Google o Telegram), usamos la información que nos permitió recopilar de esos terceros para facilitar la creación de la cuenta y el proceso de inicio de sesión.
|
||||||
|
- **Solicitud de comentarios**. Podemos utilizar su información para solicitar comentarios y para comunicarnos con usted sobre su uso de nuestros Sitios o Aplicaciones.
|
||||||
|
- **Para habilitar las comunicaciones de usuario a usuario** con su consentimiento. Podemos usar su información para permitir las comunicaciones de usuario a usuario con el consentimiento de cada usuario.
|
||||||
|
- **Para hacer cumplir nuestros términos, condiciones y políticas**.
|
||||||
|
- **Para responder a solicitudes legales y evitar daños** por razones legales. Si recibimos una citación u otra solicitud legal, es posible que tengamos que inspeccionar los datos que tenemos para determinar cómo responder.
|
||||||
|
|
||||||
**¿Usamos 'cookies'?**
|
|
||||||
|
|
||||||
Sí. Las cookies son pequeños archivos que un sitio o su proveedor de servicios transfiere al disco duro de su computadora a través de su navegador web (si lo permite) que permite que los sistemas del sitio o del proveedor de servicios reconozcan su navegador y capturen y recuerden cierta información. Por ejemplo, usamos cookies para ayudarnos a recordar y procesar su idioma preferido. También se utilizan para ayudarnos a comprender sus preferencias en función de la actividad del sitio anterior o actual, lo que nos permite brindarle mejores servicios. También utilizamos cookies para ayudarnos a recopilar datos agregados sobre el tráfico del sitio y la interacción del sitio para que podamos ofrecer mejores experiencias y herramientas del sitio en el futuro.
|
## 3. ¿SU INFORMACIÓN SERÁ COMPARTIDA CON ALGUIEN? ##
|
||||||
|
|
||||||
Usamos cookies para comprender y guardar las preferencias del usuario para futuras visitas.
|
**En resumen:** *Solo compartimos información con su consentimiento, para cumplir con las leyes o proteger sus derechos*
|
||||||
|
|
||||||
Puede optar por que su computadora lo advierta cada vez que se envía una cookie, o puede optar por desactivar todas las cookies. Lo haces a través de la configuración de tu navegador. Dado que el navegador es un poco diferente, consulte el Menú de Ayuda de su navegador para conocer la forma correcta de modificar sus cookies.
|
Solo compartimos y divulgamos su información en las siguientes situaciones:
|
||||||
|
|
||||||
Si desactiva las cookies, es posible que algunas de las funciones que hacen que su sitio sea más eficiente no funcionen correctamente.
|
- **De acuerdo con las leyes**. Podemos divulgar su información cuando se nos requiera legalmente para cumplir con la ley aplicable, las solicitudes gubernamentales, un procedimiento judicial, una orden judicial o un proceso legal, como por ejemplo en respuesta a una orden judicial o una citación judicial (incluso en respuesta a las autoridades públicas para cumplir con los requisitos de seguridad nacional o cumplimiento de la ley).
|
||||||
|
- **Intereses vitales y derechos legales**. Podemos divulgar su información cuando creemos que es necesario investigar, prevenir o tomar medidas con respecto a posibles violaciones de nuestras políticas, sospecha de fraude, situaciones que involucren amenazas potenciales a la seguridad de cualquier persona y actividades ilegales, o como evidencia en un litigio en el cual estamos involucrados.
|
||||||
|
- **Con su consentimiento**. Podemos divulgar su información personal para cualquier otro propósito con su consentimiento.
|
||||||
|
|
||||||
**Proveedores de servicio**
|
|
||||||
|
|
||||||
Podemos emplear compañías e individuos de terceros para facilitar nuestro Servicio.
|
## 4. ¿USAMOS COOKIES Y OTRAS TECNOLOGÍAS DE SEGUIMIENTO? ##
|
||||||
|
|
||||||
**Divulgación de terceros**
|
**En resumen:** *Podemos utilizar cookies y otras tecnologías de seguimiento para recopilar y almacenar su información.*
|
||||||
|
|
||||||
No vendemos, comercializamos ni transferimos a terceros su información de identificación personal.
|
Podemos usar cookies para acceder o almacenar información sobre el inicio de sesión de la cuenta y sus preferencias de usuario (como su idioma preferido).
|
||||||
|
|
||||||
**Seguridad**
|
|
||||||
|
|
||||||
Valoramos su confianza al proporcionarnos su información personal, por lo tanto, nos esforzamos por utilizar medios comercialmente aceptables para protegerla. Pero recuerde que ningún método de transmisión a través de Internet o método de almacenamiento electrónico es 100% seguro y confiable, y no podemos garantizar su seguridad absoluta.
|
## 5. ¿USAMOS GOOGLE MAPS? ##
|
||||||
|
|
||||||
**Enlaces a otros sitios**
|
**En resumen:** *Sí, utilizamos Google Maps con el fin de proporcionar un mejor servicio.*
|
||||||
|
|
||||||
No incluimos ni ofrecemos productos o servicios de terceros en nuestro sitio web, pero este Servicio puede contener enlaces a otros sitios en los comentarios de los usuarios. Si hace clic en un enlace de un tercero, se lo dirigirá a ese sitio. Tenga en cuenta que estos sitios externos no son operados por nosotros. Por lo tanto, le recomendamos encarecidamente que revise la Política de privacidad de estos sitios web. No tenemos control ni asumimos ninguna responsabilidad por el contenido, las políticas de privacidad o las prácticas de sitios o servicios de terceros.
|
Este sitio web, aplicación móvil o aplicación Telegram utiliza las API de Google Maps. Puede encontrar las Condiciones del servicio de las API de Google Maps [aquí](https://developers.google.com/maps/terms). Para comprender mejor la Política de privacidad de Google, consulte este [enlace](https://policies.google.com/privacy).
|
||||||
|
|
||||||
**Privacidad de los niños**
|
Al utilizar nuestra implementación de Maps API, usted acepta estar sujeto a los Términos de Servicio de Google. Usted acepta permitirnos obtener o almacenar en caché su ubicación. Puede revocar su consentimiento en cualquier momento. Usamos información sobre la ubicación junto con datos de otros proveedores de datos.
|
||||||
|
|
||||||
Estos servicios no se dirigen a personas menores de 13 años. No recopilamos deliberadamente información personal identificable de niños menores de 13 años. En el caso de que descubramos que un niño menor de 13 años nos ha proporcionado información personal, la borramos inmediatamente de nuestros servidores. Si usted es un padre o tutor y sabe que su hijo nos ha proporcionado información personal, comuníquese con nosotros para que podamos hacer las acciones necesarias.
|
|
||||||
|
|
||||||
**Prácticas justas de información**
|
## 6. ¿CUÁNTO TIEMPO CONSERVAMOS SU INFORMACIÓN? ##
|
||||||
|
|
||||||
Los principios de prácticas justas de información (del inglés 'Fair Information Practices') forman la columna vertebral de la ley de privacidad en los Estados Unidos y los conceptos que incluyen han jugado un papel importante en el desarrollo de las leyes de protección de datos en todo el mundo. Comprender los principios prácticos de información justa y cómo deben implementarse es fundamental para cumplir con las diversas leyes de privacidad que protegen la información personal.
|
**En resumen:** *Conservamos su información el tiempo que sea necesario para cumplir con los fines descritos en esta política de privacidad, a menos que la ley así lo requiera.*
|
||||||
|
|
||||||
Para estar en línea con las prácticas de información justas, tomaremos la siguiente medida de respuesta, en caso de que se produzca una violación de datos:
|
Solo conservaremos su información personal durante el tiempo que sea necesario para los fines establecidos en esta política de privacidad, a menos que la ley exija o permita un período de retención más largo (como impuestos, contabilidad u otros requisitos legales). Ningún propósito de esta política nos obligará a conservar su información personal por un período mayor al que el usuario tenga una cuenta con nosotras/os.
|
||||||
* Le notificaremos por correo electrónico dentro de los 7 días hábiles
|
|
||||||
|
|
||||||
También aceptamos el principio de reparación individual que exige que las personas tengan derecho a buscar legalmente derechos exigibles contra los recolectores de datos y procesadores que no cumplan con la ley. Este principio requiere no solo que los individuos tengan derechos exigibles contra los usuarios de los datos, sino también que los individuos recurran a los tribunales o agencias gubernamentales para investigar y/o enjuiciar el incumplimiento por parte de los procesadores de datos.
|
Cuando no tenemos una necesidad legítima continua de procesar su información personal, la borraremos o la anonimizamos, o, si esto no es posible (por ejemplo, porque su información personal ha sido almacenada en archivos de respaldo), almacenaremos de manera segura su información personal y aislarlo de cualquier procesamiento posterior hasta que la eliminación sea posible.
|
||||||
|
|
||||||
**Cambios a esta política de privacidad**
|
|
||||||
|
|
||||||
Es posible que actualicemos nuestra política de privacidad de vez en cuando. Por lo tanto, se recomienda revisar esta página periódicamente para cualquier cambio. Le notificaremos de cualquier cambio publicando la nueva política de privacidad en esta página. Estos cambios entran en vigencia inmediatamente después de que se publiquen en esta página.
|
## 7. ¿CÓMO MANTENEMOS SU INFORMACIÓN SEGURA? ##
|
||||||
|
|
||||||
**Tu consentimiento**
|
**En resumen:** *Nuestro objetivo es proteger su información personal a través de un sistema de medidas de seguridad técnicas y organizativas.*
|
||||||
|
|
||||||
Al utilizar la aplicación, usted está dando su consentimiento para que procesemos su información según lo establecido en esta política de privacidad ahora y en la forma en que nosotros lo modifiquemos. "Procesamiento" significa usar cookies en una computadora/dispositivo de mano o usar o tocar información de cualquier manera, incluyendo, pero no limitado a, recolectar, almacenar, eliminar, usar, combinar y divulgar información, todas las cuales se llevarán a cabo en España. Si reside fuera de España, su información será transferida, procesada y almacenada allí según los estándares de privacidad de España.
|
Hemos implementado medidas de seguridad organizacionales y técnicas adecuadas diseñadas para proteger la seguridad de cualquier información personal que procesamos. Sin embargo, recuerde que no podemos garantizar que Internet sea 100% seguro. Aunque haremos nuestro mejor esfuerzo para proteger su información personal, la transmisión de información personal hacia y desde nuestros Sitios o Aplicaciones es bajo su propio riesgo. Solo debe acceder a los servicios dentro de un entorno seguro.
|
||||||
|
|
||||||
**Contáctenos**
|
|
||||||
|
|
||||||
Si tiene alguna pregunta o sugerencia sobre nuestra Política de privacidad, no dude en ponerse en contacto con nosotros: info ARROBA comunes PUNTO org.
|
## 8. ¿RECOPILAMOS INFORMACIÓN DE MENORES DE EDAD? ##
|
||||||
|
|
||||||
|
**En resumen:** *No recopilamos deliberadamente ni comercializamos datos de niños menores de 18 años.*
|
||||||
|
|
||||||
|
No solicitamos intencionalmente ni comercializamos datos de niños menores de 18 años. Al utilizar los Sitios o Aplicaciones, usted declara que tiene al menos 18 años o que es el padre o tutor de dicho menor y acepta el uso de los Sitios o Aplicaciones por parte de dicho menor dependiente. Si descubrimos que se ha recopilado información personal de usuarios menores de 18 años, desactivaremos la cuenta y tomaremos medidas razonables para eliminar dichos datos de inmediato de nuestros registros. Si toma conocimiento de cualquier información que hayamos recopilado de niños menores de 18 años, contáctenos a info@comunes.org.
|
||||||
|
|
||||||
|
|
||||||
|
## 9. ¿CUÁLES SON SUS DERECHOS DE PRIVACIDAD? ##
|
||||||
|
|
||||||
|
**En resumen:** *En algunas regiones, como el Unión Europea, tiene derechos que le permiten un mayor acceso y control sobre su información personal. Puede revisar, cambiar o cancelar su cuenta en cualquier momento.*
|
||||||
|
|
||||||
|
En algunas regiones (como la Unión Europea), usted tiene ciertos derechos bajo las leyes de protección de datos aplicables. Estos pueden incluir el derecho (i) de solicitar acceso y obtener una copia de su información personal, (ii) solicitar una rectificación o cancelación; (iii) restringir el procesamiento de su información personal; y (iv) si corresponde, a la portabilidad de datos. En determinadas circunstancias, también puede tener derecho a oponerse al procesamiento de su información personal. Para realizar dicha solicitud, utilice los datos de contacto que se proporcionan a continuación. Consideraremos y actuaremos en cualquier solicitud de acuerdo con las leyes de protección de datos aplicables.
|
||||||
|
|
||||||
|
Si confiamos en su consentimiento para procesar su información personal, tiene derecho a retirar su consentimiento en cualquier momento. Sin embargo, tenga en cuenta que esto no afectará la legalidad del procesamiento antes de su retirada.
|
||||||
|
|
||||||
|
Si usted reside en la Unión Europea y considera que estamos procesando ilegalmente su información personal, también tiene derecho a presentar una queja ante la autoridad de supervisión de protección de datos local. Puede encontrar sus datos de contacto aquí: [http://ec.europa.eu/justice/data-protection/bodies/authorities/index_en.htm](http://ec.europa.eu/justice/data-protection/bodies/authorities/index_en.htm)
|
||||||
|
|
||||||
|
### Información de la cuenta ###
|
||||||
|
|
||||||
|
Si en algún momento desea revisar o cambiar la información en su cuenta o cancelar su cuenta, puede:
|
||||||
|
|
||||||
|
- Inicie sesión en la configuración de su cuenta y actualice su cuenta de usuario.
|
||||||
|
|
||||||
|
Tras su solicitud para cancelar su cuenta, desactivaremos o eliminaremos su cuenta y la información de nuestras bases de datos activas. Sin embargo, parte de la información se puede conservar en nuestros archivos para evitar fraudes, solucionar problemas, ayudar con cualquier investigación, hacer cumplir nuestros Términos de uso y/o cumplir con los requisitos legales.
|
||||||
|
|
||||||
|
**Cookies y tecnologías similares:** La mayoría de los navegadores web están configurados para aceptar cookies por defecto. Si lo prefiere, generalmente puede elegir configurar su navegador para que elimine las cookies y rechace las cookies. Si elige eliminar las cookies o rechazar las cookies, esto podría afectar ciertas funciones o servicios de nuestros sitios o aplicaciones.
|
||||||
|
|
||||||
|
**Optar por no recibir notificaciones por correo electrónico:** Puede darse de baja de nuestros correos electrónicos de notificación en cualquier momento haciendo clic en el enlace para darse de baja en los correos electrónicos que le enviamos o poniéndose en contacto con nosotras/os utilizando los detalles que se proporcionan a continuación. A continuación, se le eliminará de estos correos electrónicos; sin embargo, aún así le enviaremos correos electrónicos relacionados con el servicio que son necesarios para la administración y el uso de su cuenta. Para optar por no participar, usted puede:
|
||||||
|
|
||||||
|
- Tener en cuenta sus preferencias cuando registra una cuenta en el sitio.
|
||||||
|
- Acceder a la configuración de tu cuenta y actualizar tus preferencias.
|
||||||
|
|
||||||
|
|
||||||
|
## 10. ¿LOS RESIDENTES DE CALIFORNIA TIENEN DERECHOS DE PRIVACIDAD ESPECÍFICOS? ##
|
||||||
|
|
||||||
|
**En resumen:** *Sí, si usted es residente de California, se le otorgan derechos específicos con respecto al acceso a su información personal.*
|
||||||
|
|
||||||
|
La Sección 1798.83 del Código Civil de California, también conocida como la ley "Shine The Light", permite a nuestros usuarios que son residentes de California solicitar y obtener de nosotras/os, una vez al año y sin cargo, información sobre categorías de información personal (si corresponde) que divulgado a terceros para fines de marketing directo y los nombres y direcciones de todos los terceros con quienes compartimos información personal en el año calendario inmediatamente anterior. Si usted es residente de California y desea realizar dicha solicitud, envíe su solicitud por escrito a nosotras/os utilizando la información de contacto que se proporciona a continuación.
|
||||||
|
|
||||||
|
Si eres menor de 18 años, resides en California y tienes una cuenta registrada en los Sitios o Aplicaciones, tienes el derecho de solicitar la eliminación de datos no deseados que publiques en los Sitios o Aplicaciones. Para solicitar la eliminación de dichos datos, contáctenos utilizando la información de contacto que se proporciona a continuación, e incluya la dirección de correo electrónico asociada a su cuenta y una declaración de que reside en California. Nos aseguraremos de que los datos no se muestren públicamente en los Sitios o Aplicaciones, pero tenga en cuenta que es posible que los datos no se eliminen completa o completamente de nuestros sistemas.
|
||||||
|
|
||||||
|
|
||||||
|
## 11. ¿HACEMOS ACTUALIZACIONES DE ESTA POLÍTICA? ##
|
||||||
|
|
||||||
|
**En resumen:** *Sí, actualizaremos esta política según sea necesario para cumplir con las leyes pertinentes.*
|
||||||
|
|
||||||
|
Podemos actualizar esta política de privacidad de vez en cuando. La versión actualizada se indicará con una fecha actualizada "Revisada" y la versión actualizada entrará en vigencia tan pronto como sea accesible. Si realizamos cambios sustanciales a esta política de privacidad, podemos notificarlo publicando de manera prominente un aviso de dichos cambios o enviándole directamente una notificación. Le recomendamos que revise esta política de privacidad con frecuencia para recibir información sobre cómo protegemos su información.
|
||||||
|
|
||||||
|
|
||||||
|
## 12. ¿CÓMO PUEDE CONTACTARNOS SOBRE ESTA POLÍTICA? ##
|
||||||
|
|
||||||
|
Si tiene preguntas o comentarios sobre esta política, puede enviarnos un correo electrónico a info@comunes.org o por correo postal a:
|
||||||
|
|
||||||
|
ASOCIACIÓN COMUNES
|
||||||
|
C/ COLÓN 21, 5º DCHA, ESC DCHA
|
||||||
|
03001 ALICANTE
|
||||||
|
ESPAÑA
|
||||||
|
|
||||||
|
Si tiene más preguntas o comentarios sobre nosotras/os o nuestras políticas, envíenos un correo electrónico a info@comunes.org o por correo postal a:
|
||||||
|
|
||||||
|
ASOCIACIÓN COMUNES
|
||||||
|
C/ COLÓN 21, 5º DCHA, ESC DCHA
|
||||||
|
03001 ALICANTE
|
||||||
|
ESPAÑA
|
||||||
|
|
|
||||||
|
|
@ -167,6 +167,7 @@
|
||||||
"Verifica tu dirección de correo": "Verify Your Email Address",
|
"Verifica tu dirección de correo": "Verify Your Email Address",
|
||||||
"Zonas vigiladas": "Monitored areas",
|
"Zonas vigiladas": "Monitored areas",
|
||||||
"En verde, las zonas vigiladas por nuestros usuari@s actualmente": "In green, the areas monitored by our users currently",
|
"En verde, las zonas vigiladas por nuestros usuari@s actualmente": "In green, the areas monitored by our users currently",
|
||||||
|
"Actualizado <1></1>.": "Updated <1></1>.",
|
||||||
"Actualizado <1></1>, último fuego detectado <3></3>.": "Updated <1></1>, last fire detected <3></3>.",
|
"Actualizado <1></1>, último fuego detectado <3></3>.": "Updated <1></1>, last fire detected <3></3>.",
|
||||||
"Información adicional sobre fuego": "Additional information about fire",
|
"Información adicional sobre fuego": "Additional information about fire",
|
||||||
"CO2emisions": "Did you know that wildfires <1>produce as much CO² as cars</1> and <3>about ⅕ of all our carbon emissions</3>?",
|
"CO2emisions": "Did you know that wildfires <1>produce as much CO² as cars</1> and <3>about ⅕ of all our carbon emissions</3>?",
|
||||||
|
|
@ -248,5 +249,7 @@
|
||||||
"Note: Cloud cover might obscure active fire detections.",
|
"Note: Cloud cover might obscure active fire detections.",
|
||||||
"Hay más información sobre un fuego":
|
"Hay más información sobre un fuego":
|
||||||
"There is more information about a fire",
|
"There is more information about a fire",
|
||||||
"esDecirTalDia": "That is on {{date}}"
|
"esDecirTalDia": "That is on {{date}}",
|
||||||
|
"Actualizando...": "Updating...",
|
||||||
|
"Alerta de fuego": "Alert of fire"
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -237,6 +237,7 @@
|
||||||
"Verifica tu dirección de correo",
|
"Verifica tu dirección de correo",
|
||||||
"Zonas vigiladas": "Zonas vigiladas",
|
"Zonas vigiladas": "Zonas vigiladas",
|
||||||
"En verde, las zonas vigiladas por nuestros usuari@s actualmente": "En verde, las zonas vigiladas por nuestros usuari@s actualmente",
|
"En verde, las zonas vigiladas por nuestros usuari@s actualmente": "En verde, las zonas vigiladas por nuestros usuari@s actualmente",
|
||||||
|
"Actualizado <1></1>.": "Actualizado <1></1>.",
|
||||||
"Actualizado <1></1>, último fuego detectado <3></3>.": "Actualizado <1></1>, último fuego detectado <3></3>.",
|
"Actualizado <1></1>, último fuego detectado <3></3>.": "Actualizado <1></1>, último fuego detectado <3></3>.",
|
||||||
"Tomamos nota, ¡gracias por colaborar!":
|
"Tomamos nota, ¡gracias por colaborar!":
|
||||||
"Tomamos nota, ¡gracias por colaborar!",
|
"Tomamos nota, ¡gracias por colaborar!",
|
||||||
|
|
@ -333,5 +334,7 @@
|
||||||
"Nota: Las nubes pueden entorpecer la detección de fuegos activos.",
|
"Nota: Las nubes pueden entorpecer la detección de fuegos activos.",
|
||||||
"Hay más información sobre un fuego":
|
"Hay más información sobre un fuego":
|
||||||
"Hay más información sobre un fuego",
|
"Hay más información sobre un fuego",
|
||||||
"esDecirTalDia": "Es decir el {{date}}"
|
"esDecirTalDia": "Es decir el {{date}}",
|
||||||
|
"Actualizando...": "Actualizando...",
|
||||||
|
"Alerta de fuego": "Alerta de fuego"
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -31,6 +31,7 @@
|
||||||
"loginStyle": "popup"
|
"loginStyle": "popup"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"fcmApiToken": "",
|
||||||
"proxies_count": 0,
|
"proxies_count": 0,
|
||||||
"ironPassword": "SomePasswordAlLeast32LongYouKnowHowToCount",
|
"ironPassword": "SomePasswordAlLeast32LongYouKnowHowToCount",
|
||||||
"internalApiToken": "someSimilarPasswordForUseOurAPIinternally",
|
"internalApiToken": "someSimilarPasswordForUseOurAPIinternally",
|
||||||
|
|
|
||||||
|
|
@ -57,21 +57,23 @@ async function encAndDec(obj) {
|
||||||
}
|
}
|
||||||
|
|
||||||
describe('url encoding', () => {
|
describe('url encoding', () => {
|
||||||
it('should encrypt and dcrypt basic objects', async () => {
|
it('should encrypt and dcrypt basic objects', async (done) => {
|
||||||
const obj = { lat: 2 };
|
const obj = { lat: 2 };
|
||||||
const sealed = await urlEnc.encrypt(obj);
|
const sealed = await urlEnc.encrypt(obj);
|
||||||
const unsealed = await urlEnc.decrypt(sealed);
|
const unsealed = await urlEnc.decrypt(sealed);
|
||||||
chai.expect(unsealed).to.deep.equal(obj);
|
chai.expect(unsealed).to.deep.equal(obj);
|
||||||
|
done();
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should encrypt and dcrypt objects with date', async () => {
|
it('should encrypt and dcrypt objects with date', async (done) => {
|
||||||
const obj = { lat: 40.234503, lon: -3.350386, when: (new Date()).toString() };
|
const obj = { lat: 40.234503, lon: -3.350386, when: (new Date()).toString() };
|
||||||
const sealed = await urlEnc.encrypt(obj);
|
const sealed = await urlEnc.encrypt(obj);
|
||||||
const unsealed = await urlEnc.decrypt(sealed);
|
const unsealed = await urlEnc.decrypt(sealed);
|
||||||
chai.expect(unsealed).to.deep.equal(obj);
|
chai.expect(unsealed).to.deep.equal(obj);
|
||||||
|
done();
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should encrypt complete objects', async () => {
|
it('should encrypt complete objects', async (done) => {
|
||||||
const obj = {
|
const obj = {
|
||||||
ourid: {
|
ourid: {
|
||||||
type: 'Point',
|
type: 'Point',
|
||||||
|
|
@ -105,13 +107,15 @@ describe('url encoding', () => {
|
||||||
chai.expect(sealed).to.not.equal(sealed2);
|
chai.expect(sealed).to.not.equal(sealed2);
|
||||||
chai.expect(sealed).to.not.include('/');
|
chai.expect(sealed).to.not.include('/');
|
||||||
chai.expect(sealed).to.not.include('%');
|
chai.expect(sealed).to.not.include('%');
|
||||||
|
done();
|
||||||
});
|
});
|
||||||
|
|
||||||
// This fails because Date is return as String (not as Date) and because _id
|
// This fails because Date is return as String (not as Date) and because _id
|
||||||
it('should encrypt and dcrypt collection objects', async () => {
|
it('should encrypt and dcrypt collection objects', async (done) => {
|
||||||
seeder(ActiveFiresCollection, firesSeed());
|
seeder(ActiveFiresCollection, firesSeed());
|
||||||
const obj = ActiveFiresCollection.findOne();
|
const obj = ActiveFiresCollection.findOne();
|
||||||
encAndDec(obj);
|
encAndDec(obj);
|
||||||
|
done();
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should decrypt node-red enc objects', async () => {
|
it('should decrypt node-red enc objects', async () => {
|
||||||
|
|
@ -120,7 +124,7 @@ describe('url encoding', () => {
|
||||||
// console.log(unsealed);
|
// console.log(unsealed);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should encrypt and dcrypt Accounts hashes', async () => {
|
it('should encrypt and dcrypt Accounts hashes', async (done) => {
|
||||||
const token = Accounts._generateStampedLoginToken();
|
const token = Accounts._generateStampedLoginToken();
|
||||||
const obj = Accounts._hashStampedToken(token);
|
const obj = Accounts._hashStampedToken(token);
|
||||||
console.log(obj);
|
console.log(obj);
|
||||||
|
|
@ -129,13 +133,15 @@ describe('url encoding', () => {
|
||||||
const w = unsealed.when;
|
const w = unsealed.when;
|
||||||
unsealed.when = new Date(w);
|
unsealed.when = new Date(w);
|
||||||
chai.expect(unsealed).to.deep.equal(obj);
|
chai.expect(unsealed).to.deep.equal(obj);
|
||||||
|
done();
|
||||||
});
|
});
|
||||||
|
|
||||||
// limit 0 for no limit and test everything (and increase timeout)
|
// limit 0 for no limit and test everything (and increase timeout)
|
||||||
it('should encrypt and dcrypt all collection objects', async () => {
|
it('should encrypt and dcrypt all collection objects', async (done) => {
|
||||||
seeder(ActiveFiresCollection, firesSeed());
|
seeder(ActiveFiresCollection, firesSeed());
|
||||||
ActiveFiresCollection.find({}, { limit: 1000 }).fetch().forEach((obj) => {
|
ActiveFiresCollection.find({}, { limit: 1000 }).fetch().forEach((obj) => {
|
||||||
encAndDec(obj);
|
encAndDec(obj);
|
||||||
});
|
});
|
||||||
|
done();
|
||||||
}).timeout(5000);
|
}).timeout(5000);
|
||||||
});
|
});
|
||||||
|
|
|
||||||
293
test/rest.test.js
Normal file
293
test/rest.test.js
Normal file
|
|
@ -0,0 +1,293 @@
|
||||||
|
/* eslint-disable import/no-absolute-path */
|
||||||
|
/* eslint-env mocha */
|
||||||
|
/* eslint-disable func-names, prefer-arrow-callback */
|
||||||
|
|
||||||
|
import { chai } from 'meteor/practicalmeteor:chai';
|
||||||
|
import { Meteor } from 'meteor/meteor';
|
||||||
|
import { HTTP } from 'meteor/http';
|
||||||
|
|
||||||
|
const testPort = process.env.TEST_PORT;
|
||||||
|
function url(path) {
|
||||||
|
return `http://127.0.0.1:${testPort}/${path}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
describe('basic api v1 returns', () => {
|
||||||
|
it('should return uptime', async done =>
|
||||||
|
HTTP.get(url('api/v1/status/uptime'), {
|
||||||
|
data: {
|
||||||
|
}
|
||||||
|
}, (error, result) => {
|
||||||
|
chai.expect(error, null);
|
||||||
|
chai.expect(result.statusCode).to.equal(200);
|
||||||
|
chai.expect(typeof result.data.ms).to.equal('number');
|
||||||
|
done();
|
||||||
|
}));
|
||||||
|
|
||||||
|
it('should return last-fire', async done =>
|
||||||
|
HTTP.get(url('api/v1/status/active-fires-count'), {
|
||||||
|
data: {
|
||||||
|
}
|
||||||
|
}, (error, result) => {
|
||||||
|
chai.expect(error, null);
|
||||||
|
chai.expect(result.statusCode).equal(200);
|
||||||
|
chai.expect(typeof result.data.total).to.equal('number');
|
||||||
|
done();
|
||||||
|
}));
|
||||||
|
|
||||||
|
const token = Meteor.settings.private.internalApiToken;
|
||||||
|
|
||||||
|
it('should return fires in some location', async done =>
|
||||||
|
HTTP.get(url(`api/v1/fires-in/${token}/38.736946/-9.142685/100`), {
|
||||||
|
data: {
|
||||||
|
}
|
||||||
|
}, (error, result) => {
|
||||||
|
chai.expect(error, null);
|
||||||
|
chai.expect(result.statusCode).equal(200);
|
||||||
|
chai.expect(typeof result.data.total).to.equal('number');
|
||||||
|
chai.expect(typeof result.data.real).to.equal('number');
|
||||||
|
done();
|
||||||
|
}));
|
||||||
|
|
||||||
|
it('should return full fires in some location', async done =>
|
||||||
|
HTTP.get(url(`api/v1/fires-in-full/${token}/38.736946/-9.142685/100`), {
|
||||||
|
data: {
|
||||||
|
}
|
||||||
|
}, (error, result) => {
|
||||||
|
chai.expect(error, null);
|
||||||
|
chai.expect(result.statusCode).equal(200);
|
||||||
|
chai.expect(typeof result.data.total).to.equal('number');
|
||||||
|
chai.expect(typeof result.data.real).to.equal('number');
|
||||||
|
chai.expect(typeof result.data.falsePos).to.equal('object');
|
||||||
|
chai.expect(typeof result.data.industries).to.equal('object');
|
||||||
|
chai.expect(typeof result.data.fires).to.equal('object');
|
||||||
|
done();
|
||||||
|
}));
|
||||||
|
|
||||||
|
it('should not return full fires with some wrong token', async done =>
|
||||||
|
HTTP.get(url('api/v1/fires-in-full/token/38.736946/-9.142685/100'), {
|
||||||
|
data: {
|
||||||
|
}
|
||||||
|
}, (error, result) => {
|
||||||
|
chai.expect(error, null);
|
||||||
|
chai.expect(result.statusCode).equal(401);
|
||||||
|
done();
|
||||||
|
}));
|
||||||
|
|
||||||
|
it('should not return fires with some wrong token', async done =>
|
||||||
|
HTTP.get(url('api/v1/fires-in/token/38.736946/-9.142685/100'), {
|
||||||
|
data: {
|
||||||
|
}
|
||||||
|
}, (error, result) => {
|
||||||
|
chai.expect(error, null);
|
||||||
|
chai.expect(result.statusCode).equal(401);
|
||||||
|
done();
|
||||||
|
}));
|
||||||
|
|
||||||
|
it('should not return fires with some wrong distance', async done =>
|
||||||
|
HTTP.get(url(`api/v1/fires-in-full/${token}/38.736946/-9.142685/1100`), (error, result) => {
|
||||||
|
chai.expect(error, null);
|
||||||
|
chai.expect(result.statusCode).equal(400);
|
||||||
|
done();
|
||||||
|
}));
|
||||||
|
|
||||||
|
const mobileToken = 'user-mobile-firebase-token';
|
||||||
|
let testUserId;
|
||||||
|
let returnedSubsId;
|
||||||
|
const objId = new Meteor.Collection.ObjectID();
|
||||||
|
|
||||||
|
it('should create mobile users', async (done) => {
|
||||||
|
// this are removed in the test database but we are testing in dev/ci database
|
||||||
|
|
||||||
|
// Delete the test account if it's still present
|
||||||
|
// Meteor.users.remove({ fireBaseToken: mobileToken });
|
||||||
|
|
||||||
|
HTTP.post(url('api/v1/mobile/users'), {
|
||||||
|
data: {
|
||||||
|
token,
|
||||||
|
mobileToken,
|
||||||
|
lang: 'en'
|
||||||
|
}
|
||||||
|
}, (error, result) => {
|
||||||
|
chai.expect(error, null);
|
||||||
|
const jsendResult = result.data;
|
||||||
|
chai.expect(jsendResult.status).equal('success');
|
||||||
|
chai.expect(result.statusCode).equal(200);
|
||||||
|
chai.expect(jsendResult.data.upsertResult.numberAffected).to.equal(1);
|
||||||
|
chai.expect(jsendResult.data.mobileToken).to.equal(mobileToken);
|
||||||
|
chai.expect(jsendResult.data.lang).to.equal('en');
|
||||||
|
chai.expect(typeof jsendResult.data.username).to.equal('string');
|
||||||
|
testUserId = jsendResult.data.userId;
|
||||||
|
chai.expect(typeof testUserId).to.equal('string');
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should not create mobile users with wrong token', async (done) => {
|
||||||
|
// this are removed in the test database but we are testing in dev/ci database
|
||||||
|
// Delete the test account if it's still present
|
||||||
|
// Meteor.users.remove({ fireBaseToken: mobileToken });
|
||||||
|
|
||||||
|
HTTP.post(url('api/v1/mobile/users'), {
|
||||||
|
data: {
|
||||||
|
token: 'wrong token',
|
||||||
|
mobileToken,
|
||||||
|
lang: 'en'
|
||||||
|
}
|
||||||
|
}, (error, result) => {
|
||||||
|
chai.expect(error, null);
|
||||||
|
chai.expect(result.statusCode).equal(401);
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
function addSubs(done) {
|
||||||
|
// this are removed in the test database but we are testing in dev/ci database
|
||||||
|
// Remove previous subs of this user
|
||||||
|
// Subscriptions.remove({ owner: testUserId });
|
||||||
|
// chai.expect(Subscriptions.find({ owner: testUserId }).count()).to.equal(0);
|
||||||
|
HTTP.post(url('api/v1/mobile/subscriptions'), {
|
||||||
|
data: {
|
||||||
|
token,
|
||||||
|
mobileToken,
|
||||||
|
id: objId._str,
|
||||||
|
lat: 3.106111,
|
||||||
|
lon: 53.5775,
|
||||||
|
distance: 100
|
||||||
|
}
|
||||||
|
}, (error, result) => {
|
||||||
|
chai.expect(error, null);
|
||||||
|
if (result.statusCode !== 200) console.log(result);
|
||||||
|
chai.expect(result.statusCode).equal(200);
|
||||||
|
const jsendResult = result.data;
|
||||||
|
chai.expect(jsendResult.status).equal('success');
|
||||||
|
returnedSubsId = jsendResult.data.subsId;
|
||||||
|
chai.expect(returnedSubsId.toString()).equal(objId._str);
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
it('should create mobile user subscriptions', async (done) => {
|
||||||
|
addSubs(done);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should not create mobile user subscriptions with wrong token', async (done) => {
|
||||||
|
HTTP.post(url('api/v1/mobile/subscriptions'), {
|
||||||
|
data: {
|
||||||
|
token: 'wrongOne',
|
||||||
|
mobileToken,
|
||||||
|
id: objId._str,
|
||||||
|
lat: 3.106111,
|
||||||
|
lon: 53.5775,
|
||||||
|
distance: 100
|
||||||
|
}
|
||||||
|
}, (error, result) => {
|
||||||
|
chai.expect(error, null);
|
||||||
|
if (result.statusCode !== 401) console.log(result);
|
||||||
|
chai.expect(result.statusCode).equal(401);
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should get all mobile user subscriptions', async (done) => {
|
||||||
|
HTTP.get(url(`api/v1/mobile/subscriptions/all/${token}/${mobileToken}`), {
|
||||||
|
}, (error, result) => {
|
||||||
|
chai.expect(error, null);
|
||||||
|
if (result.statusCode !== 200) console.log(result);
|
||||||
|
chai.expect(result.statusCode).equal(200);
|
||||||
|
const jsendResult = result.data;
|
||||||
|
chai.expect(jsendResult.status).equal('success');
|
||||||
|
chai.expect(typeof jsendResult.data.subscriptions).to.equal('object');
|
||||||
|
chai.expect(typeof jsendResult.data.count).to.equal('number');
|
||||||
|
if (jsendResult.data.count !== 1) console.log(result);
|
||||||
|
chai.expect(jsendResult.data.count).to.equal(1);
|
||||||
|
// console.log(JSON.stringify(jsendResult.data.subscriptions));
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
it('should remove mobile user subscriptions', async (done) => {
|
||||||
|
// this are removed in the test database but we are testing in dev/ci database
|
||||||
|
|
||||||
|
HTTP.del(url(`api/v1/mobile/subscriptions/${token}/${mobileToken}/${returnedSubsId}`), {
|
||||||
|
}, (error, result) => {
|
||||||
|
chai.expect(error, null);
|
||||||
|
if (result.statusCode !== 200) console.log(result);
|
||||||
|
chai.expect(result.statusCode).equal(200);
|
||||||
|
const jsendResult = result.data;
|
||||||
|
chai.expect(jsendResult.status).equal('success');
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should get all mobile user subscriptions after deletion', async (done) => {
|
||||||
|
HTTP.get(url(`api/v1/mobile/subscriptions/all/${token}/${mobileToken}`), {
|
||||||
|
}, (error, result) => {
|
||||||
|
chai.expect(error, null);
|
||||||
|
if (result.statusCode !== 200) console.log(result);
|
||||||
|
chai.expect(result.statusCode).equal(200);
|
||||||
|
const jsendResult = result.data;
|
||||||
|
chai.expect(jsendResult.status).equal('success');
|
||||||
|
chai.expect(typeof jsendResult.data.subscriptions).to.equal('object');
|
||||||
|
chai.expect(typeof jsendResult.data.count).to.equal('number');
|
||||||
|
if (jsendResult.data.count !== 0) console.log(result);
|
||||||
|
chai.expect(jsendResult.data.count).to.equal(0);
|
||||||
|
// console.log(JSON.stringify(jsendResult.data.subscriptions));
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should not remove mobile user subscriptions with wrong token', async (done) => {
|
||||||
|
HTTP.del(url(`api/v1/mobile/subscriptions/wrongOne/${mobileToken}/${returnedSubsId}`), {
|
||||||
|
}, (error, result) => {
|
||||||
|
chai.expect(error, null);
|
||||||
|
if (result.statusCode !== 401) console.log(result);
|
||||||
|
chai.expect(result.statusCode).equal(401);
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should not get mobile user subscriptions with wrong token', async (done) => {
|
||||||
|
HTTP.get(url(`api/v1/mobile/subscriptions/all/wrongOne/${mobileToken}`), {
|
||||||
|
}, (error, result) => {
|
||||||
|
chai.expect(error, null);
|
||||||
|
if (result.statusCode !== 401) console.log(result);
|
||||||
|
chai.expect(result.statusCode).equal(401);
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should get all mobile user subscriptions', async (done) => {
|
||||||
|
HTTP.get(url(`api/v1/mobile/subscriptions/all/${token}/${mobileToken}`), {
|
||||||
|
}, (error, result) => {
|
||||||
|
chai.expect(error, null);
|
||||||
|
// console.log(result);
|
||||||
|
if (result.statusCode !== 200) console.log(result);
|
||||||
|
chai.expect(result.statusCode).equal(200);
|
||||||
|
const jsendResult = result.data;
|
||||||
|
chai.expect(jsendResult.status).equal('success');
|
||||||
|
chai.expect(typeof jsendResult.data.subscriptions).to.equal('object');
|
||||||
|
chai.expect(typeof jsendResult.data.count).to.equal('number');
|
||||||
|
if (jsendResult.data.count !== 0) console.log(result);
|
||||||
|
chai.expect(jsendResult.data.count).to.equal(0);
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should del all mobile user subscriptions', async (done) => {
|
||||||
|
// Add subs
|
||||||
|
addSubs(() => {
|
||||||
|
HTTP.del(url(`api/v1/mobile/subscriptions/all/${token}/${mobileToken}`), {
|
||||||
|
}, (error, result) => {
|
||||||
|
chai.expect(error, null);
|
||||||
|
if (result.statusCode !== 200) console.log(result);
|
||||||
|
chai.expect(result.statusCode).equal(200);
|
||||||
|
const jsendResult = result.data;
|
||||||
|
chai.expect(jsendResult.status).equal('success');
|
||||||
|
chai.expect(typeof jsendResult.data.count).to.equal('number');
|
||||||
|
chai.expect(jsendResult.data.count).to.be.at.most(1);
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
Loading…
Add table
Add a link
Reference in a new issue