diff --git a/src/_helpers/generators.js b/src/_helpers/generators.js index df5b241..1ea7180 100644 --- a/src/_helpers/generators.js +++ b/src/_helpers/generators.js @@ -54,6 +54,18 @@ const scan = (fn) => const flow = (...reducers) => (input) => reducers.reduce((c, fn) => fn(c), input); +function* zip(...iterables) { + const iterators = iterables.map((iterable) => iterable[Symbol.iterator]()); + while (true) { + const results = iterators.map((iterator) => iterator.next()); + if (results.some((r) => r.done)) { + return; + } + + yield results.map((r) => r.value); + } +} + module.exports = { filter, map, @@ -62,4 +74,5 @@ module.exports = { flow, reduce, scan, + zip, }; diff --git a/src/models/PrivacyZone.js b/src/models/PrivacyZone.js new file mode 100644 index 0000000..0ba38e4 --- /dev/null +++ b/src/models/PrivacyZone.js @@ -0,0 +1,35 @@ +const mongoose = require('mongoose'); + +const pointSchema = new mongoose.Schema({ + type: { + type: String, + enum: ['Point'], + required: true, + }, + coordinates: { + type: [Number], + required: true, + }, +}); + +const schema = new mongoose.Schema( + { + center: { + type: pointSchema, + required: true, + }, + radius: { + type: Number, + required: true, + }, + name: String, + user: { type: mongoose.Schema.Types.ObjectId, ref: 'User' }, + }, + { timestamps: true }, +); + +class PrivacyZone extends mongoose.Model {} + +mongoose.model(PrivacyZone, schema); + +module.exports = PrivacyZone; diff --git a/src/models/Track.js b/src/models/Track.js index 738c5ee..c76e40d 100644 --- a/src/models/Track.js +++ b/src/models/Track.js @@ -131,12 +131,15 @@ class Track extends mongoose.Model { const trackData = TrackData.createFromPoints(points); await trackData.save(); - this.trackData = trackData._id; if (this.visible) { // TODO: create a distinct object with filtered data - this.publicTrackData = trackData._id; + const { author } = await this.populate('author').execPopulate(); + const publicPoints = await author.privatizeTrackPoints(points); + const publicTrackData = TrackData.createFromPoints(publicPoints); + await publicTrackData.save(); + this.publicTrackData = publicTrackData._id; } await this.save(); diff --git a/src/models/User.js b/src/models/User.js index 16efd55..bf78778 100644 --- a/src/models/User.js +++ b/src/models/User.js @@ -3,6 +3,9 @@ const uniqueValidator = require('mongoose-unique-validator'); const crypto = require('crypto'); const jwt = require('jsonwebtoken'); const secret = require('../config').secret; +const turf = require('turf'); +const { zip } = require('../_helpers/generators'); +const PrivacyZone = require('./PrivacyZone'); const schema = new mongoose.Schema( { @@ -84,6 +87,30 @@ class User extends mongoose.Model { image: this.image || 'https://static.productionready.io/images/smiley-cyrus.jpg', }; } + + async privatizeTrackPoints(points) { + const privacyZones = await PrivacyZone.find({ user: this._id }); + const centers = privacyZones.map((pz) => turf.point(pz.center.coordinates)); + const radii = privacyZones.map((pz) => pz.radius); + + const result = []; + for (const point of points) { + let skip = false; + const p = turf.point([point.longitude, point.latitude]); + for (const [center, radius] of zip(centers, radii)) { + const distanceMeters = turf.distance(p, center) * 1000 + console.log(distanceMeters); + if (distanceMeters <= radius) { + skip = true; + break; + } + } + if (!skip) { + result.push(point); + } + } + return result; + } } mongoose.model(User, schema); diff --git a/src/routes/api/index.js b/src/routes/api/index.js index 0433c69..7e752f9 100644 --- a/src/routes/api/index.js +++ b/src/routes/api/index.js @@ -4,6 +4,7 @@ router.use('/', require('./users')); router.use('/profiles', require('./profiles')); router.use('/tracks', require('./tracks')); router.use('/tags', require('./tags')); +router.use('/privacy-zones', require('./privacyZones')); router.use('/accounts', require('../../accounts/accounts.controller')); router.use(function (err, req, res, next) { diff --git a/src/routes/api/privacyZones.js b/src/routes/api/privacyZones.js new file mode 100644 index 0000000..e778abd --- /dev/null +++ b/src/routes/api/privacyZones.js @@ -0,0 +1,97 @@ +const router = require('express').Router(); +const mongoose = require('mongoose'); +const PrivacyZone = mongoose.model('PrivacyZone'); +const busboy = require('connect-busboy'); +const auth = require('../auth'); +const wrapRoute = require('../../_helpers/wrapRoute'); + +function preloadByParam(target, getValueFromParam) { + return async (req, res, next, paramValue) => { + try { + const value = await getValueFromParam(paramValue); + + if (!value) { + return res.sendStatus(404); + } + + req[target] = value; + return next(); + } catch (err) { + return next(err); + } + }; +} + +router.param( + 'privacyZone', + preloadByParam('privacyZone', (id) => PrivacyZone.findOne({ _id: id }).populate('user')), +); + +router.get( + '/', + auth.required, + wrapRoute(async (req, res) => { + const privacyZones = await PrivacyZone.find({ user: req.user._id }); + + return res.json({ + privacyZones, + }); + }), +); + +router.post( + '/', + auth.required, + wrapRoute(async (req, res) => { + const privacyZone = new PrivacyZone(req.body); + privacyZone.user = req.user._id; + await privacyZone.save(); + return res.json({ privacyZone }); + }), +); + +router.get( + '/:privacyZone', + auth.required, + wrapRoute(async (req, res) => { + if (!req.privacyZone.user._id.equals(req.user._id)) { + return res.sendStatus(403); + } + + return res.json({ privacyZone: req.privacyZone }); + }), +); + +router.put( + '/:privacyZone', + busboy(), + auth.required, + wrapRoute(async (req, res) => { + if (!req.privacyZone.user._id.equals(req.user.id)) { + return res.sendStatus(403); + } + + for (const key of ['center', 'radius', 'title']) { + if (key in req.body) { + req.privacyZone[key] = req.body[key] + } + } + + return res.json({ privacyZone: req.privacyZone}) + }), +); + +router.delete( + '/:privacyZone', + auth.required, + wrapRoute(async (req, res) => { + if (!req.privacyZone.user._id.equals(req.user.id)) { + return res.sendStatus(403); + } + + await req.privacyZone.remove(); + return res.sendStatus(204); + }), +); + +module.exports = router;