<commit_before>import Controller from '@ember/controller';
import config from 'ghost-admin/config/environment';
import fetch from 'fetch';
import ghostPaths from 'ghost-admin/utils/ghost-paths';
import moment from 'moment';
import {A} from '@ember/array';
import {action} from '@ember/object';
import {ghPluralize} from 'ghost-admin/helpers/gh-pluralize';
import {resetQueryParams} from 'ghost-admin/helpers/reset-query-params';
import {inject as service} from '@ember/service';
import {task} from 'ember-concurrency-decorators';
import {timeout} from 'ember-concurrency';
import {tracked} from '@glimmer/tracking';

const PAID_PARAMS = [{
    name: 'All members',
    value: null
}, {
    name: 'Free members',
    value: 'false'
}, {
    name: 'Paid members',
    value: 'true'
}];

export default class MembersController extends Controller {
    @service ajax;
    @service config;
    @service ellaSparse;
    @service feature;
    @service ghostPaths;
    @service membersStats;
    @service router;
    @service store;

    queryParams = [
        'label',
        {paidParam: 'paid'},
        {searchParam: 'search'},
        {orderParam: 'order'},
        {filterParam: 'filter'}
    ];

    @tracked members = A([]);
    @tracked searchText = '';
    @tracked searchParam = '';
    @tracked filterParam = null;
    @tracked paidParam = null;
    @tracked label = null;
    @tracked orderParam = null;
    @tracked modalLabel = null;
    @tracked showLabelModal = false;
    @tracked showDeleteMembersModal = false;

    @tracked _availableLabels = A([]);

    paidParams = PAID_PARAMS;

    constructor() {
        super(...arguments);
        this._availableLabels = this.store.peekAll('label');

        if (this.isTesting === undefined) {
            this.isTesting = config.environment === 'test';
        }
    }

    // Computed properties -----------------------------------------------------

    get listHeader() {
        let {searchText, selectedLabel, members} = this;

        if (members.loading) {
            return 'Loading...';
        }

        if (searchText) {
            return 'Search result';
        }

        let count = ghPluralize(members.length, 'member');

        if (selectedLabel && selectedLabel.slug) {
            if (members.length > 1) {
                return `${count} match current filter`;
            } else {
                return `${count} matches current filter`;
            }
        }

        return count;
    }

    get showingAll() {
        return !this.searchParam && !this.paidParam && !this.label && !this.filterParam;
    }

    get availableOrders() {
        // don't return anything if email analytics is disabled because
        // we don't want to show an order dropdown with only a single option

        if (this.feature.get('emailAnalytics')) {
            return [{
                name: 'Newest',
                value: null
            }, {
                name: 'Open rate',
                value: 'email_open_rate'
            }];
        }

        return [];
    }

    get selectedOrder() {
        return this.availableOrders.find(order => order.value === this.orderParam);
    }

    get availableLabels() {
        let labels = this._availableLabels
            .filter(label => !label.isNew)
            .filter(label => label.id !== null)
            .sort((labelA, labelB) => labelA.name.localeCompare(labelB.name, undefined, {ignorePunctuation: true}));
        let options = labels.toArray();

        options.unshiftObject({name: 'All labels', slug: null});

        return options;
    }

    get selectedLabel() {
        let {label, availableLabels} = this;
        return availableLabels.findBy('slug', label);
    }

    get labelModalData() {
        let label = this.modalLabel;
        let labels = this.availableLabels;

        return {
            label,
            labels
        };
    }

    get selectedPaidParam() {
        return this.paidParams.findBy('value', this.paidParam) || {value: '!unknown'};
    }

    get isFiltered() {
        return !!(this.label || this.paidParam || this.searchParam || this.filterParam);
    }

    getApiQueryObject({params, extraFilters = []} = {}) {
        let {label, paidParam, searchParam, filterParam} = params ? params : this;

        let filters = [];

        filters = filters.concat(extraFilters);

        if (label) {
            filters.push(`label:'${label}'`);
        }

        if (paidParam !== null) {
            if (paidParam === 'true') {
                filters.push('status:-free');
            } else {
                filters.push('status:free');
            }
        }

        if (filterParam && this.feature.get('membersFiltering')) {
            filters.push(filterParam);
        }

        let searchQuery = searchParam ? {search: searchParam} : {};

        return Object.assign({}, {filter: filters.join('+')}, searchQuery);
    }

    // Actions -----------------------------------------------------------------

    @action
    refreshData() {
        this.fetchMembersTask.perform();
        this.fetchLabelsTask.perform();
        this.membersStats.invalidate();
        this.membersStats.fetchCounts();
    }

    @action
    changeOrder(order) {
        this.orderParam = order.value;
    }

    @action
    search(e) {
        this.searchTask.perform(e.target.value);
    }

    @action
    exportData() {
        let exportUrl = ghostPaths().url.api('members/upload');
        let downloadParams = new URLSearchParams(this.getApiQueryObject());
        downloadParams.set('limit', 'all');

        let iframe = document.getElementById('iframeDownload');

        if (!iframe) {
            iframe = document.createElement('iframe');
            iframe.id = 'iframeDownload';
            iframe.style.display = 'none';
            document.body.append(iframe);
        }
        iframe.setAttribute('src', `${exportUrl}?${downloadParams.toString()}`);
    }

    @action
    changeLabel(label, e) {
        if (e) {
            e.preventDefault();
            e.stopPropagation();
        }
        this.label = label.slug;
    }

    @action
    addLabel(e) {
        if (e) {
            e.preventDefault();
            e.stopPropagation();
        }
        const newLabel = this.store.createRecord('label');
        this.modalLabel = newLabel;
        this.showLabelModal = !this.showLabelModal;
    }

    @action
    editLabel(label, e) {
        if (e) {
            e.preventDefault();
            e.stopPropagation();
        }
        let modalLabel = this.availableLabels.findBy('slug', label);
        this.modalLabel = modalLabel;
        this.showLabelModal = !this.showLabelModal;
    }

    @action
    toggleLabelModal() {
        this.showLabelModal = !this.showLabelModal;
    }

    @action
    changePaidParam(paid) {
        this.paidParam = paid.value;
    }

    @action
    toggleDeleteMembersModal() {
        this.showDeleteMembersModal = !this.showDeleteMembersModal;
    }

    @action
    deleteMembers() {
        return this.deleteMembersTask.perform();
    }

    // Tasks -------------------------------------------------------------------

    @task({restartable: true})
    *searchTask(query) {
        yield timeout(250); // debounce
        this.searchParam = query;
    }

    @task
    *fetchLabelsTask() {
        yield this.store.query('label', {limit: 'all'});
    }

    @task({restartable: true})
    *fetchMembersTask(params) {
        // params is undefined when called as a "refresh" of the model
        let {label, paidParam, searchParam, orderParam, filterParam} = typeof params === 'undefined' ? this : params;

        if (!searchParam) {
            this.resetSearch();
        }

        // use a fixed created_at date so that subsequent pages have a consistent index
        let startDate = new Date();

        // bypass the stale data shortcut if params change
        let forceReload = !params
            || label !== this._lastLabel
            || paidParam !== this._lastPaidParam
            || searchParam !== this._lastSearchParam
            || orderParam !== this._orderParam
            || filterParam !== this._lastFilterParam;
        this._lastLabel = label;
        this._lastPaidParam = paidParam;
        this._lastSearchParam = searchParam;
        this._lastOrderParam = orderParam;
        this._lastFilterParam = filterParam;

        // unless we have a forced reload, do not re-fetch the members list unless it's more than a minute old
        // keeps navigation between list->details->list snappy
        if (!forceReload && this._startDate && !(this._startDate - startDate > 1 * 60 * 1000)) {
            return this.members;
        }

        this._startDate = startDate;

        this.members = yield this.ellaSparse.array((range = {}, query = {}) => {
            const searchQuery = this.getApiQueryObject({
                params,
                extraFilters: [`created_at:<='${moment.utc(this._startDate).format('YYYY-MM-DD HH:mm:ss')}'`]
            });
            const order = orderParam ? `${orderParam} desc` : `created_at desc`;

            query = Object.assign({
                order,
                limit: range.length,
                page: range.page
            }, searchQuery, query);

            return this.store.query('member', query).then((result) => {
                return {
                    data: result,
                    total: result.meta.pagination.total
                };
            });
        }, {
            limit: 50
        });
    }

    @task({drop: true})
    *deleteMembersTask() {
        const query = new URLSearchParams(this.getApiQueryObject());

        // Trigger download before deleting. Uses the CSV export endpoint but
        // needs to fetch the file and trigger a download directly rather than
        // via an iframe. The iframe approach can't tell us when a download has
        // started/finished meaning we could end up deleting the data before exporting it
        const exportUrl = ghostPaths().url.api('members/upload');
        const exportParams = new URLSearchParams(this.getApiQueryObject());
        exportParams.set('limit', 'all');

        yield fetch(exportUrl, {method: 'GET'})
            .then(res => res.blob())
            .then((blob) => {
                const blobUrl = window.URL.createObjectURL(blob);
                const a = document.createElement('a');
                a.href = blobUrl;
                a.download = `members.${moment().format('YYYY-MM-DD')}.csv`;
                document.body.appendChild(a);
                if (!this.isTesting) {
                    a.click();
                }
                a.remove();
                URL.revokeObjectURL(blobUrl);
            });

        // backup downloaded, continue with deletion

        const deleteUrl = `${this.ghostPaths.url.api('members')}?${query}`;

        // response contains details of which members failed to be deleted
        const response = yield this.ajax.del(deleteUrl);

        // reset and reload
        this.store.unloadAll('member');
        this.router.transitionTo('members.index', {queryParams: Object.assign(resetQueryParams('members.index'))});
        this.membersStats.invalidate();
        this.membersStats.fetchCounts();

        return response.meta;
    }

