Added false positives management to website
This commit is contained in:
parent
379e72a9e7
commit
674c07460b
10 changed files with 192 additions and 29 deletions
7
imports/api/FalsePositives/FalsePositiveTypes.js
Normal file
7
imports/api/FalsePositives/FalsePositiveTypes.js
Normal file
|
|
@ -0,0 +1,7 @@
|
||||||
|
const types = {
|
||||||
|
industry: 'Es una industria (altos hornos, incineradora, etc)',
|
||||||
|
controled: 'Es una quema controlada',
|
||||||
|
falsealarm: 'Parece una falsa alarma'
|
||||||
|
};
|
||||||
|
|
||||||
|
export default types;
|
||||||
37
imports/api/FalsePositives/FalsePositives.js
Normal file
37
imports/api/FalsePositives/FalsePositives.js
Normal file
|
|
@ -0,0 +1,37 @@
|
||||||
|
/* eslint-disable consistent-return */
|
||||||
|
|
||||||
|
import { Meteor } from 'meteor/meteor';
|
||||||
|
import { Mongo } from 'meteor/mongo';
|
||||||
|
import SimpleSchema from 'simpl-schema';
|
||||||
|
import { defaultCreatedAt, defaultUpdateAt } from '/imports/api/Utility/Utils.js';
|
||||||
|
import LocationSchema from '/imports/api/Utility/LocationSchema.js';
|
||||||
|
|
||||||
|
const FalsePositives = new Mongo.Collection('falsePositives', { idGeneration: 'MONGO' });
|
||||||
|
|
||||||
|
FalsePositives.allow({
|
||||||
|
insert: () => false,
|
||||||
|
update: () => false,
|
||||||
|
remove: () => false
|
||||||
|
});
|
||||||
|
|
||||||
|
FalsePositives.deny({
|
||||||
|
insert: () => true,
|
||||||
|
update: () => true,
|
||||||
|
remove: () => true
|
||||||
|
});
|
||||||
|
|
||||||
|
FalsePositives.schema = new SimpleSchema({
|
||||||
|
geo: LocationSchema,
|
||||||
|
fireId: { type: Meteor.Collection.ObjectID, optional: true, blackbox: true },
|
||||||
|
chatId: { type: Number, optional: true }, // only in 'telegram' type
|
||||||
|
owner: String,
|
||||||
|
type: String,
|
||||||
|
when: Date,
|
||||||
|
whendateformat: String,
|
||||||
|
createdAt: defaultCreatedAt,
|
||||||
|
updatedAt: defaultUpdateAt
|
||||||
|
});
|
||||||
|
|
||||||
|
FalsePositives.attachSchema(FalsePositives.schema);
|
||||||
|
|
||||||
|
export default FalsePositives;
|
||||||
53
imports/api/FalsePositives/methods.js
Normal file
53
imports/api/FalsePositives/methods.js
Normal file
|
|
@ -0,0 +1,53 @@
|
||||||
|
import { Meteor } from 'meteor/meteor';
|
||||||
|
import { check } from 'meteor/check';
|
||||||
|
import moment from 'moment';
|
||||||
|
import FalsePositives from './FalsePositives';
|
||||||
|
import FalsePositiveTypes from './FalsePositiveTypes';
|
||||||
|
import Fires from '../Fires/Fires';
|
||||||
|
import rateLimit from '../../modules/rate-limit';
|
||||||
|
|
||||||
|
Meteor.methods({
|
||||||
|
'falsePositives.insert': function falsePositivesInsert(fireId, keyType) {
|
||||||
|
check(fireId, Meteor.Collection.ObjectID);
|
||||||
|
check(keyType, String);
|
||||||
|
const type = FalsePositiveTypes[keyType];
|
||||||
|
check(type, String);
|
||||||
|
if (!this.userId) {
|
||||||
|
throw new Meteor.Error('500', 'Regístrate o inicia sesión para aportar información sobre este fuego');
|
||||||
|
}
|
||||||
|
check(this.userId, String);
|
||||||
|
const fire = Fires.findOne(fireId);
|
||||||
|
if (fire) {
|
||||||
|
const fireType = fire.type;
|
||||||
|
if (fireType === 'vecinal') {
|
||||||
|
throw new Meteor.Error('500', 'No se puede marcar este tipo de fuego');
|
||||||
|
}
|
||||||
|
const date = new Date();
|
||||||
|
const formated = moment(date).format('YYYYMMDD');
|
||||||
|
try {
|
||||||
|
const set = {
|
||||||
|
owner: this.userId,
|
||||||
|
type: keyType,
|
||||||
|
when: date,
|
||||||
|
whendateformat: formated,
|
||||||
|
fireId,
|
||||||
|
geo: fire.ourid
|
||||||
|
};
|
||||||
|
return FalsePositives.upsert({ geo: fire.ourid, owner: this.userId }, { $set: set }, { multi: false, upsert: true });
|
||||||
|
} catch (exception) {
|
||||||
|
console.log(exception);
|
||||||
|
throw new Meteor.Error('500', exception);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
throw new Meteor.Error('500', 'Fuego no encontrado');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
rateLimit({
|
||||||
|
methods: [
|
||||||
|
'falsePositives.insert'
|
||||||
|
],
|
||||||
|
limit: 5,
|
||||||
|
timeRange: 1000
|
||||||
|
});
|
||||||
|
|
@ -30,13 +30,11 @@ Meteor.publish('fireFromHash', function fireFromHash(fireEnc) {
|
||||||
// console.log(fireEnc);
|
// console.log(fireEnc);
|
||||||
const unsealed = Promise.await(urlEnc.decrypt(fireEnc));
|
const unsealed = Promise.await(urlEnc.decrypt(fireEnc));
|
||||||
const w = unsealed.when;
|
const w = unsealed.when;
|
||||||
// console.log(w);
|
|
||||||
unsealed.when = new Date(w);
|
unsealed.when = new Date(w);
|
||||||
// console.log(unsealed.when);
|
|
||||||
const c = unsealed.createdAt;
|
const c = unsealed.createdAt;
|
||||||
unsealed.createdAt = new Date(c);
|
unsealed.createdAt = !c ? new Date() : new Date(c);
|
||||||
const u = unsealed.updatedAt;
|
const u = unsealed.updatedAt;
|
||||||
unsealed.updatedAt = new Date(u);
|
unsealed.updatedAt = !u ? new Date() : new Date(u);
|
||||||
// console.log(unsealed);
|
// console.log(unsealed);
|
||||||
FiresCollection.schema.validate(unsealed);
|
FiresCollection.schema.validate(unsealed);
|
||||||
const fire = findFire(unsealed);
|
const fire = findFire(unsealed);
|
||||||
|
|
|
||||||
|
|
@ -9,7 +9,7 @@ Comments.config({
|
||||||
profile: 1
|
profile: 1
|
||||||
},
|
},
|
||||||
generateUsername: function genUser(user) {
|
generateUsername: function genUser(user) {
|
||||||
console.log(JSON.stringify(user));
|
// console.log(JSON.stringify(user));
|
||||||
// FIXME
|
// FIXME
|
||||||
return user.profile && user.profile.name && user.profile.name.first ? user.profile.name.first : '';
|
return user.profile && user.profile.name && user.profile.name.first ? user.profile.name.first : '';
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -24,3 +24,5 @@ import '../../api/Fires/server/publications';
|
||||||
|
|
||||||
import '../../api/SiteSettings/methods';
|
import '../../api/SiteSettings/methods';
|
||||||
import '../../api/SiteSettings/server/publications';
|
import '../../api/SiteSettings/server/publications';
|
||||||
|
|
||||||
|
import '../../api/FalsePositives/methods';
|
||||||
|
|
|
||||||
|
|
@ -6,6 +6,7 @@ import randomHex from 'crypto-random-hex';
|
||||||
import UserSubsToFiresCollection from '/imports/api/Subscriptions/Subscriptions';
|
import UserSubsToFiresCollection from '/imports/api/Subscriptions/Subscriptions';
|
||||||
import FireAlertsCollection from '/imports/api/FireAlerts/FireAlerts';
|
import FireAlertsCollection from '/imports/api/FireAlerts/FireAlerts';
|
||||||
import SiteSettings from '/imports/api/SiteSettings/SiteSettings';
|
import SiteSettings from '/imports/api/SiteSettings/SiteSettings';
|
||||||
|
import FalsePositives from '/imports/api/FalsePositives/FalsePositives';
|
||||||
|
|
||||||
Meteor.startup(() => {
|
Meteor.startup(() => {
|
||||||
// https://github.com/percolatestudio/meteor-migrations
|
// https://github.com/percolatestudio/meteor-migrations
|
||||||
|
|
@ -92,6 +93,17 @@ Meteor.startup(() => {
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
Migrations.add({
|
||||||
|
version: 6,
|
||||||
|
up: function falsePositiveIndexes() {
|
||||||
|
FalsePositives._ensureIndex({ chatId: 1 });
|
||||||
|
FalsePositives._ensureIndex({ owner: 1 });
|
||||||
|
FalsePositives._ensureIndex({ fireId: 1 });
|
||||||
|
FalsePositives._ensureIndex({ type: 1 });
|
||||||
|
FalsePositives._ensureIndex({ geo: '2dsphere' });
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
// Set createdAt in users & subs
|
// Set createdAt in users & subs
|
||||||
Migrations.migrateTo('latest');
|
Migrations.migrateTo('latest');
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -6,7 +6,9 @@ import React, { Fragment } from 'react';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import { withTracker } from 'meteor/react-meteor-data';
|
import { withTracker } from 'meteor/react-meteor-data';
|
||||||
import { translate, Trans } from 'react-i18next';
|
import { translate, Trans } from 'react-i18next';
|
||||||
|
import { FormGroup } from 'react-bootstrap';
|
||||||
import { Meteor } from 'meteor/meteor';
|
import { Meteor } from 'meteor/meteor';
|
||||||
|
import { Bert } from 'meteor/themeteorchef:bert';
|
||||||
import { Map, Circle } from 'react-leaflet';
|
import { Map, Circle } from 'react-leaflet';
|
||||||
import Blaze from 'meteor/gadicc:blaze-react-component';
|
import Blaze from 'meteor/gadicc:blaze-react-component';
|
||||||
import DefMapLayers from '/imports/ui/components/Maps/DefMapLayers';
|
import DefMapLayers from '/imports/ui/components/Maps/DefMapLayers';
|
||||||
|
|
@ -15,6 +17,7 @@ import FiresCollection from '/imports/api/Fires/Fires';
|
||||||
import FromNow from '/imports/ui/components/FromNow/FromNow';
|
import FromNow from '/imports/ui/components/FromNow/FromNow';
|
||||||
import { dateLongFormat } from '/imports/api/Common/dates';
|
import { dateLongFormat } from '/imports/api/Common/dates';
|
||||||
import '/imports/startup/client/comments';
|
import '/imports/startup/client/comments';
|
||||||
|
import FalsePositiveTypes from '/imports/api/FalsePositives/FalsePositiveTypes';
|
||||||
import './Fires.scss';
|
import './Fires.scss';
|
||||||
|
|
||||||
class Fire extends React.Component {
|
class Fire extends React.Component {
|
||||||
|
|
@ -38,14 +41,25 @@ class Fire extends React.Component {
|
||||||
return !(nextState.when === this.state.when);
|
return !(nextState.when === this.state.when);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
onTypeSelect(key) {
|
||||||
|
// console.log(key);
|
||||||
|
Meteor.call('falsePositives.insert', this.props.fire._id, key, (error) => {
|
||||||
|
if (error) {
|
||||||
|
// console.log(error);
|
||||||
|
Bert.alert(this.props.t(error.reason), 'danger');
|
||||||
|
} else {
|
||||||
|
Bert.alert(this.props.t('Tomamos nota, ¡gracias por colaborar!'), 'success');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const { loading, fire, t } = this.props;
|
const { loading, fire, t } = this.props;
|
||||||
if (fire && fire.when) {
|
if (fire && fire.when) {
|
||||||
this.dateLongFormat = dateLongFormat(fire.when);
|
this.dateLongFormat = dateLongFormat(fire.when);
|
||||||
// this.dateFromNow = () => (<FromNow {...this.props} />);
|
|
||||||
}
|
}
|
||||||
return (fire ?
|
return (fire ? (
|
||||||
(<div className="ViewFire">
|
<div className="ViewFire">
|
||||||
{!loading &&
|
{!loading &&
|
||||||
<Fragment>
|
<Fragment>
|
||||||
<h4 className="page-header">
|
<h4 className="page-header">
|
||||||
|
|
@ -82,7 +96,35 @@ class Fire extends React.Component {
|
||||||
{(fire.type === 'vecinal') &&
|
{(fire.type === 'vecinal') &&
|
||||||
<p><Trans>Fuego notificado por uno de nuestros usuarios/as <FromNow {...this.props} /></Trans></p>
|
<p><Trans>Fuego notificado por uno de nuestros usuarios/as <FromNow {...this.props} /></Trans></p>
|
||||||
}
|
}
|
||||||
{/* TODO: marcar tipo de fuego, industria, etc */}
|
|
||||||
|
{(fire.type !== 'vecinal') &&
|
||||||
|
<Fragment>
|
||||||
|
<h5>{t('¿No es un fuego forestal?')}</h5>
|
||||||
|
<div>
|
||||||
|
<Trans>Indícanos de que tipo de fuego se trata y ayúdanos así a mejorar nuestras notificaciones:</Trans>
|
||||||
|
</div>
|
||||||
|
<FormGroup>
|
||||||
|
<div className="btn-group">
|
||||||
|
<button className="btn btn-secondary btn-sm dropdown-toggle lang-selector" type="button" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
|
||||||
|
{'Elige un tipo'}
|
||||||
|
</button>
|
||||||
|
<div className="dropdown-menu">
|
||||||
|
{Object.keys(FalsePositiveTypes).map(key => (
|
||||||
|
<button
|
||||||
|
className="dropdown-item"
|
||||||
|
onClick={() => this.onTypeSelect(key)}
|
||||||
|
key={key}
|
||||||
|
type="button"
|
||||||
|
>
|
||||||
|
{FalsePositiveTypes[key]}
|
||||||
|
</button>
|
||||||
|
))
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</FormGroup>
|
||||||
|
</Fragment> }
|
||||||
|
|
||||||
<h4>{t('Comentarios')}</h4>
|
<h4>{t('Comentarios')}</h4>
|
||||||
<div className="comments-info">
|
<div className="comments-info">
|
||||||
{t('Puedes añadir un comentario si tienes información adicional sobre este fuego.')}
|
{t('Puedes añadir un comentario si tienes información adicional sobre este fuego.')}
|
||||||
|
|
@ -109,7 +151,7 @@ Fire.propTypes = {
|
||||||
t: PropTypes.func.isRequired,
|
t: PropTypes.func.isRequired,
|
||||||
loading: PropTypes.bool.isRequired,
|
loading: PropTypes.bool.isRequired,
|
||||||
when: PropTypes.instanceOf(Date),
|
when: PropTypes.instanceOf(Date),
|
||||||
fire: PropTypes.object // .isRequired
|
fire: PropTypes.object
|
||||||
};
|
};
|
||||||
|
|
||||||
Fire.defaultProps = {
|
Fire.defaultProps = {
|
||||||
|
|
@ -117,7 +159,7 @@ Fire.defaultProps = {
|
||||||
|
|
||||||
// export default translate([], { wait: true })(withTracker((props) => {
|
// export default translate([], { wait: true })(withTracker((props) => {
|
||||||
|
|
||||||
const FireContainer = withTracker(({ match, i18n }) => {
|
const FireContainer = withTracker(({ match }) => {
|
||||||
const fireEncrypt = match.params.id;
|
const fireEncrypt = match.params.id;
|
||||||
const subscription = Meteor.subscribe('fireFromHash', fireEncrypt);
|
const subscription = Meteor.subscribe('fireFromHash', fireEncrypt);
|
||||||
// console.log(`Subs ready: ${subscription.ready()}, fire: ${JSON.stringify(FiresCollection.findOne())}`);
|
// console.log(`Subs ready: ${subscription.ready()}, fire: ${JSON.stringify(FiresCollection.findOne())}`);
|
||||||
|
|
|
||||||
|
|
@ -36,3 +36,7 @@ h4.page-header {
|
||||||
margin-top: 10px;
|
margin-top: 10px;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#react-root > div > div.container > div > div.alert {
|
||||||
|
margin-top: 20px;
|
||||||
|
}
|
||||||
|
|
@ -232,5 +232,13 @@
|
||||||
"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",
|
||||||
"Datos actualizados <1></1>": "Datos actualizados <1></1>"
|
"Datos actualizados <1></1>": "Datos actualizados <1></1>",
|
||||||
|
"Tomamos nota, ¡gracias por colaborar! Tomamos nota, ¡gracias por colaborar!":
|
||||||
|
"Tomamos nota, ¡gracias por colaborar! Tomamos nota, ¡gracias por colaborar!",
|
||||||
|
"Indícanos de que tipo de fuego se trata y ayúdanos así a mejorar nuestras notificaciones:": "Indícanos de que tipo de fuego se trata y ayúdanos así a mejorar nuestras notificaciones:",
|
||||||
|
"¿No es un fuego forestal?": "¿No es un fuego forestal?",
|
||||||
|
"Regístrate o inicia sesión para aportar información sobre este fuego":
|
||||||
|
"Regístrate o inicia sesión para aportar información sobre este fuego",
|
||||||
|
"Fuego no encontrado":
|
||||||
|
"Fuego no encontrado"
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue