<commit_before>var mongoose = require('mongoose');
var e = require('../helpers/errorhandler');
var Group = mongoose.model('Group');
var User = mongoose.model('User');
var hash = require('../helpers/hash');
var mailer = require('../helpers/mailer');
var image = require('../helpers/image');
var socket = require('../socket');
var moment = require('moment');
var _ = require('underscore');

//Fields from user to populate into member array
var userFields = 'id name email verified hashedEmail gravatar image';
var validBodyKeys = ['name', 'description', 'startDate', 'endDate', 'public', 'minParticipants', 'maxParticipants', 'incrementDays'];
var validMemberKeys = ['status'];
var validAdminMemberKeys = ['admin'];

var filterValidKeys = function (o) {
    return _.pick(o, validBodyKeys);
};

exports.findAll = function (req, res) {
    Group.find(function (err, groups) {
        e(err, res, 'Error finding groups') || res.send(groups);
    });
};

exports.findByUser = function (req, res) {
    var user = req.user;
    var now = new Date();
    Group.find({startDate: { $gt: now } }).or([
        {'members.user': user},
        { public: true }
    ]).sort({ startDate: 'desc'}).exec(function (err, groups) {
        e(err, res, 'Error finding groups by user') || res.send(groups);
    });
};

exports.archive = function (req, res) {
    var user = req.user;
    Group.aggregate({
        $match: {
            startDate: { $lt: new Date() }
        }
    }, {
        $project: {
            key: 1,
            name: 1,
            startDate: 1,
            members: 1
        }
    }, {
        $unwind: "$members"
    }, {
        $sort: {
            startDate: -1
        }
    }, {
        $group: {
            _id: { id: "$_id", key: "$key" },
            name: {
                $last: "$name"
            },
            startDate: {
                $last: "$startDate"
            },
            participants: {
                $sum: {
                    $cond: [
                        { $eq: [ "$members.status", "Yes" ] },
                        1,
                        0
                    ]
                }
            }
        }
    }, {
        $group: {
            _id: "$_id.key",
            id: {
                $last: "$_id.id"
            },
            name: {
                $last: "$name"
            },
            startDate: {
                $last: "$startDate"
            },
            count: {
                $sum: 1
            },
            participants: {
                $avg: "$participants"
            }
        }
    }, {
        $sort: {
            startDate: -1
        }
    })
    .exec(function (err, results) {
        e(err, res, 'Error creating group archive') || res.send(results);
    });
};

exports.find = function (req, res) {
    res.send(req.group);
};

exports.create = function (req, res) {
    var group = filterValidKeys(req.body);
    group.createdBy = req.user.id;
    group.members = [
        { user: req.user.id, admin: true, status: 'Yes' }
    ];
    Group.create(group, function (err, group) {
        if (!e(err, res, 'Error creating group')) {
            socket.groupChanged(group);
            res.send(group);
        }

    });
};

exports.update = function (req, res) {
    _.extend(req.group, filterValidKeys(req.body));
    req.group.save(function (err, group, numRows) {
        if (!e(err, res, 'Error updating group')) {
            socket.groupChanged(group);
            res.send(group);
        }
    });
};

exports.delete = function (req, res) {
    req.group.remove(function (err) {
        e(err, res, 'Error removing group') || res.send();
    });
};

exports.updateMember = function (req, res) {
    var body = _.pick(req.body, validMemberKeys);
    var data = {};

    if (req.group.isAdmin(req.user)) {
        _.extend(body, _.pick(req.body, validAdminMemberKeys));
    }

    _.each(body, function (value, key) {
        key = 'members.$.' + key;
        data[key] = value;
    });

    Group.findOneAndUpdate({'members._id': req.params.memberId }, data).
        populate('members.user', userFields).
        populate('comments.user', userFields).
        exec(function (err, group) {
            if (!e(err, res, 'Error updating groupmember')) {
                if (!group) {
                    res.status(404).send({error: 'Error updating groupmember', message: 'Groupmember not found'});
                } else {
                    socket.groupChanged(group);
                    res.send(group);
                }
            }
        });
};

exports.deleteMember = function (req, res) {
    var group = req.group;
    Group.findOneAndUpdate({ _id: group.id }, { '$pull': { members: { _id: req.params.memberId } } }).
        populate('members.user', userFields).
        populate('comments.user', userFields).
        exec(function (err, group) {
            if (!e(err, res, 'Error removing member from group')) {
                socket.groupChanged(group);
                res.send(group);
            }
        }
    );
};

exports.join = function (req, res) {
    var group = req.group;

    if (!req.user) {
        if (!group.public) {
            return res.status(403).send({error: 'Error joining group', message: 'Group is private'});
        }
        var email = req.body.email;
        if (!email) {
            return res.status(500).send({error: 'Error joining group', message: 'Email missing'});
        }

        User.create({ email: email }, function (err, user) {
            if (!e(err, res, 'Error creating user')) {
                mailer.sendRegistrationMail(user);
                addUserToGroup(req, res, group, user);
            }
        });
    } else {
        addUserToGroup(req, res, group, req.user, 'Yes');
    }
};

var addUserToGroup = function (req, res, group, user, status) {
    Group.find({ _id: group.id, 'members.user': user.id}, function (err, groups) {
        if (!e(err, res, 'Error finding group to join')) {
            if (groups.length > 0) {
                res.status(409).send({error: 'Error when joining group', message: 'Already a member of this group'});
            } else {
                Group.findOneAndUpdate({_id: group.id }, { $addToSet: { members: { user: user.id, status: status } } }).
                    populate('members.user', userFields).
                    populate('comments.user', userFields).
                    exec(function (err, group) {
                        if (!e(err, res, 'Error joining group')) {
                            socket.groupChanged(group);
                            res.send(group);

                            if (req.user && req.user.id != user.id) {
                                mailer.sendInvitationMail(user, group, req.user);
                            }
                        }
                    }
                );
            }
        }
    });
};

exports.invite = function (req, res) {
    var email = req.body.email;
    var group = req.group;
    User.findOne({email: email}, function (err, user) {
        if (!e(err, res, 'Error finding invited user')) {
            if (!user) {
                User.create({ email: email }, function (err, user) {
                    if (!e(err, res, 'Error creating invited user')) {
                        addUserToGroup(req, res, group, user);
                    }
                });
            } else {
                addUserToGroup(req, res, group, user);
            }
        }
    });
};

exports.uploadThumbnail = function (req, res) {
    //TODO: Real validation
    if (!req.files || !req.files.thumbnail) {
        res.status(500).send('Missing thumbnail file');
        return;
    }

    var thumbnail = req.files.thumbnail;

    var format = thumbnail.headers['content-type'];

    if (format.indexOf('image') == -1) {
        res.status(500).send('Only images allowed');
        return;
    }

    if (thumbnail.size > 1000 * 1000) {
        res.status(500).send('File too big: ' + thumbnail.size);
        return;
    }

    image.thumbnail(thumbnail.path, { size: 100, remove: true }, function (err, buffer) {
        if (err) {
            if (err.code === -1) {
                res.status(500).send('Error resizing image');
            } else {
                res.status(500).send(err);
            }
        } else {
            var image = {
                data: "data:" + format + ";base64," + buffer.toString('base64'),
                format: format,
                size: thumbnail.size
            };

            Group.findOneAndUpdate({_id: req.group.id}, { image: image }).
                populate('members.user', userFields).
                populate('comments.user', userFields).
                exec(function (err, group) {
                    if (!e(err, res, 'Error uploading thumbnail')) {
                        socket.groupChanged(group);
                        res.send(group);
                    }
                });
        }
    });
};

exports.addComment = function (req, res) {
    var group = req.group;
    User.findOne({_id: req.body.userRefId}, function (err, user) {

        Group.findOneAndUpdate({ _id: group.id }, {
            $addToSet: {
                comments: {
                    text: req.body.text,
                    user: user.id
                }
            }
        }).
            populate('members.user', userFields).
            populate('comments.user', userFields).
            exec(function (err, group) {
                if (!e(err, res, 'Error adding comment')) {
                    socket.groupChanged(group);
                    res.send(group);
                }
            }
        );
    });
};

exports.deleteComment = function (req, res) {
    var group = req.group;
    Group.findOneAndUpdate({ _id: group.id }, { '$pull': { comments: { _id: req.params.commentId } } }).
        populate('comments.user', userFields).
        populate('members.user', userFields).
        exec(function (err, group) {
            if (!e(err, res, 'Error removing comment from group')) {
                socket.groupChanged(group);
                res.send(group);
            }
        }
    );
};

exports.increment = function (req, res) {
    var group = req.group.toJSON();
    delete group.id;
    delete group.createdAt;
    group.startDate = moment(group.startDate).add('days', group.incrementDays || 7).toDate();
    if (group.endDate) {
        group.endDate = moment(group.endDate).add('days', group.incrementDays || 7).toDate();
    }
    group.comments = [];
    group.members.forEach(function (member) {
        delete member.id;
        delete member.createdAt;
        delete member._hash;
        member.status = '';
        member.user = member.user.id;
    });
    Group.create(group, function (err, group) {
        if (!e(err, res, 'Error creating next group cycle')) {
            socket.groupChanged(group);
            res.send(group);
        }

    });
};

exports.remind = function (req, res) {
    req.group.members.forEach(function (member) {
        if (!member.status.match('(Yes|No)')) {
            mailer.sendReminderMail(member, req.group);
        }
    });
    res.send();
};

exports.status = function (req, res) {
    req.group.members.forEach(function (member) {
        if (member.status.match('(Yes|No|Maybe)')) {
            mailer.sendStatusMail(member, req.group);
        }
    });
    res.send();
};

exports.autoLogin = function (req, res) {
    if (req.body.hash) {
        var found = false;
        req.group.members.forEach(function (member) {
            if (req.body.hash == member._hash) {
                found = true;
                req.logIn(member.user, function (err) {
                    if (!e(err, res, 'Error auto logging in user')) {
                        res.send(req.user);
                    }
                });
            }
        });
        if (!found) {
            res.status(404).send({error: 'Error auto logging in user', message: 'Invalid hash'});
        }
    }
};

// param parsing
var fromParam = function (req, res, next, q) {
    var query = Group.findOne(q).sort({ startDate: 'desc'});
    /*if (req.user) {
     query.or([
     {'members.user': req.user },
     { 'public': true }
     ]);
     } else {
     query.where('public', true);
     }*/

    query.
        populate('members.user', userFields).
        populate('comments.user', userFields).
        exec(function (err, group) {
            if (!e(err, res, 'Error finding group')) {
                if (!group) {
                    next(new Error('Group not found'));
                } else {
                    req.group = group;
                    next();
                }
            }
        });

};

exports.fromKey = function (req, res, next, key) {
    var q;
    if (key.length < 24) {
        q = { key: key };
    } else {
        q = { _id: key };
    }
    fromParam(req, res, next, q);
};

exports.fromId = function (req, res, next, id) {
    fromParam(req, res, next, { _id: id });
};<commit_msg>Round average participants<commit_after>var mongoose = require('mongoose');
var e = require('../helpers/errorhandler');
var Group = mongoose.model('Group');
var User = mongoose.model('User');
var hash = require('../helpers/hash');
var mailer = require('../helpers/mailer');
var image = require('../helpers/image');
var socket = require('../socket');
var moment = require('moment');
var _ = require('underscore');