    // Internal ----------------------------------------------------------------

    resetSearch() {
        this.searchText = '';
    }

    reload(params) {
        this.membersStats.invalidate();
        this.membersStats.fetchCounts();
        this.fetchMembersTask.perform(params);
    }
}
<commit_msg>Fixed incorrect order param comparison in force reload<commit_after>import Controller from '@ember/controller';
import config from 'ghost-admin/config/environment';
import fetch from 'fetch';
import ghostPaths from 'ghost-admin/utils/ghost-paths';
import moment from 'moment';
import {A} from '@ember/array';
import {action} from '@ember/object';
import {ghPluralize} from 'ghost-admin/helpers/gh-pluralize';
import {resetQueryParams} from 'ghost-admin/helpers/reset-query-params';
import {inject as service} from '@ember/service';
import {task} from 'ember-concurrency-decorators';
import {timeout} from 'ember-concurrency';
import {tracked} from '@glimmer/tracking';

const PAID_PARAMS = [{
    name: 'All members',
    value: null
}, {
    name: 'Free members',
    value: 'false'
}, {
    name: 'Paid members',
    value: 'true'
}];

export default class MembersController extends Controller {
    @service ajax;
    @service config;
    @service ellaSparse;
    @service feature;
    @service ghostPaths;
    @service membersStats;
    @service router;
    @service store;

    queryParams = [
        'label',
        {paidParam: 'paid'},
        {searchParam: 'search'},
        {orderParam: 'order'},
        {filterParam: 'filter'}
    ];

    @tracked members = A([]);
    @tracked searchText = '';
    @tracked searchParam = '';
    @tracked filterParam = null;
    @tracked paidParam = null;
    @tracked label = null;
    @tracked orderParam = null;
    @tracked modalLabel = null;
    @tracked showLabelModal = false;
    @tracked showDeleteMembersModal = false;

    @tracked _availableLabels = A([]);

    paidParams = PAID_PARAMS;

    constructor() {
        super(...arguments);
        this._availableLabels = this.store.peekAll('label');

        if (this.isTesting === undefined) {
            this.isTesting = config.environment === 'test';
        }
    }

    // Computed properties -----------------------------------------------------

    get listHeader() {
        let {searchText, selectedLabel, members} = this;

        if (members.loading) {
            return 'Loading...';
        }

        if (searchText) {
            return 'Search result';
        }

        let count = ghPluralize(members.length, 'member');

        if (selectedLabel && selectedLabel.slug) {
            if (members.length > 1) {
                return `${count} match current filter`;
            } else {
                return `${count} matches current filter`;
            }
        }

        return count;
    }

    get showingAll() {
        return !this.searchParam && !this.paidParam && !this.label && !this.filterParam;
    }

    get availableOrders() {
        // don't return anything if email analytics is disabled because
        // we don't want to show an order dropdown with only a single option

        if (this.feature.get('emailAnalytics')) {
            return [{
                name: 'Newest',
                value: null
            }, {
                name: 'Open rate',
                value: 'email_open_rate'
            }];
        }

        return [];
    }

    get selectedOrder() {
        return this.availableOrders.find(order => order.value === this.orderParam);
    }

    get availableLabels() {
        let labels = this._availableLabels
            .filter(label => !label.isNew)
            .filter(label => label.id !== null)
            .sort((labelA, labelB) => labelA.name.localeCompare(labelB.name, undefined, {ignorePunctuation: true}));
        let options = labels.toArray();

        options.unshiftObject({name: 'All labels', slug: null});

        return options;
    }

    get selectedLabel() {
        let {label, availableLabels} = this;
        return availableLabels.findBy('slug', label);
    }

    get labelModalData() {
        let label = this.modalLabel;
        let labels = this.availableLabels;

        return {
            label,
            labels
        };
    }

    get selectedPaidParam() {
        return this.paidParams.findBy('value', this.paidParam) || {value: '!unknown'};
    }

    get isFiltered() {
        return !!(this.label || this.paidParam || this.searchParam || this.filterParam);
    }

    getApiQueryObject({params, extraFilters = []} = {}) {
        let {label, paidParam, searchParam, filterParam} = params ? params : this;

        let filters = [];

        filters = filters.concat(extraFilters);

        if (label) {
            filters.push(`label:'${label}'`);
        }

        if (paidParam !== null) {
            if (paidParam === 'true') {
                filters.push('status:-free');
            } else {
                filters.push('status:free');
            }
        }

        if (filterParam && this.feature.get('membersFiltering')) {
            filters.push(filterParam);
        }

        let searchQuery = searchParam ? {search: searchParam} : {};

        return Object.assign({}, {filter: filters.join('+')}, searchQuery);
    }

    // Actions -----------------------------------------------------------------

    @action
    refreshData() {
        this.fetchMembersTask.perform();
        this.fetchLabelsTask.perform();
        this.membersStats.invalidate();
        this.membersStats.fetchCounts();
    }

    @action
    changeOrder(order) {
        this.orderParam = order.value;
    }

    @action
    search(e) {
        this.searchTask.perform(e.target.value);
    }

    @action
    exportData() {
        let exportUrl = ghostPaths().url.api('members/upload');
        let downloadParams = new URLSearchParams(this.getApiQueryObject());
        downloadParams.set('limit', 'all');

        let iframe = document.getElementById('iframeDownload');

        if (!iframe) {
            iframe = document.createElement('iframe');
            iframe.id = 'iframeDownload';
            iframe.style.display = 'none';
            document.body.append(iframe);
        }
        iframe.setAttribute('src', `${exportUrl}?${downloadParams.toString()}`);
    }

    @action
    changeLabel(label, e) {
        if (e) {
            e.preventDefault();
            e.stopPropagation();
        }
        this.label = label.slug;
    }

    @action
    addLabel(e) {
        if (e) {
            e.preventDefault();
            e.stopPropagation();
        }
        const newLabel = this.store.createRecord('label');
        this.modalLabel = newLabel;
        this.showLabelModal = !this.showLabelModal;
    }

    @action
    editLabel(label, e) {
        if (e) {
            e.preventDefault();
            e.stopPropagation();
        }
        let modalLabel = this.availableLabels.findBy('slug', label);
        this.modalLabel = modalLabel;
        this.showLabelModal = !this.showLabelModal;
    }

    @action
    toggleLabelModal() {
        this.showLabelModal = !this.showLabelModal;
    }

    @action
    changePaidParam(paid) {
        this.paidParam = paid.value;
    }

    @action
    toggleDeleteMembersModal() {
        this.showDeleteMembersModal = !this.showDeleteMembersModal;
    }

    @action
    deleteMembers() {
        return this.deleteMembersTask.perform();
    }

    // Tasks -------------------------------------------------------------------

    @task({restartable: true})
    *searchTask(query) {
        yield timeout(250); // debounce
        this.searchParam = query;
    }

    @task
    *fetchLabelsTask() {
        yield this.store.query('label', {limit: 'all'});
    }

    @task({restartable: true})
    *fetchMembersTask(params) {
        // params is undefined when called as a "refresh" of the model
        let {label, paidParam, searchParam, orderParam, filterParam} = typeof params === 'undefined' ? this : params;

        if (!searchParam) {
            this.resetSearch();
        }

        // use a fixed created_at date so that subsequent pages have a consistent index
        let startDate = new Date();

        // bypass the stale data shortcut if params change
        let forceReload = !params
            || label !== this._lastLabel
            || paidParam !== this._lastPaidParam
            || searchParam !== this._lastSearchParam
            || orderParam !== this._lastOrderParam
            || filterParam !== this._lastFilterParam;
        this._lastLabel = label;
        this._lastPaidParam = paidParam;
        this._lastSearchParam = searchParam;
        this._lastOrderParam = orderParam;
        this._lastFilterParam = filterParam;

        // unless we have a forced reload, do not re-fetch the members list unless it's more than a minute old
        // keeps navigation between list->details->list snappy
        if (!forceReload && this._startDate && !(this._startDate - startDate > 1 * 60 * 1000)) {
            return this.members;
        }

        this._startDate = startDate;

        this.members = yield this.ellaSparse.array((range = {}, query = {}) => {
            const searchQuery = this.getApiQueryObject({
                params,
                extraFilters: [`created_at:<='${moment.utc(this._startDate).format('YYYY-MM-DD HH:mm:ss')}'`]
            });
            const order = orderParam ? `${orderParam} desc` : `created_at desc`;

            query = Object.assign({
                order,
                limit: range.length,
                page: range.page
            }, searchQuery, query);

            return this.store.query('member', query).then((result) => {
                return {
                    data: result,
                    total: result.meta.pagination.total
                };
            });
        }, {
            limit: 50
        });
    }

    @task({drop: true})
    *deleteMembersTask() {
        const query = new URLSearchParams(this.getApiQueryObject());

        // Trigger download before deleting. Uses the CSV export endpoint but
        // needs to fetch the file and trigger a download directly rather than
        // via an iframe. The iframe approach can't tell us when a download has
        // started/finished meaning we could end up deleting the data before exporting it
        const exportUrl = ghostPaths().url.api('members/upload');
        const exportParams = new URLSearchParams(this.getApiQueryObject());
        exportParams.set('limit', 'all');

        yield fetch(exportUrl, {method: 'GET'})
            .then(res => res.blob())
            .then((blob) => {
                const blobUrl = window.URL.createObjectURL(blob);
                const a = document.createElement('a');
                a.href = blobUrl;
                a.download = `members.${moment().format('YYYY-MM-DD')}.csv`;
                document.body.appendChild(a);
                if (!this.isTesting) {
                    a.click();
                }
                a.remove();
                URL.revokeObjectURL(blobUrl);
            });

        // backup downloaded, continue with deletion

        const deleteUrl = `${this.ghostPaths.url.api('members')}?${query}`;

        // response contains details of which members failed to be deleted
        const response = yield this.ajax.del(deleteUrl);

        // reset and reload
        this.store.unloadAll('member');
        this.router.transitionTo('members.index', {queryParams: Object.assign(resetQueryParams('members.index'))});
        this.membersStats.invalidate();
        this.membersStats.fetchCounts();

        return response.meta;
    }

