<commit_before>/*jshint multistr: true */

// Template operations
gApp.service('TemplateService', function ($q, $resource, SettingsService) {
    var self = this;

    SettingsService.get('apiBaseURL').then(function (apiBaseURL) {

        self.qRes = $resource(apiBaseURL + 'quicktexts/:quicktextId', {
            quicktextId: '@remote_id'
        }, {
            query: {
                method: "GET",
                isArray: true,
                timeout: 20 * 1000
            },
            update: {
                method: "PUT"
            },
            delete: {
                method: "DELETE",
                isArray: false
            }
        });

        self.statsRes = $resource(apiBaseURL + 'quicktexts/stats');
    });

    self.isLoggedin = false;
    SettingsService.get("isLoggedIn").then(function (isLoggedIn) {
        self.isLoggedin = isLoggedIn;
    });

    self.quicktexts = function (limit) {
        var deferred = $q.defer();
        // get all keys
        TemplateStorage.get(null, function (res) {
            var templates = [];
            for (var id in res) {
                var row = res[id];
                if (row.deleted === 0) {
                    templates.push(row);
                }
            }
            // sort by created_datetime desc
            templates.sort(function (a, b) {
                return new Date(b.created_datetime) - new Date(a.created_datetime);
            });
            // sort by updated_datetime desc
            // templates.sort(function (a, b) {
            //     return new Date(b.updated_datetime) - new Date(a.updated_datetime);
            // });

            if (limit) {
                templates = templates.splice(0, limit);
            }
            deferred.resolve(templates);
        });
        return deferred.promise;
    };

    self.filtered = function (filters, limit) {
        var deferred = $q.defer();
        self.quicktexts().then(function (templates) {
            for (var i in filters) {
                templates = _.filter(templates, filters[i]);
            }
            if (limit) {
                templates = templates.splice(0, limit);
            }
            deferred.resolve(templates);
        });
        return deferred.promise;
    };

    /* Sync - assume that there was no connectivity and now we have it

     Remote templates (after local sync):

     * Created (no similar remote_id found locally) - update sync_date
     * Updated (found remote_id - update) - update sync_date
     * Deleted (present locally, but not present in remote templates)
     */

    self.lastSync = null;

    self.sync = function () {
        var deferred = $q.defer();

        if (!self.isLoggedin) {
            deferred.resolve();
            return deferred.promise;
        }

        // Get the new or updated templates from the remote server
        self.qRes.query(function (remoteTemplates) {
            var now = new Date().toISOString();

            var localSeen = [];
            var remoteSeen = [];
            TemplateStorage.get(null, function (localTemplates) {
                for (var id in localTemplates) {
                    var t = localTemplates[id];
                    if (t.remote_id) {
                        localSeen.push(t.remote_id);
                    }
                }

                _.each(remoteTemplates, function (remoteTemplate) {
                    var localTemplate;
                    var lastVersion = remoteTemplate.versions[0];

                    remoteSeen.push(remoteTemplate.id);

                    var updated = false;
                    for (var id in localTemplates) {
                        localTemplate = localTemplates[id];

                        if (localTemplate.remote_id === remoteTemplate.id) {
                            localTemplate = self._copy(lastVersion, localTemplate);
                            localTemplate.remote_id = remoteTemplate.id;
                            // use the remote created_datetime as reference
                            localTemplate.created_datetime = remoteTemplate.created_datetime;
                            self.update(localTemplate, true, true);

                            updated = true;
                            break;
                        }
                    }

                    // If we haven't seen a local template, create it
                    // I wish there was for..else in JS
                    if (!updated) {
                        localTemplate = self._copy(lastVersion, {});
                        localTemplate.remote_id = remoteTemplate.id;
                        localTemplate.sync_datetime = now;

                        self.create(localTemplate, true).then(function () {

                        });
                    }
                });

                // delete local templates that have a remote_id, but are not present through the API request
                var deleteLocal = _.difference(localSeen, remoteSeen);
                _.each(deleteLocal, function (remoteId) {
                    TemplateStorage.get(null, function (localTemplates) {
                        _.each(localTemplates, function (localTemplate) {
                            if (localTemplate.remote_id === remoteId) {
                                self.delete(localTemplate, true);
                            }
                        });
                    });
                });
                self.lastSync = new Date();
                deferred.resolve(self.lastSync);
            });
        });

        return deferred.promise;
    };

    /**
     * Local templates:
     * Created (doesn't have a 'remote_id' set)
     * Deleted (deleted=1 in the db) - delete remotely and then completely in the db
     * Updated (sync_datetime is null or lower than the updated_date)
     *
     */
    self.syncLocal = function (callback) {
        if (!self.isLoggedin) {
            return;
        }

        // Handling all local templates
        TemplateStorage.get(null, function (templates) {
            for (var id in templates) {
                var t = templates[id];
                if (t.nosync !== 0) {
                    continue;
                }

                // no remote_id means that it's local only and we have to sync it with the remote sync service
                if (!t.remote_id) {
                    // skipping deleted templates - there should not be any.. but ok.
                    if (t.deleted === 1) {
                        continue;
                    }

                    var tRemote = new self.qRes();
                    tRemote = self._copy(angular.copy(t), tRemote);

                    // create new template on the server
                    var save = function (ut) {
                        // we're in a for loop so we need this closure here because the `t` var will be overridden
                        // before the remote request is finished
                        return function (res) {
                            ut.remote_id = res.id;
                            ut.sync_datetime = new Date().toISOString();

                            var data = {};
                            data[ut.id] = ut;
                            self.update(ut, true);
                        };
                    };
                    tRemote.$save(save(angular.copy(t)));
                } else { // was synced at some point
                    // if it's deleted locally, delete it remotely and then delete it completely
                    if (t.deleted === 1) {
                        var deleted = function (ut) {
                            return function (remote) {
                                remote.remote_id = ut.remote_id;
                                remote.$delete(function () {
                                    TemplateStorage.remove(ut.id);
                                });
                            }
                        };
                        self.qRes.get({quicktextId: t.remote_id}, deleted(angular.copy(t)));
                    } else if (t.updated_datetime) { // only if we have an updated_datetime
                        if (!t.sync_datetime || new Date(t.sync_datetime) < new Date(t.updated_datetime)) {
                            var update = function (ut) {
                                // we're in a for loop so we need this closure here because the `t` var will be overridden
                                // before the remote request is finished
                                return function (remote) {
                                    remote = self._copy(ut, remote);
                                    remote.$update(function () {
                                        ut.sync_datetime = new Date().toISOString();
                                        var data = {};
                                        data[ut.id] = ut;
                                        TemplateStorage.set(data);
                                    });
                                };
                            };
                            // template was updated locally, not synced yet
                            self.qRes.get({quicktextId: t.remote_id}, update(angular.copy(t)));
                        }
                    }

                    // send stats to server if we templates used
                    if (t.use_count) { // if we have a use_count, then we can update the stats on the server.
                        var stat = new self.statsRes();
                        stat.quicktext_id = t.remote_id;
                        stat.key = 'use_count';
                        stat.value = t.use_count;

                        // we need this closure to make sure we don't duplicate the same template
                        var save = function (ut) {
                            return function () {
                                ut.use_count = 0;
                                var data = {};
                                data[ut.id] = ut;
                                TemplateStorage.set(data, function () {
                                });
                            }
                        };
                        stat.$save(save(angular.copy(t)));
                    }
                }
            }
        });

        self.lastSync = new Date();
        if (callback) {
            callback(self.lastSync);
        }
    };

// given a string with tags give a clean list
// remove spaces, duplicates and so on
    self._clean_tags = function (tags) {
        var tArray = _.filter(tags.split(','), function (tag) {
            if (tag.trim() !== '') {
                return true;
            }
        });
        tags = _.unique(_.map(tArray, function (t) {
            return t.trim();
        })).join(', ');
        return tags;
    };

// Copy one template object to another - used for the remote saving
    self._copy = function (source, target) {
        for (var k in source) {
            // ignore the no own property or id
            if (k === 'id' && !source.hasOwnProperty(k)) {
                continue;
            }
            if (k === 'tags') {
                target[k] = self._clean_tags(source[k]);
            } else {
                target[k] = source[k];
            }
        }
        return target;
    };

// get template object given an id or null
    self.get = function (id) {
        var deferred = $q.defer();
        TemplateStorage.get(id, function (res) {
            deferred.resolve(res[id]);
        });
        return deferred.promise;
    };

// create and try to sync with the server
    self.create = function (t, onlyLocal) {
        var deferred = $q.defer();

        // UUID4 as an id for the template
        t.id = 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function (c) {
            var r = Math.random() * 16 | 0, v = c == 'x' ? r : (r & 0x3 | 0x8);
            return v.toString(16);
        });
        t.nosync = typeof t.nosync !== 'undefined' ? t.nosync : 0;
        t.deleted = 0;
        t.use_count = 0;
        t.created_datetime = new Date().toISOString();
        t.updated_datetime = t.updated_datetime || "";
        t.sync_datetime = t.sync_datetime || "";
        t.lastuse_datetime = t.lastuse_datetime || "";
        t.tags = self._clean_tags(t.tags);

        var data = {};
        data[t.id] = t;

        TemplateStorage.set(data, function () {
            if (onlyLocal) { // create only locally - don't do any remote operations
                deferred.resolve();
                return;
            }

            mixpanel.track("Created template", {
                "with_subject": true ? t.subject : false,
                "with_shortcut": true ? t.shortcut : false,
                "with_tags": true ? t.tags : false,
                "title_size": t.title.length,
                "body_size": t.body.length
            });

            if (!self.isLoggedin) {
                deferred.resolve();
                return;
            }

            var remote = new self.qRes();
            remote = self._copy(t, remote);
            // make sure we don't have a remote_id (it's a new template sow there should not be any remote_id)
            remote.remote_id = '';
            remote.$save(function (remote) {
                // once it's saved server side, store the remote_id in the database
                t.remote_id = remote.id;
                t.sync_datetime = new Date().toISOString();
                TemplateStorage.set(data, function () {
                    deferred.resolve(t.id);
                });
            });
        });
        return deferred.promise;
    };

    // update a template and try to sync
    self.update = function (t, onlyLocal, synced) {
        var deferred = $q.defer();

        // this template was synced. Update only sync_datetime and not updated_datetime
        if (synced) {
            t.sync_datetime = new Date().toISOString();
        } else {
            t.updated_datetime = t.updated_datetime || new Date().toISOString();
        }
        var data = {};
        data[t.id] = t;

        TemplateStorage.set(data, function () {
            if (onlyLocal) { // update only locally - don't do any remote operations
                deferred.resolve();
                return;
            }
            // Send some info about the creation of templates
            mixpanel.track("Updated template", {
                "with_subject": true ? t.subject : false,
                "with_shortcut": true ? t.shortcut : false,
                "with_tags": true ? t.tags : false,
                "title_size": t.title.length,
                "body_size": t.body.length
            });

            if (!self.isLoggedin) { // if it's not logged in
                deferred.resolve();
                return;
            }

            if (!t.remote_id) {
                var remote = new self.qRes();
                remote = self._copy(t, remote);
                remote.$save(function (res) {

                    t.remote_id = res.id;
                    t.sync_datetime = new Date().toISOString();

                    var data = {};
                    data[t.id] = t;
                    TemplateStorage.set(data, function () {
                        deferred.resolve();
                    });
                });
                deferred.resolve();
            } else {
                self.qRes.get({quicktextId: t.remote_id}, function (remote) {
                    remote = self._copy(t, remote);
                    remote.$update(function () {
                        t.sync_datetime = new Date().toISOString();
                        var data = {};
                        data[t.id] = t;
                        TemplateStorage.set(data, function () {
                            deferred.resolve();
                        });
                    });
                });
            }
        });

        return deferred.promise;
    };

// delete a template and try to sync
    self.delete = function (t, onlyLocal) {
        var deferred = $q.defer();
        if (onlyLocal || !t.remote_id) {
            TemplateStorage.remove(t.id, function () {
                mixpanel.track("Deleted template");
                deferred.resolve();
            });
        } else {
            var data = {};
            t.deleted = 1;
            data[t.id] = t;
            TemplateStorage.set(data, function () {
                mixpanel.track("Deleted template");
                self.qRes.get({quicktextId: t.remote_id}, function (remote) {
                    // make sure we have the remote id otherwise the delete will not find the right resource
                    remote.remote_id = remote.id;
                    remote.$delete(function () {
                        // Do a local "DELETE" only if deleted remotely.
                        // If remote operation fails, try again when syncing.
                        //
                        // NOTE: We delete locally to save space.
                        TemplateStorage.remove(t.id, function () {
                            mixpanel.track("Deleted template");
                            deferred.resolve();
                        });
                    });
                });
            });
        }
        return deferred.promise;
    };

//TODO: Decide here at some point
// delete all but don't delete from server
    self.deleteAll = function () {
        var deferred = $q.defer();
        TemplateStorage.clear(function () {
            mixpanel.track("Deleted all templates");
            deferred.resolve();
        });
        return deferred.promise;
    };

// get all tags from a template
    self.tags = function (t) {
        var retTags = [];
        _.each(t.tags.split(","), function (tag) {
            retTags.push(tag.replace(/ /g, ""));
        });
        return retTags;
    };

// get all tags from all templates
    self.allTags = function () {
        var deferred = $q.defer();
        self.quicktexts().then(function (quicktexts) {
            var tagsCount = {};
            _.each(quicktexts, function (t) {
                _.each(t.tags.split(","), function (tag) {
                    tag = tag.replace(/ /g, "");
                    if (!tag) {
                        return;
                    }
                    if (!tagsCount[tag]) {
                        tagsCount[tag] = 1;
                    } else {
                        tagsCount[tag]++;
                    }
                });
            });
            deferred.resolve(tagsCount);
        });
        return deferred.promise;
    };

    // Update lastuse_datetime
    self.used = function (id, onlyLocal) {
        var deferred = $q.defer();
        self.get(id).then(function (template) {
            var data = {};
            if (typeof template.use_count === 'undefined') {
                template.use_count = 0;
            }
            template.use_count++;
            template.lastuse_datetime = new Date().toISOString();
            data[template.id] = template;
            TemplateStorage.set(data, function () {
                deferred.resolve();
            });
        });
        return deferred.promise;
    };

});