//Fields from user to populate into member array
var userFields = 'id name email verified hashedEmail gravatar image';
var validBodyKeys = ['name', 'description', 'startDate', 'endDate', 'public', 'minParticipants', 'maxParticipants', 'incrementDays'];
var validMemberKeys = ['status'];
var validAdminMemberKeys = ['admin'];

var filterValidKeys = function (o) {
    return _.pick(o, validBodyKeys);
};

exports.findAll = function (req, res) {
    Group.find(function (err, groups) {
        e(err, res, 'Error finding groups') || res.send(groups);
    });
};

exports.findByUser = function (req, res) {
    var user = req.user;
    var now = new Date();
    Group.find({startDate: { $gt: now } }).or([
        {'members.user': user},
        { public: true }
    ]).sort({ startDate: 'desc'}).exec(function (err, groups) {
        e(err, res, 'Error finding groups by user') || res.send(groups);
    });
};

exports.archive = function (req, res) {
    var user = req.user;
    Group.aggregate({
        $match: {
            startDate: { $lt: new Date() }
        }
    }, {
        $project: {
            key: 1,
            name: 1,
            startDate: 1,
            members: 1
        }
    }, {
        $unwind: "$members"
    }, {
        $sort: {
            startDate: -1
        }
    }, {
        $group: {
            _id: { id: "$_id", key: "$key" },
            name: {
                $last: "$name"
            },
            startDate: {
                $last: "$startDate"
            },
            participants: {
                $sum: {
                    $cond: [
                        { $eq: [ "$members.status", "Yes" ] },
                        1,
                        0
                    ]
                }
            }
        }
    }, {
        $group: {
            _id: "$_id.key",
            id: {
                $last: "$_id.id"
            },
            name: {
                $last: "$name"
            },
            startDate: {
                $last: "$startDate"
            },
            count: {
                $sum: 1
            },
            participants: {
                $avg: "$participants"
            }
        }
    }, {
        $sort: {
            startDate: -1
        }
    })
    .exec(function (err, results) {
        for(var i=0; i<results.length;i++) {
            results[0].participants = Math.round(results[0].participants);
        }
        e(err, res, 'Error creating group archive') || res.send(results);
    });
};

exports.find = function (req, res) {
    res.send(req.group);
};

exports.create = function (req, res) {
    var group = filterValidKeys(req.body);
    group.createdBy = req.user.id;
    group.members = [
        { user: req.user.id, admin: true, status: 'Yes' }
    ];
    Group.create(group, function (err, group) {
        if (!e(err, res, 'Error creating group')) {
            socket.groupChanged(group);
            res.send(group);
        }

    });
};

exports.update = function (req, res) {
    _.extend(req.group, filterValidKeys(req.body));
    req.group.save(function (err, group, numRows) {
        if (!e(err, res, 'Error updating group')) {
            socket.groupChanged(group);
            res.send(group);
        }
    });
};

exports.delete = function (req, res) {
    req.group.remove(function (err) {
        e(err, res, 'Error removing group') || res.send();
    });
};

exports.updateMember = function (req, res) {
    var body = _.pick(req.body, validMemberKeys);
    var data = {};

    if (req.group.isAdmin(req.user)) {
        _.extend(body, _.pick(req.body, validAdminMemberKeys));
    }

    _.each(body, function (value, key) {
        key = 'members.$.' + key;
        data[key] = value;
    });

    Group.findOneAndUpdate({'members._id': req.params.memberId }, data).
        populate('members.user', userFields).
        populate('comments.user', userFields).
        exec(function (err, group) {
            if (!e(err, res, 'Error updating groupmember')) {
                if (!group) {
                    res.status(404).send({error: 'Error updating groupmember', message: 'Groupmember not found'});
                } else {
                    socket.groupChanged(group);
                    res.send(group);
                }
            }
        });
};

exports.deleteMember = function (req, res) {
    var group = req.group;
    Group.findOneAndUpdate({ _id: group.id }, { '$pull': { members: { _id: req.params.memberId } } }).
        populate('members.user', userFields).
        populate('comments.user', userFields).
        exec(function (err, group) {
            if (!e(err, res, 'Error removing member from group')) {
                socket.groupChanged(group);
                res.send(group);
            }
        }
    );
};

exports.join = function (req, res) {
    var group = req.group;

    if (!req.user) {
        if (!group.public) {
            return res.status(403).send({error: 'Error joining group', message: 'Group is private'});
        }
        var email = req.body.email;
        if (!email) {
            return res.status(500).send({error: 'Error joining group', message: 'Email missing'});
        }

        User.create({ email: email }, function (err, user) {
            if (!e(err, res, 'Error creating user')) {
                mailer.sendRegistrationMail(user);
                addUserToGroup(req, res, group, user);
            }
        });
    } else {
        addUserToGroup(req, res, group, req.user, 'Yes');
    }
};

var addUserToGroup = function (req, res, group, user, status) {
    Group.find({ _id: group.id, 'members.user': user.id}, function (err, groups) {
        if (!e(err, res, 'Error finding group to join')) {
            if (groups.length > 0) {
                res.status(409).send({error: 'Error when joining group', message: 'Already a member of this group'});
            } else {
                Group.findOneAndUpdate({_id: group.id }, { $addToSet: { members: { user: user.id, status: status } } }).
                    populate('members.user', userFields).
                    populate('comments.user', userFields).
                    exec(function (err, group) {
                        if (!e(err, res, 'Error joining group')) {
                            socket.groupChanged(group);
                            res.send(group);

                            if (req.user && req.user.id != user.id) {
                                mailer.sendInvitationMail(user, group, req.user);
                            }
                        }
                    }
                );
            }
        }
    });
};

exports.invite = function (req, res) {
    var email = req.body.email;
    var group = req.group;
    User.findOne({email: email}, function (err, user) {
        if (!e(err, res, 'Error finding invited user')) {
            if (!user) {
                User.create({ email: email }, function (err, user) {
                    if (!e(err, res, 'Error creating invited user')) {
                        addUserToGroup(req, res, group, user);
                    }
                });
            } else {
                addUserToGroup(req, res, group, user);
            }
        }
    });
};

exports.uploadThumbnail = function (req, res) {
    //TODO: Real validation
    if (!req.files || !req.files.thumbnail) {
        res.status(500).send('Missing thumbnail file');
        return;
    }

    var thumbnail = req.files.thumbnail;

    var format = thumbnail.headers['content-type'];

    if (format.indexOf('image') == -1) {
        res.status(500).send('Only images allowed');
        return;
    }

    if (thumbnail.size > 1000 * 1000) {
        res.status(500).send('File too big: ' + thumbnail.size);
        return;
    }

    image.thumbnail(thumbnail.path, { size: 100, remove: true }, function (err, buffer) {
        if (err) {
            if (err.code === -1) {
                res.status(500).send('Error resizing image');
            } else {
                res.status(500).send(err);
            }
        } else {
            var image = {
                data: "data:" + format + ";base64," + buffer.toString('base64'),
                format: format,
                size: thumbnail.size
            };

            Group.findOneAndUpdate({_id: req.group.id}, { image: image }).
                populate('members.user', userFields).
                populate('comments.user', userFields).
                exec(function (err, group) {
                    if (!e(err, res, 'Error uploading thumbnail')) {
                        socket.groupChanged(group);
                        res.send(group);
                    }
                });
        }
    });
};

exports.addComment = function (req, res) {
    var group = req.group;
    User.findOne({_id: req.body.userRefId}, function (err, user) {

        Group.findOneAndUpdate({ _id: group.id }, {
            $addToSet: {
                comments: {
                    text: req.body.text,
                    user: user.id
                }
            }
        }).
            populate('members.user', userFields).
            populate('comments.user', userFields).
            exec(function (err, group) {
                if (!e(err, res, 'Error adding comment')) {
                    socket.groupChanged(group);
                    res.send(group);
                }
            }
        );
    });
};

exports.deleteComment = function (req, res) {
    var group = req.group;
    Group.findOneAndUpdate({ _id: group.id }, { '$pull': { comments: { _id: req.params.commentId } } }).
        populate('comments.user', userFields).
        populate('members.user', userFields).
        exec(function (err, group) {
            if (!e(err, res, 'Error removing comment from group')) {
                socket.groupChanged(group);
                res.send(group);
            }
        }
    );
};

exports.increment = function (req, res) {
    var group = req.group.toJSON();
    delete group.id;
    delete group.createdAt;
    group.startDate = moment(group.startDate).add('days', group.incrementDays || 7).toDate();
    if (group.endDate) {
        group.endDate = moment(group.endDate).add('days', group.incrementDays || 7).toDate();
    }
    group.comments = [];
    group.members.forEach(function (member) {
        delete member.id;
        delete member.createdAt;
        delete member._hash;
        member.status = '';
        member.user = member.user.id;
    });
    Group.create(group, function (err, group) {
        if (!e(err, res, 'Error creating next group cycle')) {
            socket.groupChanged(group);
            res.send(group);
        }

    });
};

exports.remind = function (req, res) {
    req.group.members.forEach(function (member) {
        if (!member.status.match('(Yes|No)')) {
            mailer.sendReminderMail(member, req.group);
        }
    });
    res.send();
};

exports.status = function (req, res) {
    req.group.members.forEach(function (member) {
        if (member.status.match('(Yes|No|Maybe)')) {
            mailer.sendStatusMail(member, req.group);
        }
    });
    res.send();
};

exports.autoLogin = function (req, res) {
    if (req.body.hash) {
        var found = false;
        req.group.members.forEach(function (member) {
            if (req.body.hash == member._hash) {
                found = true;
                req.logIn(member.user, function (err) {
                    if (!e(err, res, 'Error auto logging in user')) {
                        res.send(req.user);
                    }
                });
            }
        });
        if (!found) {
            res.status(404).send({error: 'Error auto logging in user', message: 'Invalid hash'});
        }
    }
};

// param parsing
var fromParam = function (req, res, next, q) {
    var query = Group.findOne(q).sort({ startDate: 'desc'});
    /*if (req.user) {
     query.or([
     {'members.user': req.user },
     { 'public': true }
     ]);
     } else {
     query.where('public', true);
     }*/

    query.
        populate('members.user', userFields).
        populate('comments.user', userFields).
        exec(function (err, group) {
            if (!e(err, res, 'Error finding group')) {
                if (!group) {
                    next(new Error('Group not found'));
                } else {
                    req.group = group;
                    next();
                }
            }
        });

};

exports.fromKey = function (req, res, next, key) {
    var q;
    if (key.length < 24) {
        q = { key: key };
    } else {
        q = { _id: key };
    }
    fromParam(req, res, next, q);
};

exports.fromId = function (req, res, next, id) {
    fromParam(req, res, next, { _id: id });
};<|endoftext|>


<commit_before>import User from '../models/user'