    // Internal ----------------------------------------------------------------

    resetSearch() {
        this.searchText = '';
    }

    reload(params) {
        this.membersStats.invalidate();
        this.membersStats.fetchCounts();
        this.fetchMembersTask.perform(params);
    }
}
<|endoftext|>


<commit_before>var User = require('../models/user'),
    Project = require('../models/project'),
    permissionManager = require('../middleware/permission_manager');

module.exports = function(app) {
  this.getProject = function (req, res, next) {
  	/*
  		{
  			id: project id,
  			title: project title,
  			publisher:
  			{
  				publisher_id: id of owner,
  				publisher_name: name of project owner
  			},
  			members:
  			[
  				{
  					member_id: id of owner,
  					member_name: name of project owner
  				}
  			],
  			short_intro: short description, 300 characters max,
  			long_intro: longer intro with no character limit,
  			showcase:
  			[
  				{
  					path: path to the file,
  					type: type of the file
  				}
  			]
  			latest_update: date of the latest update, in seconds,
  			status: project status,
  			tags: [list of tags],
  			open_jobs:
  			[
  				{
  					job_id: job id,
  					job_title: job title,
  					job_tags: [list of skill names with ratings],
  					job_budget: budget level estimation between 1 to 5,
  					job_deadline: job deadline
  				}
  			],
  		}
  	*/
  	try {
  		var json = new Object();
  		Project.findById(req.params.project_id, function(err, project){
  			if (!project.length) {
  				res.status(404);
  				// project page not found
  				if (req.accepts('html')) {
  					res.render('404', { url: req.url });
  				}
  				return;
  			}
  			// Build the file
  			json.id = project._id;
  			json.title = project.name;
  			json.publisher = new Object();
  			json.publisher.publisher_id = project.owner;
  			db.User.findById(project.owner, function(err, publisher_info){
  				json.publisher.publisher_name = publisher_info.name;
  				json.members = [];
  			});
  			var i;
  			var numMembers = project.members.length;
  			for (i=0;i<numMembers;i++) {
  				var newMember = new Object();
  				newMember.member_id = project.members[i].user;
  				db.User.findById(project.members[i].user, function(err, memberName){
  					newMember.member_name = memberName.name;
  					json.members.push(newMember);
  				});
  			}
  			json.short_intro = project.basicInfo;
  			json.long_intro = project.detailedInfo;*/

  			json.long_intro = [];
  			var numParagraph = project.detailedInfo.length;
  			for (i=0;i<numParagraph;i++) {
  				var newParagraph = new Object();
  				newParagraph.paragraph_title = project.detailedInfo[i].title;
  				newParagraph.paragraph_content = project.detailedInfo[i].content;
  				json.long_intro.push(newParagraph);
  			}*/

        json.showcase = [];
  			var numShowcase = project.showcase.assetPaths.length;
  			for (i=0;i<numShowcase;i++) {
  				var current_path = project.showcase.assetPaths[i];
  				var current_type = project.showcase.mediaTypes[i];
  				var newShowcase = new Object();
  				newShowcase.path = current_path;
  				newShowcase.type = currentcurrent_type;
  			}
  			json.latest_update = project.updatedAt;
  			json.status = project.status;
  			json.tags = project.tags;
  			var jobs = db.Job.find({"project": ObjectId(project_id)});
  			json.open_jobs = [];
  			while (jobs.hasNext()) {
  				var newJob = new Object();
  				var current = jobs.next();
  				newJob.job_id = current._id;
  				newJob.job_title = current.name;
  				newJob.job_tags = current.skillTags;
  				newJob.job_budget = current.budget;
  				newJob.job_deadline = current.deadline;
  			}
  			res.send(JSON.stringify(json));

  		});

  	}
  	catch (e) {
  		res.status(404);
  		// page not found
  		if (req.accepts('html')) {
  			res.render('404', { url: req.url });
  		}
  	}
    //res.send('AIDA Home Page!');
    /*
    var project1 =
      {
        "id":"1",
        "title":"Super Awesome Game",
        "publisher": {
          "id":"1",
          "username": "jbelfort",
          "name":"Jordan Belfort"
        },
        "members": [{
          "id":"2",
          "name": "Donald Trump",
          "username": "bigDTrump"
        }],
        "latest_update": "100 million years ago",
        "status": "abandoned and forgotten",
        "tags": [{
          "name":"Fatasy"
        }, {
          "name":"Adventure"
        }],
        "brief": "This is a cool game about awesomeness and stuff I guess",
        "details": "This game features hundreds of awesome features that are featured throughout the game. Features include featuring features featured in the game. Specifically, you can specifically specify specific specifics. Additionally, many innovative innovations have been innovated.",
        "showcase": {
            "path": "/images/gs1.jpg"
        },
        "jobs": [{
          "id": "1",
          "title": "2D Animator",
          "status": "open",
          "tags": [{
            "name": "Cooking",
            "rating": 3
          }, {
            "name": "Eating",
            "rating": 5
          }],
          "budget": 2000,
          "deadline": "June 13 1995",
        }]
      }

      res.json(project1);*/
  };

  this.getPopularProjects = function (req, res, next) {
  	// get name, tags, showcase
  	var json = new Object();
  	var cursor = db.Project.find({},{"name": 1, "tags": 1, "showcase": 1}).sort({"numFollowers": -1}).limit(10);
  	json.topTen = cursor.toArray();
  	json.following = [];
  	var userId = req.session.userId;
  	if (userId) {
  		db.User.findById(userId, function(err, user){
  			var followings = user.followings;
  			var numFollowings = followings.length;
  			var i;
  			for (i=0;i<numFollowings;i++) {
  				db.Project.findById(followings[i], function(){
  					// the object being followed is an existing project
  					if (project.length) {
  						var newProject = new Object();
  						newProject._id = project._id;
  						newProject.name = project.name;
  						newProject.tags = project.tags;
  						json.following.push(newProject);
  					}
  				});
  			}
  		});
  	}
  	res.send(JSON.stringify(json));
    //res.send('AIDA Home Page!');
  };

  this.createProject = function (req, res, next) {
  	/*
  		TODO:
  		after the posting, send to the front end the link
  		to the project page, so that the front end will
  		redirect to the page
  		{
  			success: true/false
  			url: the link to the new project page
  		}
  	*/
  	var json = new Object();
  	try {
  		var projectForm = qs.parse(req.data);
      var tags = jobForm.descriptionTags.replace(/\s+/g, '').split(",");
  		// may createJob return job _id or something...
      var newProject = Project({
        name: projectForm.name,
        owner: req.user._id,
        tags: tags,
        members: projectForm.members,
        details: projectForm.details
      });

      newProject.save(function(err, project) {
        json.url = req.baseUrl + '/' + newProject._id;
        json.success = true;
      });
  	}
  	catch (e) {
  		json.success = "false";
  		console.log(e.message);
  	}

  	res.send(JSON.stringify(json));
    //res.send('AIDA Home Page!');
  };
  return this;
}
<commit_msg>change api calls<commit_after>var User = require('../models/user'),
    Project = require('../models/project'),
    permissionManager = require('../middleware/permission_manager');