<commit_msg>Remove opening and closing divs from new templates.<commit_after>/*jshint multistr: true */

// Template operations
gApp.service('TemplateService', function ($q, $resource, SettingsService) {
    var self = this;

    SettingsService.get('apiBaseURL').then(function (apiBaseURL) {

        self.qRes = $resource(apiBaseURL + 'quicktexts/:quicktextId', {
            quicktextId: '@remote_id'
        }, {
            query: {
                method: "GET",
                isArray: true,
                timeout: 20 * 1000
            },
            update: {
                method: "PUT"
            },
            delete: {
                method: "DELETE",
                isArray: false
            }
        });

        self.statsRes = $resource(apiBaseURL + 'quicktexts/stats');
    });

    self.isLoggedin = false;
    SettingsService.get("isLoggedIn").then(function (isLoggedIn) {
        self.isLoggedin = isLoggedIn;
    });

    self.quicktexts = function (limit) {
        var deferred = $q.defer();
        // get all keys
        TemplateStorage.get(null, function (res) {
            var templates = [];
            for (var id in res) {
                var row = res[id];
                if (row.deleted === 0) {
                    templates.push(row);
                }
            }
            // sort by created_datetime desc
            templates.sort(function (a, b) {
                return new Date(b.created_datetime) - new Date(a.created_datetime);
            });
            // sort by updated_datetime desc
            // templates.sort(function (a, b) {
            //     return new Date(b.updated_datetime) - new Date(a.updated_datetime);
            // });

            if (limit) {
                templates = templates.splice(0, limit);
            }
            deferred.resolve(templates);
        });
        return deferred.promise;
    };

    self.filtered = function (filters, limit) {
        var deferred = $q.defer();
        self.quicktexts().then(function (templates) {
            for (var i in filters) {
                templates = _.filter(templates, filters[i]);
            }
            if (limit) {
                templates = templates.splice(0, limit);
            }
            deferred.resolve(templates);
        });
        return deferred.promise;
    };

    /* Sync - assume that there was no connectivity and now we have it

     Remote templates (after local sync):

     * Created (no similar remote_id found locally) - update sync_date
     * Updated (found remote_id - update) - update sync_date
     * Deleted (present locally, but not present in remote templates)
     */

    self.lastSync = null;

    self.sync = function () {
        var deferred = $q.defer();

        if (!self.isLoggedin) {
            deferred.resolve();
            return deferred.promise;
        }

        // Get the new or updated templates from the remote server
        self.qRes.query(function (remoteTemplates) {
            var now = new Date().toISOString();

            var localSeen = [];
            var remoteSeen = [];
            TemplateStorage.get(null, function (localTemplates) {
                for (var id in localTemplates) {
                    var t = localTemplates[id];
                    if (t.remote_id) {
                        localSeen.push(t.remote_id);
                    }
                }

                _.each(remoteTemplates, function (remoteTemplate) {
                    var localTemplate;
                    var lastVersion = remoteTemplate.versions[0];

                    remoteSeen.push(remoteTemplate.id);

                    var updated = false;
                    for (var id in localTemplates) {
                        localTemplate = localTemplates[id];

                        if (localTemplate.remote_id === remoteTemplate.id) {
                            localTemplate = self._copy(lastVersion, localTemplate);
                            localTemplate.remote_id = remoteTemplate.id;
                            // use the remote created_datetime as reference
                            localTemplate.created_datetime = remoteTemplate.created_datetime;
                            self.update(localTemplate, true, true);

                            updated = true;
                            break;
                        }
                    }

                    // If we haven't seen a local template, create it
                    // I wish there was for..else in JS
                    if (!updated) {
                        localTemplate = self._copy(lastVersion, {});
                        localTemplate.remote_id = remoteTemplate.id;
                        localTemplate.sync_datetime = now;

                        self.create(localTemplate, true).then(function () {

                        });
                    }
                });

                // delete local templates that have a remote_id, but are not present through the API request
                var deleteLocal = _.difference(localSeen, remoteSeen);
                _.each(deleteLocal, function (remoteId) {
                    TemplateStorage.get(null, function (localTemplates) {
                        _.each(localTemplates, function (localTemplate) {
                            if (localTemplate.remote_id === remoteId) {
                                self.delete(localTemplate, true);
                            }
                        });
                    });
                });
                self.lastSync = new Date();
                deferred.resolve(self.lastSync);
            });
        });

        return deferred.promise;
    };

    /**
     * Local templates:
     * Created (doesn't have a 'remote_id' set)
     * Deleted (deleted=1 in the db) - delete remotely and then completely in the db
     * Updated (sync_datetime is null or lower than the updated_date)
     *
     */
    self.syncLocal = function (callback) {
        if (!self.isLoggedin) {
            return;
        }

        // Handling all local templates
        TemplateStorage.get(null, function (templates) {
            for (var id in templates) {
                var t = templates[id];
                if (t.nosync !== 0) {
                    continue;
                }

                // no remote_id means that it's local only and we have to sync it with the remote sync service
                if (!t.remote_id) {
                    // skipping deleted templates - there should not be any.. but ok.
                    if (t.deleted === 1) {
                        continue;
                    }

                    var tRemote = new self.qRes();
                    tRemote = self._copy(angular.copy(t), tRemote);

                    // create new template on the server
                    var save = function (ut) {
                        // we're in a for loop so we need this closure here because the `t` var will be overridden
                        // before the remote request is finished
                        return function (res) {
                            ut.remote_id = res.id;
                            ut.sync_datetime = new Date().toISOString();

                            var data = {};
                            data[ut.id] = ut;
                            self.update(ut, true);
                        };
                    };
                    tRemote.$save(save(angular.copy(t)));
                } else { // was synced at some point
                    // if it's deleted locally, delete it remotely and then delete it completely
                    if (t.deleted === 1) {
                        var deleted = function (ut) {
                            return function (remote) {
                                remote.remote_id = ut.remote_id;
                                remote.$delete(function () {
                                    TemplateStorage.remove(ut.id);
                                });
                            }
                        };
                        self.qRes.get({quicktextId: t.remote_id}, deleted(angular.copy(t)));
                    } else if (t.updated_datetime) { // only if we have an updated_datetime
                        if (!t.sync_datetime || new Date(t.sync_datetime) < new Date(t.updated_datetime)) {
                            var update = function (ut) {
                                // we're in a for loop so we need this closure here because the `t` var will be overridden
                                // before the remote request is finished
                                return function (remote) {
                                    remote = self._copy(ut, remote);
                                    remote.$update(function () {
                                        ut.sync_datetime = new Date().toISOString();
                                        var data = {};
                                        data[ut.id] = ut;
                                        TemplateStorage.set(data);
                                    });
                                };
                            };
                            // template was updated locally, not synced yet
                            self.qRes.get({quicktextId: t.remote_id}, update(angular.copy(t)));
                        }
                    }

                    // send stats to server if we templates used
                    if (t.use_count) { // if we have a use_count, then we can update the stats on the server.
                        var stat = new self.statsRes();
                        stat.quicktext_id = t.remote_id;
                        stat.key = 'use_count';
                        stat.value = t.use_count;

                        // we need this closure to make sure we don't duplicate the same template
                        var save = function (ut) {
                            return function () {
                                ut.use_count = 0;
                                var data = {};
                                data[ut.id] = ut;
                                TemplateStorage.set(data, function () {
                                });
                            }
                        };
                        stat.$save(save(angular.copy(t)));
                    }
                }
            }
        });

        self.lastSync = new Date();
        if (callback) {
            callback(self.lastSync);
        }
    };

// given a string with tags give a clean list
// remove spaces, duplicates and so on
    self._clean_tags = function (tags) {
        var tArray = _.filter(tags.split(','), function (tag) {
            if (tag.trim() !== '') {
                return true;
            }
        });
        tags = _.unique(_.map(tArray, function (t) {
            return t.trim();
        })).join(', ');
        return tags;
    };

// Copy one template object to another - used for the remote saving
    self._copy = function (source, target) {
        for (var k in source) {
            // ignore the no own property or id
            if (k === 'id' && !source.hasOwnProperty(k)) {
                continue;
            }
            if (k === 'tags') {
                target[k] = self._clean_tags(source[k]);
            } else {
                target[k] = source[k];
            }
        }
        return target;
    };

// get template object given an id or null
    self.get = function (id) {
        var deferred = $q.defer();
        TemplateStorage.get(id, function (res) {
            deferred.resolve(res[id]);
        });
        return deferred.promise;
    };

// create and try to sync with the server
    self.create = function (t, onlyLocal) {

        // Remove wrapper div from textangular
        t.body = t.body.replace(/^<div>/, '');
        t.body = t.body.replace(/<\/div>$/, '');

        var deferred = $q.defer();

        // UUID4 as an id for the template
        t.id = 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function (c) {
            var r = Math.random() * 16 | 0, v = c == 'x' ? r : (r & 0x3 | 0x8);
            return v.toString(16);
        });
        t.nosync = typeof t.nosync !== 'undefined' ? t.nosync : 0;
        t.deleted = 0;
        t.use_count = 0;
        t.created_datetime = new Date().toISOString();
        t.updated_datetime = t.updated_datetime || "";
        t.sync_datetime = t.sync_datetime || "";
        t.lastuse_datetime = t.lastuse_datetime || "";
        t.tags = self._clean_tags(t.tags);

        var data = {};
        data[t.id] = t;

        TemplateStorage.set(data, function () {
            if (onlyLocal) { // create only locally - don't do any remote operations
                deferred.resolve();
                return;
            }

            mixpanel.track("Created template", {
                "with_subject": true ? t.subject : false,
                "with_shortcut": true ? t.shortcut : false,
                "with_tags": true ? t.tags : false,
                "title_size": t.title.length,
                "body_size": t.body.length
            });

            if (!self.isLoggedin) {
                deferred.resolve();
                return;
            }

            var remote = new self.qRes();
            remote = self._copy(t, remote);
            // make sure we don't have a remote_id (it's a new template sow there should not be any remote_id)
            remote.remote_id = '';
            remote.$save(function (remote) {
                // once it's saved server side, store the remote_id in the database
                t.remote_id = remote.id;
                t.sync_datetime = new Date().toISOString();
                TemplateStorage.set(data, function () {
                    deferred.resolve(t.id);
                });
            });
        });
        return deferred.promise;
    };

    // update a template and try to sync
    self.update = function (t, onlyLocal, synced) {
        var deferred = $q.defer();

        // this template was synced. Update only sync_datetime and not updated_datetime
        if (synced) {
            t.sync_datetime = new Date().toISOString();
        } else {
            t.updated_datetime = t.updated_datetime || new Date().toISOString();
        }
        var data = {};
        data[t.id] = t;

        TemplateStorage.set(data, function () {
            if (onlyLocal) { // update only locally - don't do any remote operations
                deferred.resolve();
                return;
            }
            // Send some info about the creation of templates
            mixpanel.track("Updated template", {
                "with_subject": true ? t.subject : false,
                "with_shortcut": true ? t.shortcut : false,
                "with_tags": true ? t.tags : false,
                "title_size": t.title.length,
                "body_size": t.body.length
            });

            if (!self.isLoggedin) { // if it's not logged in
                deferred.resolve();
                return;
            }

            if (!t.remote_id) {
                var remote = new self.qRes();
                remote = self._copy(t, remote);
                remote.$save(function (res) {

                    t.remote_id = res.id;
                    t.sync_datetime = new Date().toISOString();

                    var data = {};
                    data[t.id] = t;
                    TemplateStorage.set(data, function () {
                        deferred.resolve();
                    });
                });
                deferred.resolve();
            } else {
                self.qRes.get({quicktextId: t.remote_id}, function (remote) {
                    remote = self._copy(t, remote);
                    remote.$update(function () {
                        t.sync_datetime = new Date().toISOString();
                        var data = {};
                        data[t.id] = t;
                        TemplateStorage.set(data, function () {
                            deferred.resolve();
                        });
                    });
                });
            }
        });

        return deferred.promise;
    };

// delete a template and try to sync
    self.delete = function (t, onlyLocal) {
        var deferred = $q.defer();
        if (onlyLocal || !t.remote_id) {
            TemplateStorage.remove(t.id, function () {
                mixpanel.track("Deleted template");
                deferred.resolve();
            });
        } else {
            var data = {};
            t.deleted = 1;
            data[t.id] = t;
            TemplateStorage.set(data, function () {
                mixpanel.track("Deleted template");
                self.qRes.get({quicktextId: t.remote_id}, function (remote) {
                    // make sure we have the remote id otherwise the delete will not find the right resource
                    remote.remote_id = remote.id;
                    remote.$delete(function () {
                        // Do a local "DELETE" only if deleted remotely.
                        // If remote operation fails, try again when syncing.
                        //
                        // NOTE: We delete locally to save space.
                        TemplateStorage.remove(t.id, function () {
                            mixpanel.track("Deleted template");
                            deferred.resolve();
                        });
                    });
                });
            });
        }
        return deferred.promise;
    };

//TODO: Decide here at some point
// delete all but don't delete from server
    self.deleteAll = function () {
        var deferred = $q.defer();
        TemplateStorage.clear(function () {
            mixpanel.track("Deleted all templates");
            deferred.resolve();
        });
        return deferred.promise;
    };

// get all tags from a template
    self.tags = function (t) {
        var retTags = [];
        _.each(t.tags.split(","), function (tag) {
            retTags.push(tag.replace(/ /g, ""));
        });
        return retTags;
    };

// get all tags from all templates
    self.allTags = function () {
        var deferred = $q.defer();
        self.quicktexts().then(function (quicktexts) {
            var tagsCount = {};
            _.each(quicktexts, function (t) {
                _.each(t.tags.split(","), function (tag) {
                    tag = tag.replace(/ /g, "");
                    if (!tag) {
                        return;
                    }
                    if (!tagsCount[tag]) {
                        tagsCount[tag] = 1;
                    } else {
                        tagsCount[tag]++;
                    }
                });
            });
            deferred.resolve(tagsCount);
        });
        return deferred.promise;
    };

    // Update lastuse_datetime
    self.used = function (id, onlyLocal) {
        var deferred = $q.defer();
        self.get(id).then(function (template) {
            var data = {};
            if (typeof template.use_count === 'undefined') {
                template.use_count = 0;
            }
            template.use_count++;
            template.lastuse_datetime = new Date().toISOString();
            data[template.id] = template;
            TemplateStorage.set(data, function () {
                deferred.resolve();
            });
        });
        return deferred.promise;
    };

});