/* eslint-disable no-param-reassign */
export default (req, accessToken, refreshToken, profile, done) => {
  console.log('===PROFILE===', profile.id, profile._json)
  const email = profile._json.emails[0].value
  const netID = email.split('@')[0]
  if (req.user) {
    // return User.findOne({ google: profile.id }, (findOneErr, existingUser) => {
    return User.findOne({ netID }, (findOneErr, existingUser) => {
      if (existingUser) {
        return done(null, false, { message: 'There is already a Google account that belongs to you. Sign in with that account or delete it, then link it with your current account.' })
      }
      return User.findById(req.user.id, (findByIdErr, user) => {
        const parsedEmail = profile._json.emails[0].value
        user.name = profile.displayName
        user.netID = parsedEmail.split('@')[0]
        user.email = parsedEmail
        user.google = profile.id
        user.tokens.push({ kind: 'google', accessToken })
        user.save((err) => {
          done(err, user, { message: 'Google account has been linked.' })
        })
      })
    })
  }
  return User.findOne({ netID }, (findByGoogleIdErr, existingUser) => {
    if (existingUser) return done(null, existingUser)
    return User.findOne({ netID }, (findByEmailErr, existingEmailUser) => {
      if (existingEmailUser) {
        return done(null, false, { message: 'There is already an account using this email address. Sign in to that account and link it with Google manually from Account Settings.' })
      }
      const parsedEmail = profile._json.emails[0].value
      const user = new User()

      user.name = profile.displayName
      // Simulate a netID by splitting the e-mail address. Genius!
      user.netID = parsedEmail.split('@')[0]
      user.email = parsedEmail
      user.google = profile.id
      user.tokens.push({ kind: 'google', accessToken })
      return user.save((err) => {
        done(err, user)
      })
    })
  })
}
/* eslint-enable no-param-reassign */
<commit_msg>Not sure what's going on.<commit_after>import User from '../models/user'

/* eslint-disable no-param-reassign */
export default (req, accessToken, refreshToken, profile, done) => {
  console.log('===PROFILE===', profile.id, profile._json)
  const email = profile._json.emails[0].value
  const netID = email.split('@')[0]
  if (req.user) {
    // return User.findOne({ google: profile.id }, (findOneErr, existingUser) => {
    return User.findOne({ netID }, (findOneErr, existingUser) => {
      if (existingUser) {
        console.log('existingUser', existingUser)
        return done(null, false, { message: 'There is already a Google account that belongs to you. Sign in with that account or delete it, then link it with your current account.' })
      }
      return User.findById(req.user.id, (findByIdErr, user) => {
        console.log('found by id', req.user.id, user)
        const parsedEmail = profile._json.emails[0].value
        user.name = profile.displayName
        user.netID = parsedEmail.split('@')[0]
        user.email = parsedEmail
        user.google = profile.id
        user.tokens.push({ kind: 'google', accessToken })
        user.save((err) => {
          done(err, user, { message: 'Google account has been linked.' })
        })
      })
    })
  }
  return User.findOne({ netID }, (findByGoogleIdErr, existingUser) => {
    if (existingUser) {
      console.log('existingUser', existingUser)
      return done(null, existingUser)
    }
    return User.findOne({ netID }, (findByEmailErr, existingEmailUser) => {
      if (existingEmailUser) {
        console.log('existingEmailUser', existingEmailUser)
        return done(null, false, { message: 'There is already an account using this email address. Sign in to that account and link it with Google manually from Account Settings.' })
      }
      const parsedEmail = profile._json.emails[0].value
      const user = new User()

      user.name = profile.displayName
      // Simulate a netID by splitting the e-mail address. Genius!
      user.netID = parsedEmail.split('@')[0]
      user.email = parsedEmail
      user.google = profile.id
      user.tokens.push({ kind: 'google', accessToken })
      console.log('Created user profile??', user)
      return user.save((err) => {
        done(err, user)
      })
    })
  })
}
/* eslint-enable no-param-reassign */
<|endoftext|>


<commit_before>var Event       = require( '../../bookshelf/models/event' ),
    Promise     = require( 'bluebird' ),
    _           = require( 'lodash' ),
    url         = require( 'url' );
                  require( '../../bookshelf/collections/events' );
                  require( '../../bookshelf/models/ingredient' );
                  require( '../../bookshelf/models/tool' );
                  require( '../../bookshelf/models/tip' );
                  require( '../../bookshelf/models/step' );
                  require( '../../bookshelf/models/user' );

module.exports = function ( app, router ) {
  router.get( '/events', function ( req, res, next ) {
    db.collection( 'Events' ).fetchEvents()
    .then( function ( coll ) {
      res.json( coll.toJSON() );
    });
  });

  router.get( '/events/:eventid', function ( req, res, next ) {
    var eventid = req.params.eventId;

    if ( !_.isNumber( eventid ) ) {
      eventid = url.parse( req.url, true ).query.eventId;
    }

    if ( _.isNumber( eventid ) ) {
      db.model( 'Event' ).fetchEventbyId( eventid )
      .then( function ( event ) {
        if ( !event ) { res.sendStatus( 400 ); }
        else {
          event.eventDetails().then( function ( event ) {
            res.json( event );
          });
        }
      });
    } else {
      res.sendStatus( 400 );
    }
  });

  router.post( '/events/create', function ( req, res, next ) {
    var data        = req.body,
        chef        = req.user;

    // Our user is not logged in.
    if ( !chef ) { res.sendStatus( 400 ); return; }

    // Create our ingredients models and their promise
    var ingredients = _.map( data.ingredients, function ( ingredient ) {

      return db.model( 'Ingredient' ).newIngredient( { name: ingredient.name, buy_url: ingredient.buy_url } );
    });

    // Create our tools models and their promise
    var tools = _.map( data.tools, function ( tool ) {
      return db.model( 'Tool' ).newTool( tool );
    });

    // Create our steps models and their promise
    var steps = _.map( data.steps, function( step ) {
      return db.model( 'Step' ).newStep( {
        name: step.name,
        min_duration: step.min_duration,
        max_duration: step.max_duration,
        details: step.details,
      });
    });

    Promise.all( ingredients )
    .then( function ( coll ) {
      ingredients = coll;
      return Promise.all( tools );
    })
    .then ( function( coll ) {
      tools = coll;
      return Promise.all( steps );
    })
    .then( function ( coll ) {
      coll.forEach( function( step_model, index ) {

        var step = data.steps[index];

        var step_ingredients = _.reduce( step.ingredients, function ( memo, ingredient, index ) {
          if ( ingredient.value ) {
            memo.push( { ingredient_id: ingredients[index].get( 'id' ), qty: ingredient.qty } );
          }

          return memo;
        }, []);

        step_model.related( 'ingredients' ).attach( step_ingredients );

        var step_tools = _.reduce( step.tools, function ( memo, tool, index ) {
          if ( tool.value ) {
            memo.push( tools[index].get( 'id' ) );
          }

          return memo;
        }, []);

        step_model.related( 'tools' ).attach( step_tools );

        _.each( step.tips, function( tiptext ) {
          db.model( 'Tip' ).newTip( { text: tiptext } ).then( function( tip ) {
            step_model.related( 'tips' ).create( tip );
          });
        });
      });

      steps = coll;
      return db.model( 'Event' );
    })
    .then( function ( event ) {
      _.each( steps, function ( step ) {
          event.related( 'steps' ).create( step );
      });

      var ingrel = event.related( 'ingredients' );

      ingredients.forEach( function ( ingredient, index ) {
        event.related( 'ingredients' ).attach( {
          ingredient_id: ingredient.get( 'id' ),
          event_id: event.get( 'id' ),
          qty: data.ingredients[index].qty,
        });
      });

      event.related( 'tools' ).attach( tools );
      chef.related( 'events' ).create( event );

      process.verb( 'Responding with', { id: event.get( 'id' ), created_at: event.get( 'created_at' ) } );
      res.status(201).json( { id: event.get( 'id' ), created_at: event.get( 'created_at' ) } );
    });
  });
};
<commit_msg>[Fix] Fixes an error in adding an event.<commit_after>var Event       = require( '../../bookshelf/models/event' ),
    Promise     = require( 'bluebird' ),
    _           = require( 'lodash' ),
    url         = require( 'url' );
                  require( '../../bookshelf/collections/events' );
                  require( '../../bookshelf/models/ingredient' );
                  require( '../../bookshelf/models/tool' );
                  require( '../../bookshelf/models/tip' );
                  require( '../../bookshelf/models/step' );
                  require( '../../bookshelf/models/user' );

module.exports = function ( app, router ) {
  router.get( '/events', function ( req, res, next ) {
    db.collection( 'Events' ).fetchEvents()
    .then( function ( coll ) {
      res.json( coll.toJSON() );
    });
  });

  router.get( '/events/:eventid', function ( req, res, next ) {
    var eventid = req.params.eventId;

    if ( !_.isNumber( eventid ) ) {
      eventid = url.parse( req.url, true ).query.eventId;
    }

    if ( _.isNumber( eventid ) ) {
      db.model( 'Event' ).fetchEventbyId( eventid )
      .then( function ( event ) {
        if ( !event ) { res.sendStatus( 400 ); }
        else {
          event.eventDetails().then( function ( event ) {
            res.json( event );
          });
        }
      });
    } else {
      res.sendStatus( 400 );
    }
  });

  router.post( '/events/create', function ( req, res, next ) {
    var data        = req.body,
        chef        = req.user;

    // Our user is not logged in.
    if ( !chef ) { res.sendStatus( 400 ); return; }

    // Create our ingredients models and their promise
    var ingredients = _.map( data.ingredients, function ( ingredient ) {

      return db.model( 'Ingredient' ).newIngredient( { name: ingredient.name, buy_url: ingredient.buy_url } );
    });

    // Create our tools models and their promise
    var tools = _.map( data.tools, function ( tool ) {
      return db.model( 'Tool' ).newTool( tool );
    });

    // Create our steps models and their promise
    var steps = _.map( data.steps, function( step ) {
      return db.model( 'Step' ).newStep( {
        name: step.name,
        min_duration: step.min_duration,
        max_duration: step.max_duration,
        details: step.details,
      });
    });

    Promise.all( ingredients )
    .then( function ( coll ) {
      ingredients = coll;
      return Promise.all( tools );
    })
    .then ( function( coll ) {
      tools = coll;
      return Promise.all( steps );
    })
    .then( function ( coll ) {
      coll.forEach( function( step_model, index ) {

        var step = data.steps[index];

        var step_ingredients = _.reduce( step.ingredients, function ( memo, ingredient, index ) {
          if ( ingredient.value ) {
            memo.push( { ingredient_id: ingredients[index].get( 'id' ), qty: ingredient.qty } );
          }

          return memo;
        }, []);

        step_model.related( 'ingredients' ).attach( step_ingredients );

        var step_tools = _.reduce( step.tools, function ( memo, tool, index ) {
          if ( tool.value ) {
            memo.push( tools[index].get( 'id' ) );
          }

          return memo;
        }, []);

        step_model.related( 'tools' ).attach( step_tools );

        _.each( step.tips, function( tiptext ) {
          db.model( 'Tip' ).newTip( { text: tiptext } ).then( function( tip ) {
            step_model.related( 'tips' ).create( tip );
          });
        });
      });

      steps = coll;
      return db.model( 'Event' ).newEvent();
    })
    .then( function ( event ) {
      _.each( steps, function ( step ) {
          event.related( 'steps' ).create( step );
      });

      var ingrel = event.related( 'ingredients' );

      ingredients.forEach( function ( ingredient, index ) {
        event.related( 'ingredients' ).attach( {
          ingredient_id: ingredient.get( 'id' ),
          event_id: event.get( 'id' ),
          qty: data.ingredients[index].qty,
        });
      });

      event.related( 'tools' ).attach( tools );
      chef.related( 'events' ).create( event );

      process.verb( 'Responding with', { id: event.get( 'id' ), created_at: event.get( 'created_at' ) } );
      res.status(201).json( { id: event.get( 'id' ), created_at: event.get( 'created_at' ) } );
    });
  });
};
<|endoftext|>