module.exports = function(app) {
  this.getProject = function (req, res, next) {
  	/*
  		{
  			id: project id,
  			title: project title,
  			publisher:
  			{
  				publisher_id: id of owner,
  				publisher_name: name of project owner
  			},
  			members:
  			[
  				{
  					member_id: id of owner,
  					member_name: name of project owner
  				}
  			],
  			short_intro: short description, 300 characters max,
  			long_intro: longer intro with no character limit,
  			showcase:
  			[
  				{
  					path: path to the file,
  					type: type of the file
  				}
  			]
  			latest_update: date of the latest update, in seconds,
  			status: project status,
  			tags: [list of tags],
  			open_jobs:
  			[
  				{
  					job_id: job id,
  					job_title: job title,
  					job_tags: [list of skill names with ratings],
  					job_budget: budget level estimation between 1 to 5,
  					job_deadline: job deadline
  				}
  			],
  		}
  	*/
  	try {
  		var json = new Object();
  		Project.findById(req.params.project_id, function(err, project){
  			if (!project.length) {
  				res.status(404);
  				// project page not found
  				if (req.accepts('html')) {
  					res.render('404', { url: req.url });
  				}
  				return;
  			}
  			// Build the file
  			json.id = project._id;
  			json.title = project.name;
  			json.publisher = new Object();
  			json.publisher.publisher_id = project.owner;
  			User.findById(project.owner, function(err, publisher_info){
  				json.publisher.publisher_name = publisher_info.name;
  				json.members = [];
  			});
  			var i;
  			var numMembers = project.members.length;
  			for (i=0;i<numMembers;i++) {
  				var newMember = new Object();
  				newMember.member_id = project.members[i].user;
  				User.findById(project.members[i].user, function(err, memberName){
  					newMember.member_name = memberName.name;
  					json.members.push(newMember);
  				});
  			}
  			json.short_intro = project.basicInfo;
  			json.long_intro = project.detailedInfo;*/

  			json.long_intro = [];
  			var numParagraph = project.detailedInfo.length;
  			for (i=0;i<numParagraph;i++) {
  				var newParagraph = new Object();
  				newParagraph.paragraph_title = project.detailedInfo[i].title;
  				newParagraph.paragraph_content = project.detailedInfo[i].content;
  				json.long_intro.push(newParagraph);
  			}*/

        json.showcase = [];
  			var numShowcase = project.showcase.assetPaths.length;
  			for (i=0;i<numShowcase;i++) {
  				var current_path = project.showcase.assetPaths[i];
  				var current_type = project.showcase.mediaTypes[i];
  				var newShowcase = new Object();
  				newShowcase.path = current_path;
  				newShowcase.type = currentcurrent_type;
  			}
  			json.latest_update = project.updatedAt;
  			json.status = project.status;
  			json.tags = project.tags;
  			var jobs = Job.find({"project": ObjectId(project_id)});
  			json.open_jobs = [];
  			while (jobs.hasNext()) {
  				var newJob = new Object();
  				var current = jobs.next();
  				newJob.job_id = current._id;
  				newJob.job_title = current.name;
  				newJob.job_tags = current.skillTags;
  				newJob.job_budget = current.budget;
  				newJob.job_deadline = current.deadline;
  			}
  			res.send(JSON.stringify(json));

  		});

  	}
  	catch (e) {
  		res.status(404);
  		// page not found
  		if (req.accepts('html')) {
  			res.render('404', { url: req.url });
  		}
  	}
    //res.send('AIDA Home Page!');
    /*
    var project1 =
      {
        "id":"1",
        "title":"Super Awesome Game",
        "publisher": {
          "id":"1",
          "username": "jbelfort",
          "name":"Jordan Belfort"
        },
        "members": [{
          "id":"2",
          "name": "Donald Trump",
          "username": "bigDTrump"
        }],
        "latest_update": "100 million years ago",
        "status": "abandoned and forgotten",
        "tags": [{
          "name":"Fatasy"
        }, {
          "name":"Adventure"
        }],
        "brief": "This is a cool game about awesomeness and stuff I guess",
        "details": "This game features hundreds of awesome features that are featured throughout the game. Features include featuring features featured in the game. Specifically, you can specifically specify specific specifics. Additionally, many innovative innovations have been innovated.",
        "showcase": {
            "path": "/images/gs1.jpg"
        },
        "jobs": [{
          "id": "1",
          "title": "2D Animator",
          "status": "open",
          "tags": [{
            "name": "Cooking",
            "rating": 3
          }, {
            "name": "Eating",
            "rating": 5
          }],
          "budget": 2000,
          "deadline": "June 13 1995",
        }]
      }

      res.json(project1);*/
  };

  this.getPopularProjects = function (req, res, next) {
  	// get name, tags, showcase
  	var json = new Object();
  	var cursor = Project.find({},{"name": 1, "tags": 1, "showcase": 1}).sort({"numFollowers": -1}).limit(10);
  	json.topTen = cursor.toArray();
  	json.following = [];
  	var userId = req.session.userId;
  	if (userId) {
  		User.findById(userId, function(err, user){
  			var followings = user.followings;
  			var numFollowings = followings.length;
  			var i;
  			for (i=0;i<numFollowings;i++) {
  				Project.findById(followings[i], function(){
  					// the object being followed is an existing project
  					if (project.length) {
  						var newProject = new Object();
  						newProject._id = project._id;
  						newProject.name = project.name;
  						newProject.tags = project.tags;
  						json.following.push(newProject);
  					}
  				});
  			}
  		});
  	}
  	res.send(JSON.stringify(json));
    //res.send('AIDA Home Page!');
  };

  this.createProject = function (req, res, next) {
  	/*
  		TODO:
  		after the posting, send to the front end the link
  		to the project page, so that the front end will
  		redirect to the page
  		{
  			success: true/false
  			url: the link to the new project page
  		}
  	*/
  	var json = new Object();
  	try {
  		var projectForm = qs.parse(req.data);
      var tags = jobForm.descriptionTags.replace(/\s+/g, '').split(",");
  		// may createJob return job _id or something...
      var newProject = Project({
        name: projectForm.name,
        owner: req.user._id,
        tags: tags,
        members: projectForm.members,
        details: projectForm.details
      });

      newProject.save(function(err, project) {
        json.url = req.baseUrl + '/' + newProject._id;
        json.success = true;
      });
  	}
  	catch (e) {
  		json.success = "false";
  		console.log(e.message);
  	}

  	res.send(JSON.stringify(json));
    //res.send('AIDA Home Page!');
  };
  return this;
}
<|endoftext|>


<commit_before>/* global ININ */
import Ember from 'ember';
import Chance from 'npm:chance';
import {purecloudEnvironmentTld} from '../utils/purecloud-environment';
import config from '../config/environment';

var computed = Ember.computed;