<|endoftext|>


<commit_before>'use strict';

const _ = require('lodash');
const Boom = require('boom');
const Fs = require('fs');
const Hoek = require('hoek');
const Path = require('path');

const _applyPoints = ['onRequest',
    'onPreAuth',
    'onPostAuth',
    'onPreHandler',
    'onPostHandler',
    'onPreResponse'];

const hasValidApplyPoint = (policy) => {

    return !policy.applyPoint || _applyPoints.indexOf(policy.applyPoint) !== -1;
};

// Memoizes by converting ['policy1', ..., 'policyN'] to 'policy1,...,policyN' as key
const determineAggregateApplyPoint = _.memoize((policyNames) => {

    Hoek.assert(Array.isArray(policyNames), 'Requires array of policy names.');
    Hoek.assert(policyNames.length > 0, 'Requires non-empty array of policy names.');
    Hoek.assert(_.intersection(data.names, policyNames).length === policyNames.length, 'Requires loaded policy names.');

    const firstPolicy = policyNames[0];

    let applyPoint;
    for (let i = 0; i < _applyPoints.length; ++i) {

        // Check if the first policy appears to have this apply point.
        if (!applyPoint &&
            Object.keys(data[_applyPoints[i]]).indexOf(firstPolicy) !== -1) {

            applyPoint = _applyPoints[i];
        }
    }

    Hoek.assert(applyPoint, 'Policies must be in a valid applyPoint.');
    Hoek.assert(_.intersection(Object.keys(data[applyPoint]), policyNames).length === policyNames.length,
        'Aggregate policies must be from same applyPoint.');

    return applyPoint;
});