<commit_before>const Router = require('express').Router;
const eH = require(__dirname + '/../lib/error_handler');
const request = require('superagent');
const EventEmitter = require('events');

var newsRouter = module.exports = exports = Router();

newsRouter.get('/news', (req, res) => {
  var trigger = new EventEmitter();
  request.get('https://gateway-a.watsonplatform.net/calls/data/GetNews?outputMode=' +
  'json&return=enriched.url.url,enriched.url.title,enriched.url.publicationDate.date,' +
  'enriched.url.image,enriched.url.text&start=now-45d&end=now&q.enriched.url.title=' +
  'A[seattle^gun]&q.enriched.url.text=A[seattle^gun^violence]&return=enriched.url.title&apikey=' +
  process.env.WATSON_API2)

  .end((err, data) => {
    console.log(data);
    if (err) return eH(err);
    trigger.emit('finished', data);
  });
  trigger.on('finished', (data) => {
    res.status(200).json(data);
  });
});
<commit_msg>news app change<commit_after>const Router = require('express').Router;
const eH = require(__dirname + '/../lib/error_handler');
const request = require('superagent');
const EventEmitter = require('events');

var newsRouter = module.exports = exports = Router();

newsRouter.get('/news', (req, res) => {
  var trigger = new EventEmitter();
  request.get('https://gateway-a.watsonplatform.net/calls/data/GetNews?outputMode=' +
  'json&return=enriched.url.url,enriched.url.title,enriched.url.publicationDate.date,' +
  'enriched.url.image,enriched.url.text&start=now-45d&end=now&q.enriched.url.title=' +
  'A[seattle^gun]&q.enriched.url.text=A[seattle^gun^violence]&return=enriched.url.title&apikey=' +
  process.env.WATSON_API)

  .end((err, data) => {
    console.log(data);
    if (err) return eH(err);
    trigger.emit('finished', data);
  });
  trigger.on('finished', (data) => {
    res.status(200).json(data);
  });
});
<|endoftext|>


<commit_before>var fs = require('fs');

var router;
var videosRoute = function(router){
	this.router = router;
	setVideosRoutes();
	return;
}

var setVideosRoutes = function(){
  this.router.get('/videos', function(req, res) {
		var response = [];
    fs.readdirSync(__base + '../videos/').filter(file => file.split('.')[1] === 'mp4').forEach(file => response.push("/videos/" + file));
    res.send(response);
  });
};

module.exports = videosRoute;
<commit_msg>Ajust /api/videos response to add video id<commit_after>var fs = require('fs');

var router;
var videosRoute = function(router){
	this.router = router;
	setVideosRoutes();
	return;
}

var setVideosRoutes = function(){
  this.router.get('/videos', function(req, res) {
		var response = [];
    fs.readdirSync(__base + '../videos/')
			.filter(file => file.split('.')[1] === 'mp4') //only mp4 video format
			.forEach(file => response.push({path: "/videos/" + file, id: file.split('.')[0]}));
    res.send(response);
  });
};

module.exports = videosRoute;
<|endoftext|>


<commit_before>/*jshint -W106 */// Disable underscore_case warnings: the API uses them
'use strict';

var _ = require( 'lodash' );
/*jshint -W079 */// Suppress warning about redefiniton of `Promise`
var Promise = require( 'bluebird' );
var api = require( '../../lib/api-query' );

var getTripsFromRoutes = require( './_get-trips-from-routes' );
var batchRequests = require( './_batch-requests' );

// 15 second cache expiry
var shortCache = require( './_short-cache' );

// 5 minute cache expiry
var longCache = require( './_long-cache' );

// Hard-coded route ID list (saves an otherwise useless DB round-trip)
var routes = {
  blue: [ '946_', '948_', 'Blue' ],
  orange: [ '903_', '913_', 'Orange' ],
  red: [ '931_', '933_', 'Red' ],
  green: [
    '810_', '813_', '823_', 'Green-B',
    '830_', '831_', 'Green-C',
    '842_', '840_', '851_', '852_', 'Green-D',
    '880_', '882_', 'Green-E'
  ]
}

/**
 * Get predictions for a specified route from cache, or initiate a new
 * API request to retrieve updated prediction data
 *
 * @private
 * @param  {String} routeId An MBTA API unique route_id string
 * @return {Promise}        A promise that will resolve to the route data
 */
function predictionsByRoute( routeId ) {
  var cacheKey = 'predictions-' + routeId;
  var routePredictionsPromise = shortCache.get( cacheKey );

  if ( ! routePredictionsPromise ) {
    routePredictionsPromise = api.predictionsByRoute( routeId );
    shortCache.set( cacheKey, routePredictionsPromise );
  }

  return routePredictionsPromise;
}

/**
 * Get an array of trips operating on a given line
 *
 * @param  {String} lineSlug One of "red," "orange," "blue"
 * @return {Promise}         A promise that resolves to an array of trips
 */
function predictionsByLine( lineSlug ) {
  var routeIds = routes[ lineSlug ];
  var errorMessage;

  if ( ! routeIds ) {
    // Fail out
    errorMessage = 'No routes available for the provided line (' + lineSlug + ')';
    return Promise.reject( new Error( errorMessage ) );
  }

  return batchRequests( routeIds, predictionsByRoute ).then( getTripsFromRoutes );
}

/**
 * Make (and cache) a request to the alerts API for the provided route
 *
 * @private
 * @param  {String} routeId An MBTA API unique route_id string
 * @return {Promise}        A promise that will resolve to the route's alert data
 */
function alertsByRoute( routeId ) {
  var cacheKey = 'alerts-' + routeId;
  var routeAlertsPromise = longCache.get( cacheKey);

  if ( ! routeAlertsPromise ) {
    routeAlertsPromise = api.alertsByRoute( routeId );
    shortCache.set( cacheKey, routeAlertsPromise );
  }

  return routeAlertsPromise;
}

/**
 * Get an array of alerts active for a given line
 *
 * @method alertsByLine
 * @param  {String} lineSlug One of "red", "orange," or "blue"
 * @return {Promise}         A promise that resolves to an array of trips
 */
function alertsByLine( lineSlug ) {
  var routeIds = routes[ lineSlug ];
  var errorMessage;

  if ( ! routeIds ) {
    // Fail out
    errorMessage = 'No routes available for the provided line (' + lineSlug + ')';
    return Promise.reject( new Error( errorMessage ) );
  }

  return batchRequests( routeIds, alertsByRoute ).then(function( alerts ) {
    return _.chain( alerts )
      // Extract alerts object from data (e.g. `[ { alerts: [ [Object], [Object] ] } ]`)
      .pluck( 'alerts' )
      // Flatten alerts from multiple routes into a single array
      .flatten()
      // De-dupe on ID (one alert can affect many lines, and may be returned twice)
      .unique( 'alert_id' )
      .value()
  })
}