export default Ember.Controller.extend({
	purecloud: Ember.inject.service(),
	storageService: Ember.inject.service(),
	queueService: Ember.inject.service(),
	webChatService: Ember.inject.service(),
	org: null,
	firstName: '',
	lastName: '',
	address: '',
	city:'',
	state: '',
	zip: '',
	phone: '',
	queue: '',
	locale: 'en',
	field1name: '',
	field1value: '',
	field2name: '',
	field2value: '',
	field3name: '',
	field3value: '',
	error: '',
	errorVisibility: computed('error', function() {
		const error = this.get('error');
		return !error || error == '' ? 'hidden' :  '';
	}),
	welcomeMessage: 'Thanks for chatting using the dev tools chat page.',
	chatEnvironment: computed('purecloud.environment', function() {
		return this.get('purecloud').get('environment');
	}),
	chatRegion: computed('purecloud.environment', function() {
		let environment = this.get('purecloud').get('environment');
		if (!environment || environment === '') return;

		if (environment.endsWith('.jp'))
			return 'ap-northeast-1';
		else if (environment.endsWith('.com.au'))
			this.set('chatRegion', 'ap-southeast-2');
		else if (environment.endsWith('.ie'))
			return 'eu-west-1';
		else if (environment.endsWith('.de'))
			return 'eu-central-1';
		else if (environment.endsWith('mypurecloud.com'))
			return 'us-east-1';
		else if (environment.includes('tca')) {
			this.set('chatEnv', 'test');
			this.set('chatEnvTag', '\n  env="test"');
			return 'us-east-1';
		} else if (environment.includes('dca')) {
			this.set('chatEnv', 'dev');
			this.set('chatEnvTag', '\n  env="dev"');
			return 'us-east-1';
		} else {
			console.warn(`Failed to identify environment: ${environment}`);
			return 'us-east-1';
		}
	}),
	chatEnv: '',
	chatEnvTag: '',
	init(){
		this._super(...arguments);

		let orgApi = this.get('purecloud').orgApi();

		orgApi.getOrganizationsMe().then(result => {
			this.set('org',result);

			let storage = this.get('storageService');
			let savedData = storage.localStorageGet('webChatParams');

			if(savedData){
				this.set('firstName', savedData.firstName);
				this.set('lastName', savedData.lastName);
				this.set('address', savedData.address);
				this.set('city', savedData.city);
				this.set('state', savedData.state);
				this.set('zip', savedData.zip);
				this.set('phone', savedData.phone);
				this.set('queue', savedData.queue);
				this.set('locale', savedData.locale);
				this.set('welcomeMessage', savedData.welcomeMessage);
				this.set('deployment', savedData.deployment);
				this.set('field1name', savedData.field1name);
				this.set('field1value', savedData.field1value);
				this.set('field2name', savedData.field2name);
				this.set('field2value', savedData.field2value);
				this.set('field3name', savedData.field3name);
				this.set('field3value', savedData.field3value);
			}
		});
	},
	deployments: computed('webChatService.deployments', function() {
		return this.get('webChatService').get('deployments');
	}),
	needsDeployment: computed('webChatService.deploymentCount', function() {
		return this.get('webChatService').get('deploymentCount') == 0;
	}),
	queues: computed('queueService.queues', function() {
		return this.get('queueService').get('queues');
	}),
	chatConfig: computed('queue', 'firstName', 'lastName', 'address', 'city', 'zip', 'state', 'phone', 'locale', 'welcomeMessage', 'field1name', 'field1value', 'field2name', 'field2value', 'field3name', 'field3value', function() {
		try{
			let environment = purecloudEnvironmentTld();
			let companyLogo = $('#companyLogo').attr('src');
			let companyLogoSmall = $('#companyLogoSmall').attr('src');
			let agentAvatar = $('#agentAvatar').attr('src');

			let resourcePrefix = window.location.origin;
			if(config.APP.urlprefix){
				resourcePrefix = config.APP.urlprefix;
			}

			if(typeof companyLogo !== 'undefined' && companyLogo[0] !== 'h'){
				companyLogo = resourcePrefix + companyLogo;
				companyLogoSmall = resourcePrefix + companyLogoSmall;
				agentAvatar = resourcePrefix + agentAvatar;
			}

			let org = this.get('org');
			if(org === null){
				return '{}';
			}

			let chatConfig = {
				// Web chat application URL
				'webchatAppUrl': 'https://apps.'+ environment +'/webchat',

				// Web chat service URL
				'webchatServiceUrl': 'https://realtime.'+ environment +':443',

				'orgId': this.get('org').thirdPartyOrgId,

				// Organization name
				'orgName': this.get('org').thirdPartyOrgName,

				'queueName': this.get('queue'),

				// Log level
				'logLevel': 'DEBUG',

				// Locale code
				'locale': this.get('locale'),

				// Data that will be included with interaction
				'data': {
					'firstName': this.get('firstName'),
					'lastName': this.get('lastName'),
					'addressStreet': this.get('address'),
					'addressCity': this.get('city'),
					'addressPostalCode': this.get('zip'),
					'addressState': this.get('state'),
					'phoneNumber': this.get('phone'),
					'customField1Label': this.get('field1name'),
					'customField1': this.get('field1value'),
					'customField2Label': this.get('field2name'),
					'customField2': this.get('field2value'),
					'customField3Label': this.get('field3name'),
					'customField3': this.get('field3value'),
				},

				// Logo used at the top of the chat window
				'companyLogo': {
					'width': 600,
					'height': 149,
					'url': companyLogo
				},

				// Logo used within the chat window
				'companyLogoSmall': {
					'width': 25,
					'height': 25,
					'url': companyLogoSmall
				},

				// Image used for agent
				'agentAvatar': {
					'width': 462,
					'height': 462,
					'url': agentAvatar
				},

				// Text displayed with chat window is displayed
				'welcomeMessage': this.get('welcomeMessage'),

				// CSS class applied to the chat window
				'cssClass': 'webchat-frame',

				// Custom style applied to the chat window
				'css': {
					'width': '100%',
					'height': '100%'
				}
			};

			return JSON.stringify(chatConfig, null, '  ');
		}catch(ex){
			console.error(ex);
		}
		return '{}';
	}),
	setError(err) {
		if (!err) {
			this.set('error', '');
			return;
		}

		console.error(err);
		if (err.body && err.body.message)
			this.set('error', err.body.message);
		else if (err.message)
			this.set('error', err.message);
		else
			this.set('error', 'An error has occurred');
	},
	openChatWindow() {
		try {
			let chatConfig = JSON.parse(this.get('chatConfig'));

			ININ.webchat.create(chatConfig, (err, webchat) => {
				if (err) {
					this.setError(err);
					return;
				}

				webchat.renderPopup({
					width: 400,
					height: 400,
					title: 'PureCloud Developer Tools Web Chat'
				});
			});

			let savedData = {
				firstName: this.get('firstName'),
				lastName: this.get('lastName'),
				address: this.get('address'),
				city: this.get('city'),
				state: this.get('state'),
				zip: this.get('zip'),
				phone: this.get('phone'),
				queue: this.get('queue'),
				locale: this.get('locale'),
				welcomeMessage: this.get('welcomeMessage'),
				deployment: this.get('deployment'),
				field1name: this.get('field1name'),
				field1value: this.get('field1value'),
				field2name: this.get('field2name'),
				field2value: this.get('field2value'),
				field3name: this.get('field3name'),
				field3value: this.get('field3value'),
			};

			let storage = this.get('storageService');
			storage.localStorageSet('webChatParams', savedData);
		} catch(err) {
			this.setError(err);
		}
	},
	actions:{
		startChat() {
			try {
				this.setError();

				// Validation
				let chatConfig = JSON.parse(this.get('chatConfig'));
				if (!chatConfig.queueName || chatConfig.queueName == '')
					throw new Error('You must select a queue before starting the chat');
				if (!this.deployment || this.deployment == '')
					throw new Error('You must select a deployment before starting the chat');

				const chatScript = document.getElementById('purecloud-webchat-js');

				// Skip injecting script tag if it's already been injected.
				if (chatScript.getAttribute('src') != null) {
					this.openChatWindow();
					return;
				} else {
					// Once a script has been loaded, it cannot be unloaded.
					// Therefore, we must prevent the user from choosing another deployment until the page is reloaded.
					$('#deployment-select-container select').prop('disabled', true);
				}

				// Inject deployment script
				chatScript.async = true;
				chatScript.setAttribute('type', 'text/javascript');
				chatScript.setAttribute('region', this.get('chatRegion'));
				if (this.get('chatEnv') !== '')
					chatScript.setAttribute('env', this.get('chatEnv'));
				chatScript.setAttribute('org-guid', this.get('org').id);
				chatScript.setAttribute('deployment-key', this.get('deployment'));
				chatScript.setAttribute('src', `https://apps.${this.get('chatEnvironment')}/webchat/jsapi-v1.js`);
				chatScript.addEventListener('error', () => this.setError('Error loading script.'));
				chatScript.addEventListener('abort', () => this.setError('Script loading aborted.'));
				chatScript.addEventListener('load', this.openChatWindow.bind(this));
			} catch(err) {
				this.setError(err);
			}
		},
		populate() {
			var chance = new Chance();

			this.set('firstName', chance.first());
			this.set('lastName', chance.last());
			this.set('address', chance.address());
			this.set('city', chance.city());
			this.set('state', chance.state());
			this.set('zip', chance.zip());
			this.set('phone', chance.phone());
			this.set('locale', 'en');
			this.set('welcomeMessage', 'Thanks for chatting using the dev tools chat page.');
		},
		createDeployment() {
			this.setError();
			this.get('webChatService').createDeployment()
				.then((newDeploymentId) => {
					let storage = this.get('storageService');
					let savedData = storage.localStorageGet('webChatParams');
					if (savedData && savedData.deployment)
						this.set('deployment', savedData.deployment);
					else
						this.set('deployment', newDeploymentId);
				})
				.catch((err) => {
					this.setError(err);
				});
		}
	}
});
<commit_msg>Add the attribute names directly on webchats instead of as a value of a pre-existing attribute<commit_after>/* global ININ */
import Ember from 'ember';
import Chance from 'npm:chance';
import {purecloudEnvironmentTld} from '../utils/purecloud-environment';
import config from '../config/environment';

var computed = Ember.computed;