let data = {
    names: [],
    rawPolicies: {},
    setHandlers: {}
};

/* adding arrays, to hold the policies */
_applyPoints.forEach((applyPoint) => {

    data[applyPoint] = {};
});

const negotiateError = function (err) {

    if (_.isError(err)) {
        if (!Boom.isBoom(err)) {
            Boom.boomify(err, { statusCode: 403, override: false });
        }

        return err;
    }

    // In case someone throws a string or something equally terrifying
    return Boom.forbidden(err);
};

const runPolicies = async function (policiesToRun, request, h) {

    for (const policy of policiesToRun) {
        try {
            const policyResult = await policy(request, h);

            if (policyResult !== h.continue) {

                return policyResult;
            }
        }
        catch (err) {
            throw negotiateError(err);
        }
    }

    return h.continue;
};

/* generate handlers, one handler for each application point */
const handlers = {};
_applyPoints.forEach((applyPoint) => {

    handlers[applyPoint] = async (request, h) => {

        const applyPointPolicies = data[applyPoint];
        const routePolicies = Hoek.reach(request, 'route.settings.plugins.policies');
        if (!routePolicies) {
            return h.continue;
        }

        const policiesToRun = routePolicies.reduce((tmpList, routePolicy) => {

            // Transform array to parallel, determine apply point in advance
            let aggregateApplyPoint;
            if (Array.isArray(routePolicy)) {

                aggregateApplyPoint = determineAggregateApplyPoint(routePolicy);

                if (aggregateApplyPoint === applyPoint) {
                    routePolicy = exports.parallel.apply(this, routePolicy);
                }
                else {
                    routePolicy = null;
                }
            }

            if (typeof routePolicy === 'string') {

                // Look for missing policies.  Probably due to misspelling.
                if (data.names.indexOf(routePolicy) === -1) {
                    throw Boom.notImplemented('Missing policy: ' + routePolicy);
                }

                if (applyPointPolicies[routePolicy]) {
                    tmpList.push(applyPointPolicies[routePolicy]);
                }

            }
            else if (typeof routePolicy === 'function') {

                // If an aggregate apply point wasn't already determined
                // but an aggregate apply point seems like it will be used, determine it from `policy.runs`.
                // `policy.runs` is an array of loaded policies reported by an aggregate policy
                // such as MrHorse.parallel, specifically for determining
                // its apply point ad hoc, notably here in the extension handler.
                if (!aggregateApplyPoint && routePolicy.runs && !routePolicy.applyPoint) {

                    aggregateApplyPoint = determineAggregateApplyPoint(routePolicy.runs);
                }

                if (!hasValidApplyPoint(routePolicy)) {
                    throw Boom.badImplementation('Trying to use incorrect applyPoint for the dynamic policy: ' + routePolicy.applyPoint);
                }

                const effectiveApplyPoint = routePolicy.applyPoint || aggregateApplyPoint || request.server.plugins.mrhorse.defaultApplyPoint;

                if (effectiveApplyPoint === applyPoint) {
                    tmpList.push(routePolicy);
                }

            }
            else if (routePolicy !== null) {

                throw Boom.badImplementation('Policy not specified by name or by function.');
            }

            return tmpList;
        }, []);

        return await runPolicies(policiesToRun, request, h);
    };
});

const addPolicy = (policyName, policy, server, options) => {
    // Does this policy already exist
    if (hasPolicy(policyName)) {
        if (options.ignoreDuplicates) {
            return;
        }

        throw new Error('Trying to add a duplicate policy: ' + policyName);
    }

    // Check if the apply point is correct
    if (!hasValidApplyPoint(policy)) {
        throw new Error('Trying to set incorrect applyPoint for the policy: ' + policy.applyPoint);
    }

    // going further, filling the policies vs application points list
    if (policy.applyPoint === undefined || policy.applyPoint) {
        const applyPoint = policy.applyPoint || options.defaultApplyPoint;

        server.log(['info'], 'Adding a new policy called ' + policyName);
        data[applyPoint][policyName] = policy;
        data.rawPolicies[policyName] = policy;
        data.names.push(policyName);

        // connect the handler if this is the first pre policy
        if (!data.setHandlers[applyPoint]) {
            server.ext(applyPoint, handlers[applyPoint]);
            data.setHandlers[applyPoint] = true;
        }
    }
};

const loadPolicies = (server, options) => {

    let match = null;
    const re = /(.+)\.js$/;

    options.defaultApplyPoint = options.defaultApplyPoint || 'onPreHandler'; // default application point

    const policyFiles = Fs.readdirSync(options.policyDirectory);
    if (policyFiles.length === 0) {
        return;
    }

    const loadPolicyFile = (filename) => {

        // Only looking for .js files in the policies folder
        match = filename.match(re);
        if (match) {

            const policyData = require(Path.join(options.policyDirectory, filename));

            try {
                if (_.isPlainObject(policyData)) {
                    _.each(policyData, (policy, policyName) => addPolicy(policyName, policy, server, options));
                }
                else {
                    addPolicy(match[1], policyData, server, options);
                }
            }
            catch (err) {
                server.log(['error'], err.message);
                throw err;
            }
        }
    };

    for (const policyFile of policyFiles) {
        loadPolicyFile(policyFile);
    }
};

const hasPolicy = (policyName) => {

    return (data.names.indexOf(policyName) !== -1);
};

const reset = function reset() {

    data = {
        names: [],
        rawPolicies: {},
        setHandlers: {}
    };

    /* clear memoize cache */
    determineAggregateApplyPoint.cache.clear();

    /* adding arrays to hold the policies */
    _applyPoints.forEach((applyPoint) => {

        data[applyPoint] = {};
    });
};

exports.plugin = { pkg: require('../package.json') };