module.exports = {
  predictionsByLine: predictionsByLine,
  alertsByLine: alertsByLine
};
<commit_msg>Remove empty alerts arrays from API response (fixes #33)<commit_after>/*jshint -W106 */// Disable underscore_case warnings: the API uses them
'use strict';

var _ = require( 'lodash' );
/*jshint -W079 */// Suppress warning about redefiniton of `Promise`
var Promise = require( 'bluebird' );
var api = require( '../../lib/api-query' );

var getTripsFromRoutes = require( './_get-trips-from-routes' );
var batchRequests = require( './_batch-requests' );

// 15 second cache expiry
var shortCache = require( './_short-cache' );

// 5 minute cache expiry
var longCache = require( './_long-cache' );

// Hard-coded route ID list (saves an otherwise useless DB round-trip)
var routes = {
  blue: [ '946_', '948_', 'Blue' ],
  orange: [ '903_', '913_', 'Orange' ],
  red: [ '931_', '933_', 'Red' ],
  green: [
    '810_', '813_', '823_', 'Green-B',
    '830_', '831_', 'Green-C',
    '842_', '840_', '851_', '852_', 'Green-D',
    '880_', '882_', 'Green-E'
  ]
}

/**
 * Get predictions for a specified route from cache, or initiate a new
 * API request to retrieve updated prediction data
 *
 * @private
 * @param  {String} routeId An MBTA API unique route_id string
 * @return {Promise}        A promise that will resolve to the route data
 */
function predictionsByRoute( routeId ) {
  var cacheKey = 'predictions-' + routeId;
  var routePredictionsPromise = shortCache.get( cacheKey );

  if ( ! routePredictionsPromise ) {
    routePredictionsPromise = api.predictionsByRoute( routeId );
    shortCache.set( cacheKey, routePredictionsPromise );
  }

  return routePredictionsPromise;
}

/**
 * Get an array of trips operating on a given line
 *
 * @param  {String} lineSlug One of "red," "orange," "blue"
 * @return {Promise}         A promise that resolves to an array of trips
 */
function predictionsByLine( lineSlug ) {
  var routeIds = routes[ lineSlug ];
  var errorMessage;

  if ( ! routeIds ) {
    // Fail out
    errorMessage = 'No routes available for the provided line (' + lineSlug + ')';
    return Promise.reject( new Error( errorMessage ) );
  }

  return batchRequests( routeIds, predictionsByRoute ).then( getTripsFromRoutes );
}

/**
 * Make (and cache) a request to the alerts API for the provided route
 *
 * @private
 * @param  {String} routeId An MBTA API unique route_id string
 * @return {Promise}        A promise that will resolve to the route's alert data
 */
function alertsByRoute( routeId ) {
  var cacheKey = 'alerts-' + routeId;
  var routeAlertsPromise = longCache.get( cacheKey);

  if ( ! routeAlertsPromise ) {
    routeAlertsPromise = api.alertsByRoute( routeId );
    shortCache.set( cacheKey, routeAlertsPromise );
  }

  return routeAlertsPromise;
}

/**
 * Get an array of alerts active for a given line
 *
 * @method alertsByLine
 * @param  {String} lineSlug One of "red", "orange," or "blue"
 * @return {Promise}         A promise that resolves to an array of trips
 */
function alertsByLine( lineSlug ) {
  var routeIds = routes[ lineSlug ];
  var errorMessage;

  if ( ! routeIds ) {
    // Fail out
    errorMessage = 'No routes available for the provided line (' + lineSlug + ')';
    return Promise.reject( new Error( errorMessage ) );
  }

  return batchRequests( routeIds, alertsByRoute ).then(function( alerts ) {
    return _.chain( alerts )
      // Extract alerts object from data (e.g. `[ { alerts: [ [Object], [Object] ] } ]`)
      .pluck( 'alerts' )
      // Flatten alerts from multiple routes into a single array
      .flatten()
      // Remove leftovers from empty alerts arrays
      .filter()
      // De-dupe on ID (one alert can affect many lines, and may be returned twice)
      .unique( 'alert_id' )
      .value()
  })
}

module.exports = {
  predictionsByLine: predictionsByLine,
  alertsByLine: alertsByLine
};
<|endoftext|>


<commit_before>/*****************************************************************************
 * Project:     ShipFast API Protection (Server)
 * File:        model.js
 * Original:    Created on 29 Sept 2017 by Simon Rigg
 * Copyright(c) 2002 - 2017 by CriticalBlue Ltd.
 *
 * This file contains the server business model.
 *****************************************************************************/

const Shipment = require('./shipment').Shipment
const SHIPMENT_STATE = require('./shipment').SHIPMENT_STATE
const MersenneTwister = require('mersenne-twister');
const Rand = new MersenneTwister(Date.now());

// Define various attributes for generating sample shipment data
const MIN_GRATUITY = 0
const MAX_GRATUITY = 30
const LAT_LNG_OFFSET = 0.5
const SHIPMENT_COUNT = 7
var PICKUP_LOCATIONS = [
    'ParcelFarce Depot 192',
    'Q&B DIY Centre',
    'Quidland',
    'Magnific Wines',
    'Stone & Waters Books',
    'Dobby\'s Gardens',
    'OfficeMean Suppliers'
]
var DELIVERY_LOCATIONS = [
    'Hugwarts School',
    'LetGo Land',
    '123 Fake Street',
    'Suspicious Whirring Blue Box',
    'Terok Nor Promenade',
    'Getwell Soon Hospital',
    'Llanfairpwllgwyngyll'
]
var SHIPMENT_DESCRIPTIONS = [
    'Bob\'s Consignment #58332',
    'Alice\'s Consignment #42981',
    'Eve\'s Consignment #91113',
    'Mary\'s Consignment #12125',
    'Joe\'s Consignment #17700',
    'Titus\' Consignment #77012',
    'Pete\'s Consignment #12589'
]

// The collection of shipments
var shipments = {}

// A function to shuffle an array in-place randomly
function shuffleArray(array) {
    for (var i = array.length - 1; i > 0; i--) {
        var j = Math.floor(Rand.random() * (i + 1))
        var temp = array[i]
        array[i] = array[j]
        array[j] = temp
    }
    return array
}

// A function to calculate and return the distance between two points
// FIXME: use Haversine Formula
function calculateDistance(originLatitude, originLongitude, destinationLatitude, destinationLongitude) {
    var lat = originLatitude - destinationLatitude
    var lon = originLongitude - destinationLongitude
    return Math.abs(Math.sqrt(lat*lat + lon*lon))
}

// Calculate and return the distance in miles between the two given points using the haversine formula
function calculateDistance(originLatitude, originLongitude, destinationLatitude, destinationLongitude) {

    var radiusEarth = 3961 // radius of Earth in miles
    var dLat = deg2rad(destinationLatitude - originLatitude)
    var dLon = deg2rad(destinationLongitude - originLongitude)
    var squareHalfChordLen = Math.sin(dLat/2) * Math.sin(dLat/2) + Math.cos(deg2rad(originLatitude))
        * Math.cos(deg2rad(destinationLatitude)) * Math.sin(dLon/2) * Math.sin(dLon/2)
    var angularRadDistance = 2 * Math.atan2(Math.sqrt(squareHalfChordLen), Math.sqrt(1 - squareHalfChordLen))
    var distance = radiusEarth * angularRadDistance;
    return distance;
}

// Convert the given degrees angle to radians
function deg2rad(deg) {
    return deg * (Math.PI/180)
}

// A function to populate this model with a collection of sample shipment data base on a given location
function populateShipments(originLatitude, originLongitude) {

    // Shuffle the pickup and delivery locations and the shipment descriptions to ensure these are different between sessions
    shuffleArray(PICKUP_LOCATIONS)
    shuffleArray(DELIVERY_LOCATIONS)
    shuffleArray(SHIPMENT_DESCRIPTIONS)

    // Generate the sample data
    for (i = 0; i < SHIPMENT_COUNT; i++) {
        var shipmentID = i + 1
        var description = SHIPMENT_DESCRIPTIONS[i]
        var gratuity = i % 2 == 0 ? Math.floor((Rand.random() * MAX_GRATUITY) + MIN_GRATUITY) : MIN_GRATUITY
        var pickupName = PICKUP_LOCATIONS[i]
        var pickupLatitude = originLatitude + ((Rand.random() * LAT_LNG_OFFSET) - LAT_LNG_OFFSET)
        var pickupLongitude = originLongitude + ((Rand.random() * LAT_LNG_OFFSET) - LAT_LNG_OFFSET)
        var deliveryName = DELIVERY_LOCATIONS[i]
        var deliveryLatitude = originLatitude + ((Rand.random() * LAT_LNG_OFFSET) - LAT_LNG_OFFSET)
        var deliveryLongitude = originLongitude + ((Rand.random() * LAT_LNG_OFFSET) - LAT_LNG_OFFSET)
        var shipment = new Shipment(shipmentID, description, gratuity,
            pickupName, pickupLatitude, pickupLongitude,
            deliveryName, deliveryLatitude, deliveryLongitude)
        shipments[shipmentID] = shipment
    }

    console.log('Generated Shipments:')
    console.log(shipments)
}

// A function to calculate and return the nearest shipment to a given location
const calculateNearestShipment = function(originLatitude, originLongitude) {

    // Ensure we've populated the model with some sample data for this session
    if (Object.keys(shipments).length == 0) {
        populateShipments(originLatitude, originLongitude)
    }

    // Iterate through the shipments and find the one closest to our given location
    var minDistance = Number.MAX_VALUE
    var nearestShipment
    Object.entries(shipments).forEach(
        ([shipmentID, shipment]) => {
            if (shipment.getState() == SHIPMENT_STATE.READY) {
                var distance = calculateDistance(originLatitude, originLongitude,
                    shipment.getPickupLatitude(), shipment.getPickupLongitude())
                if (distance < minDistance) {
                    minDistance = distance
                    nearestShipment = shipment
                }
            }
        }
    );
    return nearestShipment
}

// A function to calculate and return an array of shipments in a 'DELIVERED' state
const getDeliveredShipments = function() {

    var deliveredShipments = []
    Object.entries(shipments).forEach(
        ([shipmentID, shipment]) => {
            if (shipment.getState() == SHIPMENT_STATE.DELIVERED) {
                deliveredShipments.push(shipment)
            }
        }
    );
    return deliveredShipments
}

// A function to return the shipment with the given ID (or 'undefined' if not found)
const getShipment = function(shipmentID) {
    return shipments[shipmentID]
}

// Add the model utility functions to the exports
module.exports = {
    calculateNearestShipment: calculateNearestShipment,
    getDeliveredShipments: getDeliveredShipments,
    getShipment: getShipment,
    SHIPMENT_STATE: SHIPMENT_STATE
}<commit_msg>fix distance calc<commit_after>/*****************************************************************************
 * Project:     ShipFast API Protection (Server)
 * File:        model.js
 * Original:    Created on 29 Sept 2017 by Simon Rigg
 * Copyright(c) 2002 - 2017 by CriticalBlue Ltd.
 *
 * This file contains the server business model.
 *****************************************************************************/

const Shipment = require('./shipment').Shipment
const SHIPMENT_STATE = require('./shipment').SHIPMENT_STATE
const MersenneTwister = require('mersenne-twister');
const Rand = new MersenneTwister(Date.now());

// Define various attributes for generating sample shipment data
const MIN_GRATUITY = 0
const MAX_GRATUITY = 30
const LAT_LNG_OFFSET = 0.5
const SHIPMENT_COUNT = 7
var PICKUP_LOCATIONS = [
    'ParcelFarce Depot 192',
    'Q&B DIY Centre',
    'Quidland',
    'Magnific Wines',
    'Stone & Waters Books',
    'Dobby\'s Gardens',
    'OfficeMean Suppliers'
]
var DELIVERY_LOCATIONS = [
    'Hugwarts School',
    'LetGo Land',
    '123 Fake Street',
    'Suspicious Whirring Blue Box',
    'Terok Nor Promenade',
    'Getwell Soon Hospital',
    'Llanfairpwllgwyngyll'
]
var SHIPMENT_DESCRIPTIONS = [
    'Bob\'s Consignment #58332',
    'Alice\'s Consignment #42981',
    'Eve\'s Consignment #91113',
    'Mary\'s Consignment #12125',
    'Joe\'s Consignment #17700',
    'Titus\' Consignment #77012',
    'Pete\'s Consignment #12589'
]

// The collection of shipments
var shipments = {}

// A function to shuffle an array in-place randomly
function shuffleArray(array) {
    for (var i = array.length - 1; i > 0; i--) {
        var j = Math.floor(Rand.random() * (i + 1))
        var temp = array[i]
        array[i] = array[j]
        array[j] = temp
    }
    return array
}

// Calculate and return the distance in miles between the two given points using the haversine formula
function calculateDistance(originLatitude, originLongitude, destinationLatitude, destinationLongitude) {

    var radiusEarth = 3961 // radius of Earth in miles
    var dLat = deg2rad(destinationLatitude - originLatitude)
    var dLon = deg2rad(destinationLongitude - originLongitude)
    var squareHalfChordLen = Math.sin(dLat/2) * Math.sin(dLat/2) + Math.cos(deg2rad(originLatitude))
        * Math.cos(deg2rad(destinationLatitude)) * Math.sin(dLon/2) * Math.sin(dLon/2)
    var angularRadDistance = 2 * Math.atan2(Math.sqrt(squareHalfChordLen), Math.sqrt(1 - squareHalfChordLen))
    var distance = radiusEarth * angularRadDistance;
    return distance;
}

// Convert the given degrees angle to radians
function deg2rad(deg) {
    return deg * (Math.PI/180)
}

// A function to populate this model with a collection of sample shipment data base on a given location
function populateShipments(originLatitude, originLongitude) {

    // Shuffle the pickup and delivery locations and the shipment descriptions to ensure these are different between sessions
    shuffleArray(PICKUP_LOCATIONS)
    shuffleArray(DELIVERY_LOCATIONS)
    shuffleArray(SHIPMENT_DESCRIPTIONS)

    // Generate the sample data
    for (i = 0; i < SHIPMENT_COUNT; i++) {
        var shipmentID = i + 1
        var description = SHIPMENT_DESCRIPTIONS[i]
        var gratuity = i % 2 == 0 ? Math.floor((Rand.random() * MAX_GRATUITY) + MIN_GRATUITY) : MIN_GRATUITY
        var pickupName = PICKUP_LOCATIONS[i]
        var pickupLatitude = originLatitude + ((Rand.random() * LAT_LNG_OFFSET) - LAT_LNG_OFFSET)
        var pickupLongitude = originLongitude + ((Rand.random() * LAT_LNG_OFFSET) - LAT_LNG_OFFSET)
        var deliveryName = DELIVERY_LOCATIONS[i]
        var deliveryLatitude = originLatitude + ((Rand.random() * LAT_LNG_OFFSET) - LAT_LNG_OFFSET)
        var deliveryLongitude = originLongitude + ((Rand.random() * LAT_LNG_OFFSET) - LAT_LNG_OFFSET)
        var shipment = new Shipment(shipmentID, description, gratuity,
            pickupName, pickupLatitude, pickupLongitude,
            deliveryName, deliveryLatitude, deliveryLongitude)
        shipments[shipmentID] = shipment
    }

    console.log('Generated Shipments:')
    console.log(shipments)
}

// A function to calculate and return the nearest shipment to a given location
const calculateNearestShipment = function(originLatitude, originLongitude) {

    // Ensure we've populated the model with some sample data for this session
    if (Object.keys(shipments).length == 0) {
        populateShipments(originLatitude, originLongitude)
    }

    // Iterate through the shipments and find the one closest to our given location
    var minDistance = Number.MAX_VALUE
    var nearestShipment
    Object.entries(shipments).forEach(
        ([shipmentID, shipment]) => {
            if (shipment.getState() == SHIPMENT_STATE.READY) {
                var distance = calculateDistance(originLatitude, originLongitude,
                    shipment.getPickupLatitude(), shipment.getPickupLongitude())
                if (distance < minDistance) {
                    minDistance = distance
                    nearestShipment = shipment
                }
            }
        }
    );
    return nearestShipment
}

// A function to calculate and return an array of shipments in a 'DELIVERED' state
const getDeliveredShipments = function() {

    var deliveredShipments = []
    Object.entries(shipments).forEach(
        ([shipmentID, shipment]) => {
            if (shipment.getState() == SHIPMENT_STATE.DELIVERED) {
                deliveredShipments.push(shipment)
            }
        }
    );
    return deliveredShipments
}

// A function to return the shipment with the given ID (or 'undefined' if not found)
const getShipment = function(shipmentID) {
    return shipments[shipmentID]
}

// Add the model utility functions to the exports
module.exports = {
    calculateNearestShipment: calculateNearestShipment,
    getDeliveredShipments: getDeliveredShipments,
    getShipment: getShipment,
    SHIPMENT_STATE: SHIPMENT_STATE
}<|endoftext|>


<commit_before>Meteor.publish("sites", function () {
    var user = Meteor.users.findOne({_id: this.userId});
    if (!user) {
       return null;
    }
    if (_.contains(user.roles, "admin")) {
       return Sites.find({});
    }
    if (!user.siti) {
       return null;
    }
    return Sites.find({
        _id: {
            $in: user.siti
        }
    });
});
<commit_msg>Renamed user sites field<commit_after>Meteor.publish("sites", function () {
    var user = Meteor.users.findOne({_id: this.userId});
    if (!user) {
       return null;
    }
    if (_.contains(user.roles, "admin")) {
       return Sites.find({});
    }
    if (!user.sites) {
       return null;
    }
    return Sites.find({
        _id: {
            $in: user.sites
        }
    });
});
<|endoftext|>


<commit_before>/**
 * Created by Carson on 15/01/2017.
 * File to handle logic of users subscribing to posts/comments.
 * Inserts information when a user subscribes,
 * queries when content added/edited to see if email notification
 * should be sent to users that are subscribed.
 */
var nodeMailer = require('nodemailer');
var smtpTransport = require('nodemailer-smtp-transport');
var log = require('./log.js');
var dbr = require('./DBRow.js');
var generator = require('./IDGenerator.js');
var lit = require('./Literals.js');

//object that holds the mailing information - who sends it (and their authentication) and connection details
var transport = nodeMailer.createTransport(smtpTransport({
    HOST: 'outlook.office.com',
    secureConnection: false, //use SSL as this is not a secure connection
    port: 587,
    auth: {
        user: "do-not-reply-forum@engsoc.queensu.ca",
        pass: "Forum1617" //TODO don't store here
    }
}));

//object that holds the actual email's information - subject, receiver, text etc.
var mailOptions = {
    from: 'do-not-reply-forum@engsoc.queensu.ca',
    to: '', //will be changed to userNetID@queensu.ca in emailUser()
    subject: 'Forum Subscription',
    text: 'There was a change to content you are subscribed to! Click here to see: ' //will add url here
};

/**
 * Function to be called when a user clicks a subscribe button.
 * Adds their information to the subscriptions table.
 * @param contentID The ID of the post/comment the user subscribed to.
 * @param userID The ID of the user who subscribed.
 */
function onSubscribed(contentID, userID) {
    //query comment table for content ID, if in there, set type to comment
    //if not in there, then type is a post
    var commentTable = new dbr.DBRow(lit.COMMENT_TABLE);
    commentTable.addQuery(lit.FIELD_ID, contentID);
    commentTable.query().then(function () {
        //set in database all the information
        var newRow = new dbr.DBRow(lit.SUBSCRIPTIONS_TABLE);
        if (!commentTable.next()) { //type is table
            newRow.setValue(lit.TYPE, lit.POST_TABLE);
        } else {
            newRow.setValue(lit.TYPE, lit.COMMENT_TABLE);
        }

        newRow.setValue(lit.FIELD_USER_ID, userID);
        newRow.setValue(lit.FIELD_ITEM_ID, contentID);
        newRow.setValue(lit.FIELD_ID, generator.generate());
        newRow.setValue(lit.FIELD_DATE_SUBSCRIBED, new Date().toISOString());

        newRow.insert();
    }).catch(function (err) {
        log.log("onSubscribed error: " + err);
    });
}

/**
 * Function to be called when content edited or
 * a child comment is added.
 * @param contentID ID of content added/edited.
 */
onContentAddedOrChanged('3ntcfsbrqunk27yymvmtetdvkrg33es1');
function onContentAddedOrChanged(contentID) {
    //get ID of content user actually subscribed to
    getParentContentID(contentID).then(function (contentID) {
        //add to number of notifications missed
        return addToNotificationsMissed(contentID);
    }).then(function (contentID) {
        //go through email logic
        return emailUsers(contentID);
    }).catch(function (error) {
        log.log("onContentAddedOrChanged error: " + error);
    });
}

/**
 * Function that emails users.
 * Computes logic to see if an individual *should* be emailed as well.
 * @param contentID ID of the content the user is to be emailed about.
 * @returns {Promise} Asynch tasks called/done from this function, so
 * use promise to make it synchronous. Chained from  caller method.
 */
function emailUsers(contentID) {
    return new Promise(function (resolve, reject) {
            getUserIDs(contentID).then(function (userIDs) {
                return userIDs;
            }).then(function (userIDs) {
                //get userIDs for those who should be emailed
                return findUsersToEmail(userIDs);
            }).then(function (userIDs) {
                //get net IDs from user IDs
                return getNetIDs(userIDs);
            }).then(function (netIDs) {
                //email users
                for (var i in netIDs) {
                    //TODO add url to give info
                    mailOptions[lit.TO] = netIDs[i] + lit.QUEENS_EMAIL;
                    log.log("MAIL SENT");
                    // transport.sendMail(mailOptions);//TODO uncomment
                }
                return netIDs;
            }).then(function () {
                return setNotificationsMissedToZero(contentID);
            }).then(function () {
                //update last notified to current time
                setLastNotifiedToNow(contentID);
            }).catch(function (err) {
                reject(err);
            });
        }
    );
}

/**
 * Function that increments the number of edits/child content that have gone without
 * an email to subscribed users.
 * @param contentID ID of the content to increase numMissedNotifications for
 * @returns {Promise} Asynch tasks called/done from this function, so
 * use promise to make it synchronous. Chained from  caller method.
 */
function addToNotificationsMissed(contentID) {
    var row = new dbr.DBRow(lit.SUBSCRIPTIONS_TABLE);
    row.addQuery(lit.FIELD_ITEM_ID, contentID);
    return new Promise(function (resolve, reject) {
        row.query().then(function () {
            while (row.next()) { //increment notifications missed for all users that subscribed to that content
                row.setValue(lit.FIELD_NUM_NOTIFICATIONS_MISSED, row.getValue(lit.FIELD_NUM_NOTIFICATIONS_MISSED) + 1);
                row.update().then(function (err) {
                    reject(err);
                });
            }
            resolve(contentID);
        }, function (err) {
            reject(err);
        });
    });
}

/**
 * Sets numNotificationsMissed to 0 for all pieces of content that had an email sent out.
 * @param contentID ID of the content that was emailed for, so any row with this
 * itemID needs its notifications missed to be set to 0.
 * @returns {Promise} Asynch tasks called/done from this function, so
 * use promise to make it synchronous. Chained from caller method.
 */
function setNotificationsMissedToZero(contentID) {
    var row = new dbr.DBRow(lit.SUBSCRIPTIONS_TABLE);
    row.addQuery(lit.FIELD_ITEM_ID, contentID);
    return new Promise(function (resolve, reject) {
        row.query().then(function () {
            while (row.next()) { //set notifications missed to zero for all users that subscribed to that content
                row.setValue(lit.FIELD_NUM_NOTIFICATIONS_MISSED, 0);
                row.update();
            }
            resolve(contentID);
        }, function (err) {
            reject(err);
        });
    });
}

/**
 * Function that sets the lastNotified field for content that users have been emailed about.
 * Field is set to the current date/time.
 * @param contentID ID of content that was emailed for.
 * No return because this is the last function called from the promise chain.
 */
function setLastNotifiedToNow(contentID) {
    var row = new dbr.DBRow(lit.SUBSCRIPTIONS_TABLE);
    row.addQuery(lit.FIELD_ITEM_ID, contentID);
    row.query().then(function () {
        while (row.next()) {
            row.setValue(lit.FIELD_LAST_NOTIFIED, new Date().toISOString());
            row.update();
        }
    });
}

/**
 * Function to get the parent content of added content.
 * Added content is only ever comments, so an issue of a table
 * not having parent content will never come up. Able to handle
 * a top level comment and a child comment.
 * @param contentID The ID of the content for which the parent is to be found.
 * @returns {Promise} Asynch tasks called/done from this function, so
 * use promise to make it synchronous. Chained from caller method.
 */
function getParentContentID(contentID) {
    var row = new dbr.DBRow(lit.COMMENT_TABLE);
    row.addQuery(lit.FIELD_ID, contentID);
    return new Promise(function (resolve, reject) {
        row.query().then(function () {
            if (!row.next()) {
                reject("got nothing back");
            }
            //if parent of the content is a post, send back that post's ID
            //else it is a comment, so send back that comment's ID
            (row.getValue(lit.FIELD_PARENT_POST) != lit.UNDEFINED) ? resolve(row.getValue(lit.FIELD_PARENT_POST)) :
                resolve(row.getValue(lit.FIELD_PARENT_COMMENT));
        }, function () {
            reject("No parent");
        });
    });
}

/**
 * Function that gets user IDs that have subscribed to a
 * specified contentID.
 * @param contentID ID of the content for which users are subscribed to.
 * @returns {Promise} Asynch tasks called/done from this function, so
 * use promise to make it synchronous. Chained from  caller method.
 */
function getUserIDs(contentID) {
    var userIDs = [];
    var row = new dbr.DBRow(lit.SUBSCRIPTIONS_TABLE);
    row.addQuery(lit.FIELD_ITEM_ID, contentID);
    return new Promise(function (resolve, reject) {
        row.query().then(function () {
            while (row.next()) {
                userIDs.push(row.getValue(lit.FIELD_USER_ID));
            }
            resolve(userIDs);
        }, function () {
            log.log("No rows match query or there was an error");
            reject(null);
        });
    });
}

/**
 * Gets net IDs from user IDs. The user IDs are ones that have been
 * filtered so that these users are the ones to be emailed.
 * @param userIDs Array of user IDs that need their net ID found.
 * @returns {Promise} Asynch tasks called/done from this function, so
 * use promise to make it synchronous. Chained from  caller method.
 */
function getNetIDs(userIDs) {
    //check that userIDs can actually be used to find users
    //if not (empty), stop this function and continue to next then chain
    if (userIDs.length == 0) {
        return userIDs;
    }
    var netIDs = [];
    var row = new dbr.DBRow(lit.USER_TABLE);
    return new Promise(function (resolve, reject) {
        for (var i in userIDs) {
            row.addQuery(lit.FIELD_ID, userIDs[i]);
        }
        row.query().then(function () {
            while (row.next()) {
                netIDs.push(row.getValue(lit.FIELD_NETID));
            }
            resolve(netIDs);
        }, function (err) {
            reject(err);
        });
    });
}

/**
 * Function that filters through user IDs to find user IDs
 * for those that should be emailed. They should be emailed
 * if there is at least one missed notification and it has been
 * long enough since the last email was sent.
 * @param userIDs Array of all users that have subscribed to something.
 * @returns {Promise} Asynch tasks called/done from this function, so
 * use promise to make it synchronous. Chained from  caller method.
 */
function findUsersToEmail(userIDs) {
    //check that userIDs can actually be used to find users
    //if not (empty), stop this function and continue to next then chain
    if (userIDs.length == 0) {
        return userIDs;
    }
    var row = new dbr.DBRow(lit.SUBSCRIPTIONS_TABLE);
    var numNotificationsMissed;
    var lastNotified;
    var goodUserIDs = [];
    return new Promise(function (resolve, reject) {
        for (var i in userIDs) {
            row.addQuery(lit.FIELD_USER_ID, userIDs[i]);
        }
        row.query().then(function () {
            while (row.next()) {
                //compare number of missed notifications to preset minimum number needed to email user
                //also check and make sure that it has been long enough since user was last emailed
                //both need to pass the compare to email the user
                numNotificationsMissed = row.getValue(lit.FIELD_NUM_NOTIFICATIONS_MISSED);
                lastNotified = row.getValue(lit.FIELD_LAST_NOTIFIED);
                if (numNotificationsMissed > lit.MIN_NUM_MISSED_NOTIFICATIONS && longEnoughAgo(lastNotified)) {
                    goodUserIDs.push(row.getValue(lit.FIELD_USER_ID));
                }
            }
            resolve(goodUserIDs);
        }, function (err) {
            reject(err);
        });
    });
}

function longEnoughAgo(lastNotified) {
    //TODO figure out logic for getting how long ago user was notified
    return true;
}

<commit_msg>Should have been in last commit<commit_after>/**
 * Created by Carson on 15/01/2017.
 * File to handle logic of users subscribing to posts/comments.
 * Inserts information when a user subscribes,
 * queries when content added/edited to see if email notification
 * should be sent to users that are subscribed.
 */
var nodeMailer = require('nodemailer');
var smtpTransport = require('nodemailer-smtp-transport');
var log = require('./log.js');
var dbr = require('./DBRow.js');
var generator = require('./IDGenerator.js');
var lit = require('./Literals.js');

//object that holds the mailing information - who sends it (and their authentication) and connection details
var transport = nodeMailer.createTransport(smtpTransport({
    HOST: 'outlook.office.com',
    secureConnection: false, //use SSL as this is not a secure connection
    port: 587,
    auth: {
        user: "do-not-reply-forum@engsoc.queensu.ca",
        pass: "Forum1617" //TODO don't store here
    }
}));

//object that holds the actual email's information - subject, receiver, text etc.
var mailOptions = {
    from: 'do-not-reply-forum@engsoc.queensu.ca',
    to: '', //will be changed to userNetID@queensu.ca in emailUser()
    subject: 'Forum Subscription',
    text: 'There was a change to content you are subscribed to! Click here to see: ' //will add url here
};

/**
 * Function to be called when a user clicks a subscribe button.
 * Adds their information to the subscriptions table.
 * @param contentID The ID of the post/comment the user subscribed to.
 * @param userID The ID of the user who subscribed.
 */
exports.onSubscribed = function (contentID, userID) {
    //query comment table for content ID, if in there, set type to comment
    //if not in there, then type is a post
    var commentTable = new dbr.DBRow(lit.COMMENT_TABLE);
    commentTable.addQuery(lit.FIELD_ID, contentID);
    commentTable.query().then(function () {
        //set in database all the information
        var newRow = new dbr.DBRow(lit.SUBSCRIPTIONS_TABLE);
        if (!commentTable.next()) { //type is table
            newRow.setValue(lit.TYPE, lit.POST_TABLE);
        } else {
            newRow.setValue(lit.TYPE, lit.COMMENT_TABLE);
        }

        newRow.setValue(lit.FIELD_USER_ID, userID);
        newRow.setValue(lit.FIELD_ITEM_ID, contentID);
        newRow.setValue(lit.FIELD_ID, generator.generate());
        newRow.setValue(lit.FIELD_DATE_SUBSCRIBED, new Date().toISOString());

        newRow.insert();
    }).catch(function (err) {
        log.log("onSubscribed error: " + err);
    });
};

/**
 * Function to be called when content edited or
 * a child comment is added.
 * @param contentID ID of content added/edited.
 */
exports.onContentAddedOrChanged = function (contentID) {
    //get ID of content user actually subscribed to
    getParentContentID(contentID).then(function (contentID) {
        //add to number of notifications missed
        return addToNotificationsMissed(contentID);
    }).then(function (contentID) {
        //go through email logic
        return emailUsers(contentID);
    }).catch(function (error) {
        log.log("onContentAddedOrChanged error: " + error);
    });
};

/**
 * Function that emails users.
 * Computes logic to see if an individual *should* be emailed as well.
 * @param contentID ID of the content the user is to be emailed about.
 * @returns {Promise} Asynch tasks called/done from this function, so
 * use promise to make it synchronous. Chained from  caller method.
 */
function emailUsers(contentID) {
    var usersEmailed = [];
    return new Promise(function (resolve, reject) {
            getUserIDs(contentID).then(function (userIDs) {
                return userIDs;
            }).then(function (userIDs) {
                //get userIDs for those who should be emailed
                return findUsersToEmail(userIDs);
            }).then(function (userIDs) {
                usersEmailed = userIDs; //save the user IDs for those who are being emailed
                //get net IDs from user IDs
                return getNetIDs(userIDs);
            }).then(function (netIDs) {
                //email users
                for (var i in netIDs) {
                    //TODO add url to give info
                    mailOptions[lit.TO] = netIDs[i] + lit.QUEENS_EMAIL;
                    log.log("Mail sent for content: " + contentID);
                    transport.sendMail(mailOptions);
                }
            }).then(function () {
                return getSubscriptionIDs(usersEmailed, contentID);
            }).then(function (subIDs) {
                return setNotificationsMissedToZero(subIDs);
            }).then(function (subIDs) {
                //update last notified to current time
                setLastNotifiedToNow(subIDs);
            }).catch(function (err) {
                reject(err);
            });
        }
    );
}

/**
 * Function that increments the number of edits/child content that have gone without
 * an email to subscribed users.
 * @param contentID ID of the content to increase numMissedNotifications for
 * @returns {Promise} Asynch tasks called/done from this function, so
 * use promise to make it synchronous. Chained from  caller method.
 */
function addToNotificationsMissed(contentID) {
    var row = new dbr.DBRow(lit.SUBSCRIPTIONS_TABLE);
    row.addQuery(lit.FIELD_ITEM_ID, contentID);
    return new Promise(function (resolve, reject) {
        row.query().then(function () {
            while (row.next()) { //increment notifications missed for all users that subscribed to that content
                row.setValue(lit.FIELD_NUM_NOTIFICATIONS_MISSED, row.getValue(lit.FIELD_NUM_NOTIFICATIONS_MISSED) + 1);
                row.update().then(function (err) {
                    reject(err);
                });
            }
            resolve(contentID);
        }, function (err) {
            reject(err);
        });
    });
}

/**
 * Sets numNotificationsMissed to 0 for all pieces of content that had an email sent out.
 * @param subIDs IDs of the subscriptions rows that were emailed for.
 * @returns {*} Asynch tasks called/done from this function, so
 * use promise to make it synchronous. Chained from caller method. Unless there is no
 * work to be done with the parameter, in which case the function is just ended and the
 * parameter is passed along the promise chain.
 */
function setNotificationsMissedToZero(subIDs) {
    //check that subIDs can actually be used to find users emailed
    //if not (empty), stop this function and continue to next then chain
    if (subIDs.length == 0) {
        return subIDs;
    }
    var row = new dbr.DBRow(lit.SUBSCRIPTIONS_TABLE);
    for (var i in subIDs) {
        row.addQuery(lit.FIELD_ID, subIDs[i]);
    }
    return new Promise(function (resolve, reject) {
        row.query().then(function () {
            while (row.next()) { //set notifications missed to zero for all users that subscribed to that content
                row.setValue(lit.FIELD_NUM_NOTIFICATIONS_MISSED, 0);
                row.update();
            }
            resolve(subIDs);
        }, function (err) {
            reject(err);
        });
    });
}

/**
 * Function that sets the lastNotified field for content that users have been emailed about.
 * Field is set to the current date/time.
 * @param subIDs IDs of the subscriptions rows that were emailed for.
 * No return because this is the last function called from the promise chain.
 */
function setLastNotifiedToNow(subIDs) {
    //check that subIDs can actually be used to find users emailed
    //if not (empty), stop this function and continue to next then chain
    if (subIDs.length == 0) {
        return;
    }
    var row = new dbr.DBRow(lit.SUBSCRIPTIONS_TABLE);
    for (var i in subIDs) {
        row.addQuery(lit.FIELD_ID, subIDs[i]);
    }
    row.query().then(function () {
        while (row.next()) {
            row.setValue(lit.FIELD_LAST_NOTIFIED, new Date().toISOString());
            row.update();
        }
    });
}

/**
 * Function that gets the subscriptions table row IDs for users that are going to be emailed.
 * Because users can subscribe to multiple content IDs, need to filter through the userIDs for
 * the ones that are subscribed to the specific content.
 * @param userIDs User IDs for those that will be emailed.
 * @param contentID Content ID for the content that will be emailed for.
 * @returns {*} Asynch tasks called/done from this function, so
 * use promise to make it synchronous. Chained from  caller method. Unless there is no
 * work to be done with the parameter, in which case the function is just ended and the
 * parameter is passed along the promise chain.
 */
function getSubscriptionIDs(userIDs, contentID) {
    //check that userIDs can actually be used to find users
    //if not (empty), stop this function and continue to next then chain
    if (userIDs.length == 0) {
        return userIDs;
    }
    var subIDs = [];
    var row = new dbr.DBRow(lit.SUBSCRIPTIONS_TABLE);
    for (var i in userIDs) {
        row.addQuery(lit.FIELD_USER_ID, userIDs[i]);
    }
    return new Promise(function (resolve, reject) {
        row.query().then(function () {
            while (row.next()) {
                if (row.getValue(lit.FIELD_ITEM_ID) == contentID) { //ID of row that was emailed for
                    subIDs.push(row.getValue(lit.FIELD_ID));
                }
            }
            resolve(subIDs);
        }, function (err) {
            reject(err);
        });
    });
}

/**
 * Function to get the parent content of added content.
 * Added content is only ever comments, so an issue of a table
 * not having parent content will never come up. Able to handle
 * a top level comment and a child comment.
 * @param contentID The ID of the content for which the parent is to be found.
 * @returns {Promise} Asynch tasks called/done from this function, so
 * use promise to make it synchronous. Chained from caller method. Unless there is no
 * work to be done with the parameter, in which case the function is just ended and the
 * parameter is passed along the promise chain.
 */
function getParentContentID(contentID) {
    var row = new dbr.DBRow(lit.COMMENT_TABLE);
    row.addQuery(lit.FIELD_ID, contentID);
    return new Promise(function (resolve, reject) {
        row.query().then(function () {
            if (!row.next()) {
                reject("got nothing back");
            }
            //if parent of the content is a post, send back that post's ID
            //else it is a comment, so send back that comment's ID
            (row.getValue(lit.FIELD_PARENT_POST) != lit.UNDEFINED) ? resolve(row.getValue(lit.FIELD_PARENT_POST)) :
                resolve(row.getValue(lit.FIELD_PARENT_COMMENT));
        }, function () {
            reject("No parent");
        });
    });
}

/**
 * Function that gets user IDs that have subscribed to a
 * specified contentID.
 * @param contentID ID of the content for which users are subscribed to.
 * @returns {Promise} Asynch tasks called/done from this function, so
 * use promise to make it synchronous. Chained from  caller method.
 */
function getUserIDs(contentID) {
    var userIDs = [];
    var row = new dbr.DBRow(lit.SUBSCRIPTIONS_TABLE);
    row.addQuery(lit.FIELD_ITEM_ID, contentID);
    return new Promise(function (resolve, reject) {
        row.query().then(function () {
            while (row.next()) {
                userIDs.push(row.getValue(lit.FIELD_USER_ID));
            }
            resolve(userIDs);
        }, function () {
            log.log("No rows match query or there was an error");
            reject(null);
        });
    });
}

/**
 * Gets net IDs from user IDs. The user IDs are ones that have been
 * filtered so that these users are the ones to be emailed.
 * @param userIDs Array of user IDs that need their net ID found.
 * @returns {*} If userIdsAsynch tasks called/done from this function, so
 * use promise to make it synchronous. Chained from  caller method. Unless there is no
 * work to be done with the parameter, in which case the function is just ended and the
 * parameter is passed along the promise chain.
 */
function getNetIDs(userIDs) {
    //check that userIDs can actually be used to find users
    //if not (empty), stop this function and continue to next then chain
    if (userIDs.length == 0) {
        return userIDs;
    }
    var netIDs = [];
    var row = new dbr.DBRow(lit.USER_TABLE);
    return new Promise(function (resolve, reject) {
        for (var i in userIDs) {
            row.addQuery(lit.FIELD_ID, userIDs[i]);
        }
        row.query().then(function () {
            while (row.next()) {
                netIDs.push(row.getValue(lit.FIELD_NETID));
            }
            resolve(netIDs);
        }, function (err) {
            reject(err);
        });
    });
}

/**
 * Function that filters through user IDs to find user IDs
 * for those that should be emailed. They should be emailed
 * if there is at least one missed notification and it has been
 * long enough since the last email was sent.
 * @param userIDs Array of all users that have subscribed to something.
 * @returns {*} Asynch tasks called/done from this function, so
 * use promise to make it synchronous. Chained from  caller method. Unless there is no
 * work to be done with the parameter, in which case the function is just ended and the
 * parameter is passed along the promise chain.
 */
function findUsersToEmail(userIDs) {
    //check that userIDs can actually be used to find users
    //if not (empty), stop this function and continue to next then chain
    if (userIDs.length == 0) {
        return userIDs;
    }
    var row = new dbr.DBRow(lit.SUBSCRIPTIONS_TABLE);
    var goodUserIDs = [];
    return new Promise(function (resolve, reject) {
        for (var i in userIDs) {
            row.addQuery(lit.FIELD_USER_ID, userIDs[i]);
        }
        row.query().then(function () {
            while (row.next()) {
                //compare number of missed notifications to preset minimum number needed to email user
                //also check and make sure that it has been long enough since user was last emailed
                //both need to pass the compare to email the user
                var numNotificationsMissed = row.getValue(lit.FIELD_NUM_NOTIFICATIONS_MISSED);
                var lastNotified = row.getValue(lit.FIELD_LAST_NOTIFIED);
                if (numNotificationsMissed > lit.MIN_NUM_MISSED_NOTIFICATIONS && longEnoughAgo(lastNotified)) {
                    goodUserIDs.push(row.getValue(lit.FIELD_USER_ID));
                }
            }
            resolve(goodUserIDs);
        }, function (err) {
            reject(err);
        });
    });
}

/**
 * Function to determine if it has been long enough since last email to user to send another one.
 * Currently the minimum time between emails is a day.
 * Note that this is just for one piece of content.
 * A user could be emailed multiple times in a day for different content.
 * @param lastNotified Day/time the user was last emailed at.
 * @returns {boolean} True if it has been long enough (one day), false if not.
 */
function longEnoughAgo(lastNotified) {
    lastNotified = Date.parse(lastNotified); //get lastNotified in MS form
    var curMS = Date.parse(new Date().toISOString()); //get current time in MS form
    return (curMS - Date.parse(lastNotified)) >= lit.MIN_MS_TO_NOTIFY_AGAIN; //at least a day since last email
}

<|endoftext|>


<commit_before>/*jslint node: true */

module.exports = function (app, users, history, pub, util) {
    'use strict';

    //list of routes that are loaded on the client-side
    //this exists here to avoid 404 HTTP Statuses being sent
    var appRoutes = ['/tos'];

    app.get('/api/me', function (req, res, next) {
        if (!req.user) {
            res.status(204).send();
            return;
        }

        users.get(req.user, function (err, data) {
            if (err) {
                next(new Error('Failed to load user'));
                return;
            }

            res.json(data);
        });
    });

    app.post('/api/me', function (req, res, next) {
        var errors,
            data;

        if (!req.user) {
            res.status(404).send();
            return;
        }

        req.assert('notifications', 'Invalid notifications value').isIn([0, 1]);
        req.assert('blood.type').isBloodType(true);
        req.assert('blood.rh').isBloodRh(true);

        errors = req.validationErrors();

        if (errors) {
            res.json(400, {errors: errors});
            return;
        }

        data = {
            notifications: req.param('notifications'),
            blood: {
                type: req.param('blood').type,
                rh: req.param('blood').rh
            }
        };

        users.update(req.user, data, function (err) {
            if (err) {
                next(new Error('Failed to save user preferences'));
                return;
            }

            res.status(202).send();
        });
    });

    app.get('/api/history', function (req, res, next) {
        if (!req.user) {
            res.status(404).send();
            return;
        }

        history.getFromUser(req.user, function (err, data) {
            if (err) {
                next(new Error('Failed to load history'));
                return;
            }

            res.json(data);
        });
    });

    app.post('/api/history', function (req, res, next) {
        var errors;

        if (!req.user) {
            res.status(403).send();
        }

        req.assert('date').isDate8601();
        req.assert('place').len(1, 60);

        errors = req.validationErrors();

        if (errors) {
            res.json(400, {errors: errors});
            return;
        }

        history.insert({
            date: req.param('date'),
            place: req.param('place'),
            uid: req.user
        }, function (err, result) {
            if (err) {
                next(new Error('Failed to create history register'));
                return;
            }

            res.json(result);
        });
    });

    });

    app.post('/logout', function (req, res) {
        req.logout();
        res.json({logout: true});
    });

    app.use(function (err, req, res, next) {
        if (err && err.status && err.toString) {
            res.send(err.status, err.toString());
            return;
        }

        res.status(500).json({error: 500, message: '500 Internal Server Error'});
    });

    appRoutes.forEach(function (route) {
        app.all(route, function (req, res) {
            res.sendfile(pub + '/index.html');
        });
    });

    app.use(function (req, res, next) {
        res.status(404);

        if (req.accepts('html')) {
            res.sendfile(pub + '/index.html');
            return;
        }

        res.json({error: 404, message: 'not found'});
    });
};