export default Ember.Controller.extend({
	purecloud: Ember.inject.service(),
	storageService: Ember.inject.service(),
	queueService: Ember.inject.service(),
	webChatService: Ember.inject.service(),
	org: null,
	firstName: '',
	lastName: '',
	address: '',
	city:'',
	state: '',
	zip: '',
	phone: '',
	queue: '',
	locale: 'en',
	field1name: '',
	field1value: '',
	field2name: '',
	field2value: '',
	field3name: '',
	field3value: '',
	error: '',
	errorVisibility: computed('error', function() {
		const error = this.get('error');
		return !error || error == '' ? 'hidden' :  '';
	}),
	welcomeMessage: 'Thanks for chatting using the dev tools chat page.',
	chatEnvironment: computed('purecloud.environment', function() {
		return this.get('purecloud').get('environment');
	}),
	chatRegion: computed('purecloud.environment', function() {
		let environment = this.get('purecloud').get('environment');
		if (!environment || environment === '') return;

		if (environment.endsWith('.jp'))
			return 'ap-northeast-1';
		else if (environment.endsWith('.com.au'))
			this.set('chatRegion', 'ap-southeast-2');
		else if (environment.endsWith('.ie'))
			return 'eu-west-1';
		else if (environment.endsWith('.de'))
			return 'eu-central-1';
		else if (environment.endsWith('mypurecloud.com'))
			return 'us-east-1';
		else if (environment.includes('tca')) {
			this.set('chatEnv', 'test');
			this.set('chatEnvTag', '\n  env="test"');
			return 'us-east-1';
		} else if (environment.includes('dca')) {
			this.set('chatEnv', 'dev');
			this.set('chatEnvTag', '\n  env="dev"');
			return 'us-east-1';
		} else {
			console.warn(`Failed to identify environment: ${environment}`);
			return 'us-east-1';
		}
	}),
	chatEnv: '',
	chatEnvTag: '',
	init(){
		this._super(...arguments);

		let orgApi = this.get('purecloud').orgApi();

		orgApi.getOrganizationsMe().then(result => {
			this.set('org',result);

			let storage = this.get('storageService');
			let savedData = storage.localStorageGet('webChatParams');

			if(savedData){
				this.set('firstName', savedData.firstName);
				this.set('lastName', savedData.lastName);
				this.set('address', savedData.address);
				this.set('city', savedData.city);
				this.set('state', savedData.state);
				this.set('zip', savedData.zip);
				this.set('phone', savedData.phone);
				this.set('queue', savedData.queue);
				this.set('locale', savedData.locale);
				this.set('welcomeMessage', savedData.welcomeMessage);
				this.set('deployment', savedData.deployment);
				this.set('field1name', savedData.field1name);
				this.set('field1value', savedData.field1value);
				this.set('field2name', savedData.field2name);
				this.set('field2value', savedData.field2value);
				this.set('field3name', savedData.field3name);
				this.set('field3value', savedData.field3value);
			}
		});
	},
	deployments: computed('webChatService.deployments', function() {
		return this.get('webChatService').get('deployments');
	}),
	needsDeployment: computed('webChatService.deploymentCount', function() {
		return this.get('webChatService').get('deploymentCount') == 0;
	}),
	queues: computed('queueService.queues', function() {
		return this.get('queueService').get('queues');
	}),
	chatConfig: computed('queue', 'firstName', 'lastName', 'address', 'city', 'zip', 'state', 'phone', 'locale', 'welcomeMessage', 'field1name', 'field1value', 'field2name', 'field2value', 'field3name', 'field3value', function() {
		try{
			let environment = purecloudEnvironmentTld();
			let companyLogo = $('#companyLogo').attr('src');
			let companyLogoSmall = $('#companyLogoSmall').attr('src');
			let agentAvatar = $('#agentAvatar').attr('src');

			let resourcePrefix = window.location.origin;
			if(config.APP.urlprefix){
				resourcePrefix = config.APP.urlprefix;
			}

			if(typeof companyLogo !== 'undefined' && companyLogo[0] !== 'h'){
				companyLogo = resourcePrefix + companyLogo;
				companyLogoSmall = resourcePrefix + companyLogoSmall;
				agentAvatar = resourcePrefix + agentAvatar;
			}

			let org = this.get('org');
			if(org === null){
				return '{}';
			}

			let chatData = {
				'firstName': this.get('firstName'),
				'lastName': this.get('lastName'),
				'addressStreet': this.get('address'),
				'addressCity': this.get('city'),
				'addressPostalCode': this.get('zip'),
				'addressState': this.get('state'),
				'phoneNumber': this.get('phone'),
			};

			for (let i = 1; i <= 3; i++) {
				const fieldName = this.get(`field${i}name`);
				if (fieldName != '') {
					chatData[fieldName] = this.get(`field${i}value`);
				}
			}

			let chatConfig = {
				// Web chat application URL
				'webchatAppUrl': 'https://apps.'+ environment +'/webchat',

				// Web chat service URL
				'webchatServiceUrl': 'https://realtime.'+ environment +':443',

				'orgId': this.get('org').thirdPartyOrgId,

				// Organization name
				'orgName': this.get('org').thirdPartyOrgName,

				'queueName': this.get('queue'),

				// Log level
				'logLevel': 'DEBUG',

				// Locale code
				'locale': this.get('locale'),

				// Data that will be included with interaction
				'data': chatData,

				// Logo used at the top of the chat window
				'companyLogo': {
					'width': 600,
					'height': 149,
					'url': companyLogo
				},

				// Logo used within the chat window
				'companyLogoSmall': {
					'width': 25,
					'height': 25,
					'url': companyLogoSmall
				},

				// Image used for agent
				'agentAvatar': {
					'width': 462,
					'height': 462,
					'url': agentAvatar
				},

				// Text displayed with chat window is displayed
				'welcomeMessage': this.get('welcomeMessage'),

				// CSS class applied to the chat window
				'cssClass': 'webchat-frame',

				// Custom style applied to the chat window
				'css': {
					'width': '100%',
					'height': '100%'
				}
			};

			return JSON.stringify(chatConfig, null, '  ');
		}catch(ex){
			console.error(ex);
		}
		return '{}';
	}),
	setError(err) {
		if (!err) {
			this.set('error', '');
			return;
		}

		console.error(err);
		if (err.body && err.body.message)
			this.set('error', err.body.message);
		else if (err.message)
			this.set('error', err.message);
		else
			this.set('error', 'An error has occurred');
	},
	openChatWindow() {
		try {
			let chatConfig = JSON.parse(this.get('chatConfig'));

			ININ.webchat.create(chatConfig, (err, webchat) => {
				if (err) {
					this.setError(err);
					return;
				}

				webchat.renderPopup({
					width: 400,
					height: 400,
					title: 'PureCloud Developer Tools Web Chat'
				});
			});

			let savedData = {
				firstName: this.get('firstName'),
				lastName: this.get('lastName'),
				address: this.get('address'),
				city: this.get('city'),
				state: this.get('state'),
				zip: this.get('zip'),
				phone: this.get('phone'),
				queue: this.get('queue'),
				locale: this.get('locale'),
				welcomeMessage: this.get('welcomeMessage'),
				deployment: this.get('deployment'),
				field1name: this.get('field1name'),
				field1value: this.get('field1value'),
				field2name: this.get('field2name'),
				field2value: this.get('field2value'),
				field3name: this.get('field3name'),
				field3value: this.get('field3value'),
			};

			let storage = this.get('storageService');
			storage.localStorageSet('webChatParams', savedData);
		} catch(err) {
			this.setError(err);
		}
	},
	actions:{
		startChat() {
			try {
				this.setError();

				// Validation
				let chatConfig = JSON.parse(this.get('chatConfig'));
				if (!chatConfig.queueName || chatConfig.queueName == '')
					throw new Error('You must select a queue before starting the chat');
				if (!this.deployment || this.deployment == '')
					throw new Error('You must select a deployment before starting the chat');

				const chatScript = document.getElementById('purecloud-webchat-js');

				// Skip injecting script tag if it's already been injected.
				if (chatScript.getAttribute('src') != null) {
					this.openChatWindow();
					return;
				} else {
					// Once a script has been loaded, it cannot be unloaded.
					// Therefore, we must prevent the user from choosing another deployment until the page is reloaded.
					$('#deployment-select-container select').prop('disabled', true);
				}

				// Inject deployment script
				chatScript.async = true;
				chatScript.setAttribute('type', 'text/javascript');
				chatScript.setAttribute('region', this.get('chatRegion'));
				if (this.get('chatEnv') !== '')
					chatScript.setAttribute('env', this.get('chatEnv'));
				chatScript.setAttribute('org-guid', this.get('org').id);
				chatScript.setAttribute('deployment-key', this.get('deployment'));
				chatScript.setAttribute('src', `https://apps.${this.get('chatEnvironment')}/webchat/jsapi-v1.js`);
				chatScript.addEventListener('error', () => this.setError('Error loading script.'));
				chatScript.addEventListener('abort', () => this.setError('Script loading aborted.'));
				chatScript.addEventListener('load', this.openChatWindow.bind(this));
			} catch(err) {
				this.setError(err);
			}
		},
		populate() {
			var chance = new Chance();

			this.set('firstName', chance.first());
			this.set('lastName', chance.last());
			this.set('address', chance.address());
			this.set('city', chance.city());
			this.set('state', chance.state());
			this.set('zip', chance.zip());
			this.set('phone', chance.phone());
			this.set('locale', 'en');
			this.set('welcomeMessage', 'Thanks for chatting using the dev tools chat page.');
		},
		createDeployment() {
			this.setError();
			this.get('webChatService').createDeployment()
				.then((newDeploymentId) => {
					let storage = this.get('storageService');
					let savedData = storage.localStorageGet('webChatParams');
					if (savedData && savedData.deployment)
						this.set('deployment', savedData.deployment);
					else
						this.set('deployment', newDeploymentId);
				})
				.catch((err) => {
					this.setError(err);
				});
		}
	}
});
<|endoftext|>


<commit_before>var express = require('express'),
    router = express.Router(),
    config = require('../../config/config'),
    db = require('../models'),
    fb = require('../helpers/facebookGraphAPI'),
    bodyParser = require('body-parser'),
    request = require('request');

module.exports = function (app) {
  app.use('/', router);
};

// Facebook verification
router.get('/webhook/', function (req, res) {
  if (req.query['hub.verify_token'] === config.verify_token) {
    res.send(req.query['hub.challenge']);
  }
  res.send('Error, wrong token');
});

router.post('/webhook/', function (req, res) {
  messaging_events = req.body.entry[0].messaging;

  for (i = 0; i < messaging_events.length; i++) {
    event = req.body.entry[0].messaging[i];
    sender = event.sender.id;

    // Handle opt-in via the Send-to-Messenger Plugin, store user data and greet the user by name
    if (event.optin) {
      userNamePromise = fb.getUserName(sender);

      userNamePromise.then(function(userName) {
        fb.sendTextMessage(sender, "Hi there, " + userName.first_name + "! I'm Thomas. Type 'Start' to begin a conversation or 'Help' for tips on how I can talk to you.");
      });
    }

    // Handle receipt of a message
    if (event.message && event.message.text) {
      text = event.message.text;

      // Handle special keywords
      if (text === 'Start') {
        fb.sendGenericMessage(sender);
        continue;

      } else if (text === 'Help') {
        fb.sendTextMessage(sender, "List of commands:\n1. Start - Initiate Conversation Flow\n2.Quick - Choose Department");
        continue;

      } else if (text === 'Quick') {
        fb.sendQuickReply(sender);
        continue;

      } else {
        // Failsafe + reminder for anything else
        fb.sendTextMessage(sender, "I'm sorry, I don't understand that input.\nRemember to type 'Help' for a list of commands.");
        continue;
      }
    }

    // Handle receipt of a postback
    if (event.postback) {
      text = JSON.stringify(event.postback);
      fb.sendTextMessage(sender, "Postback received: "+text.substring(0, 200));
      continue;
    }

  }
  res.sendStatus(200);

});
<commit_msg>IT selection<commit_after>var express = require('express'),
    router = express.Router(),
    config = require('../../config/config'),
    db = require('../models'),
    fb = require('../helpers/facebookGraphAPI'),
    bodyParser = require('body-parser'),
    request = require('request');

module.exports = function (app) {
  app.use('/', router);
};

// Facebook verification
router.get('/webhook/', function (req, res) {
  if (req.query['hub.verify_token'] === config.verify_token) {
    res.send(req.query['hub.challenge']);
  }
  res.send('Error, wrong token');
});

router.post('/webhook/', function (req, res) {
  messaging_events = req.body.entry[0].messaging;

  for (i = 0; i < messaging_events.length; i++) {
    event = req.body.entry[0].messaging[i];
    sender = event.sender.id;

    // Handle opt-in via the Send-to-Messenger Plugin, store user data and greet the user by name
    if (event.optin) {
      userNamePromise = fb.getUserName(sender);

      userNamePromise.then(function(userName) {
        fb.sendTextMessage(sender, "Hi there, " + userName.first_name + "! I'm Thomas. Type 'Start' to begin a conversation or 'Help' for tips on how I can talk to you.");
      });
    }

    // Handle receipt of a message
    if (event.message && event.message.text) {
      text = event.message.text;

      // Handle special keywords
      if (text === 'Start') {
        fb.sendGenericMessage(sender);
        continue;

      } else if (text === 'Help') {
        fb.sendTextMessage(sender, "List of commands:\n1. Start - Initiate Conversation Flow\n2.Quick - Choose Department");
        continue;

      } else if (text === 'Quick') {
        fb.sendQuickReply(sender);
        continue;

        } else if (text === 'IT') {
        fb.sendITReply(sender);
        continue;

      } else {
        // Failsafe + reminder for anything else
        fb.sendTextMessage(sender, "I'm sorry, I don't understand that input.\nRemember to type 'Help' for a list of commands.");
        continue;
      }
    }

    // Handle receipt of a postback
    if (event.postback) {
      text = JSON.stringify(event.postback);
      fb.sendTextMessage(sender, "Postback received: "+text.substring(0, 200));
      continue;
    }

  }
  res.sendStatus(200);

});
<|endoftext|>