exports.plugin.register = async function register(server, options) {

    options.defaultApplyPoint = options.defaultApplyPoint || 'onPreHandler'; // default application point

    Hoek.assert(_applyPoints.indexOf(options.defaultApplyPoint) !== -1, 'Specified invalid defaultApplyPoint: ' + options.defaultApplyPoint);

    server.expose('loadPolicies', loadPolicies);
    server.expose('data', data);
    server.expose('reset', reset);
    server.expose('orPolicy', exports.orPolicy);
    server.expose('addPolicy', (policyName, policy) => addPolicy(policyName, policy, server, options));
    server.expose('hasPolicy', hasPolicy);
    server.expose('defaultApplyPoint', options.defaultApplyPoint);

    if (options.policyDirectory !== undefined) {
        await loadPolicies(server, options);
    }
};

/* Policy aggregation tools */
exports.parallel = function (/*policy1, policy2, [cb]*/) {

    Hoek.assert(arguments.length, 'Requires at least one argument.');

    const args = Array.prototype.slice.call(arguments);

    // This error aggregator is used by default, giving priority to error responses
    // by the policies' listed order.
    const defaultErrorHandler = (ranPolicies, results) => {

        for (let i = 0; i < ranPolicies.length; ++i) {

            const result = results[ranPolicies[i]];

            if (result.err || result.status === 'error') {

                throw result.err;
            }

        }
    };

    // Determine the error handler and policies we're using
    let errorHandler;
    let policyNames;

    if (typeof args[args.length - 1] === 'function') {

        errorHandler = args[args.length - 1];
        policyNames = args.slice(0, -1);
    }
    else {

        errorHandler = defaultErrorHandler;
        policyNames = args;
    }

    Hoek.assert(_.uniq(policyNames).length === policyNames.length, 'Listed policies must be unique.');

    const managePolicy = async (policy, request, h) => {

        try {
            const result = await policy(request, h);

            return { status: 'ok', result };
        }
        catch (err) {
            return { status: 'error', err };
        }
    };

    // Aggregate policy
    const aggregatePolicy = async (request, h) => {

        const policies = _(data.rawPolicies)
            .pick(policyNames)
            .mapValues((policy) => (managePolicy(policy, request, h)))
            .value();

        const results = _.zipObject(_.keys(policies), await Promise.all(_.values(policies)));

        errorHandler(policyNames, results);

        return h.continue;
    };

    // Report to MrHorse handler which policies are going to be run
    aggregatePolicy.runs = policyNames;

    // Here ya go!
    return aggregatePolicy;
};

exports.orPolicy = function () {

    const args = Array.prototype.slice.call(arguments);

    args.push(
        (ranPolicies, results) => {

            // successful if at least one policy was OK
            const success = !_.every(ranPolicies, (policyName) => (results[ policyName ].status === 'error'));

            if (success) {
                return;
            }

            // find left-most failed policy
            const failed = _.find(results, { status : 'error' });

            throw failed.err;
        }
    );

    return exports.parallel.apply(this, args);
};
<commit_msg>Exposed parallel() via server.plugins.parallel<commit_after>'use strict';

const _ = require('lodash');
const Boom = require('boom');
const Fs = require('fs');
const Hoek = require('hoek');
const Path = require('path');

const _applyPoints = ['onRequest',
    'onPreAuth',
    'onPostAuth',
    'onPreHandler',
    'onPostHandler',
    'onPreResponse'];

const hasValidApplyPoint = (policy) => {

    return !policy.applyPoint || _applyPoints.indexOf(policy.applyPoint) !== -1;
};

// Memoizes by converting ['policy1', ..., 'policyN'] to 'policy1,...,policyN' as key
const determineAggregateApplyPoint = _.memoize((policyNames) => {

    Hoek.assert(Array.isArray(policyNames), 'Requires array of policy names.');
    Hoek.assert(policyNames.length > 0, 'Requires non-empty array of policy names.');
    Hoek.assert(_.intersection(data.names, policyNames).length === policyNames.length, 'Requires loaded policy names.');

    const firstPolicy = policyNames[0];

    let applyPoint;
    for (let i = 0; i < _applyPoints.length; ++i) {

        // Check if the first policy appears to have this apply point.
        if (!applyPoint &&
            Object.keys(data[_applyPoints[i]]).indexOf(firstPolicy) !== -1) {

            applyPoint = _applyPoints[i];
        }
    }

    Hoek.assert(applyPoint, 'Policies must be in a valid applyPoint.');
    Hoek.assert(_.intersection(Object.keys(data[applyPoint]), policyNames).length === policyNames.length,
        'Aggregate policies must be from same applyPoint.');

    return applyPoint;
});

let data = {
    names: [],
    rawPolicies: {},
    setHandlers: {}
};

/* adding arrays, to hold the policies */
_applyPoints.forEach((applyPoint) => {

    data[applyPoint] = {};
});

const negotiateError = function (err) {

    if (_.isError(err)) {
        if (!Boom.isBoom(err)) {
            Boom.boomify(err, { statusCode: 403, override: false });
        }

        return err;
    }

    // In case someone throws a string or something equally terrifying
    return Boom.forbidden(err);
};

const runPolicies = async function (policiesToRun, request, h) {

    for (const policy of policiesToRun) {
        try {
            const policyResult = await policy(request, h);

            if (policyResult !== h.continue) {

                return policyResult;
            }
        }
        catch (err) {
            throw negotiateError(err);
        }
    }

    return h.continue;
};

/* generate handlers, one handler for each application point */
const handlers = {};
_applyPoints.forEach((applyPoint) => {

    handlers[applyPoint] = async (request, h) => {

        const applyPointPolicies = data[applyPoint];
        const routePolicies = Hoek.reach(request, 'route.settings.plugins.policies');
        if (!routePolicies) {
            return h.continue;
        }

        const policiesToRun = routePolicies.reduce((tmpList, routePolicy) => {

            // Transform array to parallel, determine apply point in advance
            let aggregateApplyPoint;
            if (Array.isArray(routePolicy)) {

                aggregateApplyPoint = determineAggregateApplyPoint(routePolicy);

                if (aggregateApplyPoint === applyPoint) {
                    routePolicy = exports.parallel.apply(this, routePolicy);
                }
                else {
                    routePolicy = null;
                }
            }

            if (typeof routePolicy === 'string') {

                // Look for missing policies.  Probably due to misspelling.
                if (data.names.indexOf(routePolicy) === -1) {
                    throw Boom.notImplemented('Missing policy: ' + routePolicy);
                }

                if (applyPointPolicies[routePolicy]) {
                    tmpList.push(applyPointPolicies[routePolicy]);
                }

            }
            else if (typeof routePolicy === 'function') {

                // If an aggregate apply point wasn't already determined
                // but an aggregate apply point seems like it will be used, determine it from `policy.runs`.
                // `policy.runs` is an array of loaded policies reported by an aggregate policy
                // such as MrHorse.parallel, specifically for determining
                // its apply point ad hoc, notably here in the extension handler.
                if (!aggregateApplyPoint && routePolicy.runs && !routePolicy.applyPoint) {

                    aggregateApplyPoint = determineAggregateApplyPoint(routePolicy.runs);
                }

                if (!hasValidApplyPoint(routePolicy)) {
                    throw Boom.badImplementation('Trying to use incorrect applyPoint for the dynamic policy: ' + routePolicy.applyPoint);
                }

                const effectiveApplyPoint = routePolicy.applyPoint || aggregateApplyPoint || request.server.plugins.mrhorse.defaultApplyPoint;

                if (effectiveApplyPoint === applyPoint) {
                    tmpList.push(routePolicy);
                }

            }
            else if (routePolicy !== null) {

                throw Boom.badImplementation('Policy not specified by name or by function.');
            }

            return tmpList;
        }, []);

        return await runPolicies(policiesToRun, request, h);
    };
});

const addPolicy = (policyName, policy, server, options) => {
    // Does this policy already exist
    if (hasPolicy(policyName)) {
        if (options.ignoreDuplicates) {
            return;
        }

        throw new Error('Trying to add a duplicate policy: ' + policyName);
    }

    // Check if the apply point is correct
    if (!hasValidApplyPoint(policy)) {
        throw new Error('Trying to set incorrect applyPoint for the policy: ' + policy.applyPoint);
    }

    // going further, filling the policies vs application points list
    if (policy.applyPoint === undefined || policy.applyPoint) {
        const applyPoint = policy.applyPoint || options.defaultApplyPoint;

        server.log(['info'], 'Adding a new policy called ' + policyName);
        data[applyPoint][policyName] = policy;
        data.rawPolicies[policyName] = policy;
        data.names.push(policyName);

        // connect the handler if this is the first pre policy
        if (!data.setHandlers[applyPoint]) {
            server.ext(applyPoint, handlers[applyPoint]);
            data.setHandlers[applyPoint] = true;
        }
    }
};

