From b653fd2eaffb74715934eb0187a850f891905a9d Mon Sep 17 00:00:00 2001 From: Mike Fitzpatrick Date: Thu, 8 Mar 2018 14:42:01 -0500 Subject: [PATCH] Added cruising api endpoint support --- app.js | 2 + models/cruise.js | 303 ++++++++++++++++++++++++++++++++++++++++++++ modules/geocoder.js | 15 ++- routes/cruises.js | 206 ++++++++++++++++++++++++++++++ 4 files changed, 524 insertions(+), 2 deletions(-) create mode 100644 models/cruise.js create mode 100644 routes/cruises.js diff --git a/app.js b/app.js index ac137f1..66cbede 100644 --- a/app.js +++ b/app.js @@ -3,6 +3,7 @@ var logger = require('morgan'); var bodyParser = require('body-parser'); var auth = require('./routes/auth'); +var cruises = require('./routes/cruises'); var geocache = require('./routes/geocache'); var profiles = require('./routes/profiles'); var users = require('./routes/users'); @@ -20,6 +21,7 @@ app.use(function (req, res, next) { app.use('/auth', auth); +app.use('/cruises', cruises); app.use('/geocache', geocache); app.use('/profiles', profiles); app.use('/users', users); diff --git a/models/cruise.js b/models/cruise.js new file mode 100644 index 0000000..754d99d --- /dev/null +++ b/models/cruise.js @@ -0,0 +1,303 @@ +const Geos = require('../modules/geocoder'); +const Images = require('../modules/images'); +const Logger = require('../modules/logger'); +const Mongoose = require('mongoose'); + +const CruiseSchema = new Mongoose.Schema({ + order: { type: Number, default: 0 }, + location: { + address: { + street1: String, + street2: String, + locality: { + type: String, + index: true + }, + region: { + type: String, + index: true + }, + postal: { + type: String, + index: true + }, + country: { + type: String, + index: true + } + }, + loc: { + type: { + type: String, + default: 'Point' + }, + coordinates: [{ + type: Number, + default: [0, 0] + }] + } + }, + name: String, + pic: { + detail: { + type: String, + default: 'cruise/default_detail.png' + }, + thumb: { + type: String, + default: 'cruise/default_thumbnail.png' + } + }, + text: String +}); + +CruiseSchema.index({ 'location.loc': '2dsphere' }); + +CruiseSchema.pre('findOneAndUpdate', function (next) { + var cnt = 0 + var pic; + + if (this.details && this.details.pic) { + pic = this.details.pic; + cnt = cnt + (typeof pic.detail === 'object' ? 1 : 0) + (typeof pic.thumb === 'object' ? 1 : 0); + } + + if (cnt > 0) { + if (typeof pic.detail === 'object') { + Images.saveCruiseDetailImage(pic.detail, (err, filename) => { + if (err) { + Logger.error('[CruiseSchema.pre(save)] There was an error processing the cruise image. [' + err + ']', { error: err }); + } + + if (filename) { + pic.detail = filename; + cnt -= 1; + if (cnt === 0) next(); + } + }); + } + + if (typeof pic.thumb === 'object') { + Images.saveCruiseThumbnailImage(pic.thumb, (err, filename) => { + if (err) { + Logger.error('[CruiseSchema.pre(save)] There was an error processing the cruise image. [' + err + ']', { error: err }); + } + + if (filename) { + pic.thumb = filename; + cnt -= 1; + if (cnt === 0) next(); + } + }); + } + } else { + next(); + } +}); + +CruiseSchema.pre('save', function (next) { + var cnt = 0; + var pic; + + if (this.details && this.details.pic) { + pic = this.details.pic + cnt = cnt + (typeof pic.detail === 'object' ? 1 : 0) + (typeof pic.thumb === 'object' ? 1 : 0); + } + + if (cnt > 0) { + if (typeof pic.detail === 'object') { + Images.saveCruiseDetailImage(pic.detail, (err, filename) => { + if (err) { + Logger.error('[CruiseSchema.pre(save)] There was an error processing the cruise image. [' + err + ']', { error: err }); + } + + if (filename) { + pic.detail = filename; + cnt -= 1; + if (cnt === 0) next(); + } + }); + } + + if (typeof pic.thumb === 'object') { + Images.saveCruiseThumbnailImage(pic.thumb, (err, filename) => { + if (err) { + Logger.error('[CruiseSchema.pre(save)] There was an error processing the cruise image. [' + err + ']', { error: err }); + } + + if (filename) { + pic.thumb = filename; + cnt -= 1; + if (cnt === 0) next(); + } + }); + } + } else { + next(); + } +}); + +const CruiseModel = Mongoose.model('cruises', CruiseSchema); + +module.exports = { + + all: (e) => { + const promise = new Promise((resolve, reject) => { + CruiseModel + .find({}) + .sort({ order: 1 }) + .exec((err, result) => { + if (err) { + reject(err); + } + + if (result) { + resolve(result); + } + }); + }); + + promise.then((result) => { + e.emit('all', null, result); + }) + .catch((err) => { + e.emit('all', err, null); + }); + }, + + create: (e, cruises) => { + var count = cruises.length; + var errors = []; + var results = []; + const promise = new Promise((resolve, reject) => { + for (let i = 0; i < cruises.length; i++) { + var cruise = cruises[i]; + var cruiseInstance = new CruiseModel(cruise); + + cruiseInstance.save((err, result) => { + if (err) { + count -= 1; + errors.push({ + cruise: cruise, + error: err + }); + if (count === 0) { + reject({ results: results, errors: errors }); + } + } + + if (result) { + count -= 1; + results.push(result); + if (count === 0) { + resolve({ results: results, errors: errors }); + } + } + }); + + } + + }); + + promise.then((result) => { + e.emit('create', null, result); + }) + .catch((err) => { + e.emit('create', err, null); + }); + }, + + delete: (e, id) => { + const promise = new Promise((resolve, reject) => { + CruiseModel.remove({ _id: id }, (err, result) => { + if (err) { + reject(err); + } + + if (result) { + resolve(result); + } + }); + }); + + promise.then((result) => { + e.emit('delete', null, result); + }) + .catch((err) => { + e.emit('delete', err, null); + }); + }, + + find: (e, find) => { + const promise = new Promise((resolve, reject) => { + var query = CruiseModel + .find(find.find) + .skip(find.options.skip) + .limit(find.options.limit) + .sort(find.options.sort) + .select(find.select || '') + .exec((err, results) => { + if (err) { + reject(err); + } + + if (results) { + resolve(results); + } + }); + }); + + promise.then((result) => { + e.emit('find', null, result); + }) + .catch((err) => { + e.emit('find', err, null); + }); + }, + + get: (e, id) => { + const promise = new Promise((resolve, reject) => { + CruiseModel.findById(id, (err, result) => { + if (err) { + reject(err); + } + + if (result) { + resolve(result); + } + }); + }); + + promise.then((result) => { + e.emit('get', null, result); + }) + .catch((err) => { + e.emit('get', err, null); + }); + }, + + update: (e, id, cruise) => { + const promise = new Promise((resolve, reject) => { + CruiseModel.findOneAndUpdate( + { _id: id }, + { $set: cruise }, + { new: true }, + (err, result) => { + if (err) { + reject(err); + } + + if (result) { + resolve(result); + } + }); + }); + + promise.then((result) => { + e.emit('update', null, result); + }) + .catch((err) => { + e.emit('update', err, null); + }); + } +}; diff --git a/modules/geocoder.js b/modules/geocoder.js index 638139e..635d8fe 100644 --- a/modules/geocoder.js +++ b/modules/geocoder.js @@ -1,8 +1,17 @@ -var NodeGeocoder = require('node-geocoder'); +const NodeGeocoder = require('node-geocoder'); + +function addressToString (address) { + var string = ''; + string = address.address1 ? address.street1 + ',' : ''; + string = string + (address.locality ? ' ' + address.locality + ',' : ''); + string = string + (address.region ? ' ' + address.region : ''); + string = string + (address.postal ? ' ' + address.postal : ''); + string = string + (address.country ? ' ' + address.country : ''); + return string; +} var options = { provider: 'google', - // Optional depending on the providers httpAdapter: 'https', // Default apiKey: 'AIzaSyCvpBGztvxtRUNigOW9f0GXVRWlukJZsps', // for Mapquest, OpenCage, Google Premier @@ -12,6 +21,8 @@ var options = { var geocoder = NodeGeocoder(options); + +exports.addressToString = addressToString; exports.geocoder = geocoder; diff --git a/routes/cruises.js b/routes/cruises.js new file mode 100644 index 0000000..200d3ad --- /dev/null +++ b/routes/cruises.js @@ -0,0 +1,206 @@ +const Cruises = require('../models/cruise'); +const EventEmitter = require('events'); +const Express = require('express'); +const ParamStr = '/:limit?/:skip?/:locale?/:distance?'; +const Router = Express.Router(); +const Token = require('../modules/token'); + +function processQueryParams (params) { + var query = {}; + + if (params.locale) { + var geo = {}; // geocode locale + query['location.loc'] = { $near: geo }; + } + + return query; +} + +function update (req, res, next) { + Token.verifyThen(req.get('authorization'), 'update', (err, decoded) => { + if (err || (decoded && !decoded.hasPermission)) { + res.status(403).json({ message: 'User not authorized to perform this action.', err: err }); + return; + } + + if (decoded && decoded.hasPermission) { + var CruiseEvents = new EventEmitter(); + var id = req.params.id; + var data = req.body; + + if (!id || !data) { + res.status(500).json({ message: 'No cruise id or data specified.', err: err }); + return; + } + + CruiseEvents.once('update', (err, result) => { + if (err) { + res.status(500).json({message: 'Could not update cruise id: ' + id, err: err}); + } + + if (result) { + res.status(200).json(result); + } + }); + + Cruises.update(CruiseEvents, id, data); + } + }); +} + +Router.route('/find' + ParamStr) + .get((req, res) => { + Token.verifyThen(req.get('authorization'), 'view', (err, decoded) => { + if (err || (decoded && !decoded.hasPermission)) { + res.status(403).json({ message: 'User not authorized to perform this action.', err: err }); + return; + } + + if (decoded && decoded.hasPermission) { + var CruiseEvents = new EventEmitter(); + var find = processQueryParams(req.params); + + var query = { + find: find, + select: null, + options: { + limit: !isNaN(parseInt(req.params.limit)) ? parseInt(req.params.limit) : 0, + skip: !isNaN(parseInt(req.params.skip)) ? parseInt(req.params.skip) : 0, + sort: { 'order': 1 } + } + }; + + CruiseEvents.once('find', (err, result) => { + if (err) { + res.status(500).json({ message: 'There was an error getting the getting the cruises [' + err + ']', err: err }); + } + + if (result) { + res.status(200).json(result); + } + }); + + Cruises.find(CruiseEvents, query); + } + }); + }); + +Router.route('/list' + ParamStr) + .get((req, res) => { + Token.verifyThen(req.get('authorization'), 'view', (err, decoded) => { + if (err || (decoded && !decoded.hasPermission)) { + res.status(403).json({ message: 'User not authorized to perform this action.', err: err }); + return; + } + + if (decoded && decoded.hasPermission) { + var CruiseEvents = new EventEmitter(); + var find = processQueryParams(req.params); + + var query = { + find: find, + select: { order: 1, 'details.name': 1, 'details.pic.thumb': 1 }, + options: { + limit: (!isNaN(parseInt(req.params.limit)) ? parseInt(req.params.limit) : 0), + skip: (!isNaN(parseInt(req.params.skip)) ? parseInt(req.params.skip) : 0), + sort: { 'order': 1 } + } + }; + + CruiseEvents.once('find', (err, result) => { + if (err) { + res.status(500).json({ message: 'There was an error getting the cruise list [' + err + ']', err: err }); + } + + if (result) { + res.status(200).json(result); + } + }); + + Cruises.find(CruiseEvents, query); + } + }); + }); + +Router.route('/:id?') + .delete( (req, res) => { + Token.verifyThen(req.get('authorization'), 'delete', (err, decoded) => { + if (err || (decoded && !decoded.hasPermission)) { + res.status(403).json({ message: 'User not authorized to perform this action.', err: err }); + return; + } + + if (decoded && decoded.hasPermission) { + var CruiseEvents = new EventEmitter(); + var id = req.params.id; + + CruiseEvents.once('delete', (err, result) => { + if (err) { + res.status(500).json({message: 'Could not delete cruise id: ' + id, err: err}); + } + + if (result) { + res.status(204).json({}); + } + }); + + Cruises.delete(CruiseEvents, id); + } + }); + }) + .get( (req, res) => { + Token.verifyThen(req.get('authorization'), 'view', (err, decoded) => { + if (err || (decoded && !decoded.hasPermission)) { + res.status(403).json({ message: 'User not authorized to perform this action.', err: err }); + return; + } + + if (decoded && decoded.hasPermission) { + var CruiseEvents = new EventEmitter(); + var id = req.params.id || null; + var method = id ? 'get' : 'all'; + + CruiseEvents.once(method, (err, result) => { + if (err) { + res.status(500).json({ message: 'Could not get cruise' + (id ? '' : 's'), err: err }); + } + + if (result) { + res.status(200).json(result); + } + }); + + Cruises[method](CruiseEvents, id); + } + }); + }) + .patch( update ) + .post((req, res) => { + Token.verifyThen(req.get('authorization'), 'add', (err, decoded) => { + if (err || (decoded && !decoded.hasPermission)) { + res.status(403).json({ message: 'User not authorized to perform this action.', err: err }); + return; + } + + if (decoded && decoded.hasPermission) { + var CruiseEvents = new EventEmitter(); + var cruise = Array.isArray(req.body) ? req.body : [ req.body ]; + var multi = cruise.length > 1; + + CruiseEvents.once('create', (err, result) => { + if (err) { + res.status(500).json({ message: 'Could not create cruise' + (multi ? 's' : ''), err: err, cruise: cruise }); + } + + if (result) { + res.status(200).json(result); + } + }); + + Cruises.create(CruiseEvents, cruise); + } + }); + }) + .put( update ); + +module.exports = Router;