<commit_before>(function() {
  'use strict';

  angular
    .module('users')
    .controller('IndividualController', IndividualController);

  IndividualController.$inject = ['$scope', 'userResolve', '$resource', '$http', 'Authentication', '$location'];

  function IndividualController($scope, user, $resource, $http, Authentication, $location) {
    var vm = this;
    $scope.showAFButton=true;

    // Individual controller logic
    // ...
    $scope.user = user;

    var friends = Authentication.user.friends;

    if(friends.includes(user._id)){
      $scope.showAFButton=false;
    }

    $scope.addFriend = function(){

      $http({
            method: 'PUT',
            url: '/api/users/friends',
            data: {userId : user._id}
          }).then(function successCallback(response) {

               $scope.showAFButton=false;
               $scope.addFriendSuccess=true;

              // this callback will be called asynchronously
              // when the response is available
            }, function errorCallback(response) {

                $scope.addFriendFail = true;
              // called asynchronously if an error occurs
              // or server returns response with an error status.
            });
    };

    $scope.goHomePage = function(){

      $location.url('/');

    };

    init();

    function init() {
    }
  }
})();<commit_msg>update search friend for not add me<commit_after>(function() {
  'use strict';

  angular
    .module('users')
    .controller('IndividualController', IndividualController);

  IndividualController.$inject = ['$scope', 'userResolve', '$resource', '$http', 'Authentication', '$location'];

  function IndividualController($scope, user, $resource, $http, Authentication, $location) {
    var vm = this;
    $scope.showAFButton=true;

    // Individual controller logic
    // ...
    $scope.user = user;

    var friends = Authentication.user.friends;

    if(friends.includes(user._id)||Authentication.user._id===user._id){
      $scope.showAFButton=false;
    }

    $scope.addFriend = function(){

      $http({
            method: 'PUT',
            url: '/api/users/friends',
            data: {userId : user._id}
          }).then(function successCallback(response) {

               $scope.showAFButton=false;
               $scope.addFriendSuccess=true;

              // this callback will be called asynchronously
              // when the response is available
            }, function errorCallback(response) {

                $scope.addFriendFail = true;
              // called asynchronously if an error occurs
              // or server returns response with an error status.
            });
    };

    $scope.goHomePage = function(){

      $location.url('/');

    };

    init();

    function init() {
    }
  }
})();<|endoftext|>


<commit_before>'use strict';

const Base = require('../base.js');
const Computation = require('../computation/computation.js');
const assign = require('lodash/assign');
const noop = require('lodash/noop');
const joi = require('joi');
const EventEmitter = require('events');

/**
 * Emits a request for the PipelineRunner to save a PipelineResult
 *
 * @event Pipeline#save-request
 * @param {ComputationResult} rslt
 * @param {Error} [err] run error
 * @param {boolean} [force] force saving of result document
 */

/**
 * @class Pipeline
 * @constructor
 * @extends Base
 * @param {object} opts
 * @param {Computation[]} opts.computations
 * @property {Computation[]} computations computations in Pipeline
 * @property {number} step current integer step
 * @property {Computation} computation current computation instance
 * @property {boolean} inProgress state indicating that Pipeline is active
 * @property {EventEmitter} events
 */
class Pipeline extends Base {

  constructor(opts) {
    super(opts);
    this.events = new EventEmitter();
    this.computation = this.computations[this.step];
    this._preRunTasks = [];
    this._postRunTasks = [];
    this._applyDefaultPlugins();
  }

  /**
   * import/convert string plugin requests into actual plugin functions
   * @private
   * @returns {undefined}
   */
  _applyDefaultPlugins() {
    if (this.plugins && this.plugins.preRun.length) {
      this._preRunTasks = this.plugins.preRun;
    }
    if (this.plugins && this.plugins.postRun.length) {
      this._postRunTasks = this.plugins.postRun;
    }
  }

  /**
   * run any plugins post-run (each step)
   * @private
   * @param {object} opts
   * @param {object} opts.runOutput computation output
   * @param {ComputationResult} opts.compResult
   * @param {object} opts.pluginHooks
   * @returns {Promise}
   */
  _postRunPlugins(opts) {
    const runOutput = opts.runOutput;
    const compResult = opts.compResult;
    const pluginHooks = opts.pluginHooks;
    if (!this._postRunTasks.length) { return Promise.resolve(); }
    return Promise.all(
      this._postRunTasks.map((fn) => fn(runOutput, compResult, pluginHooks))
    );
  }

  /**
   * run any plugins pre-run (each step)
   * @private
   * @param {object} opts
   * @param {object} opts.runInputs computation input
   * @param {ComputationResult} opts.compResult
   * @param {object} opts.pluginHooks
   * @returns {Promise}
   */
  _preRunPlugins(opts) {
    const runInputs = opts.runInputs;
    const compResult = opts.compResult;
    const pluginHooks = opts.pluginHooks;
    if (!this._preRunTasks.length) { return Promise.resolve(); }
    return Promise.all(
      this._preRunTasks.map((fn) => {
        if (!pluginHooks.isRunCancelled()) {
          return fn(runInputs, compResult, pluginHooks);
        }
        return noop;
      })
    );
  }

  /**
   * @description run the pipeline from its current state, feeding in inputs
   * provided by the pipeline caller
   * @param {object} runInputs object that is fed to the computation, provided by
   *                      the pipeline runner
   * @param {ComputationResult} compResult the ComputationResult for the environment.
   *                                @note this should only be touched by plugins.
   * @returns {Promise}
   */
  run(runInputs, compResult) {
    if (!runInputs || !compResult) {
      return Promise.reject(
        new ReferenceError('options & ComputationResult required to pass to computations')
      );
    }
    if (this.inProgress) {
      return Promise.reject(
        new Error([
          'pipelines do not permit concurrent running. please spawn a new',
          'pipeline instance if concurrency is required',
        ].join(' '))
      );
    }
    this.inProgress = true;
    this.computation = this.computations[this.step];
    let runCancelled = false;
    let forceSave = false;
    // offer plugin hooks as functions.  restrict plugins from meddling with
    // run state. simply expose some run modifiers.
    const pluginHooks = {
      isRunCancelled() { return runCancelled; },
      cancelRun() { runCancelled = true; },
      forceSave() { forceSave = true; },
    };
    return this.tryNext(runInputs, { preRun: true })
    .then(() => this.events.emit('computation:start', runInputs))
    .then(() => this._preRunPlugins({ runInputs, compResult, pluginHooks }))
    .then(() => {
      if (runCancelled) { return Promise.resolve(null); }
      return this.computation.run(runInputs);
    })
    .then((runOutput) => {
      if (runCancelled) { return Promise.resolve(runOutput); }
      // postRun plugins are only run if a computation _actually_ ran
      return this._postRunPlugins({ runOutput, compResult, pluginHooks })
      .then(() => runOutput);
    })
    .then((runOutput) => this._postRun({
      runInputs,
      runOutput,
      compResult,
      runCancelled,
      forceSave,
    }))
    .catch((err) => {
      this.inProgress = false;
      if (err) { err.isRunError = true; }
      throw err;
    });
  }

  /**
   * Executes pipeline activity following a completed (successful or
   * not) computation run.
   * @private
   * @param {Error|null} err
   * @param {object} opts
   * @param {object} opts.runInputs original opts passed to run
   * @param {object} opts.runRslt
   * @param {ComputationResult} opts.compResult the ComputationResult for the environment.
   *                                @note this should only be touched by plugins.
   * @param {boolean} opts.forceSave
   * @param {boolean} opts.runCancelled
   * @returns {Promise}
   */
  _postRun(opts) {
    const runInputs = opts.runInputs;
    const runOutput = opts.runOutput;
    const compResult = opts.compResult;
    const forceSave = opts.forceSave;
    const runCancelled = opts.runCancelled;

    this.events.emit('computation:end', runInputs);
    const postRunInputs = assign({}, runInputs, { previousData: runOutput });
    return (runCancelled ?
      Promise.resolve({ didStep: false }) :
      this.tryNext(postRunInputs, { postRun: true })
    )
    .then((stepRslt) => {
      // set `inProgress` to represent whether we are about to be inProgress
      // again (e.g. if pipeline will run again). in this regard cb()s get
      // accurate depiction of state
      this.inProgress = !!stepRslt.didStep;
      this.events.emit('save-request', runOutput, null, forceSave);
      this.inProgress = false; // reset
      if (stepRslt.didStep) {
        this.events.emit('inProgress', runOutput);
        return this.run(postRunInputs, compResult);
      }
      return runOutput;
    });
  }

  /**
   * @extends Base::serialize
   * @description serialize a Pipeline per standard serialization,
   * however remove hefty computaton definitions
   * @returns {object}
   */
  serialize() {
    const serialized = Base.prototype.serialize.call(this);
    delete serialized.computations;
    return serialized;
  }

  /**
   * Attempt to bump the pipeline step.  runs the `next` computation
   * if present, and conditionally bumps the pipeline state. to be clear, this
   * runs a computation's `next` computation, _not the next step in the pipeline_
   *
   * if tryNext is called pre-`run` (`cycle.preRun`) and no `next` is defined,
   * the pipeline will **not progress** its current computation index.
   *
   * if tryNext is called post-`run` (`cycle.postRun`) and no `next` is defined,
   * the pipeline **will progress** its current computation index.
   *
   * @private
   * @param {object} opts inputs to next computation
   * @param {object} cycle
   * @param {boolean=} cycle.preRun tryNext is being called before `run` is called
   * @param {boolean=} cycle.postRun tryNext is being called after `run` is called
   * @returns {Promise}
   */
  tryNext(opts, cycle) {
    const resp = { didStep: false };
    const handleNextComplete = (doNext) => {
      const endOfPipe = !this.computations[this.step + 1];
      if (doNext && !endOfPipe) {
        ++this.step;
        this.computation = this.computations[this.step];
        resp.didStep = true;
      }
      return resp;
    };
    if (this.computation.next) {
      return this.computation.next.run(opts)
      .then((doNext) => handleNextComplete(doNext));
    }
    // do not auto-progress w/out .next fn on-pre-run
    if (cycle.preRun) { return Promise.resolve(resp); }
    return Promise.resolve(handleNextComplete(true));
  }
}