const loadPolicies = (server, options) => {

    let match = null;
    const re = /(.+)\.js$/;

    options.defaultApplyPoint = options.defaultApplyPoint || 'onPreHandler'; // default application point

    const policyFiles = Fs.readdirSync(options.policyDirectory);
    if (policyFiles.length === 0) {
        return;
    }

    const loadPolicyFile = (filename) => {

        // Only looking for .js files in the policies folder
        match = filename.match(re);
        if (match) {

            const policyData = require(Path.join(options.policyDirectory, filename));

            try {
                if (_.isPlainObject(policyData)) {
                    _.each(policyData, (policy, policyName) => addPolicy(policyName, policy, server, options));
                }
                else {
                    addPolicy(match[1], policyData, server, options);
                }
            }
            catch (err) {
                server.log(['error'], err.message);
                throw err;
            }
        }
    };

    for (const policyFile of policyFiles) {
        loadPolicyFile(policyFile);
    }
};

const hasPolicy = (policyName) => {

    return (data.names.indexOf(policyName) !== -1);
};

const reset = function reset() {

    data = {
        names: [],
        rawPolicies: {},
        setHandlers: {}
    };

    /* clear memoize cache */
    determineAggregateApplyPoint.cache.clear();

    /* adding arrays to hold the policies */
    _applyPoints.forEach((applyPoint) => {

        data[applyPoint] = {};
    });
};

exports.plugin = { pkg: require('../package.json') };

exports.plugin.register = async function register(server, options) {

    options.defaultApplyPoint = options.defaultApplyPoint || 'onPreHandler'; // default application point

    Hoek.assert(_applyPoints.indexOf(options.defaultApplyPoint) !== -1, 'Specified invalid defaultApplyPoint: ' + options.defaultApplyPoint);

    server.expose('loadPolicies', loadPolicies);
    server.expose('data', data);
    server.expose('reset', reset);
    server.expose('orPolicy', exports.orPolicy);
    server.expose('parallel', exports.parallel);
    server.expose('addPolicy', (policyName, policy) => addPolicy(policyName, policy, server, options));
    server.expose('hasPolicy', hasPolicy);
    server.expose('defaultApplyPoint', options.defaultApplyPoint);

    if (options.policyDirectory !== undefined) {
        await loadPolicies(server, options);
    }
};

/* Policy aggregation tools */
exports.parallel = function (/*policy1, policy2, [cb]*/) {

    Hoek.assert(arguments.length, 'Requires at least one argument.');

    const args = Array.prototype.slice.call(arguments);

    // This error aggregator is used by default, giving priority to error responses
    // by the policies' listed order.
    const defaultErrorHandler = (ranPolicies, results) => {

        for (let i = 0; i < ranPolicies.length; ++i) {

            const result = results[ranPolicies[i]];

            if (result.err || result.status === 'error') {

                throw result.err;
            }

        }
    };

    // Determine the error handler and policies we're using
    let errorHandler;
    let policyNames;

    if (typeof args[args.length - 1] === 'function') {

        errorHandler = args[args.length - 1];
        policyNames = args.slice(0, -1);
    }
    else {

        errorHandler = defaultErrorHandler;
        policyNames = args;
    }

    Hoek.assert(_.uniq(policyNames).length === policyNames.length, 'Listed policies must be unique.');

    const managePolicy = async (policy, request, h) => {

        try {
            const result = await policy(request, h);

            return { status: 'ok', result };
        }
        catch (err) {
            return { status: 'error', err };
        }
    };

    // Aggregate policy
    const aggregatePolicy = async (request, h) => {

        const policies = _(data.rawPolicies)
            .pick(policyNames)
            .mapValues((policy) => (managePolicy(policy, request, h)))
            .value();

        const results = _.zipObject(_.keys(policies), await Promise.all(_.values(policies)));

        errorHandler(policyNames, results);

        return h.continue;
    };

    // Report to MrHorse handler which policies are going to be run
    aggregatePolicy.runs = policyNames;

    // Here ya go!
    return aggregatePolicy;
};

exports.orPolicy = function () {

    const args = Array.prototype.slice.call(arguments);

    args.push(
        (ranPolicies, results) => {

            // successful if at least one policy was OK
            const success = !_.every(ranPolicies, (policyName) => (results[ policyName ].status === 'error'));

            if (success) {
                return;
            }

            // find left-most failed policy
            const failed = _.find(results, { status : 'error' });

            throw failed.err;
        }
    );

    return exports.parallel.apply(this, args);
};
<|endoftext|>


<commit_before>"use strict";

var _ = require('lodash');
var path = require('path');
var Schema = require('node-verifier-schema');
var iterate = require('async-iterate');

var defaultsOptions = {
	cwd: process.cwd(),

	preparePath: function (filepath) {
		return path.join(this.cwd, filepath);
	},

	prepareSchema: function (schema, options) {
		var _rawSchema = schema;
		if (_.isString(schema)) {
			if (/\.(yaml|yml)$/.test(schema)) {
				schema = options.preparePath(schema);
				schema = Schema.load(schema);
			} else {
				schema = Schema.get(schema);
			}
		} else if (_.isFunction(schema)) {
			schema = new Schema();
			_rawSchema.call(schema, schema.required.bind(schema), schema.optional.bind(schema));
		}

		if (!(schema instanceof Schema)) {
			throw new Error('must be Schema instance. ' + _rawSchema + ' given');
		}

		return schema;
	}
};

var expressVerifier = function (globalOptions) {
	var verifier = function (object, options) {
		options = _.extend({}, defaultsOptions, globalOptions, options);

		if (!_.isPlainObject(object)) {
			object = options.prepareSchema(object, options).fields;
		}

		var verifiers = _.reduce(verifier.componentNames, function(verifiers, name) {
			if (_.has(object, name)) {
				verifiers.push({
					name: name,
					verify: verifier.components[name](object[name], options)
				});
			}

			return verifiers;
		}, []);

		if (!verifiers.length) {
			throw new Error('invalid parameters for verify');
		}

		var middleware = function (req, res, next) {
			iterate.each(middleware.verifiers, function (obj, index, done) {
				obj.verify(req, res, function (err) {
					if (err instanceof Schema.ValidationError) {
						err.object = obj.name;
					}

					done(err);
				});
			}, next);
		};

		middleware.verifiers = verifiers;

		return middleware;
	};

	verifier.componentNames = ['params', 'headers', 'query', 'body'];
	verifier.components = {};

	_.each(verifier.componentNames, function (name) {
		verifier.components[name] = function (schema, options) {
			schema = options.prepareSchema(schema, options);
			var verifier = schema.verifier(options);

			var middleware = function (req, res, next) {
				verifier.verify(middleware.getData(req, res), next);
			};

			middleware.schema = schema;
			middleware.verifier = verifier;

			middleware.getData = function (req, res) {
				return req[name];
			};

			return middleware;
		};

		verifier[name] = function (object, options) {
			var obj = {};
			obj[name] = object;
			return verifier(obj, options);
		};
	});

	return verifier;
};

expressVerifier.Schema = Schema;
expressVerifier.Verifier = Schema.Verifier;

module.exports = expressVerifier;
<commit_msg>add callback for parameters<commit_after>"use strict";

var _ = require('lodash');
var path = require('path');
var Schema = require('node-verifier-schema');
var iterate = require('async-iterate');

var defaultsOptions = {
	cwd: process.cwd(),

	preparePath: function (filepath) {
		return path.join(this.cwd, filepath);
	},

	prepareSchema: function (schema, options) {
		var _rawSchema = schema;
		if (_.isString(schema)) {
			if (/\.(yaml|yml)$/.test(schema)) {
				schema = options.preparePath(schema);
				schema = Schema.load(schema);
			} else {
				schema = Schema.get(schema);
			}
		} else if (_.isFunction(schema)) {
			schema = new Schema();
			_rawSchema.call(schema, schema.required.bind(schema), schema.optional.bind(schema));
		}

		if (!(schema instanceof Schema)) {
			throw new Error('must be Schema instance. ' + _rawSchema + ' given');
		}

		return schema;
	}
};

var expressVerifier = function (globalOptions) {
	var verifier = function (object, options) {
		if (_.isFunction(options)) {
			options = {
				callback: options
			};
		}

		options = _.extend({
			callback: function (err, req, res, next) {
				next(err);
			}
		}, defaultsOptions, globalOptions, options);

		if (!_.isPlainObject(object)) {
			object = options.prepareSchema(object, options).fields;
		}

		var verifiers = _.reduce(verifier.componentNames, function(verifiers, name) {
			if (_.has(object, name)) {
				verifiers.push({
					name: name,
					verify: verifier.components[name](object[name], options)
				});
			}

			return verifiers;
		}, []);

		if (!verifiers.length) {
			throw new Error('invalid parameters for verify');
		}

		var middleware = function (req, res, next) {
			iterate.each(middleware.verifiers, function (obj, index, done) {
				obj.verify(req, res, function (err) {
					if (err instanceof Schema.ValidationError) {
						err.object = obj.name;
					}

					done(err);
				});
			}, function (err) {
				options.callback(err, req, res, next);
			});
		};

		middleware.verifiers = verifiers;

		return middleware;
	};

	verifier.componentNames = ['params', 'headers', 'query', 'body'];
	verifier.components = {};

	_.each(verifier.componentNames, function (name) {
		verifier.components[name] = function (schema, options) {
			schema = options.prepareSchema(schema, options);
			var verifier = schema.verifier(options);

			var middleware = function (req, res, next) {
				verifier.verify(middleware.getData(req, res), next);
			};

			middleware.schema = schema;
			middleware.verifier = verifier;

			middleware.getData = function (req, res) {
				return req[name];
			};

			return middleware;
		};

		verifier[name] = function (object, options) {
			var obj = {};
			obj[name] = object;
			return verifier(obj, options);
		};
	});

	return verifier;
};

