<commit_before>'use strict'

const { map, reduce, flow, toPairs, filter, intersection } = require('lodash/fp')
const { mongo } = require('mongoose')
const { ServerError, UnauthorizedError, NotFoundError } = require('./errors')
const { Organization, People, Activity } = require('./model')
const debug = require('debug')('isari:permissions')

// Helper to safely get a string from Mongoose instance, ObjectId, or direct string (populate-proof)
// Object|ObjectID|String => String
const mongoID = o => (o instanceof mongo.ObjectID) ? o.toHexString() : (o ? (o.id ? o.id : (o._id ? o._id.toHexString() : o)) : null)

// Helper returning a YYYY-MM-DD string for today
// TODO cache (it shouldn't change more than once a day, right)
const today = () => {
	const d = new Date()
	const pad = s => String(s).length === 1 ? '0' + s : String(s)
	return `${d.getFullYear()}-${pad(d.getMonth()+1)}-${pad(d.getDate)}`
}

// Extract roles from isariAuthorizedCenters
// People => Object(OrganizationID, Role)
const getRoles = (people) => {
	const roles = {}
	people.isariAuthorizedCenters.forEach(({ organization, isariRole: role }) => {
		roles[mongoID(organization)] = role
	})
	return roles
}

// Extract "central_*" roles, keep only highest
// People => null|'admin'|'reader'
const getCentralRole = exports.getPeopleCentralRole = flow(getRoles, reduce((result, role) => {
	if (role.isariRole === 'central_admin') {
		return 'admin'
	} else if (!result && role.isariRole === 'central_reader') {
		return 'reader'
	}
}, null))

// Returns matched expectedCredentials by user roles for given organization ids
// Object({ OrganizationId: Role }), Array(OrganizationId), Array(Role) => Array(Role)
const getMatchingCredentials = (userRoles, organizationIds, expectedRoles) => {
	organizationIds = map(mongoID)(organizationIds)
	return flow(
		toPairs, // Array([OrganizationId, Role])
		filter(([id]) => organizationIds.includes(id)), // Scope
		map(1), // Array(Role)
		intersection(expectedRoles) // Array(Role), only the ones matching
	)(userRoles)
}
const hasMatchingCredentials = (u, o, e) => getMatchingCredentials(u, o, e).length > 0

// Credentials middleware
// sets a bunch of user* properties easing permissions checking
exports.rolesMiddleware = (req, res, next) => {
	req._rolesMiddleware = true

	if (!req.session.login) {
		debug('not logged in')
		return next()
	}

	People.findOne({ ldapUid: req.session.login }).then(people => {
		if (!people) {
			debug('invalid login: force-disconnect!')
			req.session.login = null
			return next()
		}

		req.userId = people.id
		req.userPeople = people
		req.userRoles = getRoles(people)
		req.userCentralRole = getCentralRole(people)

		// Credential helpers
		req.userCanEditPeople = p => canEditPeople(req, p)
		req.userListViewablePeople = () => listViewablePeople(req)
		req.userCanEditActivity = a => canEditActivity(req, a)
		req.userListViewableActivities = () => listViewableActivities(req)
		req.userCanEditOrganization = o => canEditOrganization(req, o)

		debug(req.userRoles)
		next()
	})
}

// scope middleware
// Sets "req.userScopeOrganizationId" and check its validity
exports.scopeOrganizationMiddleware = (req, res, next) => {
	if (!req._rolesMiddleware) {
		return next(ServerError({ title: 'Invalid usage of "scopeOrganizationMiddleware" without prior usage of "rolesMiddleware"' }))
	}

	req._scopeOrganizationMiddleware = true

	const orgId = req.query.organization // maybe change later, part of URL, different name…

	// Scope requested: check if it is valid for connected user
	if (orgId && !req.userRoles[orgId] && !req.userCentralRole) {
		return next(UnauthorizedError({ title: `Specified invalid scope (organization id) "${orgId}" (user is not central and has no permission on this organization)` }))
	}

	// Scope requested: check if organization exists and then let go
	if (orgId) {
		// Wrap around promises to catch invalid ObjectId format
		return Promise.resolve(orgId)
		.then(id => Organization.findById(id))
		.then(found => {
			if (!found) {
				return Promise.reject(NotFoundError({ title: `Specified invalid scope (organization id) "${orgId}" (not found)` }))
			}
			// Sets scope and let go
			req.userScopeOrganizationId = orgId
			next()
		})
		.catch(next)
	}

	// Global scope requested: check if it is valid for connected user
	if (!orgId && !req.userCentralRole) {
		return next(UnauthorizedError({ title: 'Access to global scope refused for non central people, please add "?organization=…" to scope your queries' }))
	}

	// Global scope requested for central user: set scope and let go
	req.userScopeOrganizationId = null
	next()
}

// Return viewable people for current user, scope included
/*
Who can *view* a people?
- anyone sharing academicMembership (done by lookup below)
*/
const listViewablePeople = (req) => {
	if (!req._scopeOrganizationMiddleware) {
		return Promise.reject(Error('Invalid usage of "listViewablePeople" without prior usage of "scopeOrganizationMiddleware"'))
	}

	const isInScope = req.userScopeOrganizationId
		? // Scoped: limit to people from this organization
			{ 'memberships.orgId': req.userScopeOrganizationId }
		: // Unscoped: limit to people from organizations he has access to
			{ 'memberships.orgId': { $in: Object.keys(req.userRoles) } }

	// External people = ALL memberships are either expired or linked to an unmonitored organization
	const isExternal = { memberships: { $not: { $elemMatch: {
		$or: [ { endDate: { $gte: today() } }, { endDate: { $exists: false } } ],
		orgMonitored: true
	} } } }

	// Use aggregation to lookup organization and calculate union of "in scope" + "externals"
	/* First version kept for history: much more performant, but we must return a Mongoose.Query to allow proper population,
		formatting, templating… in rest-utils. A wide rewrite should be planned to allow using aggregations in REST endpoints

	return People.aggregate()
		.project({ _id: 1, people: '$$ROOT' }) // Keep original data intact for later use
		.unwind('people.academicMemberships') // Lookup only works with flat data, unwind array
		.lookup({ from: Organization.collection.name, localField: 'people.academicMemberships.organization', foreignField: '_id', as: 'orgs' }) // LEFT JOIN Organization
		.unwind('orgs') // "orgs" can be a zero-or-one-item array, unwind that before grouping
		.project({ _id: 1, people: 1, membership: { orgId: '$orgs._id', endDate: '$people.academicMemberships.endDate', orgMonitored: '$orgs.isariMonitored' } }) // Group membership info together
		.group({ _id: '$_id', memberships: { $push: '$membership' }, people: { $first: '$people' } }) // Now regroup membership data to single people
		.match({ $or: [ isInScope, isExternal ] }) // Finally apply filters
		.then(map(p => Object.assign(p.people, { // And keep only People (untouched) data with additional membership info
			_external: !p.memberships.some(m => m.endDate && m.endDate >= today() && m.orgMonitored)
		})))
	*/
	return People.aggregate()
		.project({ _id: 1, academicMemberships: 1 }) // Keep original data intact for later use
		.unwind('academicMemberships') // Lookup only works with flat data, unwind array
		.lookup({ from: Organization.collection.name, localField: 'academicMemberships.organization', foreignField: '_id', as: 'org' }) // LEFT JOIN Organization
		.unwind('org') // "orgs" can be a zero-or-one-item array, unwind that before grouping
		.project({ _id: 1, membership: { orgId: '$org._id', endDate: '$academicMemberships.endDate', orgMonitored: '$org.isariMonitored' } }) // Group membership info together
		.group({ _id: '$_id', memberships: { $push: '$membership' }, people: { $first: '$people' } }) // Now regroup membership data to single people
		.match({ $or: [ isInScope, isExternal ] }) // Finally apply filters
		.project({ _id: 1 }) // Keep only id
		.then(map('_id'))
		.then(ids => ({
			// Populate to allow getPeoplePermissions to work
			query: People.find({ _id: { $in: ids } }).populate('academicMemberships.organization')
		}))
}

// Check if a people is editable by current user
/*
Who can *write* a people?
- himself
- central admin
- anyone if people is external
- center admin of people's centers (academicMemberships)
- center editor of people's centers (academicMemberships)
*/
const isExternalPeople = p => !p.academicMemberships.some(m => m.endDate && m.endDate >= today() && m.organization.isariMonitored)
const canEditPeople = (req, p) =>
	req.userId === String(p._id) || // himself
	req.userCentralRole === 'admin' || // central admin or central reader
	isExternalPeople(p) || // external people
	hasMatchingCredentials(req.userRoles, map('organization')(p.academicMemberships), ['center_editor', 'center_admin'])

// Return viewable activities for current user, scope included
/*
Who can *view* an activity?
- anyone having access to any of its organizations
(access has been checked by scope earlier, so it's just about checking if activity.organizations contains requested scope)
*/
const listViewableActivities = (req) => {
	if (!req._scopeOrganizationMiddleware) {
		return Promise.reject(Error('Invalid usage of "listViewableActivities" without prior usage of "scopeOrganizationMiddleware"'))
	}

	const filter = req.userScopeOrganizationId
		? // Scoped: limit to people from this organization
			{ 'organizations.organization': req.userScopeOrganizationId }
		: // Unscoped: limit to people from organizations he has access to
			{ 'organizations.organization': { $in: Object.keys(req.userRoles) } }

	return Promise.resolve({ query: Activity.find(filter) })
}

// Check if an activity is editable by current user
/*
Who can *edit* an activity?
- central admin
*/
const canEditActivity = (req, a) => // eslint-disable-line no-unused-vars
	req.userCentralRole === 'admin' // central admin or central reader

// Check if an organization is editable by current user
/*
Who can *edit* an organization?
- central admin
- center admin for this organization
*/
const canEditOrganization = (req, o) => // eslint-disable-line no-unused-vars
	req.userCentralRole === 'admin' || // central admin or central reader
	hasMatchingCredentials(req.userRoles, [o], ['center_admin'])