Pipeline.schema = Object.assign({}, Base.schema, {
  computations: joi.array().min(1).items(joi.object().type(Computation))
    .required(),
  plugins: joi.object().keys({
    preRun: joi.array().items(joi.func()),
    postRun: joi.array().items(joi.func()),
  }), // @TODO add shapel { preRun: [], postRun: [] }
  inProgress: joi.boolean().default(false),
  step: joi.number().integer().default(0), // current computation step
});

module.exports = Pipeline;
<commit_msg>Clean up `Pipeline#run` method.<commit_after>'use strict';

const Base = require('../base.js');
const Computation = require('../computation/computation.js');
const assign = require('lodash/assign');
const noop = require('lodash/noop');
const joi = require('joi');
const EventEmitter = require('events');

/**
 * Emits a request for the PipelineRunner to save a PipelineResult
 *
 * @event Pipeline#save-request
 * @param {ComputationResult} rslt
 * @param {Error} [err] run error
 * @param {boolean} [force] force saving of result document
 */

/**
 * @class Pipeline
 * @constructor
 * @extends Base
 * @param {object} opts
 * @param {Computation[]} opts.computations
 * @property {Computation[]} computations computations in Pipeline
 * @property {number} step current integer step
 * @property {Computation} computation current computation instance
 * @property {boolean} inProgress state indicating that Pipeline is active
 * @property {EventEmitter} events
 */
class Pipeline extends Base {

  constructor(opts) {
    super(opts);
    this.events = new EventEmitter();
    this.computation = this.computations[this.step];
    this._preRunTasks = [];
    this._postRunTasks = [];
    this._applyDefaultPlugins();
  }

  /**
   * import/convert string plugin requests into actual plugin functions
   * @private
   * @returns {undefined}
   */
  _applyDefaultPlugins() {
    if (this.plugins && this.plugins.preRun.length) {
      this._preRunTasks = this.plugins.preRun;
    }
    if (this.plugins && this.plugins.postRun.length) {
      this._postRunTasks = this.plugins.postRun;
    }
  }

  /**
   * run any plugins post-run (each step)
   * @private
   * @param {object} opts
   * @param {object} opts.runOutput computation output
   * @param {ComputationResult} opts.compResult
   * @param {object} opts.pluginHooks
   * @returns {Promise}
   */
  _postRunPlugins(opts) {
    const runOutput = opts.runOutput;
    const compResult = opts.compResult;
    const pluginHooks = opts.pluginHooks;
    if (!this._postRunTasks.length) { return Promise.resolve(); }
    return Promise.all(
      this._postRunTasks.map((fn) => fn(runOutput, compResult, pluginHooks))
    );
  }

  /**
   * run any plugins pre-run (each step)
   * @private
   * @param {object} opts
   * @param {object} opts.runInputs computation input
   * @param {ComputationResult} opts.compResult
   * @param {object} opts.pluginHooks
   * @returns {Promise}
   */
  _preRunPlugins(opts) {
    const runInputs = opts.runInputs;
    const compResult = opts.compResult;
    const pluginHooks = opts.pluginHooks;
    if (!this._preRunTasks.length) { return Promise.resolve(); }
    return Promise.all(
      this._preRunTasks.map((fn) => {
        if (!pluginHooks.isRunCancelled()) {
          return fn(runInputs, compResult, pluginHooks);
        }
        return noop;
      })
    );
  }

  /**
   * Run.
   * @description Run the pipeline from its current state, feeding in inputs
   * provided by the pipeline caller
   *
   * @param {Object} runInputs Input passed to the computation, provided by the
   * pipeline runner.
   * @param {ComputationResult} compResult Environment-specific
   * ComputationResult. This should only be touched by plugins.
   * @returns {Promise}
   */
  run(runInputs, compResult) {
    if (!runInputs || !compResult) {
      return Promise.reject(new Error(
        'Run inputs and ComputationResult required to pass to computations'
      ));
    } else if (this.inProgress) {
      return Promise.reject(new Error(
        /* eslint-disable max-len */
        'Pipelines do not permit concurrent running. Please spawn a new pipeline instance if concurrency is required.'
        /* eslint-enable max-len */
      ));
    }

    this.inProgress = true;
    this.computation = this.computations[this.step];
    let forceSave = false;
    let shouldContinueRun = true;

    /**
     * Offer plugin hooks as functions. Restrict plugins from meddling with
     * run state: simply expose some run modifiers.
     */
    const pluginHooks = {
      isRunCancelled() {
        return !shouldContinueRun;
      },
      cancelRun() {
        shouldContinueRun = false;
      },
      forceSave() {
        forceSave = true;
      },
    };

    return this.tryNext(runInputs, { preRun: true })
      .then(() => {
        this.events.emit('computation:start', runInputs);
        return this._preRunPlugins({ runInputs, compResult, pluginHooks });
      })
      .then(() => (
        shouldContinueRun ?
          this.computation.run(runInputs) :
          undefined
      ))
      .then((runOutput) => (
        // postRun plugins are only run if a computation _actually_ ran
        shouldContinueRun ?
          Promise.all([
            runOutput,
            this._postRunPlugins({ runOutput, compResult, pluginHooks }),
          ]) :
          [runOutput]
      ))
      .then(([runOutput]) => this._postRun({
        runInputs,
        runOutput,
        compResult,
        runCancelled: !shouldContinueRun,
        forceSave,
      }))
      .catch((error) => {
        this.inProgress = false;
        error.isRunError = true;
        throw error;
      });
  }

  /**
   * Executes pipeline activity following a completed (successful or
   * not) computation run.
   * @private
   * @param {Error|null} err
   * @param {object} opts
   * @param {object} opts.runInputs original opts passed to run
   * @param {object} opts.runRslt
   * @param {ComputationResult} opts.compResult the ComputationResult for the environment.
   *                                @note this should only be touched by plugins.
   * @param {boolean} opts.forceSave
   * @param {boolean} opts.runCancelled
   * @returns {Promise}
   */
  _postRun(opts) {
    const runInputs = opts.runInputs;
    const runOutput = opts.runOutput;
    const compResult = opts.compResult;
    const forceSave = opts.forceSave;
    const runCancelled = opts.runCancelled;

    this.events.emit('computation:end', runInputs);
    const postRunInputs = assign({}, runInputs, { previousData: runOutput });
    return (runCancelled ?
      Promise.resolve({ didStep: false }) :
      this.tryNext(postRunInputs, { postRun: true })
    )
    .then((stepRslt) => {
      // set `inProgress` to represent whether we are about to be inProgress
      // again (e.g. if pipeline will run again). in this regard cb()s get
      // accurate depiction of state
      this.inProgress = !!stepRslt.didStep;
      this.events.emit('save-request', runOutput, null, forceSave);
      this.inProgress = false; // reset
      if (stepRslt.didStep) {
        this.events.emit('inProgress', runOutput);
        return this.run(postRunInputs, compResult);
      }
      return runOutput;
    });
  }

  /**
   * @extends Base::serialize
   * @description serialize a Pipeline per standard serialization,
   * however remove hefty computaton definitions
   * @returns {object}
   */
  serialize() {
    const serialized = Base.prototype.serialize.call(this);
    delete serialized.computations;
    return serialized;
  }

  /**
   * Attempt to bump the pipeline step.  runs the `next` computation
   * if present, and conditionally bumps the pipeline state. to be clear, this
   * runs a computation's `next` computation, _not the next step in the pipeline_
   *
   * if tryNext is called pre-`run` (`cycle.preRun`) and no `next` is defined,
   * the pipeline will **not progress** its current computation index.
   *
   * if tryNext is called post-`run` (`cycle.postRun`) and no `next` is defined,
   * the pipeline **will progress** its current computation index.
   *
   * @private
   * @param {object} opts inputs to next computation
   * @param {object} cycle
   * @param {boolean=} cycle.preRun tryNext is being called before `run` is called
   * @param {boolean=} cycle.postRun tryNext is being called after `run` is called
   * @returns {Promise}
   */
  tryNext(opts, cycle) {
    const resp = { didStep: false };
    const handleNextComplete = (doNext) => {
      const endOfPipe = !this.computations[this.step + 1];
      if (doNext && !endOfPipe) {
        ++this.step;
        this.computation = this.computations[this.step];
        resp.didStep = true;
      }
      return resp;
    };
    if (this.computation.next) {
      return this.computation.next.run(opts)
      .then((doNext) => handleNextComplete(doNext));
    }
    // do not auto-progress w/out .next fn on-pre-run
    if (cycle.preRun) { return Promise.resolve(resp); }
    return Promise.resolve(handleNextComplete(true));
  }
}

Pipeline.schema = Object.assign({}, Base.schema, {
  computations: joi.array().min(1).items(joi.object().type(Computation))
    .required(),
  plugins: joi.object().keys({
    preRun: joi.array().items(joi.func()),
    postRun: joi.array().items(joi.func()),
  }), // @TODO add shapel { preRun: [], postRun: [] }
  inProgress: joi.boolean().default(false),
  step: joi.number().integer().default(0), // current computation step
});

module.exports = Pipeline;
<|endoftext|>