expressVerifier.Schema = Schema;
expressVerifier.Verifier = Schema.Verifier;

module.exports = expressVerifier;
<|endoftext|>


<commit_before>/*
Copyright (c) 2012, Yahoo! Inc. All rights reserved.
Code licensed under the BSD License:
http://yuilibrary.com/license/
*/

var fs = require('fs'),
    path = require('path'),
    exists = fs.exists || path.exists;

var walkFile = function(str, cb) {
    var data = [], item;

    [ 'end_of_record' ].concat(str.split('\n')).forEach(function(line) {
        line = line.trim();

        var parts = line.split(':'), lines, fn;

        switch (parts[0].toUpperCase()) {
            case 'TN':
                item.title = parts[1].trim();
                break;
            case 'SF':
                item.file = parts.slice(1).join(':').trim();
                break;
            case 'FNF':
                item.functions.found = Number(parts[1].trim());
                break;
            case 'FNH':
                item.functions.hit = Number(parts[1].trim());
                break;
            case 'LF':
                item.lines.found = Number(parts[1].trim());
                break;
            case 'LH':
                item.lines.hit = Number(parts[1].trim());
                break;
            case 'DA':
                lines = parts[1].split(',');
                item.lines.details.push({
                    line: Number(lines[0]),
                    hit: Number(lines[1])
                });
                break;
            case 'FN':
                fn = parts[1].split(',');
                item.functions.details.push({
                    name: fn[1],
                    line: Number(fn[0])
                });
                break;
            case 'FNDA':
                fn = parts[1].split(',');
                item.functions.details.some(function(i, k) {
                    if (i.name === fn[1] && i.hit === undefined) {
                        item.functions.details[k].hit = Number(fn[0]);
                        return true;
                    }
                });
                break;
            case 'BRDA':
                fn = parts[1].split(',');
                item.branches.details.push({
                    line: Number(fn[0]),
                    block: Number(fn[1]),
                    branch: Number(fn[2]),
                    taken: ((fn[3] === '-') ? 0 : Number(fn[3]))
                });
                break;
            case 'BRF':
                item.branches.found = Number(parts[1]);
                break;
            case 'BRH':
                item.branches.hit = Number(parts[1]);
                break;
        }

        if (line.indexOf('end_of_record') > -1) {
            data.push(item);
            item = {
              lines: {
                  found: 0,
                  hit: 0,
                  details: []
              },
              functions: {
                  hit: 0,
                  found: 0,
                  details: []
              },
              branches: {
                hit: 0,
                found: 0,
                details: []
              }
            };
        }
    });

    data.shift();

    if (data.length) {
        cb(null, data);
    } else {
        cb('Failed to parse string');
    }
};

var parse = function(file, cb) {
    exists(file, function(x) {
        if (!x) {
            return walkFile(file, cb);
        }
        fs.readFile(file, 'utf8', function(err, str) {
            walkFile(str, cb);
        });
    });

};

module.exports = parse;
<commit_msg>Fixed windows file path issues<commit_after>/*
Copyright (c) 2012, Yahoo! Inc. All rights reserved.
Code licensed under the BSD License:
http://yuilibrary.com/license/
*/

var fs = require('fs'),
    path = require('path'),
    exists = fs.exists || path.exists;

var walkFile = function(str, cb) {
    var data = [], item;

    [ 'end_of_record' ].concat(str.split('\n')).forEach(function(line) {
        line = line.trim();
        var allparts = line.split(':');
		var parts = [allparts.shift(), allparts.join(':')];
        var lines, fn;

        switch (parts[0].toUpperCase()) {
            case 'TN':
                item.title = parts[1].trim();
                break;
            case 'SF':
                item.file = parts.slice(1).join(':').trim();
                break;
            case 'FNF':
                item.functions.found = Number(parts[1].trim());
                break;
            case 'FNH':
                item.functions.hit = Number(parts[1].trim());
                break;
            case 'LF':
                item.lines.found = Number(parts[1].trim());
                break;
            case 'LH':
                item.lines.hit = Number(parts[1].trim());
                break;
            case 'DA':
                lines = parts[1].split(',');
                item.lines.details.push({
                    line: Number(lines[0]),
                    hit: Number(lines[1])
                });
                break;
            case 'FN':
                fn = parts[1].split(',');
                item.functions.details.push({
                    name: fn[1],
                    line: Number(fn[0])
                });
                break;
            case 'FNDA':
                fn = parts[1].split(',');
                item.functions.details.some(function(i, k) {
                    if (i.name === fn[1] && i.hit === undefined) {
                        item.functions.details[k].hit = Number(fn[0]);
                        return true;
                    }
                });
                break;
            case 'BRDA':
                fn = parts[1].split(',');
                item.branches.details.push({
                    line: Number(fn[0]),
                    block: Number(fn[1]),
                    branch: Number(fn[2]),
                    taken: ((fn[3] === '-') ? 0 : Number(fn[3]))
                });
                break;
            case 'BRF':
                item.branches.found = Number(parts[1]);
                break;
            case 'BRH':
                item.branches.hit = Number(parts[1]);
                break;
        }

        if (line.indexOf('end_of_record') > -1) {
            data.push(item);
            item = {
              lines: {
                  found: 0,
                  hit: 0,
                  details: []
              },
              functions: {
                  hit: 0,
                  found: 0,
                  details: []
              },
              branches: {
                hit: 0,
                found: 0,
                details: []
              }
            };
        }
    });

    data.shift();

    if (data.length) {
        cb(null, data);
    } else {
        cb('Failed to parse string');
    }
};

var parse = function(file, cb) {
    exists(file, function(x) {
        if (!x) {
            return walkFile(file, cb);
        }
        fs.readFile(file, 'utf8', function(err, str) {
            walkFile(str, cb);
        });
    });

};

module.exports = parse;
<|endoftext|>


<commit_before>'use strict';

Object.defineProperty(exports, "__esModule", {
    value: true
});
exports.datify = undefined;

var _extends = Object.assign || function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; };

var _createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; }();

var _react = require('react');

var _react2 = _interopRequireDefault(_react);

var _propTypes = require('prop-types');

var _propTypes2 = _interopRequireDefault(_propTypes);

var _DatePicker = require('material-ui/DatePicker');

var _DatePicker2 = _interopRequireDefault(_DatePicker);

var _TimePicker = require('material-ui/TimePicker');

var _TimePicker2 = _interopRequireDefault(_TimePicker);

var _backspace = require('material-ui/svg-icons/content/backspace');

var _backspace2 = _interopRequireDefault(_backspace);

var _IconButton = require('material-ui/IconButton');

var _IconButton2 = _interopRequireDefault(_IconButton);

var _adminOnRest = require('admin-on-rest');

function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }

function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }

function _possibleConstructorReturn(self, call) { if (!self) { throw new ReferenceError("this hasn't been initialised - super() hasn't been called"); } return call && (typeof call === "object" || typeof call === "function") ? call : self; }