<commit_msg>Adding the api/history/:id delete method<commit_after>/*jslint node: true */

module.exports = function (app, users, history, pub, util) {
    'use strict';

    //list of routes that are loaded on the client-side
    //this exists here to avoid 404 HTTP Statuses being sent
    var appRoutes = ['/tos'];

    app.get('/api/me', function (req, res, next) {
        if (!req.user) {
            res.status(204).send();
            return;
        }

        users.get(req.user, function (err, data) {
            if (err) {
                next(new Error('Failed to load user'));
                return;
            }

            res.json(data);
        });
    });

    app.post('/api/me', function (req, res, next) {
        var errors,
            data;

        if (!req.user) {
            res.status(404).send();
            return;
        }

        req.assert('notifications', 'Invalid notifications value').isIn([0, 1]);
        req.assert('blood.type').isBloodType(true);
        req.assert('blood.rh').isBloodRh(true);

        errors = req.validationErrors();

        if (errors) {
            res.json(400, {errors: errors});
            return;
        }

        data = {
            notifications: req.param('notifications'),
            blood: {
                type: req.param('blood').type,
                rh: req.param('blood').rh
            }
        };

        users.update(req.user, data, function (err) {
            if (err) {
                next(new Error('Failed to save user preferences'));
                return;
            }

            res.status(202).send();
        });
    });

    app.get('/api/history', function (req, res, next) {
        if (!req.user) {
            res.status(404).send();
            return;
        }

        history.getFromUser(req.user, function (err, data) {
            if (err) {
                next(new Error('Failed to load history'));
                return;
            }

            res.json(data);
        });
    });

    app.post('/api/history', function (req, res, next) {
        var errors;

        if (!req.user) {
            res.status(403).send();
        }

        req.assert('date').isDate8601();
        req.assert('place').len(1, 60);

        errors = req.validationErrors();

        if (errors) {
            res.json(400, {errors: errors});
            return;
        }

        history.insert({
            date: req.param('date'),
            place: req.param('place'),
            uid: req.user
        }, function (err, result) {
            if (err) {
                next(new Error('Failed to create history register'));
                return;
            }

            res.json(result);
        });
    });

    app.delete('/api/history/:id', function (req, res, next) {
        history.get(req.params.id, function (err, data) {
            if (data && data.uid === req.user) {
                history.remove(req.params.id, function (err) {
                    if (!err) {
                        res.status(204).send();
                        return;
                    }

                    next(new Error('Failed to delete history record'));
                });
            } else {
                res.status(404);
            }
        });
    });

    app.post('/logout', function (req, res) {
        req.logout();
        res.json({logout: true});
    });

    app.use(function (err, req, res, next) {
        if (err && err.status && err.toString) {
            res.send(err.status, err.toString());
            return;
        }

        res.status(500).json({error: 500, message: '500 Internal Server Error'});
    });

    appRoutes.forEach(function (route) {
        app.all(route, function (req, res) {
            res.sendfile(pub + '/index.html');
        });
    });

    app.use(function (req, res, next) {
        res.status(404);

        if (req.accepts('html')) {
            res.sendfile(pub + '/index.html');
            return;
        }

        res.json({error: 404, message: 'not found'});
    });
};

<|endoftext|>