<commit_msg>fix(permissions): fixes retrieval of central role (refs #34)<commit_after>'use strict'

const { map, reduce, flow, toPairs, filter, intersection } = require('lodash/fp')
const { mongo } = require('mongoose')
const { ServerError, UnauthorizedError, NotFoundError } = require('./errors')
const { Organization, People, Activity } = require('./model')
const debug = require('debug')('isari:permissions')

// Helper to safely get a string from Mongoose instance, ObjectId, or direct string (populate-proof)
// Object|ObjectID|String => String
const mongoID = o => (o instanceof mongo.ObjectID) ? o.toHexString() : (o ? (o.id ? o.id : (o._id ? o._id.toHexString() : o)) : null)

// Helper returning a YYYY-MM-DD string for today
// TODO cache (it shouldn't change more than once a day, right)
const today = () => {
	const d = new Date()
	const pad = s => String(s).length === 1 ? '0' + s : String(s)
	return `${d.getFullYear()}-${pad(d.getMonth()+1)}-${pad(d.getDate)}`
}

// Extract roles from isariAuthorizedCenters
// People => Object(OrganizationID, Role)
const getRoles = (people) => {
	const roles = {}
	people.isariAuthorizedCenters.forEach(({ organization, isariRole: role }) => {
		roles[mongoID(organization)] = role
	})
	return roles
}

// Extract "central_*" roles, keep only highest
// People => null|'admin'|'reader'
const getCentralRole = exports.getPeopleCentralRole = flow(getRoles, reduce((result, role) => {
	if (role === 'central_admin') {
		return 'admin'
	} else if (!result && role === 'central_reader') {
		return 'reader'
	} else {
		return result
	}
}, null))

// Returns matched expectedCredentials by user roles for given organization ids
// Object({ OrganizationId: Role }), Array(OrganizationId), Array(Role) => Array(Role)
const getMatchingCredentials = (userRoles, organizationIds, expectedRoles) => {
	organizationIds = map(mongoID)(organizationIds)
	return flow(
		toPairs, // Array([OrganizationId, Role])
		filter(([id]) => organizationIds.includes(id)), // Scope
		map(1), // Array(Role)
		intersection(expectedRoles) // Array(Role), only the ones matching
	)(userRoles)
}
const hasMatchingCredentials = (u, o, e) => getMatchingCredentials(u, o, e).length > 0

// Credentials middleware
// sets a bunch of user* properties easing permissions checking
exports.rolesMiddleware = (req, res, next) => {
	req._rolesMiddleware = true

	if (!req.session.login) {
		debug('not logged in')
		return next()
	}

	People.findOne({ ldapUid: req.session.login }).then(people => {
		if (!people) {
			debug('invalid login: force-disconnect!')
			req.session.login = null
			return next()
		}

		req.userId = people.id
		req.userPeople = people
		req.userRoles = getRoles(people)
		req.userCentralRole = getCentralRole(people)

		// Credential helpers
		req.userCanEditPeople = p => canEditPeople(req, p)
		req.userListViewablePeople = () => listViewablePeople(req)
		req.userCanEditActivity = a => canEditActivity(req, a)
		req.userListViewableActivities = () => listViewableActivities(req)
		req.userCanEditOrganization = o => canEditOrganization(req, o)

		debug(req.userRoles)
		next()
	})
}

// scope middleware
// Sets "req.userScopeOrganizationId" and check its validity
exports.scopeOrganizationMiddleware = (req, res, next) => {
	if (!req._rolesMiddleware) {
		return next(ServerError({ title: 'Invalid usage of "scopeOrganizationMiddleware" without prior usage of "rolesMiddleware"' }))
	}

	req._scopeOrganizationMiddleware = true

	const orgId = req.query.organization // maybe change later, part of URL, different name…

	// Scope requested: check if it is valid for connected user
	if (orgId && !req.userRoles[orgId] && !req.userCentralRole) {
		return next(UnauthorizedError({ title: `Specified invalid scope (organization id) "${orgId}" (user is not central and has no permission on this organization)` }))
	}

	// Scope requested: check if organization exists and then let go
	if (orgId) {
		// Wrap around promises to catch invalid ObjectId format
		return Promise.resolve(orgId)
		.then(id => Organization.findById(id))
		.then(found => {
			if (!found) {
				return Promise.reject(NotFoundError({ title: `Specified invalid scope (organization id) "${orgId}" (not found)` }))
			}
			// Sets scope and let go
			req.userScopeOrganizationId = orgId
			next()
		})
		.catch(next)
	}

	// Global scope requested: check if it is valid for connected user
	if (!orgId && !req.userCentralRole) {
		return next(UnauthorizedError({ title: 'Access to global scope refused for non central people, please add "?organization=…" to scope your queries' }))
	}

	// Global scope requested for central user: set scope and let go
	req.userScopeOrganizationId = null
	next()
}

// Return viewable people for current user, scope included
/*
Who can *view* a people?
- anyone sharing academicMembership (done by lookup below)
*/
const listViewablePeople = (req) => {
	if (!req._scopeOrganizationMiddleware) {
		return Promise.reject(Error('Invalid usage of "listViewablePeople" without prior usage of "scopeOrganizationMiddleware"'))
	}

	const isInScope = req.userScopeOrganizationId
		? // Scoped: limit to people from this organization
			{ 'memberships.orgId': req.userScopeOrganizationId }
		: // Unscoped: limit to people from organizations he has access to
			{ 'memberships.orgId': { $in: Object.keys(req.userRoles) } }

	// External people = ALL memberships are either expired or linked to an unmonitored organization
	const isExternal = { memberships: { $not: { $elemMatch: {
		$or: [ { endDate: { $gte: today() } }, { endDate: { $exists: false } } ],
		orgMonitored: true
	} } } }

	// Use aggregation to lookup organization and calculate union of "in scope" + "externals"
	/* First version kept for history: much more performant, but we must return a Mongoose.Query to allow proper population,
		formatting, templating… in rest-utils. A wide rewrite should be planned to allow using aggregations in REST endpoints

	return People.aggregate()
		.project({ _id: 1, people: '$$ROOT' }) // Keep original data intact for later use
		.unwind('people.academicMemberships') // Lookup only works with flat data, unwind array
		.lookup({ from: Organization.collection.name, localField: 'people.academicMemberships.organization', foreignField: '_id', as: 'orgs' }) // LEFT JOIN Organization
		.unwind('orgs') // "orgs" can be a zero-or-one-item array, unwind that before grouping
		.project({ _id: 1, people: 1, membership: { orgId: '$orgs._id', endDate: '$people.academicMemberships.endDate', orgMonitored: '$orgs.isariMonitored' } }) // Group membership info together
		.group({ _id: '$_id', memberships: { $push: '$membership' }, people: { $first: '$people' } }) // Now regroup membership data to single people
		.match({ $or: [ isInScope, isExternal ] }) // Finally apply filters
		.then(map(p => Object.assign(p.people, { // And keep only People (untouched) data with additional membership info
			_external: !p.memberships.some(m => m.endDate && m.endDate >= today() && m.orgMonitored)
		})))
	*/
	return People.aggregate()
		.project({ _id: 1, academicMemberships: 1 }) // Keep original data intact for later use
		.unwind('academicMemberships') // Lookup only works with flat data, unwind array
		.lookup({ from: Organization.collection.name, localField: 'academicMemberships.organization', foreignField: '_id', as: 'org' }) // LEFT JOIN Organization
		.unwind('org') // "orgs" can be a zero-or-one-item array, unwind that before grouping
		.project({ _id: 1, membership: { orgId: '$org._id', endDate: '$academicMemberships.endDate', orgMonitored: '$org.isariMonitored' } }) // Group membership info together
		.group({ _id: '$_id', memberships: { $push: '$membership' }, people: { $first: '$people' } }) // Now regroup membership data to single people
		.match({ $or: [ isInScope, isExternal ] }) // Finally apply filters
		.project({ _id: 1 }) // Keep only id
		.then(map('_id'))
		.then(ids => ({
			// Populate to allow getPeoplePermissions to work
			query: People.find({ _id: { $in: ids } }).populate('academicMemberships.organization')
		}))
}

// Check if a people is editable by current user
/*
Who can *write* a people?
- himself
- central admin
- anyone if people is external
- center admin of people's centers (academicMemberships)
- center editor of people's centers (academicMemberships)
*/
const isExternalPeople = p => !p.academicMemberships.some(m => m.endDate && m.endDate >= today() && m.organization.isariMonitored)
const canEditPeople = (req, p) =>
	req.userId === String(p._id) || // himself
	req.userCentralRole === 'admin' || // central admin or central reader
	isExternalPeople(p) || // external people
	hasMatchingCredentials(req.userRoles, map('organization')(p.academicMemberships), ['center_editor', 'center_admin'])

// Return viewable activities for current user, scope included
/*
Who can *view* an activity?
- anyone having access to any of its organizations
(access has been checked by scope earlier, so it's just about checking if activity.organizations contains requested scope)
*/
const listViewableActivities = (req) => {
	if (!req._scopeOrganizationMiddleware) {
		return Promise.reject(Error('Invalid usage of "listViewableActivities" without prior usage of "scopeOrganizationMiddleware"'))
	}

	const filter = req.userScopeOrganizationId
		? // Scoped: limit to people from this organization
			{ 'organizations.organization': req.userScopeOrganizationId }
		: // Unscoped: limit to people from organizations he has access to
			{ 'organizations.organization': { $in: Object.keys(req.userRoles) } }

	return Promise.resolve({ query: Activity.find(filter) })
}

// Check if an activity is editable by current user
/*
Who can *edit* an activity?
- central admin
*/
const canEditActivity = (req, a) => // eslint-disable-line no-unused-vars
	req.userCentralRole === 'admin' // central admin or central reader

// Check if an organization is editable by current user
/*
Who can *edit* an organization?
- central admin
- center admin for this organization
*/
const canEditOrganization = (req, o) => // eslint-disable-line no-unused-vars
	req.userCentralRole === 'admin' || // central admin or central reader
	hasMatchingCredentials(req.userRoles, [o], ['center_admin'])
<|endoftext|>


<commit_before>// Helpers for custom tasks, mainly around promises / exec
var exec = require('faithful-exec');
var shjs = require('shelljs');

module.exports = function (grunt) {
    // URI paths for our tasks to use
    grunt.dist = './dist/';
    grunt.uriTask = './grunt/';
    grunt.uriSrc = './src/UI';
    grunt.uriBuild = './build/';

    function promising(task, promise) {
        var done = task.async();
        promise.then(function () {
            done();
        }, function (error) {
            grunt.log.write(error + '\n');
            done(false);
        });
    }

    function system(cmd) {
        grunt.log.write('% ' + cmd + '\n');
        return exec(cmd).then(function (result) {
            grunt.log.write(result.stderr + result.stdout);
        }, function (error) {
            grunt.log.write(error.stderr + '\n');
            throw 'Failed to run \'' + cmd + '\'';
        });
    }

    function ensureCleanMaster() {
        return exec('git symbolic-ref HEAD').then(function (result) {
            if (result.stdout.trim() !== 'refs/heads/master') throw 'Not on master branch, aborting';
            return exec('git status --porcelain');
        }).then(function (result) {
            if (result.stdout.trim() !== '') throw 'Working copy is dirty, aborting';
        });
    }

    // Each task has it's own JS file.
    var tasks = {};
    tasks.pkg = grunt.file.readJSON('package.json');

    // General tasks
    tasks = require(grunt.uriTask + 'clean.js')(grunt, tasks);
    tasks = require(grunt.uriTask + 'watch.js')(grunt, tasks);

    // Concatenation Tasks
    tasks = require(grunt.uriTask + 'concat-js.js')(grunt, tasks);

    // Documentation Generator
    tasks = require(grunt.uriTask + 'ngdocs.js')(grunt, tasks);

    // Compass Tasks
    tasks = require(grunt.uriTask + 'sass.js')(grunt, tasks);

    // Minify Tasks
    tasks = require(grunt.uriTask + 'minify-html.js')(grunt, tasks);
    tasks = require(grunt.uriTask + 'minify-js.js')(grunt, tasks);

    grunt.registerTask('docs', 'Generate documentation', [
        'ngdocs'
    ]);

    grunt.registerTask('build', 'Perform a normal build', [
        'clean',
        'sass:prod',
        'sass:dev',
        'htmlmin:prod',
        'concat:js',
        'uglify:js',
        'ngdocs'
    ]);

    grunt.registerTask('default', [
        'build'
    ]);

    grunt.registerTask('publish', 'Publish a clean build, docs, and sample to github.io', function () {
        promising(this,
            ensureCleanMaster()
                .then(function () {
                    return system('cp -R -u -v docs/* build/docs');
                }).then(function () {
                    return system('git checkout gh-pages');
                }).then(function () {
                    return system('rm -rf css');
                }).then(function () {
                    return system('rm -rf font');
                }).then(function () {
                    return system('rm -rf js');
                }).then(function () {
                    return system('rm -rf partials');
                }).then(function () {
                    return system('cp -R -u -v build/docs/* .');
                }).then(function () {
                    return system('git add --all');
                }).then(function () {
                    return system('git commit -m "Automatic gh-pages build"');
                }).then(function () {
                    return system('git push');
                }).then(function () {
                    return system('git checkout master');
                })
        );
    });

    // Initialize The Grunt Configuration
    grunt.initConfig(tasks);
};
<commit_msg>update docs<commit_after>// Helpers for custom tasks, mainly around promises / exec
var exec = require('faithful-exec');
var shjs = require('shelljs');

module.exports = function (grunt) {
    // URI paths for our tasks to use
    grunt.dist = './dist/';
    grunt.uriTask = './grunt/';
    grunt.uriSrc = './src/UI';
    grunt.uriBuild = './build/';

    function promising(task, promise) {
        var done = task.async();
        promise.then(function () {
            done();
        }, function (error) {
            grunt.log.write(error + '\n');
            done(false);
        });
    }

    function system(cmd) {
        grunt.log.write('% ' + cmd + '\n');
        return exec(cmd).then(function (result) {
            grunt.log.write(result.stderr + result.stdout);
        }, function (error) {
            grunt.log.write(error.stderr + '\n');
            throw 'Failed to run \'' + cmd + '\'';
        });
    }

    function ensureCleanMaster() {
        return exec('git symbolic-ref HEAD').then(function (result) {
            if (result.stdout.trim() !== 'refs/heads/master') throw 'Not on master branch, aborting';
            return exec('git status --porcelain');
        }).then(function (result) {
            if (result.stdout.trim() !== '') throw 'Working copy is dirty, aborting';
        });
    }

    // Each task has it's own JS file.
    var tasks = {};
    tasks.pkg = grunt.file.readJSON('package.json');

    // General tasks
    tasks = require(grunt.uriTask + 'clean.js')(grunt, tasks);
    tasks = require(grunt.uriTask + 'watch.js')(grunt, tasks);

    // Concatenation Tasks
    tasks = require(grunt.uriTask + 'concat-js.js')(grunt, tasks);

    // Documentation Generator
    tasks = require(grunt.uriTask + 'ngdocs.js')(grunt, tasks);

    // Compass Tasks
    tasks = require(grunt.uriTask + 'sass.js')(grunt, tasks);

    // Minify Tasks
    tasks = require(grunt.uriTask + 'minify-html.js')(grunt, tasks);
    tasks = require(grunt.uriTask + 'minify-js.js')(grunt, tasks);

    grunt.registerTask('docs', 'Generate documentation', [
        'ngdocs'
    ]);

    grunt.registerTask('build', 'Perform a normal build', [
        'clean',
        'sass:prod',
        'sass:dev',
        'htmlmin:prod',
        'concat:js',
        'uglify:js',
        'ngdocs'
    ]);

    grunt.registerTask('default', [
        'build'
    ]);

    grunt.registerTask('publish', 'Publish a clean build, docs, and sample to github.io', function () {
        promising(this,
            ensureCleanMaster()
                .then(function () {
                    return system('cp -R -u -v docs/* build/docs');
                }).then(function () {
                    return system('git checkout gh-pages');
                }).then(function () {
                    return system('rm -rf css');
                }).then(function () {
                    return system('rm -rf font');
                }).then(function () {
                    return system('rm -rf js');
                }).then(function () {
                    return system('rm -rf partials');
                }).then(function () {
                    return system('cp -R -u -v build/docs/* .');
                }).then(function () {
                    return system('git add --all');
                }).then(function () {
                    return system('git commit -m "Automatic gh-pages build"');
                }).then(function () {
                    return system('git push');
                }).then(function () {
                    return system('git checkout master');
                })
        );
    });

    // Initialize The Grunt Configuration
    grunt.initConfig(tasks);
};
<|endoftext|>


<commit_before>module.exports = function(grunt) {
	grunt.initConfig({
		connect: {
			server: {
				options: {
					port: 9000,
					base: 'public/',
					hostname: '*',
					useAvailablePort: true,
					livereload: true
				}
			}
		},
		watch: {
			project: {
				files: [
					'public/**/*.js',
					'public/**/*.html',
					'public/**/*.json',
					'public/**/*.css'
				],
				options: {
					livereload: true
				}
			}
		}
	});

	grunt.loadNpmTasks('grunt-contrib-connect');
	grunt.loadNpmTasks('grunt-contrib-watch');

	grunt.registerTask('default', ['connect', 'watch']);

};
<commit_msg>Changes applied to Gruntfile<commit_after>module.exports = function(grunt) {
	grunt.initConfig({
		connect: {
			server: {
				options: {
					port: 9000,
					base: 'public/',
					hostname: '*',
					useAvailablePort: true,
					livereload: true
				}
			}
		},
		stylus: {
			compile: {
				options: {
					paths: ['public/css'],
					'include css': true
				},
				files: {
					'public/css/main.css': 'public/css/main.styl'
				}
			}
		},
		watch: {
			project: {
				files: [
					'public/**/*.js',
					'public/**/*.html',
					'public/**/*.json',
					'public/**/*.styl'
				],
				options: {
					livereload: true
				}
			},
			stylesheets: {
				files: ['public/css/**/*.styl', 'public/css/**/*.css'],
				tasks: ['stylus'],
				options: {
					interrupt: true
				}
			}
		}
	});

	grunt.loadNpmTasks('grunt-contrib-connect');
	grunt.loadNpmTasks('grunt-contrib-watch');
	grunt.loadNpmTasks('grunt-contrib-stylus');

	grunt.registerTask('default', ['connect', 'watch', 'stylus']);

};
<|endoftext|>


<commit_before>import './engines';
import './thrusters';
import './sensorContacts';
import './clientPing';
import './reactor';
import './phasers';
import './coolant';
import './selfDestruct';
<commit_msg>And processes<commit_after>import fs from "fs";
import path from "path";

const dir = "./server/processes/";
fs
  .readdirSync(path.resolve(dir))
  .filter(p => p.indexOf(".js") > -1 && p !== "index.js")
  .forEach(p => require(`./${p}`));
<|endoftext|>


<commit_before>module.exports = function(grunt) {
	// Project configuration.
	grunt.initConfig({
		pkg: grunt.file.readJSON('package.json'),
		concat: {
			options: {
				separator: ';',
				process: function(src, filepath) {
				  return src.replace(/@VERSION/g, grunt.config.get('pkg.version'));
				}
			},
			dist: {
	  			src: [
					'bower_components/modernizr/modernizr.js',
					'media/boom/js/string.js',
					'bower_components/jquery/dist/jquery.js',
					'bower_components/jquery-ui/jquery-ui.js',
					'media/boom/js/jquery/ui.splitbutton.js',
					'media/boom/js/jquery/ui.tree.js',
					'bower_components/jgrowl/jquery.jgrowl.js',
					'bower_components/tablesorter/jquery.tablesorter.js',
					'bower_components/datetimepicker/jquery.datetimepicker.js',
					'media/boom/js/boom/plugins.js',
					'media/boom/js/boom/config.js',
					'media/boom/js/boom/loader.js',
					'media/boom/js/boom/notification.js',
					'media/boom/js/boom/core.js',
					'media/boom/js/boom/history2.js',
					'media/boom/js/boom/log.js',
					'media/boom/js/boom/dialog.js',
					'media/boom/js/boom/alert.js',
					'media/boom/js/boom/confirmation.js',
					'bower_components/pushy/js/pushy.js',
					'media/boom/js/boom/tagAutocomplete.js',
					'media/boom/js/boom/page/status.js',
					'media/boom/js/boom/page/page.js',
					'media/boom/js/boom/page/settings.js',
					'media/boom/js/boom/page/editor.js',
					'media/boom/js/boom/page/toolbar.js',
					'media/boom/js/boom/page/tree.js',
					'media/boom/js/boom/page/url.js',
					'media/boom/js/boom/page/tagEditor.js',
					'media/boom/js/boom/page/tagSearch.js',
					'media/boom/js/boom/page/tagAutocomplete.js',
					'media/boom/js/boom/urlEditor.js',
					'media/boom/js/boom/page/featureEditor.js',
					'media/boom/js/boom/page/visibilityEditor.js',
					'media/boom/js/boom/textEditor.js',
					'media/boom/js/boom/chunk.js',
					'media/boom/js/boom/chunk/chunk.js',
					'media/boom/js/boom/chunk/text.js',
					'media/boom/js/boom/chunk/linkset.js',
					'media/boom/js/boom/chunk/feature.js',
					'media/boom/js/boom/chunk/asset.js',
					'media/boom/js/boom/chunk/slideshow.js',
					'media/boom/js/boom/chunk/timestamp.js',
					'media/boom/js/boom/chunk/tag.js',
					'media/boom/js/boom/chunk/slideshow/editor.js',
					'media/boom/js/boom/chunk/linkset/editor.js',
					'media/boom/js/boom/chunk/asset/editor.js',
					'media/boom/js/boom/chunk/pageTags.js',
					'media/boom/js/boom/chunk/pageVisibility.js',
					'media/boom/js/boom/page/title.js',
					'media/boom/js/boom/link/link.js',
					'media/boom/js/boom/link/picker.js',
					'media/boom/js/boom/template/manager.js',
					'media/boom/js/boom/asset/asset.js',
					'media/boom/js/boom/asset/manager.js',
					'media/boom/js/boom/asset/picker.js',
					'media/boom/js/boom/asset/titleFilter.js',
					'media/boom/js/boom/asset/uploader.js',
					'media/boom/js/boom/asset/justify.js',
					'media/boom/js/boom/asset/tagAutocomplete.js',
					'media/boom/js/boom/asset/tagSearch.js',
					'media/boom/js/boom/group/group.js',
					'media/boom/js/boom/group/permissionsEditor.js',
					'media/boom/js/boom/person.js',
					'media/boom/js/boom/peopleManager.js',
					'bower_componenets/jquery-file-upload/js/jquery.fileupload.js',
					'bower_components/wysihtml/dist/wysihtml5x-toolbar.js',
					'media/boom/js/wysihtml5/parser_rules/full.js',
					'media/boom/js/wysihtml5/parser_rules/inline.js',
					'media/boom/js/wysihtml5/commands/insertBoomAsset.js',
					'media/boom/js/wysihtml5/commands/createBoomLink.js',
					'media/boom/js/wysihtml5/commands/cta.js',
					'media/boom/js/wysihtml5/commands/insertSuperscript.js',
					'media/boom/js/wysihtml5/commands/insertSubscript.js',
					'media/boom/js/jquery/jqpagination.js',
					'media/boom/js/boom/page/manager.js'
				],
				dest: 'media/boom/js/cms.js'
			}
		},
		uglify: {
			options: {
				banner: '/*! <%= pkg.name %> - v<%= pkg.version %> (<%= grunt.template.today("yyyy-mm-dd") %>) */\n',
				sourceMap: true
			},
			build: {
				files: {
					'media/boom/js/cms.js': 'media/boom/js/cms.js'
				}
			}
		},
		less: {
			production: {
				options: {
					paths: ["media/boom/css"]
				},
				files: {
					"media/boom/css/cms.css": "media/boom/css/cms.less"
				}
			}
		},
		cssmin: {
			options: {
				shorthandCompacting: false,
				roundingPrecision: -1
			},
			target: {
				files: {
					 'media/boom/css/cms.css': [
						'bower_components/pushy/css/pushy.css',
						'bower_components/datetimepicker/jquery.datetimepicker.css',
						'bower_components/jquery-ui/themes/base/autocomplete.css',
						'bower_components/jquery-ui/themes/base/tabs.css',
						'bower_components/jquery-ui/themes/base/sortable.css',
						'bower_components/jquery-ui/themes/base/core.css',
						'bower_components/jquery-ui/themes/base/button.css',
						'bower_components/jquery-ui/themes/base/draggable.css',
						'bower_components/jquery-ui/themes/base/dialog.css',
						'media/boom/css/libraries/jqpagination.css',
						'media/boom/css/cms.css'
					 ]
				}
			}
		},
		watch: {
			css: {
				files: '**/*.less',
				tasks: ['build']
			},
			js: {
				files: 'media/boom/js/*.js',
				tasks: ['build']
			}
		}
	});

	grunt.loadNpmTasks('grunt-contrib-watch');
	grunt.loadNpmTasks('grunt-contrib-concat');
	grunt.loadNpmTasks('grunt-contrib-uglify');
	grunt.loadNpmTasks('grunt-contrib-less');
	grunt.loadNpmTasks('grunt-contrib-cssmin');

	grunt.registerTask('build', ['less', 'concat:dist']);
	grunt.registerTask('dist', ['less', 'cssmin', 'concat:dist', 'uglify']);
	grunt.registerTask('default',['watch']);
};
<commit_msg>Updated locations in Gruntfile.js<commit_after>module.exports = function(grunt) {
	// Project configuration.
	grunt.initConfig({
		pkg: grunt.file.readJSON('package.json'),
		concat: {
			options: {
				separator: ';',
				process: function(src, filepath) {
				  return src.replace(/@VERSION/g, grunt.config.get('pkg.version'));
				}
			},
			dist: {
	  			src: [
					'bower_components/modernizr/modernizr.js',
					'public/boom/js/string.js',
					'bower_components/jquery/dist/jquery.js',
					'bower_components/jquery-ui/jquery-ui.js',
					'public/boom/js/jquery/ui.splitbutton.js',
					'public/boom/js/jquery/ui.tree.js',
					'bower_components/jgrowl/jquery.jgrowl.js',
					'bower_components/tablesorter/jquery.tablesorter.js',
					'bower_components/datetimepicker/jquery.datetimepicker.js',
					'public/boom/js/boom/plugins.js',
					'public/boom/js/boom/config.js',
					'public/boom/js/boom/loader.js',
					'public/boom/js/boom/notification.js',
					'public/boom/js/boom/core.js',
					'public/boom/js/boom/history2.js',
					'public/boom/js/boom/log.js',
					'public/boom/js/boom/dialog.js',
					'public/boom/js/boom/alert.js',
					'public/boom/js/boom/confirmation.js',
					'bower_components/pushy/js/pushy.js',
					'public/boom/js/boom/tagAutocomplete.js',
					'public/boom/js/boom/page/status.js',
					'public/boom/js/boom/page/page.js',
					'public/boom/js/boom/page/settings.js',
					'public/boom/js/boom/page/editor.js',
					'public/boom/js/boom/page/toolbar.js',
					'public/boom/js/boom/page/tree.js',
					'public/boom/js/boom/page/url.js',
					'public/boom/js/boom/page/tagEditor.js',
					'public/boom/js/boom/page/tagSearch.js',
					'public/boom/js/boom/page/tagAutocomplete.js',
					'public/boom/js/boom/urlEditor.js',
					'public/boom/js/boom/page/featureEditor.js',
					'public/boom/js/boom/page/visibilityEditor.js',
					'public/boom/js/boom/textEditor.js',
					'public/boom/js/boom/chunk.js',
					'public/boom/js/boom/chunk/chunk.js',
					'public/boom/js/boom/chunk/text.js',
					'public/boom/js/boom/chunk/linkset.js',
					'public/boom/js/boom/chunk/feature.js',
					'public/boom/js/boom/chunk/asset.js',
					'public/boom/js/boom/chunk/slideshow.js',
					'public/boom/js/boom/chunk/timestamp.js',
					'public/boom/js/boom/chunk/tag.js',
					'public/boom/js/boom/chunk/slideshow/editor.js',
					'public/boom/js/boom/chunk/linkset/editor.js',
					'public/boom/js/boom/chunk/asset/editor.js',
					'public/boom/js/boom/chunk/pageTags.js',
					'public/boom/js/boom/chunk/pageVisibility.js',
					'public/boom/js/boom/page/title.js',
					'public/boom/js/boom/link/link.js',
					'public/boom/js/boom/link/picker.js',
					'public/boom/js/boom/template/manager.js',
					'public/boom/js/boom/asset/asset.js',
					'public/boom/js/boom/asset/manager.js',
					'public/boom/js/boom/asset/picker.js',
					'public/boom/js/boom/asset/titleFilter.js',
					'public/boom/js/boom/asset/uploader.js',
					'public/boom/js/boom/asset/justify.js',
					'public/boom/js/boom/asset/tagAutocomplete.js',
					'public/boom/js/boom/asset/tagSearch.js',
					'public/boom/js/boom/group/group.js',
					'public/boom/js/boom/group/permissionsEditor.js',
					'public/boom/js/boom/person.js',
					'public/boom/js/boom/peopleManager.js',
					'bower_componenets/jquery-file-upload/js/jquery.fileupload.js',
					'bower_components/wysihtml/dist/wysihtml5x-toolbar.js',
					'public/boom/js/wysihtml5/parser_rules/full.js',
					'public/boom/js/wysihtml5/parser_rules/inline.js',
					'public/boom/js/wysihtml5/commands/insertBoomAsset.js',
					'public/boom/js/wysihtml5/commands/createBoomLink.js',
					'public/boom/js/wysihtml5/commands/cta.js',
					'public/boom/js/wysihtml5/commands/insertSuperscript.js',
					'public/boom/js/wysihtml5/commands/insertSubscript.js',
					'public/boom/js/jquery/jqpagination.js',
					'public/boom/js/boom/page/manager.js'
				],
				dest: 'public/boom/js/cms.js'
			}
		},
		uglify: {
			options: {
				banner: '/*! <%= pkg.name %> - v<%= pkg.version %> (<%= grunt.template.today("yyyy-mm-dd") %>) */\n',
				sourceMap: true
			},
			build: {
				files: {
					'public/boom/js/cms.js': 'public/boom/js/cms.js'
				}
			}
		},
		less: {
			production: {
				options: {
					paths: ["public/boom/css"]
				},
				files: {
					"public/boom/css/cms.css": "public/boom/css/cms.less"
				}
			}
		},
		cssmin: {
			options: {
				shorthandCompacting: false,
				roundingPrecision: -1
			},
			target: {
				files: {
					 'public/boom/css/cms.css': [
						'bower_components/pushy/css/pushy.css',
						'bower_components/datetimepicker/jquery.datetimepicker.css',
						'bower_components/jquery-ui/themes/base/autocomplete.css',
						'bower_components/jquery-ui/themes/base/tabs.css',
						'bower_components/jquery-ui/themes/base/sortable.css',
						'bower_components/jquery-ui/themes/base/core.css',
						'bower_components/jquery-ui/themes/base/button.css',
						'bower_components/jquery-ui/themes/base/draggable.css',
						'bower_components/jquery-ui/themes/base/dialog.css',
						'public/boom/css/libraries/jqpagination.css',
						'public/boom/css/cms.css'
					 ]
				}
			}
		},
		watch: {
			css: {
				files: '**/*.less',
				tasks: ['build']
			},
			js: {
				files: 'public/boom/js/*.js',
				tasks: ['build']
			}
		}
	});

	grunt.loadNpmTasks('grunt-contrib-watch');
	grunt.loadNpmTasks('grunt-contrib-concat');
	grunt.loadNpmTasks('grunt-contrib-uglify');
	grunt.loadNpmTasks('grunt-contrib-less');
	grunt.loadNpmTasks('grunt-contrib-cssmin');

	grunt.registerTask('build', ['less', 'concat:dist']);
	grunt.registerTask('dist', ['less', 'cssmin', 'concat:dist', 'uglify']);
	grunt.registerTask('default',['watch']);
};
<|endoftext|>


<commit_before>const express = require('express');
const _ = require('lodash');
const request = require('request');
const requestProm = require('request-promise');
const fs = require('fs');
const base64url = require('base64url');
const URL = require('url').URL;

const google = require('googleapis');
const slackWebClient = require('@slack/client').WebClient;
const graph = require('fbgraph');
const PubSub = require('@google-cloud/pubsub');

const authTokens = require(__base + 'secret/auth-tokens.json');
const authConf = {
    'facebook' : {
        'clientID' : authTokens.fbClientID, // also refer to as app-ID
        'clientSecret' : authTokens.fbClientSecret, // also refer to as app-secret
        'scope' : 'email, public_profile, user_friends, user_posts',
        'redirectUri' : 'https://lynxapp.me/api/auth/facebook_oauth'
    },
    'gmail' : {
        'clientID': authTokens.gmailClientID,
        'clientSecret' : authTokens.gmailClientSecret,
        'scope' : ['https://www.googleapis.com/auth/plus.me',
                   'https://www.googleapis.com/auth/gmail.readonly'],
        'redirectUri' : 'https://lynxapp.me/api/auth/gmail_oauth'
    },
    'slack' : {
        'clientID' : authTokens.slackClientID,
        'clientSecret' : authTokens.slackClientSecret,
        'scope': 'users.profile%3Aread+users%3Aread+channels%3Ahistory+channels%3Aread+im%3Aread+im%3Ahistory+mpim%3Aread+mpim%3Ahistory',
        'redirectUri' : 'https://lynxapp.me/api/auth/slack_oauth'
    }
};

module.exports.Router = function (MessageDB, socketIo) {
	const router = express.Router();

    // Parses a string and returns an array of links if there are any.
    // @param {string} text - the text to be processed.
    function regParser(message) {
        // Resource: https://gist.github.com/dperini/729294
        // defensive
        console.log("message in reg parser", message);
        if (message) {
            // Slack puts brackets around their links, so we need to remove them.
            var words = message.replace(/[<>]/g,'').split(' ');

            var urls = [];

            for (i = 0; i < words.length; i++) {
                console.log('The word is: ' + words[i]);

                // if it is a link, we will call the link summary API
                try {
                    const url = new URL(words[i]);
                    urls.push(url);
                } catch (e) {
                    console.log("not a link");
                }
            }
            return urls;
        }
    }

    // Calls Ena's link summary API. We return the result back to the client so it can format it.
    // @param {Object} linkinfo - other additional information about the link, which has the following properties:
    //                              service {string} - name of the platform
    //                              sender {string} - name of the sender
    function generateLinkSummary(url, linkInfo) {
        // defensive
        let linkSummary = {
            url: url.href,
            platformName: linkInfo.platform,
            sender: linkInfo.sender,
            timeSent: linkInfo.timeStamp,
            domainName: url.hostname,
            note: linkInfo.bodyText,
            isRead: false,
            tags: [],
            type: "article",
            title: "",
            description: "",
            imageUrl: ""
        };

        const prefix = 'https://info344api.enamarkovic.com/v1/summary?url=';
        return requestProm(prefix + url.href).then(body => {
            const urlData = JSON.parse(body);
            linkSummary.type = urlData.type && urlData.type.length > 0 ? urlData.type : "article";
            linkSummary.title = urlData.title ? urlData.title : "";
            linkSummary.description = urlData.description ? urlData.description : "";
            linkSummary.imageUrl = urlData.image ? urlData.image : "";
        }).catch(err => {
            console.error(err)
        }).then(() => linkSummary)
    }

    // Facebook oauth: https://developers.facebook.com/docs/facebook-login/manually-build-a-login-flow
    router.get('/facebook_oauth', function(req, res) {
        if (!req.query.code) {
            const oauthUrl = 'https://www.facebook.com/v2.9/dialog/oauth?client_id='
                + authConf.facebook.clientID
                + '&scope=' + authConf.facebook.scope
                + '&redirect_uri=' + authConf.facebook.redirectUri;

            if (!req.query.error) {
                res.redirect(oauthUrl);
            } else {
                res.status(401).send('Access denied to Facebook');
            }
        } else {
            // Exchanging an authorization code for an API access token
            const apiTokenUrl = 'https://graph.facebook.com/v2.9/oauth/access_token?client_id='
                + authConf.facebook.clientID
                + '&redirect_uri=' + authConf.facebook.redirectUri
                + '&client_secret=' + authConf.facebook.clientSecret
                + '&code=' + req.query.code;

            request(apiTokenUrl, function (err, apiTokenRes, body) {
                if (!err && apiTokenRes.statusCode == 200) {
                    const info = JSON.parse(body);
                    graph.setAccessToken(info.access_token);
                    res.redirect('https://lynxapp.me/app');
                } else {
                    console.log('Error: unable to get Facebook API token.');
                }
            });
        }
    });

    // Facebook webhook: https://developers.facebook.com/docs/graph-api/webhooks
    router.post('/facebook_incoming', function(facebookReq, facebookRes) {
        // Facebook challenge. Saving just in case.
        // if (req.query['hub.mode'] === 'subscribe' && req.query['hub.verify_token'] === 'my_voice_is_my_password_verify_me') {
        //     console.log("Validating webhook");
        //     res.status(200).send(req.query['hub.challenge']);
        // } else {
        //     console.error("Failed validation. Make sure the validation tokens match.");
        //     res.sendStatus(403);
        // }
        //
        // changes: [ { field: 'status',
        //              id: '44444444_444444444',
        //              value: 'This is an Example Status.' } ]
        const newStatus = facebookReq.body.entry[0].changes[0];

        const statusValue = newStatus.value;

        const links = regParser(statusValue);

        if (links && links.length) {
            const reqParam = {
                fields: 'type,caption,description,link,updated_time,from,message'
            };

            // Grab information about the status via the API
            graph.get(newStatus.id, reqParam, function(err, res) {
                if (err) {
                    console.log("Error: not authorized with Facebook");
                    facebookRes.redirect('/facebook_oauth');
                } else {
                    var message = res;
                    console.log(message);

                    if (message.type === 'video'|| message.type === 'link') {
                        var linkInfo = {
                            platform: 'facebook',
                            sender: message.from.name,
                            bodyText: facebookReq.body.entry[0].changes[0].value ? facebookReq.body.entry[0].changes[0].value : "",
                            timeStamp: message.updated_time
                        };
                        console.log("link info", linkInfo);
                        console.log("link", message.link);
                        generateLinkSummary(message.link, linkInfo).then(linkSummary => {
                            // add the message to the database
                            console.log("link summary:", linkSummary);
                            return MessageDB.insertMessage(1, linkSummary)
                        }).then((message) => {
                            console.log(message);

                            socketIo.emit("new_message", {message: message});
                            // send the added message back to the user through web socket
                            // this should broadcast to users
                            facebookRes.status(200).send(message);
                        }).catch(console.log);
                    } else {
                        facebookRes.status(200).send("not a link");
                    }
                }
            });
        }
    });

    // Gmail Oauth: https://developers.google.com/identity/protocols/OAuth2WebServer
    router.get('/gmail_oauth', function(req, res) {
        /* const oauthUrl = 'https://accounts.google.com/o/oauth2/v2/auth?client_id='
        + authConf.gmail.clientID
        + '&redirect_uri' + authConf.gmail.redirectUri
        + '&response_type=code&scope=' + auth.gmail.scope;
        res.redirect(oauthUrl); */
        const oauth2 = google.auth.OAuth2;
        const oauth2Client = new oauth2(
            authConf.gmail.clientID,
            authConf.gmail.clientSecret,
            authConf.gmail.redirectUri
        );

        if (!req.query.code) {
            const oauthUrl = oauth2Client.generateAuthUrl({
                access_type: 'offline',
                scope: authConf.gmail.scope
            });
            res.redirect(oauthUrl);
        } else {
            oauth2Client.getToken(req.query.code, function(err, tokens) {
                if (!err) {
                    oauth2Client.setCredentials(tokens);
                    res.redirect('https://lynxapp.me/app');
                }
            });
        }
    });

    router.post('/gmail_incoming', function() {
        const projectId = 'civil-ripple-167409';

        const request = {
            'labelIds' : ['INBOX'],
            'topicName' : 'projects/civil-ripple-167409/subscriptions/gmail'
        };

        const gmail = google.gmail('v1');
        gmail.users().watch('me', request).execute();

        // Instantiates a client
        const pubsubClient = PubSub({
            projectId: projectId
        });

        // The name for the new topic
        const topicName = 'my-new-topic';

        // Creates the new topic
        pubsubClient.createTopic(topicName)
            .then((results) => {
                const topic = results[0];
                console.log(`Topic ${topic.name} created.`);
            })
            .catch((err) => {
                console.error('ERROR:', err);
            });
    });

    // Slack Oauth: https://api.slack.com/docs/oauth
    router.get('/slack_oauth', function(req, res) {
        if (!req.query.code) {
            const oauthUrl = 'https://slack.com/oauth/authorize?client_id='
                + authConf.slack.clientID
                + '&scope=' + authConf.slack.scope
                + '&redirect_uri=' + authConf.slack.redirectUri;
            res.redirect(oauthUrl);
        } else {
            // Exchanging an authorization code for an API access token
            const apiTokenUrl = 'https://slack.com/api/oauth.access?client_id='
                + authConf.slack.clientID
                + '&client_secret=' + authConf.slack.clientSecret
                + '&code=' + req.query.code
                + '&redirect_uri=' + authConf.slack.redirectUri;

            request(apiTokenUrl, function (err, apiTokenRes, body) {
                if (!err && apiTokenRes.statusCode == 200) {
                    const info = JSON.parse(body);
                    // There seems to be no way to set the token after the object is initiated.
                    // This is the work around.
                    authConf.slack.accessToken = info.access_token;
                    res.redirect('https://lynxapp.me/app');
                }
            });
        }
    });

    router.post('/slack_incoming', function(req, res) {
        // This is used to respond to slack challenges. Saved in case
        // the verification expires in the future.
        // res.type('html');
        // res.status(200).send(req.body.challenge);
        // event:
        //        { type: 'message',
        //          user: 'U50SS1PLJ',
        //          text: 'denver',
        //          ts: '1495684015.632873',
        //          channel: 'D51MCEQ1M',
        //          event_ts: '1495684015.632873' },
        if (req.body.event.text) {

            var info =  req.body.event;
            const slackWeb = new slackWebClient(authConf.slack.accessToken);

            var links = regParser(info.text);

            // parsed data will be the urls
            // let links = regParser(req.body.event.text);
            if (links.length > 0) {

                var linkInfo = {
                    platform : 'slack',
                    timeStamp: info.event_ts,
                    bodyText: (info.text).replace(/[<>]/g,'')
                };

                // identify the user through the Slack API
                slackWeb.users.info(info.user, function(usersInfoErr, usersInfo) {
                    if (usersInfoErr || !usersInfo.ok) {
                        console.log('Error: Unable to identify user.');
                        linkInfo.sender = '';
                    } else {
                        linkInfo.sender = usersInfo.user.profile.real_name;
                    }

                    // send the urls through 344 handler
                    // generate link summary is expecting url object ( new URL() )
                    generateLinkSummary(links[0], linkInfo).then(linkSummary => {
                        // add the message to the database
                        console.log("link summary:", linkSummary);
                        return MessageDB.insertMessage(1, linkSummary)
                    }).then((message) => {
                        console.log(message);

                        socketIo.emit("new_message", {message: message});
                        // send the added message back to the user through web socket
                        // this should broadcast to users
                        res.status(200).send(message);
                    }).catch(console.log)
                });
            } else {
                res.status(200).send("did not have a link");
            }
        } else {
            res.status(200).send("non-text event");
        }
    });

	return router;
};

<commit_msg>attempting using requests<commit_after>const express = require('express');
const _ = require('lodash');
const request = require('request');
const requestProm = require('request-promise');
const fs = require('fs');
const base64url = require('base64url');
const URL = require('url').URL;

const google = require('googleapis');
const slackWebClient = require('@slack/client').WebClient;
const graph = require('fbgraph');
const PubSub = require('@google-cloud/pubsub');

const authTokens = require(__base + 'secret/auth-tokens.json');
const authConf = {
    'facebook' : {
        'clientID' : authTokens.fbClientID, // also refer to as app-ID
        'clientSecret' : authTokens.fbClientSecret, // also refer to as app-secret
        'scope' : 'email, public_profile, user_friends, user_posts',
        'redirectUri' : 'https://lynxapp.me/api/auth/facebook_oauth'
    },
    'gmail' : {
        'clientID': authTokens.gmailClientID,
        'clientSecret' : authTokens.gmailClientSecret,
        'scope' : ['https://www.googleapis.com/auth/plus.me',
                   'https://www.googleapis.com/auth/gmail.readonly'],
        'redirectUri' : 'https://lynxapp.me/api/auth/gmail_oauth'
    },
    'slack' : {
        'clientID' : authTokens.slackClientID,
        'clientSecret' : authTokens.slackClientSecret,
        'scope': 'users.profile%3Aread+users%3Aread+channels%3Ahistory+channels%3Aread+im%3Aread+im%3Ahistory+mpim%3Aread+mpim%3Ahistory',
        'redirectUri' : 'https://lynxapp.me/api/auth/slack_oauth'
    }
};

module.exports.Router = function (MessageDB, socketIo) {
	const router = express.Router();

    // Parses a string and returns an array of links if there are any.
    // @param {string} text - the text to be processed.
    function regParser(message) {
        // Resource: https://gist.github.com/dperini/729294
        // defensive
        console.log("message in reg parser", message);
        if (message) {
            // Slack puts brackets around their links, so we need to remove them.
            var words = message.replace(/[<>]/g,'').split(' ');

            var urls = [];

            for (i = 0; i < words.length; i++) {
                console.log('The word is: ' + words[i]);

                // if it is a link, we will call the link summary API
                try {
                    const url = new URL(words[i]);
                    urls.push(url);
                } catch (e) {
                    console.log("not a link");
                }
            }
            return urls;
        }
    }

    // Calls Ena's link summary API. We return the result back to the client so it can format it.
    // @param {Object} linkinfo - other additional information about the link, which has the following properties:
    //                              service {string} - name of the platform
    //                              sender {string} - name of the sender
    function generateLinkSummary(url, linkInfo) {
        // defensive
        let linkSummary = {
            url: url.href,
            platformName: linkInfo.platform,
            sender: linkInfo.sender,
            timeSent: linkInfo.timeStamp,
            domainName: url.hostname,
            note: linkInfo.bodyText,
            isRead: false,
            tags: [],
            type: "article",
            title: "",
            description: "",
            imageUrl: ""
        };

        const prefix = 'https://info344api.enamarkovic.com/v1/summary?url=';
        return requestProm(prefix + url.href).then(body => {
            const urlData = JSON.parse(body);
            linkSummary.type = urlData.type && urlData.type.length > 0 ? urlData.type : "article";
            linkSummary.title = urlData.title ? urlData.title : "";
            linkSummary.description = urlData.description ? urlData.description : "";
            linkSummary.imageUrl = urlData.image ? urlData.image : "";
        }).catch(err => {
            console.error(err)
        }).then(() => linkSummary)
    }

    // Facebook oauth: https://developers.facebook.com/docs/facebook-login/manually-build-a-login-flow
    router.get('/facebook_oauth', function(req, res) {
        if (!req.query.code) {
            const oauthUrl = 'https://www.facebook.com/v2.9/dialog/oauth?client_id='
                + authConf.facebook.clientID
                + '&scope=' + authConf.facebook.scope
                + '&redirect_uri=' + authConf.facebook.redirectUri;

            if (!req.query.error) {
                res.redirect(oauthUrl);
            } else {
                res.status(401).send('Access denied to Facebook');
            }
        } else {
            // Exchanging an authorization code for an API access token
            const apiTokenUrl = 'https://graph.facebook.com/v2.9/oauth/access_token?client_id='
                + authConf.facebook.clientID
                + '&redirect_uri=' + authConf.facebook.redirectUri
                + '&client_secret=' + authConf.facebook.clientSecret
                + '&code=' + req.query.code;

            request(apiTokenUrl, function (err, apiTokenRes, body) {
                if (!err && apiTokenRes.statusCode == 200) {
                    const info = JSON.parse(body);
                    graph.setAccessToken(info.access_token);
                    res.redirect('https://lynxapp.me/app');
                } else {
                    console.log('Error: unable to get Facebook API token.');
                }
            });
        }
    });

    // Facebook webhook: https://developers.facebook.com/docs/graph-api/webhooks
    router.post('/facebook_incoming', function(facebookReq, facebookRes) {
        // Facebook challenge. Saving just in case.
        // if (req.query['hub.mode'] === 'subscribe' && req.query['hub.verify_token'] === 'my_voice_is_my_password_verify_me') {
        //     console.log("Validating webhook");
        //     res.status(200).send(req.query['hub.challenge']);
        // } else {
        //     console.error("Failed validation. Make sure the validation tokens match.");
        //     res.sendStatus(403);
        // }
        //
        // changes: [ { field: 'status',
        //              id: '44444444_444444444',
        //              value: 'This is an Example Status.' } ]
        const newStatus = facebookReq.body.entry[0].changes[0];

        const statusValue = newStatus.value;

        const links = regParser(statusValue);

        if (links && links.length) {
            const reqParam = {
                fields: 'type,caption,description,link,updated_time,from,message'
            };

            // Grab information about the status via the API
            graph.get(newStatus.id, reqParam, function(err, res) {
                if (err) {
                    console.log("Error: not authorized with Facebook");
                    facebookRes.redirect('/facebook_oauth');
                } else {
                    var message = res;
                    console.log(message);

                    if (message.type === 'video'|| message.type === 'link') {
                        var linkInfo = {
                            platform: 'facebook',
                            sender: message.from.name,
                            bodyText: facebookReq.body.entry[0].changes[0].value ? facebookReq.body.entry[0].changes[0].value : "",
                            timeStamp: message.updated_time
                        };
                        console.log("link info", linkInfo);
                        console.log("link", message.link);
                        generateLinkSummary(message.link, linkInfo).then(linkSummary => {
                            // add the message to the database
                            console.log("link summary:", linkSummary);
                            return MessageDB.insertMessage(1, linkSummary)
                        }).then((message) => {
                            console.log(message);

                            socketIo.emit("new_message", {message: message});
                            // send the added message back to the user through web socket
                            // this should broadcast to users
                            facebookRes.status(200).send(message);
                        }).catch(console.log);
                    } else {
                        facebookRes.status(200).send("not a link");
                    }
                }
            });
        }
    });

    // Gmail Oauth: https://developers.google.com/identity/protocols/OAuth2WebServer
    router.get('/gmail_oauth', function(req, res) {
        /* const oauthUrl = 'https://accounts.google.com/o/oauth2/v2/auth?client_id='
        + authConf.gmail.clientID
        + '&redirect_uri' + authConf.gmail.redirectUri
        + '&response_type=code&scope=' + auth.gmail.scope;
        res.redirect(oauthUrl); */
        const oauth2 = google.auth.OAuth2;
        const oauth2Client = new oauth2(
            authConf.gmail.clientID,
            authConf.gmail.clientSecret,
            authConf.gmail.redirectUri
        );

        if (!req.query.code) {
            const oauthUrl = oauth2Client.generateAuthUrl({
                access_type: 'offline',
                scope: authConf.gmail.scope
            });
            res.redirect(oauthUrl);
        } else {
            oauth2Client.getToken(req.query.code, function(err, tokens) {
                if (!err) {
                    oauth2Client.setCredentials(tokens);
                    res.redirect('https://lynxapp.me/app');
                }
            });
        }
    });

    router.post('/gmail_incoming', function() {
        const projectId = 'civil-ripple-167409';

        const request = {
            'labelIds' : ['INBOX'],
            'topicName' : 'projects/civil-ripple-167409/topics/gmail_incoming'
        };

        request({
            headers: {
                'Content-Type': 'application/json'
            },
            uri: 'https://www.googleapis.com/gmail/v1/users/me/watch'
            method: 'POST'
            }, function (err, watchRes, body) {
            if (!err && watchRes.statusCode == 200) {
                const info = JSON.parse(body);
                console.log(body)
            }
        });

    });

    // Slack Oauth: https://api.slack.com/docs/oauth
    router.get('/slack_oauth', function(req, res) {
        if (!req.query.code) {
            const oauthUrl = 'https://slack.com/oauth/authorize?client_id='
                + authConf.slack.clientID
                + '&scope=' + authConf.slack.scope
                + '&redirect_uri=' + authConf.slack.redirectUri;
            res.redirect(oauthUrl);
        } else {
            // Exchanging an authorization code for an API access token
            const apiTokenUrl = 'https://slack.com/api/oauth.access?client_id='
                + authConf.slack.clientID
                + '&client_secret=' + authConf.slack.clientSecret
                + '&code=' + req.query.code
                + '&redirect_uri=' + authConf.slack.redirectUri;

            request(apiTokenUrl, function (err, apiTokenRes, body) {
                if (!err && apiTokenRes.statusCode == 200) {
                    const info = JSON.parse(body);
                    // There seems to be no way to set the token after the object is initiated.
                    // This is the work around.
                    authConf.slack.accessToken = info.access_token;
                    res.redirect('https://lynxapp.me/app');
                }
            });
        }
    });

    router.post('/slack_incoming', function(req, res) {
        // This is used to respond to slack challenges. Saved in case
        // the verification expires in the future.
        // res.type('html');
        // res.status(200).send(req.body.challenge);
        // event:
        //        { type: 'message',
        //          user: 'U50SS1PLJ',
        //          text: 'denver',
        //          ts: '1495684015.632873',
        //          channel: 'D51MCEQ1M',
        //          event_ts: '1495684015.632873' },
        if (req.body.event.text) {

            var info =  req.body.event;
            const slackWeb = new slackWebClient(authConf.slack.accessToken);

            var links = regParser(info.text);

            // parsed data will be the urls
            // let links = regParser(req.body.event.text);
            if (links.length > 0) {

                var linkInfo = {
                    platform : 'slack',
                    timeStamp: info.event_ts,
                    bodyText: (info.text).replace(/[<>]/g,'')
                };

                // identify the user through the Slack API
                slackWeb.users.info(info.user, function(usersInfoErr, usersInfo) {
                    if (usersInfoErr || !usersInfo.ok) {
                        console.log('Error: Unable to identify user.');
                        linkInfo.sender = '';
                    } else {
                        linkInfo.sender = usersInfo.user.profile.real_name;
                    }

                    // send the urls through 344 handler
                    // generate link summary is expecting url object ( new URL() )
                    generateLinkSummary(links[0], linkInfo).then(linkSummary => {
                        // add the message to the database
                        console.log("link summary:", linkSummary);
                        return MessageDB.insertMessage(1, linkSummary)
                    }).then((message) => {
                        console.log(message);

                        socketIo.emit("new_message", {message: message});
                        // send the added message back to the user through web socket
                        // this should broadcast to users
                        res.status(200).send(message);
                    }).catch(console.log)
                });
            } else {
                res.status(200).send("did not have a link");
            }
        } else {
            res.status(200).send("non-text event");
        }
    });

	return router;
};

<|endoftext|>


<commit_before>module.exports = function(grunt) {

    // 1. CONFIG
    grunt.initConfig({
        pkg: grunt.file.readJSON("package.json"),

        jasmine: {
            messageBubble: {
                src: "dist/components/message-bubble/message-bubble.js",
                options: {
                    specs: "spec/specs-js/message-bubble-spec.js",
                    vendor: [
                        "bower_components/jquery/dist/jquery.js",
                        "bower_components/jasmine-jquery/lib/jasmine-jquery.js"
                    ],
                    helpers: "spec/helpers-js/message-bubble-helpers.js",
                    styles: "dist/components/message-bubble/message-bubble.css",
                    template: require("grunt-template-jasmine-istanbul"),
                    templateOptions: {
                        coverage: "bin/coverage/coverage.json",
                        report: [
                            {
                                type: "html",
                                options: {
                                    dir: "bin/coverage/html"
                                }
                            },

                            {
                                type: 'cobertura',
                                options: {
                                    dir: "bin/coverage/cobertura"
                                }
                            },

                            {
                                type: "text-summary"
                            }
                        ]
                    }
                }
            },

            toggleSwitch: {
                src: "dist/components/toggle-switch/toggle-switch.js",
                options: {
                    specs: "spec/specs-js/toggle-switch-spec.js",
                    vendor: [
                        "bower_components/jquery/dist/jquery.js",
                        "bower_components/jasmine-jquery/lib/jasmine-jquery.js"
                    ],
                    helpers: "spec/helpers-js/toggle-switch-helpers.js",
                    styles: "dist/components/toggle-switch/toggle-switch.css",
                    template: require("grunt-template-jasmine-istanbul"),
                    templateOptions: {
                        coverage: "bin/coverage/coverage.json",
                        report: [
                            {
                                type: "html",
                                options: {
                                    dir: "bin/coverage/html"
                                }
                            },

                            {
                                type: 'cobertura',
                                options: {
                                    dir: "bin/coverage/cobertura"
                                }
                            },

                            {
                                type: "text-summary"
                            }
                        ]
                    }
                }
            },

            options: {
                "--web-security" : false,
                "--local-to-remote-url-access" : true,
                "--ignore-ssl-errors" : true
            }
        },

        coffee: {
            build: {
                src: "src/behaviors/springs.coffee",
                dest: "dist/behaviors/springs.js"
            },

            specs: {
                files: [{
                    expand: true,
                    cwd: "spec/specs",
                    src: ["**/*.coffee"],
                    dest: "spec/specs-js",
                    ext: ".js"
                }]
            },

            specHelpers: {
                files: [{
                    expand: true,
                    cwd: "spec/helpers",
                    src: ["**/*.coffee"],
                    dest: "spec/helpers-js",
                    ext: ".js"
                }]
            },

            components: {
                files: [{
                    expand: true,
                    cwd: "src",
                    src: ["components/**/*.coffee"],
                    dest: "dist",
                    ext: ".js"
                }]
            },

            docs: {
                src: "docs/coffee/script.coffee",
                dest: "docs/public/js/script.js"
            }
        },

        uglify: {
            build: {
                src: "dist/behaviors/springs.js",
                dest: "dist/behaviors/springs.min.js"
            },

            components: {
                files: [{
                    expand: true,
                    cwd: "dist",
                    src: ["components/**/*.js"],
                    dest: "dist",
                    ext: ".min.js"
                }]
            }
        },

        concat: {
            options: {
                stripBanners: true,
                banner: "// <%= pkg.name %> - v<%= pkg.version %> - " +
                        "<%= grunt.template.today(\"yyyy.mm.dd\") %>\n\n\n",
                separator: "\n\n\n// -----\n\n\n"
            },

            // js: {
            //     src: "src/*.js",
            //     dest: "dist/springs.js"
            // },

            drag: {
                src: [
                    "dist/behaviors/drag.js",
                    "dist/components/notification-ios/notification-ios.js"
                ],
                dest: "dist/components/notification-ios/notification-ios-drag.js"
            },

            springs: {
                src: [
                    "src/behaviors/helpers.scss",
                    "src/behaviors/springs.scss"
                ],
                dest: "dist/behaviors/springs.scss"
            },

            components: {
                src: ["src/components/*/*.scss"],
                dest: "docs/scss/components.scss"
            }
        },

        sass: {
            dist: {
                options: {
                    style: "expanded",
                    loadPath: "src/"
                },

                files: {
                    "test/test.css": "test/test.scss"
                }
            },

            components: {
                options: {
                    style: "expanded",
                    loadPath: "src/"
                },

                files: [{
                    expand: true,
                    cwd: "src",
                    src: ["components/**/*.scss"],
                    dest: "dist",
                    ext: ".css"
                }]
            },

            docs: {
                files: {
                    "docs/public/css/style.css": "docs/scss/style.scss"
                }
            }
        },

        csscss: {
            options: {
                ignoreSassMixins: true
            },

            dist: {
                src: ["scss/**/*.scss"]
            }
        },

        autoprefixer: {
            dist: {
                files: {
                    "test/test.css": "test/test.css"
                }
            },

            components: {
                files: [{
                    expand: true,
                    cwd: "dist/components",
                    src: ["*/*.css"],
                    dest: "dist/components",
                    ext: ".css"
                }]
            },

            docs: {
                files: {
                    "docs/public/css/style.css": "docs/public/css/style.css"
                }
            }
        },

        compare_size: {
            files: [
                "dist/*.js",
                "dist/components/*.js"
            ]
        },

        haml: {
            components: {
                files: [{
                    expand: true,
                    cwd: "src/components",
                    src: ["**/*.haml"],
                    dest: "dist/components",
                    ext: ".html"
                }]
            },

            docs: {
                files: [{
                    expand: true,
                    cwd: "docs/haml/components-standalone",
                    src: ["**/*.haml"],
                    dest: "docs/pages/components",
                    ext: ".html"
                }]
            },

            fullDocs: {
                files: {
                    "docs/pages/site.html": "docs/haml/site.haml"
                }
            }
        },

        imagemin: {
            all: {
                options: { optimizationLevel: 1 },

                files: [{
                    expand: true,
                    cwd: "docs/img/",
                    src: ["**/*.{png,jpg,jpeg,gif}"],
                    dest: "docs/public/img/"
                }]
            }
        },

        shell: {
            copyMain: {
                command: [
                    // "cp src/springs.js dist/springs.js",
                    // "cp src/springs.scss dist/springs.scss",
                    "cp src/behaviors/drag.js dist/behaviors/drag.js",
                    "cp src/components/*/*.scss docs/scss/components",
                    "cp dist/components/toggle-switch/toggle-switch.js docs/public/js/toggle-switch.js",
                    "rsync -av src/components/ dist/components/"
                ].join("&&")
            },

            copyBehaviors: {
                command: [
                    "cp dist/components/notification-ios/notification-ios-drag.js docs/public/js/notification-ios-drag.js"
                ].join("&&")
            }
        },

        watch: {
            options: { livereload: false },

            js: {
                files: ["dist/**/*.js"],
                tasks: ["concat", "shell"],
                options: { spawn: false }
            },

            coffee: {
                files: ["src/**/*.coffee"],
                tasks: ["coffee:components", "coffee:build"]
            },

            coffeeSpecs: {
                files: "spec/**/*.coffee",
                tasks: ["coffee:specs"],
                options: { spawn: false }
            },

            jsSpecs: {
                files: "spec/**/*.js",
                tasks: ["jasmine"],
                options: { spawn: false }
            },

            scss: {
                files: ["src/**/*.scss", "test/**/*.scss"],
                tasks: ["concat", "sass", "autoprefixer", "csscss", "shell"],
                options: { spawn: false }
            },

            css: {
                files: ["dist/**/*.css", "test/**/*.css"],
                tasks: ["autoprefixer"],
                options: { spawn: false }
            },

            haml: {
                files: ["src/**/*.haml"],
                tasks: ["haml"],
                options: { spawn: false }
            },

            docsHaml: {
                files: ["docs/haml/**/*.haml"],
                tasks: ["haml:docs", "haml:fullDocs"],
                options: { spawn: false }
            },

            docsScss: {
                files: ["docs/scss/**/*.scss"],
                tasks: ["sass:docs", "autoprefixer:docs"],
                options: { spawn: false }
            },

            api: {
                files: ["docs/haml/api/**/*.yaml"],
                tasks: ["haml:docs", "haml:fullDocs"],
                options: { spawn: false }
            },

            docsCoffee: {
                files: ["docs/coffee/**/*.coffee"],
                tasks: ["coffee:docs"],
                options: { spawn: false }
            }
        }
    });

    // 2. TASKS
    require("load-grunt-tasks")(grunt);

    // 3. PERFORM
    grunt.registerTask("default", ["coffee:build", "coffee:components", "concat", "sass", "autoprefixer", "csscss", "uglify", "compare_size", "haml", "shell", "imagemin"]);
    grunt.registerTask("tests", ["coffee:specHelpers", "coffee:specs", "jasmine"]);
    grunt.registerTask("full", ["coffee", "jasmine", "concat", "sass", "autoprefixer", "csscss", "uglify", "compare_size", "slim", "shell", "imagemin"]);
    grunt.registerTask("docs", ["haml:fullDocs", "haml:docs", "sass:docs", "autoprefixer:docs", "imagemin"]);

}
<commit_msg>Added a watch task for the drag behaviour.<commit_after>module.exports = function(grunt) {

    // 1. CONFIG
    grunt.initConfig({
        pkg: grunt.file.readJSON("package.json"),

        jasmine: {
            messageBubble: {
                src: "dist/components/message-bubble/message-bubble.js",
                options: {
                    specs: "spec/specs-js/message-bubble-spec.js",
                    vendor: [
                        "bower_components/jquery/dist/jquery.js",
                        "bower_components/jasmine-jquery/lib/jasmine-jquery.js"
                    ],
                    helpers: "spec/helpers-js/message-bubble-helpers.js",
                    styles: "dist/components/message-bubble/message-bubble.css",
                    template: require("grunt-template-jasmine-istanbul"),
                    templateOptions: {
                        coverage: "bin/coverage/coverage.json",
                        report: [
                            {
                                type: "html",
                                options: {
                                    dir: "bin/coverage/html"
                                }
                            },

                            {
                                type: 'cobertura',
                                options: {
                                    dir: "bin/coverage/cobertura"
                                }
                            },

                            {
                                type: "text-summary"
                            }
                        ]
                    }
                }
            },

            toggleSwitch: {
                src: "dist/components/toggle-switch/toggle-switch.js",
                options: {
                    specs: "spec/specs-js/toggle-switch-spec.js",
                    vendor: [
                        "bower_components/jquery/dist/jquery.js",
                        "bower_components/jasmine-jquery/lib/jasmine-jquery.js"
                    ],
                    helpers: "spec/helpers-js/toggle-switch-helpers.js",
                    styles: "dist/components/toggle-switch/toggle-switch.css",
                    template: require("grunt-template-jasmine-istanbul"),
                    templateOptions: {
                        coverage: "bin/coverage/coverage.json",
                        report: [
                            {
                                type: "html",
                                options: {
                                    dir: "bin/coverage/html"
                                }
                            },

                            {
                                type: 'cobertura',
                                options: {
                                    dir: "bin/coverage/cobertura"
                                }
                            },

                            {
                                type: "text-summary"
                            }
                        ]
                    }
                }
            },

            options: {
                "--web-security" : false,
                "--local-to-remote-url-access" : true,
                "--ignore-ssl-errors" : true
            }
        },

        coffee: {
            build: {
                src: "src/behaviors/springs.coffee",
                dest: "dist/behaviors/springs.js"
            },

            specs: {
                files: [{
                    expand: true,
                    cwd: "spec/specs",
                    src: ["**/*.coffee"],
                    dest: "spec/specs-js",
                    ext: ".js"
                }]
            },

            specHelpers: {
                files: [{
                    expand: true,
                    cwd: "spec/helpers",
                    src: ["**/*.coffee"],
                    dest: "spec/helpers-js",
                    ext: ".js"
                }]
            },

            components: {
                files: [{
                    expand: true,
                    cwd: "src",
                    src: ["components/**/*.coffee"],
                    dest: "dist",
                    ext: ".js"
                }]
            },

            docs: {
                src: "docs/coffee/script.coffee",
                dest: "docs/public/js/script.js"
            }
        },

        uglify: {
            build: {
                src: "dist/behaviors/springs.js",
                dest: "dist/behaviors/springs.min.js"
            },

            components: {
                files: [{
                    expand: true,
                    cwd: "dist",
                    src: ["components/**/*.js"],
                    dest: "dist",
                    ext: ".min.js"
                }]
            }
        },

        concat: {
            options: {
                stripBanners: true,
                banner: "// <%= pkg.name %> - v<%= pkg.version %> - " +
                        "<%= grunt.template.today(\"yyyy.mm.dd\") %>\n\n\n",
                separator: "\n\n\n// -----\n\n\n"
            },

            // js: {
            //     src: "src/*.js",
            //     dest: "dist/springs.js"
            // },

            drag: {
                src: [
                    "dist/behaviors/drag.js",
                    "dist/components/notification-ios/notification-ios.js"
                ],
                dest: "dist/components/notification-ios/notification-ios-drag.js"
            },

            springs: {
                src: [
                    "src/behaviors/helpers.scss",
                    "src/behaviors/springs.scss"
                ],
                dest: "dist/behaviors/springs.scss"
            },

            components: {
                src: ["src/components/*/*.scss"],
                dest: "docs/scss/components.scss"
            }
        },

        sass: {
            dist: {
                options: {
                    style: "expanded",
                    loadPath: "src/"
                },

                files: {
                    "test/test.css": "test/test.scss"
                }
            },

            components: {
                options: {
                    style: "expanded",
                    loadPath: "src/"
                },

                files: [{
                    expand: true,
                    cwd: "src",
                    src: ["components/**/*.scss"],
                    dest: "dist",
                    ext: ".css"
                }]
            },

            docs: {
                files: {
                    "docs/public/css/style.css": "docs/scss/style.scss"
                }
            }
        },

        csscss: {
            options: {
                ignoreSassMixins: true
            },

            dist: {
                src: ["scss/**/*.scss"]
            }
        },

        autoprefixer: {
            dist: {
                files: {
                    "test/test.css": "test/test.css"
                }
            },

            components: {
                files: [{
                    expand: true,
                    cwd: "dist/components",
                    src: ["*/*.css"],
                    dest: "dist/components",
                    ext: ".css"
                }]
            },

            docs: {
                files: {
                    "docs/public/css/style.css": "docs/public/css/style.css"
                }
            }
        },

        compare_size: {
            files: [
                "dist/*.js",
                "dist/components/*.js"
            ]
        },

        haml: {
            components: {
                files: [{
                    expand: true,
                    cwd: "src/components",
                    src: ["**/*.haml"],
                    dest: "dist/components",
                    ext: ".html"
                }]
            },

            docs: {
                files: [{
                    expand: true,
                    cwd: "docs/haml/components-standalone",
                    src: ["**/*.haml"],
                    dest: "docs/pages/components",
                    ext: ".html"
                }]
            },

            fullDocs: {
                files: {
                    "docs/pages/site.html": "docs/haml/site.haml"
                }
            }
        },

        imagemin: {
            all: {
                options: { optimizationLevel: 1 },

                files: [{
                    expand: true,
                    cwd: "docs/img/",
                    src: ["**/*.{png,jpg,jpeg,gif}"],
                    dest: "docs/public/img/"
                }]
            }
        },

        shell: {
            copyMain: {
                command: [
                    // "cp src/springs.js dist/springs.js",
                    // "cp src/springs.scss dist/springs.scss",
                    "cp src/behaviors/drag.js dist/behaviors/drag.js",
                    "cp src/components/*/*.scss docs/scss/components",
                    "cp dist/components/toggle-switch/toggle-switch.js docs/public/js/toggle-switch.js",
                    "rsync -av src/components/ dist/components/"
                ].join("&&")
            },

            copyBehaviors: {
                command: [
                    "cp dist/components/notification-ios/notification-ios-drag.js docs/public/js/notification-ios-drag.js"
                ].join("&&")
            }
        },

        watch: {
            options: { livereload: false },

            js: {
                files: ["dist/**/*.js"],
                tasks: ["concat", "shell"],
                options: { spawn: false }
            },

            drag: {
                files: ["src/behaviors/drag.js"],
                tasks: ["shell:copyMain", "concat", "shell:copyBehaviors"],
                options: { spawn: false }
            },

            coffee: {
                files: ["src/**/*.coffee"],
                tasks: ["coffee:components", "coffee:build"]
            },

            coffeeSpecs: {
                files: "spec/**/*.coffee",
                tasks: ["coffee:specs"],
                options: { spawn: false }
            },

            jsSpecs: {
                files: "spec/**/*.js",
                tasks: ["jasmine"],
                options: { spawn: false }
            },

            scss: {
                files: ["src/**/*.scss", "test/**/*.scss"],
                tasks: ["concat", "sass", "autoprefixer", "csscss", "shell"],
                options: { spawn: false }
            },

            css: {
                files: ["dist/**/*.css", "test/**/*.css"],
                tasks: ["autoprefixer"],
                options: { spawn: false }
            },

            haml: {
                files: ["src/**/*.haml"],
                tasks: ["haml"],
                options: { spawn: false }
            },

            docsHaml: {
                files: ["docs/haml/**/*.haml"],
                tasks: ["haml:docs", "haml:fullDocs"],
                options: { spawn: false }
            },

            docsScss: {
                files: ["docs/scss/**/*.scss"],
                tasks: ["sass:docs", "autoprefixer:docs"],
                options: { spawn: false }
            },

            api: {
                files: ["docs/haml/api/**/*.yaml"],
                tasks: ["haml:docs", "haml:fullDocs"],
                options: { spawn: false }
            },

            docsCoffee: {
                files: ["docs/coffee/**/*.coffee"],
                tasks: ["coffee:docs"],
                options: { spawn: false }
            }
        }
    });

    // 2. TASKS
    require("load-grunt-tasks")(grunt);

    // 3. PERFORM
    grunt.registerTask("default", ["coffee:build", "coffee:components", "concat", "sass", "autoprefixer", "csscss", "uglify", "compare_size", "haml", "shell", "imagemin"]);
    grunt.registerTask("tests", ["coffee:specHelpers", "coffee:specs", "jasmine"]);
    grunt.registerTask("full", ["coffee", "jasmine", "concat", "sass", "autoprefixer", "csscss", "uglify", "compare_size", "slim", "shell", "imagemin"]);
    grunt.registerTask("docs", ["haml:fullDocs", "haml:docs", "sass:docs", "autoprefixer:docs", "imagemin"]);

}
<|endoftext|>


<commit_before>module.exports = function( grunt ) {

    "use strict";

    var pkg = grunt.file.readJSON( "package.json" );

    grunt.initConfig( {

        pkg: pkg,

        clean: [ "dist/" ],

        copy: {
            build: {
                options: {
                    process: function( content ) {
                        return content.replace( /@VERSION/g, pkg.version );
                    }
                },
                files: [
                    {
                        expand: true,
                        flatten: true,
                        cwd: "src/",
                        src: [ "**" ],
                        dest: "dist/"
                    }
                ]
            }
        },

        uglify: {
            options: {
                banner: "/*! <%=pkg.name %> | <%= pkg.version %> | <%= grunt.template.today('yyyy-mm-dd') %> */\n"
            },
            build: {
                files: {
                    "dist/jquery.ui.pinpad.min.js": "dist/jquery.ui.pinpad.js"
                }
            }
        },

        cssmin: {
            build: {
                src: "dist/jquery.ui.pinpad.css",
                dest: "dist/jquery.ui.pinpad.min.css"
            }
        }

    });

    grunt.loadNpmTasks( "grunt-contrib-clean" );
    grunt.loadNpmTasks( "grunt-contrib-copy" );
    grunt.loadNpmTasks( "grunt-contrib-uglify" );
    grunt.loadNpmTasks( "grunt-contrib-cssmin" );

    grunt.registerTask( "default", [ "clean", "copy", "uglify", "cssmin" ] );

};<commit_msg>Exclude directories during copy process<commit_after>module.exports = function( grunt ) {

    "use strict";

    var pkg = grunt.file.readJSON( "package.json" );

    grunt.initConfig( {

        pkg: pkg,

        clean: [ "dist/" ],

        copy: {
            build: {
                options: {
                    process: function( content ) {
                        return content.replace( /@VERSION/g, pkg.version );
                    }
                },
                files: [
                    {
                        expand: true,
                        flatten: true,
                        filter: "isFile",
                        cwd: "src/",
                        src: [ "**" ],
                        dest: "dist/"
                    }
                ]
            }
        },

        uglify: {
            options: {
                banner: "/*! <%=pkg.name %> | <%= pkg.version %> | <%= grunt.template.today('yyyy-mm-dd') %> */\n"
            },
            build: {
                files: {
                    "dist/jquery.ui.pinpad.min.js": "dist/jquery.ui.pinpad.js"
                }
            }
        },

        cssmin: {
            build: {
                src: "dist/jquery.ui.pinpad.css",
                dest: "dist/jquery.ui.pinpad.min.css"
            }
        }

    });

    grunt.loadNpmTasks( "grunt-contrib-clean" );
    grunt.loadNpmTasks( "grunt-contrib-copy" );
    grunt.loadNpmTasks( "grunt-contrib-uglify" );
    grunt.loadNpmTasks( "grunt-contrib-cssmin" );

    grunt.registerTask( "default", [ "clean", "copy", "uglify", "cssmin" ] );

};<|endoftext|>