function _inherits(subClass, superClass) { if (typeof superClass !== "function" && superClass !== null) { throw new TypeError("Super expression must either be null or a function, not " + typeof superClass); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass; }

var datify = exports.datify = function datify(input) {
    if (!input) {
        return null;
    }

    var date = input instanceof Date ? input : new Date(input);
    if (isNaN(date)) {
        throw new Error('Invalid date: ' + input);
    }

    return date;
};

var DateTimeInput = function (_Component) {
    _inherits(DateTimeInput, _Component);

    function DateTimeInput() {
        var _ref;

        var _temp, _this, _ret;

        _classCallCheck(this, DateTimeInput);

        for (var _len = arguments.length, args = Array(_len), _key = 0; _key < _len; _key++) {
            args[_key] = arguments[_key];
        }

        return _ret = (_temp = (_this = _possibleConstructorReturn(this, (_ref = DateTimeInput.__proto__ || Object.getPrototypeOf(DateTimeInput)).call.apply(_ref, [this].concat(args))), _this), _this.onChange = function (_, date) {
            var tempDate = new Date(_this.props.input.value);
            date.setHours(tempDate.getHours());
            date.setMinutes(tempDate.getMinutes());
            date.setSeconds(tempDate.getSeconds());
            _this.props.input.onChange(date);
            _this.props.input.onBlur();
            _this.refs[_this.props.source + '.timePicker'].openDialog();
        }, _this.onChangeTime = function (_, time) {
            _this.props.input.onChange(time);
            _this.props.input.onBlur();
        }, _this.clearDate = function () {
            _this.props.input.onChange(null);
            _this.props.input.onBlur();
        }, _this.onBlur = function () {}, _this.onDismiss = function () {
            return _this.props.input.onBlur();
        }, _temp), _possibleConstructorReturn(_this, _ret);
    }

    /**
     * This aims to fix a bug created by the conjunction of
     * redux-form, which expects onBlur to be triggered after onChange, and
     * material-ui, which triggers onBlur on <DatePicker> when the user clicks
     * on the input to bring the focus on the calendar rather than the input.
     *
     * @see https://github.com/erikras/redux-form/issues/1218#issuecomment-229072652
     */

    _createClass(DateTimeInput, [{
        key: 'render',
        value: function render() {
            // elStyle deleted because timepicker has not container prop and always show as dialog. for same showing date and time pickers.
            var _props = this.props,
                input = _props.input,
                isRequired = _props.isRequired,
                label = _props.label,
                _props$meta = _props.meta,
                touched = _props$meta.touched,
                error = _props$meta.error,
                options = _props.options,
                source = _props.source,
                resource = _props.resource,
                labelTime = _props.labelTime;

            return _react2.default.createElement(
                'div',
                null,
                _react2.default.createElement(_DatePicker2.default, _extends({}, input, {
                    errorText: touched && error,
                    floatingLabelText: _react2.default.createElement(_adminOnRest.FieldTitle, { label: label, source: source, resource: resource, isRequired: isRequired }),
                    DateTimeFormat: Intl.DateTimeFormat,
                    mode: 'portrait',
                    container: 'dialog',
                    autoOk: true,
                    value: datify(input.value),
                    onChange: this.onChange,
                    onBlur: this.onBlur,
                    onDismiss: this.onDismiss,
                    style: { display: 'inline-block', marginRight: '10px' },
                    ref: this.props.source + '.datePicker'
                }, options)),
                _react2.default.createElement(_TimePicker2.default, _extends({}, input, {
                    errorText: touched && error,
                    floatingLabelText: _react2.default.createElement(_adminOnRest.FieldTitle, { label: labelTime ? labelTime : 'Time(hours, mins.)', source: source, resource: resource, isRequired: isRequired }),
                    format: '24hr',
                    autoOk: true,
                    value: datify(input.value),
                    onChange: this.onChangeTime,
                    onBlur: this.onBlur,
                    onDismiss: this.onDismiss,
                    style: { display: 'inline-block' },
                    ref: this.props.source + '.timePicker'
                }, options)),
                _react2.default.createElement(
                    _IconButton2.default,
                    { onClick: this.clearDate, tooltip: 'Clear Date', tooltipPosition: 'top-right' },
                    _react2.default.createElement(_backspace2.default, { color: 'grey', hoverColor: 'black' })
                )
            );
        }
    }]);

    return DateTimeInput;
}(_react.Component);

DateTimeInput.propTypes = {
    addField: _propTypes2.default.bool.isRequired,
    elStyle: _propTypes2.default.object,
    input: _propTypes2.default.object,
    isRequired: _propTypes2.default.bool,
    label: _propTypes2.default.string,
    meta: _propTypes2.default.object,
    options: _propTypes2.default.object,
    resource: _propTypes2.default.string,
    source: _propTypes2.default.string,
    labelTime: _propTypes2.default.string
};

DateTimeInput.defaultProps = {
    addField: true,
    options: {}
};

exports.default = DateTimeInput;<commit_msg>Built previous commit<commit_after>'use strict';

Object.defineProperty(exports, "__esModule", {
    value: true
});
exports.datify = undefined;

var _extends = Object.assign || function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; };

var _createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; }();

var _react = require('react');

var _react2 = _interopRequireDefault(_react);

var _propTypes = require('prop-types');

var _propTypes2 = _interopRequireDefault(_propTypes);

var _DatePicker = require('material-ui/DatePicker');

var _DatePicker2 = _interopRequireDefault(_DatePicker);

var _TimePicker = require('material-ui/TimePicker');

var _TimePicker2 = _interopRequireDefault(_TimePicker);

var _backspace = require('material-ui/svg-icons/content/backspace');

var _backspace2 = _interopRequireDefault(_backspace);

var _IconButton = require('material-ui/IconButton');

var _IconButton2 = _interopRequireDefault(_IconButton);

var _adminOnRest = require('admin-on-rest');

function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }

function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }

function _possibleConstructorReturn(self, call) { if (!self) { throw new ReferenceError("this hasn't been initialised - super() hasn't been called"); } return call && (typeof call === "object" || typeof call === "function") ? call : self; }

function _inherits(subClass, superClass) { if (typeof superClass !== "function" && superClass !== null) { throw new TypeError("Super expression must either be null or a function, not " + typeof superClass); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass; }

var datify = exports.datify = function datify(input) {
    if (!input) {
        return null;
    }

    var date = input instanceof Date ? input : new Date(input);
    if (isNaN(date)) {
        throw new Error('Invalid date: ' + input);
    }

    return date;
};

var DateTimeInput = function (_Component) {
    _inherits(DateTimeInput, _Component);

    function DateTimeInput() {
        var _ref;

        var _temp, _this, _ret;

        _classCallCheck(this, DateTimeInput);

        for (var _len = arguments.length, args = Array(_len), _key = 0; _key < _len; _key++) {
            args[_key] = arguments[_key];
        }

        return _ret = (_temp = (_this = _possibleConstructorReturn(this, (_ref = DateTimeInput.__proto__ || Object.getPrototypeOf(DateTimeInput)).call.apply(_ref, [this].concat(args))), _this), _this.onChange = function (_, date) {
            if ('undefined' === _this.props.input.value) {
                var tempDate = new Date(_this.props.input.value);
                date.setHours(tempDate.getHours());
                date.setMinutes(tempDate.getMinutes());
                date.setSeconds(tempDate.getSeconds());
            }

            _this.props.input.onChange(date);
            _this.props.input.onBlur();
            _this.refs[_this.props.source + '.timePicker'].openDialog();
        }, _this.onChangeTime = function (_, time) {
            _this.props.input.onChange(time);
            _this.props.input.onBlur();
        }, _this.clearDate = function () {
            _this.props.input.onChange(null);
            _this.props.input.onBlur();
        }, _this.onBlur = function () {}, _this.onDismiss = function () {
            return _this.props.input.onBlur();
        }, _temp), _possibleConstructorReturn(_this, _ret);
    }

    /**
     * This aims to fix a bug created by the conjunction of
     * redux-form, which expects onBlur to be triggered after onChange, and
     * material-ui, which triggers onBlur on <DatePicker> when the user clicks
     * on the input to bring the focus on the calendar rather than the input.
     *
     * @see https://github.com/erikras/redux-form/issues/1218#issuecomment-229072652
     */

    _createClass(DateTimeInput, [{
        key: 'render',
        value: function render() {
            // elStyle deleted because timepicker has not container prop and always show as dialog. for same showing date and time pickers.
            var _props = this.props,
                input = _props.input,
                isRequired = _props.isRequired,
                label = _props.label,
                _props$meta = _props.meta,
                touched = _props$meta.touched,
                error = _props$meta.error,
                options = _props.options,
                source = _props.source,
                resource = _props.resource,
                labelTime = _props.labelTime;

            return _react2.default.createElement(
                'div',
                null,
                _react2.default.createElement(_DatePicker2.default, _extends({}, input, {
                    errorText: touched && error,
                    floatingLabelText: _react2.default.createElement(_adminOnRest.FieldTitle, { label: label, source: source, resource: resource, isRequired: isRequired }),
                    DateTimeFormat: Intl.DateTimeFormat,
                    mode: 'portrait',
                    container: 'dialog',
                    autoOk: true,
                    value: datify(input.value),
                    onChange: this.onChange,
                    onBlur: this.onBlur,
                    onDismiss: this.onDismiss,
                    style: { display: 'inline-block', marginRight: '10px' },
                    ref: this.props.source + '.datePicker'
                }, options)),
                _react2.default.createElement(_TimePicker2.default, _extends({}, input, {
                    errorText: touched && error,
                    floatingLabelText: _react2.default.createElement(_adminOnRest.FieldTitle, { label: labelTime ? labelTime : 'Time(hours, mins.)', source: source, resource: resource, isRequired: isRequired }),
                    format: '24hr',
                    autoOk: true,
                    value: datify(input.value),
                    onChange: this.onChangeTime,
                    onBlur: this.onBlur,
                    onDismiss: this.onDismiss,
                    style: { display: 'inline-block' },
                    ref: this.props.source + '.timePicker'
                }, options)),
                _react2.default.createElement(
                    _IconButton2.default,
                    { onClick: this.clearDate, tooltip: 'Clear Date', tooltipPosition: 'top-right' },
                    _react2.default.createElement(_backspace2.default, { color: 'grey', hoverColor: 'black' })
                )
            );
        }
    }]);

    return DateTimeInput;
}(_react.Component);

DateTimeInput.propTypes = {
    addField: _propTypes2.default.bool.isRequired,
    elStyle: _propTypes2.default.object,
    input: _propTypes2.default.object,
    isRequired: _propTypes2.default.bool,
    label: _propTypes2.default.string,
    meta: _propTypes2.default.object,
    options: _propTypes2.default.object,
    resource: _propTypes2.default.string,
    source: _propTypes2.default.string,
    labelTime: _propTypes2.default.string
};

DateTimeInput.defaultProps = {
    addField: true,
    options: {}
};

exports.default = DateTimeInput;<|endoftext|>