diff --git a/back/methods/dms/download.js b/back/methods/dms/downloadFile.js similarity index 77% rename from back/methods/dms/download.js rename to back/methods/dms/downloadFile.js index 9211cc8f8..574ad6972 100644 --- a/back/methods/dms/download.js +++ b/back/methods/dms/downloadFile.js @@ -2,7 +2,7 @@ const UserError = require('vn-loopback/util/user-error'); const fs = require('fs-extra'); module.exports = Self => { - Self.remoteMethodCtx('download', { + Self.remoteMethodCtx('downloadFile', { description: 'Download a document', accessType: 'READ', accepts: [ @@ -29,15 +29,15 @@ module.exports = Self => { } ], http: { - path: `/:id/download`, + path: `/:id/downloadFile`, verb: 'GET' } }); - Self.download = async function(ctx, id) { - const userId = ctx.req.accessToken.userId; + Self.downloadFile = async function(ctx, id) { const env = process.env.NODE_ENV; - const document = await Self.findById(id, { + const models = Self.app.models; + const dms = await Self.findById(id, { include: { relation: 'dmsType', scope: { @@ -48,18 +48,17 @@ module.exports = Self => { } } }); - const readRole = document.dmsType().readRole().name; - const hasRequiredRole = await Self.app.models.Account.hasRole(userId, readRole); - if (!hasRequiredRole) + const hasReadRole = await models.DmsType.hasReadRole(ctx, dms.dmsTypeFk); + if (!hasReadRole) throw new UserError(`You don't have enough privileges`); if (env && env != 'development') { - const path = `/${document.companyFk}/${document.dmsType().path}/${document.file}`; + const path = `/${dms.companyFk}/${dms.dmsType().path}/${dms.file}`; file = { path: `/var/lib/salix/dms/${path}`, contentType: 'application/octet-stream', - name: document.file + name: dms.file }; } else { file = { diff --git a/back/methods/dms/removeFile.js b/back/methods/dms/removeFile.js new file mode 100644 index 000000000..71d9cc2c8 --- /dev/null +++ b/back/methods/dms/removeFile.js @@ -0,0 +1,46 @@ +const UserError = require('vn-loopback/util/user-error'); +const fs = require('fs-extra'); + +module.exports = Self => { + Self.remoteMethodCtx('removeFile', { + description: 'Makes a logical delete moving a file to a trash folder', + accessType: 'WRITE', + accepts: [{ + arg: 'id', + type: 'Number', + description: 'The document id', + http: {source: 'path'} + }], + returns: { + type: 'Object', + root: true + }, + http: { + path: `/:id/removeFile`, + verb: 'POST' + } + }); + + Self.removeFile = async(ctx, id) => { + const models = Self.app.models; + const dms = await models.Dms.findById(id); + const dmsType = await models.DmsType.findById(dms.dmsTypeFk); + const trashDmsType = await models.DmsType.findOne({ + where: { + code: 'trash' + } + }); + + const hasWriteRole = await models.DmsType.hasWriteRole(ctx, dms.dmsTypeFk); + if (!hasWriteRole) + throw new UserError(`You don't have enough privileges`); + + const file = await models.Container.getFile(dmsType.path, dms.file); + const originPath = `${file.client.root}/${dmsType.path}/${file.name}`; + const destinationPath = `${file.client.root}/${trashDmsType.path}/${file.name}`; + + await fs.rename(originPath, destinationPath); + + return dms.updateAttribute('dmsTypeFk', trashDmsType.id); + }; +}; diff --git a/back/methods/dms/specs/download.spec.js b/back/methods/dms/specs/downloadFile.spec.js similarity index 69% rename from back/methods/dms/specs/download.spec.js rename to back/methods/dms/specs/downloadFile.spec.js index f7f3eb630..ae8a4fe53 100644 --- a/back/methods/dms/specs/download.spec.js +++ b/back/methods/dms/specs/downloadFile.spec.js @@ -1,11 +1,11 @@ const app = require('vn-loopback/server/server'); -describe('dms download()', () => { +describe('dms downloadFile()', () => { let dmsId = 1; it('should return a response for an employee with text content-type', async() => { - let workerFk = 107; - let ctx = {req: {accessToken: {userId: workerFk}}}; - const result = await app.models.Dms.download(ctx, dmsId); + let workerId = 107; + let ctx = {req: {accessToken: {userId: workerId}}}; + const result = await app.models.Dms.downloadFile(ctx, dmsId); expect(result[1]).toEqual('text/plain'); }); @@ -15,7 +15,7 @@ describe('dms download()', () => { let ctx = {req: {accessToken: {userId: clientId}}}; let error; - await app.models.Dms.download(ctx, dmsId).catch(e => { + await app.models.Dms.downloadFile(ctx, dmsId).catch(e => { error = e; }).finally(() => { expect(error.message).toEqual(`You don't have enough privileges`); diff --git a/back/methods/dms/specs/removeFile.spec.js b/back/methods/dms/specs/removeFile.spec.js new file mode 100644 index 000000000..1006d1329 --- /dev/null +++ b/back/methods/dms/specs/removeFile.spec.js @@ -0,0 +1,19 @@ +const app = require('vn-loopback/server/server'); + +describe('dms removeFile()', () => { + let dmsId = 1; + + it(`should return an error for a user without enough privileges`, async() => { + let clientId = 101; + let ctx = {req: {accessToken: {userId: clientId}}}; + + let error; + await app.models.Dms.removeFile(ctx, dmsId).catch(e => { + error = e; + }).finally(() => { + expect(error.message).toEqual(`You don't have enough privileges`); + }); + + expect(error).toBeDefined(); + }); +}); diff --git a/back/methods/dms/specs/uploadFile.spec.js b/back/methods/dms/specs/uploadFile.spec.js new file mode 100644 index 000000000..afd70d068 --- /dev/null +++ b/back/methods/dms/specs/uploadFile.spec.js @@ -0,0 +1,18 @@ +const app = require('vn-loopback/server/server'); + +describe('dms uploadFile()', () => { + it(`should return an error for a user without enough privileges`, async() => { + let clientId = 101; + let ticketDmsTypeId = 14; + let ctx = {req: {accessToken: {userId: clientId}}, args: {dmsTypeId: ticketDmsTypeId}}; + + let error; + await app.models.Dms.uploadFile(ctx).catch(e => { + error = e; + }).finally(() => { + expect(error.message).toEqual(`You don't have enough privileges`); + }); + + expect(error).toBeDefined(); + }); +}); diff --git a/back/methods/dms/uploadFile.js b/back/methods/dms/uploadFile.js new file mode 100644 index 000000000..1604a6afa --- /dev/null +++ b/back/methods/dms/uploadFile.js @@ -0,0 +1,118 @@ +const fs = require('fs-extra'); +const UserError = require('vn-loopback/util/user-error'); + +module.exports = Self => { + Self.remoteMethodCtx('uploadFile', { + description: 'Uploads a file and inserts into dms model', + accessType: 'WRITE', + accepts: [{ + arg: 'options', + type: 'object' + }, + { + arg: 'warehouseId', + type: 'Number', + description: '' + }, + { + arg: 'companyId', + type: 'Number', + description: '' + }, + { + arg: 'dmsTypeId', + type: 'Number', + description: '' + }, + { + arg: 'reference', + type: 'String', + description: '' + }, + { + arg: 'description', + type: 'String', + description: '' + }, + { + arg: 'hasFile', + type: 'Boolean', + description: '' + }], + returns: { + type: 'Object', + root: true + }, + http: { + path: `/uploadFile`, + verb: 'POST' + } + }); + + Self.uploadFile = async(ctx, options = {}) => { + const models = Self.app.models; + const storageConnector = Self.app.dataSources.storage.connector; + const myUserId = ctx.req.accessToken.userId; + const myWorker = await models.Worker.findOne({where: {userFk: myUserId}}); + const args = ctx.args; + const fileOptions = {}; + const hasParentTransaction = options && options.transaction; + + if (!options.transaction) + options.transaction = await Self.beginTransaction({}); + + try { + const hasWriteRole = await models.DmsType.hasWriteRole(ctx, args.dmsTypeId); + if (!hasWriteRole) + throw new UserError(`You don't have enough privileges`); + + const uploaded = await models.Container.upload('temp', ctx.req, ctx.result, fileOptions); + const files = Object.values(uploaded.files).map(file => { + return file[0]; + }); + + const dmsType = await models.DmsType.findById(args.dmsTypeId); + const promises = []; + + files.forEach(file => { + const newDms = Self.create({ + workerFk: myWorker.id, + dmsTypeFk: args.dmsTypeId, + companyFk: args.companyId, + warehouseFk: args.warehouseId, + reference: args.reference, + description: args.description, + hasFile: args.hasFile + }, options).then(newDms => { + const extension = storageConnector.getFileExtension(file.name); + const fileName = `${newDms.id}.${extension}`; + + return newDms.updateAttribute('file', fileName, options); + }).then(dms => { + return models.Container.getContainer('temp').then(container => { + const originPath = `${container.client.root}/${container.name}/${file.name}`; + const destinationPath = `${container.client.root}/${dmsType.path}/${dms.file}`; + + return fs.rename(originPath, destinationPath).then(() => { + return dms; + }); + }); + }); + + promises.push(newDms); + }); + + const resolvedPromise = await Promise.all(promises); + + if (!hasParentTransaction) + await options.transaction.commit(); + + return resolvedPromise; + } catch (e) { + if (!hasParentTransaction) + await options.transaction.rollback(); + + throw e; + } + }; +}; diff --git a/back/model-config.json b/back/model-config.json index dc67a5422..a0c3d8740 100644 --- a/back/model-config.json +++ b/back/model-config.json @@ -43,6 +43,9 @@ }, "DmsType": { "dataSource": "vn" + }, + "Container": { + "dataSource": "storage" } } diff --git a/back/models/container.json b/back/models/container.json new file mode 100644 index 000000000..eca4c30c7 --- /dev/null +++ b/back/models/container.json @@ -0,0 +1,13 @@ +{ + "name": "Container", + "base": "VnModel", + "idInjection": true, + "options": { + "validateUpsert": true + }, + "properties": {}, + "validations": [], + "relations": {}, + "acls": [], + "methods": [] + } \ No newline at end of file diff --git a/back/models/dms.js b/back/models/dms.js index 97a405eef..d3471178b 100644 --- a/back/models/dms.js +++ b/back/models/dms.js @@ -1,3 +1,5 @@ module.exports = Self => { - require('../methods/dms/download')(Self); + require('../methods/dms/downloadFile')(Self); + require('../methods/dms/uploadFile')(Self); + require('../methods/dms/removeFile')(Self); }; diff --git a/back/models/dmsType.js b/back/models/dmsType.js new file mode 100644 index 000000000..f76b095df --- /dev/null +++ b/back/models/dmsType.js @@ -0,0 +1,62 @@ +module.exports = Self => { + /** + * Checks if current user has + * read privileges over a dms + * + * @param {Object} ctx - Request context + * @param {Interger} id - DmsType id + * @return {Boolean} True for user with read privileges + */ + Self.hasReadRole = async(ctx, id) => { + const models = Self.app.models; + const dmsType = await models.DmsType.findById(id, { + include: { + relation: 'readRole' + } + }); + + return await hasRole(ctx, dmsType); + }; + + /** + * Checks if current user has + * write privileges over a dms + * + * @param {Object} ctx - Request context + * @param {Interger} id - DmsType id + * @return {Boolean} True for user with write privileges + */ + Self.hasWriteRole = async(ctx, id) => { + const models = Self.app.models; + const dmsType = await models.DmsType.findById(id, { + include: { + relation: 'writeRole' + } + }); + + return await hasRole(ctx, dmsType); + }; + + /** + * Checks if current user has + * read or write privileges + * @param {Object} ctx - Context + * @param {Object} dmsType - Dms type [read/write] + */ + async function hasRole(ctx, dmsType) { + const models = Self.app.models; + const myUserId = ctx.req.accessToken.userId; + + const readRole = dmsType.readRole() && dmsType.readRole().name; + const writeRole = dmsType.writeRole() && dmsType.writeRole().name; + const requiredRole = readRole || writeRole; + + const hasRequiredRole = await models.Account.hasRole(myUserId, requiredRole); + const isRoot = await models.Account.hasRole(myUserId, 'root'); + + if (isRoot || hasRequiredRole) + return true; + + return false; + } +}; diff --git a/db/changes/10050-pentecostes/00-ACL.sql b/db/changes/10050-pentecostes/00-ACL.sql index ca05184c1..6934ccecb 100644 --- a/db/changes/10050-pentecostes/00-ACL.sql +++ b/db/changes/10050-pentecostes/00-ACL.sql @@ -1,3 +1,12 @@ -INSERT INTO `salix`.`ACL` ( `model`, `property`, `accessType`, `permission`, `principalType`, `principalId`) VALUES ( 'ClientDms', 'remove', 'WRITE', 'ALLOW', 'ROLE', 'employee'); -INSERT INTO `salix`.`ACL` ( `model`, `property`, `accessType`, `permission`, `principalType`, `principalId`) VALUES ( 'ClientDms', '*', 'READ', 'ALLOW', 'ROLE', 'employee'); -INSERT INTO `salix`.`ACL` (`model`, `property`, `accessType`, `permission`, `principalType`, `principalId`) VALUES ('Route', 'updateVolume', 'WRITE', 'ALLOW', 'ROLE', 'deliveryBoss'); +INSERT INTO `salix`.`ACL` ( `model`, `property`, `accessType`, `permission`, `principalType`, `principalId`) + VALUES + ('Dms', 'removeFile', 'WRITE', 'ALLOW', 'ROLE', 'employee'), + ('Dms', 'uploadFile', 'WRITE', 'ALLOW', 'ROLE', 'employee'), + ('Dms', 'downloadFile', 'READ', 'ALLOW', 'ROLE', 'employee'), + ('Client', 'uploadFile', 'WRITE', 'ALLOW', 'ROLE', 'employee'), + ('ClientDms', 'removeFile', 'WRITE', 'ALLOW', 'ROLE', 'employee'), + ('ClientDms', '*', 'READ', 'ALLOW', 'ROLE', 'employee'), + ('Ticket', 'uploadFile', 'WRITE', 'ALLOW', 'ROLE', 'employee'), + ('TicketDms', 'removeFile', 'WRITE', 'ALLOW', 'ROLE', 'employee'), + ('TicketDms', '*', 'READ', 'ALLOW', 'ROLE', 'employee'), + ('Route', 'updateVolume', 'WRITE', 'ALLOW', 'ROLE', 'deliveryBoss'); diff --git a/db/changes/10050-pentecostes/00-clientesgestdoc.sql b/db/changes/10050-pentecostes/00-clientesgestdoc.sql index 3e7a023c9..113fd23e1 100644 --- a/db/changes/10050-pentecostes/00-clientesgestdoc.sql +++ b/db/changes/10050-pentecostes/00-clientesgestdoc.sql @@ -4,10 +4,6 @@ ALTER TABLE `vn2008`.`clientes_gestdoc` DROP PRIMARY KEY, ADD PRIMARY KEY (`gest_doc_id`); - -ALTER TABLE `vn2008`.`clientes_gestdoc` -DROP INDEX `fk_clientes_gestdoc_1_idx` ; - ALTER TABLE `vn2008`.`clientes_gestdoc` ADD INDEX `fk_clientes_gestdoc_1_idx` (`Id_Cliente` ASC); diff --git a/db/changes/10050-pentecostes/00-dmsType.sql b/db/changes/10050-pentecostes/00-dmsType.sql new file mode 100644 index 000000000..1692c6f50 --- /dev/null +++ b/db/changes/10050-pentecostes/00-dmsType.sql @@ -0,0 +1,4 @@ +UPDATE `vn2008`.`gesttip` SET `writeRoleFk`='1', `readRoleFk`='1' WHERE `id`='5'; +UPDATE `vn2008`.`gesttip` SET `writeRoleFk`='1', `readRoleFk`='1' WHERE `id`='12'; +UPDATE `vn2008`.`gesttip` SET `writeRoleFk`='1', `readRoleFk`='1' WHERE `id`='14'; +UPDATE `vn2008`.`gesttip` SET `writeRoleFk`='1', `readRoleFk`='1' WHERE `id`='13'; diff --git a/db/changes/10050-pentecostes/01-clienteesGestdoc.sql b/db/changes/10050-pentecostes/01-clienteesGestdoc.sql deleted file mode 100644 index 294a39577..000000000 --- a/db/changes/10050-pentecostes/01-clienteesGestdoc.sql +++ /dev/null @@ -1,9 +0,0 @@ -ALTER TABLE `vn2008`.`clientes_gestdoc` -ADD INDEX `fk_clientes_gestdoc_1_idx` (`Id_Cliente` ASC); - -ALTER TABLE `vn2008`.`clientes_gestdoc` -ADD CONSTRAINT `fk_clientes_gestdoc_3` - FOREIGN KEY (`Id_Cliente`) - REFERENCES `vn2008`.`Clientes` (`id_cliente`) - ON DELETE RESTRICT - ON UPDATE CASCADE; \ No newline at end of file diff --git a/db/dump/fixtures.sql b/db/dump/fixtures.sql index d23da60f4..a6e6f14a0 100644 --- a/db/dump/fixtures.sql +++ b/db/dump/fixtures.sql @@ -1439,16 +1439,16 @@ INSERT INTO `vn`.`dmsType`(`id`, `name`, `path`, `readRoleFk`, `writeRoleFk`, `c (2, 'Doc oficial', 'oficial', NULL, NULL, 'officialDoc'), (3, 'Laboral', 'laboral', NULL, NULL, 'hhrrData'), (4, 'Albaranes recibidos', 'entradas', NULL, NULL, 'deliveryNote'), - (5, 'Otros', 'otros', 1, NULL, 'miscellaneous'), + (5, 'Otros', 'otros', 1, 1, 'miscellaneous'), (6, 'Pruebas', 'pruebas', NULL, NULL, 'tests'), (7, 'IAE Clientes', 'IAE_Clientes', NULL, NULL, 'economicActivitiesTax'), (8, 'Fiscal', 'fiscal', NULL, NULL, 'fiscal'), (9, 'Vehiculos', 'vehiculos', NULL, NULL, 'vehicles'), (10, 'Plantillas', 'plantillas', NULL, NULL, 'templates'), (11, 'Contratos', 'contratos', NULL, NULL, 'contracts'), - (12, 'ley de pagos', 'ley pagos', NULL, NULL, 'paymentsLaw'), - (13, 'Basura', 'basura', NULL, NULL, 'trash'), - (14, 'Ticket', 'tickets', 1, NULL, 'ticket'), + (12, 'ley de pagos', 'ley pagos', 1, 1, 'paymentsLaw'), + (13, 'Basura', 'basura', 1, 1, 'trash'), + (14, 'Ticket', 'tickets', 1, 1, 'ticket'), (15, 'Presupuestos', 'Presupuestos', NULL, NULL, 'budgets'), (16, 'Logistica', 'logistica', NULL, NULL, 'logistics'), (17, 'cmr', 'cmr', NULL, NULL, 'cmr'), diff --git a/front/core/components/check/style.scss b/front/core/components/check/style.scss index 122c3e659..ade79c4c5 100644 --- a/front/core/components/check/style.scss +++ b/front/core/components/check/style.scss @@ -16,7 +16,6 @@ vn-check { } md-checkbox { - margin-bottom: 0.8em; - width: 20px; + margin-bottom: 0.8em } } diff --git a/front/core/components/index.js b/front/core/components/index.js index 9c8ae2578..d626470c3 100644 --- a/front/core/components/index.js +++ b/front/core/components/index.js @@ -41,6 +41,7 @@ import './chip'; import './color-legend'; import './input-number'; import './input-time'; +import './input-file'; import './treeview'; import './treeview/child'; import './calendar'; diff --git a/front/core/components/input-file/index.html b/front/core/components/input-file/index.html new file mode 100644 index 000000000..59cfcff7d --- /dev/null +++ b/front/core/components/input-file/index.html @@ -0,0 +1,39 @@ +
+
+
+
+
+ {{$ctrl.value}} +
+ + +
+
+
+
+ + info_outline + + + +
+
+
+
diff --git a/front/core/components/input-file/index.js b/front/core/components/input-file/index.js new file mode 100644 index 000000000..b54d5af2c --- /dev/null +++ b/front/core/components/input-file/index.js @@ -0,0 +1,132 @@ +import ngModule from '../../module'; +import Input from '../../lib/input'; +import './style.scss'; + +export default class InputFile extends Input { + constructor($element, $scope, $attrs, vnTemplate) { + super($element, $scope); + this.element = $element[0]; + this.hasFocus = false; + this._multiple = false; + this._value = 'Select a file'; + + vnTemplate.normalizeInputAttrs($attrs); + + this.registerEvents(); + } + + /** + * Registers all event emitters + */ + registerEvents() { + this.input.addEventListener('change', event => { + const target = event.target; + const fileNames = Array.from(target.files).map(file => { + return file.name; + }); + const names = fileNames.join(', '); + const label = this.element.querySelector('.value'); + label.innerHTML = names; + this.files = target.files; + + this.emit('change', {files: target.files, event}); + }); + } + + get value() { + return this._value; + } + + set value(value) { + this._value = value; + } + + /** + * Gets current value + */ + get files() { + return this._files; + } + + /** + * Sets input value + * + * @param {Number} value - Value + */ + set files(value) { + this._files = value; + this.hasValue = !(value === null || value === undefined || value === ''); + + if (this.hasValue) + this.element.classList.add('not-empty'); + else + this.element.classList.remove('not-empty'); + } + + /** + * Gets if multiple file selection + */ + get multiple() { + return this._multiple; + } + + /** + * Sets multiple file selection + * + * @param {Boolean} value - True if is multiple + */ + set multiple(value) { + this._multiple = value; + + if (value) + this.input.multiple = true; + else + this.input.multiple = false; + } + + /** + * Returns a list of valid file types + */ + get accept() { + return this._accept; + } + + /** + * Sets a list of valid file types + * + * @param {String} value - Valid file types + */ + set accept(value) { + this._accept = value; + } + + /** + * Fires file selection explorer event + */ + openFileSelector() { + this.input.click(); + } +} + +InputFile.$inject = ['$element', '$scope', '$attrs', 'vnTemplate']; + +ngModule.component('vnInputFile', { + template: require('./index.html'), + controller: InputFile, + transclude: { + leftIcons: '?tLeftIcons', + rightIcons: '?tRightIcons' + }, + bindings: { + label: '@?', + name: '@?', + disabled: ' { + let $scope; + let $attrs; + let $timeout; + let $element; + let controller; + + beforeEach(ngModule('vnCore')); + + beforeEach(angular.mock.inject(($componentController, $rootScope) => { + $scope = $rootScope.$new(); + $attrs = {}; + $element = angular.element('
'); + controller = $componentController('vnInputFile', {$element, $scope, $attrs, $timeout, $transclude: () => {}}); + controller.input = $element[0].querySelector('input'); + controller.validate = () => {}; + })); + + describe('files() setter', () => { + it(`should set a value, and then add the class 'not-empty'`, () => { + controller.files = [{name: 'MyFile'}]; + + let classes = controller.element.classList.toString(); + + expect(classes).toContain('not-empty'); + }); + + it(`should set an empty value, and then remove the class 'not-empty'`, () => { + controller.files = null; + + let classes = controller.element.classList.toString(); + + expect(classes).not.toContain('not-empty'); + }); + }); +}); diff --git a/front/core/components/input-file/style.scss b/front/core/components/input-file/style.scss new file mode 100644 index 000000000..83a66a262 --- /dev/null +++ b/front/core/components/input-file/style.scss @@ -0,0 +1,15 @@ +@import "variables"; +@import '../textfield/style.scss'; + +vn-input-file { + @extend vn-textfield; + .value { + color: $color-font-secondary; + cursor: pointer; + padding: 4px 0; + outline: 0 + } + input { + display: none !important + } +} \ No newline at end of file diff --git a/front/core/components/input-number/index.js b/front/core/components/input-number/index.js index 6c32ebb98..7eb8cbd54 100644 --- a/front/core/components/input-number/index.js +++ b/front/core/components/input-number/index.js @@ -184,6 +184,10 @@ InputNumber.$inject = ['$element', '$scope', '$attrs', 'vnTemplate']; ngModule.component('vnInputNumber', { template: require('./index.html'), controller: InputNumber, + transclude: { + leftIcons: '?tLeftIcons', + rightIcons: '?tRightIcons' + }, bindings: { label: '@?', name: '@?', diff --git a/front/core/components/multi-check/multi-check.js b/front/core/components/multi-check/multi-check.js index 9eb9a5148..1e5a1c569 100644 --- a/front/core/components/multi-check/multi-check.js +++ b/front/core/components/multi-check/multi-check.js @@ -1,5 +1,6 @@ import ngModule from '../../module'; import Input from '../../lib/input'; +import './style.scss'; /** * Draw checkbox with a drop-down and multi options diff --git a/front/core/components/multi-check/style.scss b/front/core/components/multi-check/style.scss new file mode 100644 index 000000000..5ae5f4f0b --- /dev/null +++ b/front/core/components/multi-check/style.scss @@ -0,0 +1,9 @@ +vn-multi-check { + md-checkbox { + margin-bottom: 0.8em; + + .md-label { + margin: 0 + } + } +} \ No newline at end of file diff --git a/front/core/components/textfield/style.scss b/front/core/components/textfield/style.scss index 36792ef90..3ec4134bc 100644 --- a/front/core/components/textfield/style.scss +++ b/front/core/components/textfield/style.scss @@ -29,6 +29,10 @@ vn-textfield { } } + .suffix vn-icon-button { + padding: 0 + } + t-left-icons { padding-right: 0.5em } @@ -47,6 +51,7 @@ vn-textfield { i.clear { visibility: hidden; cursor: pointer; + outline: 0; &:hover { color: #222; diff --git a/front/core/locale/es.yml b/front/core/locale/es.yml index bdf42192f..ee57de376 100644 --- a/front/core/locale/es.yml +++ b/front/core/locale/es.yml @@ -49,4 +49,5 @@ Loading: Cargando Fields to show: Campos a mostrar Create new one: Crear nuevo Toggle: Desplegar/Plegar -Check all: Seleccionar todo \ No newline at end of file +Check all: Seleccionar todo +Select a file: Selecciona un fichero \ No newline at end of file diff --git a/loopback/server/boot/root.js b/loopback/server/boot/root.js index 7a4563f61..7a5591571 100644 --- a/loopback/server/boot/root.js +++ b/loopback/server/boot/root.js @@ -1,3 +1,4 @@ +const uuid = require('uuid/v1'); module.exports = function(app) { let models = app.models(); @@ -27,9 +28,19 @@ module.exports = function(app) { app.enableAuth(); + // eslint-disable-next-line new-cap let router = app.loopback.Router(); router.get('/status', app.loopback.status()); app.use(router); + + const storageConnector = app.dataSources.storage.connector; + storageConnector.getFilename = function(file) { + return `${uuid()}.${storageConnector.getFileExtension(file.name)}`; + }; + + storageConnector.getFileExtension = function(fileName) { + return fileName.split('.').pop(); + }; /* let ds = app.dataSources.auth; //ds.automigrate(function() { diff --git a/loopback/server/datasources.json b/loopback/server/datasources.json index 401b9483d..2a64532cc 100644 --- a/loopback/server/datasources.json +++ b/loopback/server/datasources.json @@ -14,5 +14,20 @@ "multipleStatements": true, "connectTimeout": 20000, "acquireTimeout": 20000 + }, + "storage": { + "name": "storage", + "connector": "loopback-component-storage", + "provider": "filesystem", + "root": "/mnt/storage/dms", + "maxFileSize": "10485760", + "allowedContentTypes": [ + "application/pdf", + "application/zip", + "application/rar", + "image/png", + "image/jpeg", + "image/jpg" + ] } } diff --git a/modules/client/back/methods/client-dms/removeFile.js b/modules/client/back/methods/client-dms/removeFile.js new file mode 100644 index 000000000..96110bc11 --- /dev/null +++ b/modules/client/back/methods/client-dms/removeFile.js @@ -0,0 +1,33 @@ +module.exports = Self => { + Self.remoteMethodCtx('removeFile', { + description: 'Removes a client document', + accessType: 'WRITE', + accepts: { + arg: 'id', + type: 'Number', + description: 'The document id', + http: {source: 'path'} + }, + returns: { + type: 'Object', + root: true + }, + http: { + path: `/:id/removeFile`, + verb: 'POST' + } + }); + + Self.removeFile = async(ctx, id) => { + const models = Self.app.models; + const targetClientDms = await models.ClientDms.findById(id); + const targetDms = await models.Dms.findById(targetClientDms.dmsFk); + const trashDmsType = await models.DmsType.findOne({where: {code: 'trash'}}); + + await models.Dms.removeFile(ctx, targetClientDms.dmsFk); + await targetClientDms.destroy(); + + return targetDms.updateAttribute('dmsTypeFk', trashDmsType.id); + }; +}; + diff --git a/modules/client/back/methods/client-dms/specs/removeFile.spec.js b/modules/client/back/methods/client-dms/specs/removeFile.spec.js new file mode 100644 index 000000000..01cf1977b --- /dev/null +++ b/modules/client/back/methods/client-dms/specs/removeFile.spec.js @@ -0,0 +1,18 @@ +const app = require('vn-loopback/server/server'); + +describe('ClientDms removeFile()', () => { + const clientDmsFk = 3; + it(`should return an error for a user without enough privileges`, async() => { + let clientId = 101; + let ctx = {req: {accessToken: {userId: clientId}}}; + + let error; + await app.models.ClientDms.removeFile(ctx, clientDmsFk).catch(e => { + error = e; + }).finally(() => { + expect(error.message).toEqual(`You don't have enough privileges`); + }); + + expect(error).toBeDefined(); + }); +}); diff --git a/modules/client/back/methods/client/specs/uploadFile.spec.js b/modules/client/back/methods/client/specs/uploadFile.spec.js new file mode 100644 index 000000000..15110731e --- /dev/null +++ b/modules/client/back/methods/client/specs/uploadFile.spec.js @@ -0,0 +1,19 @@ +const app = require('vn-loopback/server/server'); + +describe('Client uploadFile()', () => { + it(`should return an error for a user without enough privileges`, async() => { + let clientId = 101; + let currentUserId = 102; + let paymentLawTypeId = 12; + let ctx = {req: {accessToken: {userId: currentUserId}}, args: {dmsTypeId: paymentLawTypeId}}; + + let error; + await app.models.Client.uploadFile(ctx, clientId).catch(e => { + error = e; + }).finally(() => { + expect(error.message).toEqual(`You don't have enough privileges`); + }); + + expect(error).toBeDefined(); + }); +}); diff --git a/modules/client/back/methods/client/uploadFile.js b/modules/client/back/methods/client/uploadFile.js new file mode 100644 index 000000000..7187d684f --- /dev/null +++ b/modules/client/back/methods/client/uploadFile.js @@ -0,0 +1,77 @@ +module.exports = Self => { + Self.remoteMethodCtx('uploadFile', { + description: 'Upload and attach a file to a client', + accessType: 'WRITE', + accepts: [{ + arg: 'id', + type: 'Number', + description: 'The client id', + http: {source: 'path'} + }, + { + arg: 'warehouseId', + type: 'Number', + description: '' + }, + { + arg: 'companyId', + type: 'Number', + description: '' + }, + { + arg: 'dmsTypeId', + type: 'Number', + description: '' + }, + { + arg: 'reference', + type: 'String', + description: '' + }, + { + arg: 'description', + type: 'String', + description: '' + }, + { + arg: 'hasFile', + type: 'Boolean', + description: '' + }], + returns: { + type: 'Object', + root: true + }, + http: { + path: `/:id/uploadFile`, + verb: 'POST' + } + }); + + Self.uploadFile = async(ctx, id) => { + const models = Self.app.models; + const transaction = await Self.beginTransaction({}); + const options = {transaction}; + const promises = []; + + try { + const uploadedFiles = await models.Dms.uploadFile(ctx, options); + uploadedFiles.forEach(dms => { + const newClientDms = models.ClientDms.create({ + clientFk: id, + dmsFk: dms.id + }, options); + + promises.push(newClientDms); + }); + const resolvedPromises = await Promise.all(promises); + + await transaction.commit(); + + return resolvedPromises; + } catch (err) { + await transaction.rollback(); + throw err; + } + }; +}; diff --git a/modules/client/back/methods/dms/removes.js b/modules/client/back/methods/dms/removes.js deleted file mode 100644 index c3aeeed52..000000000 --- a/modules/client/back/methods/dms/removes.js +++ /dev/null @@ -1,35 +0,0 @@ -const UserError = require('vn-loopback/util/user-error'); - -module.exports = Self => { - Self.remoteMethod('removes', { - description: 'Delete an client dms', - accessType: 'WRITE', - accepts: { - arg: 'dmsId', - type: 'number', - required: true, - description: 'dms identifier', - }, - returns: { - type: 'string', - root: true - }, - http: { - path: `/removes`, - verb: 'POST' - } - }); - - Self.removes = async dmsId => { - if (!dmsId) - throw new UserError('There is nothing to delete'); - - let targetClientDms = await Self.app.models.ClientDms.findOne({where: {dmsFk: dmsId}}); - let targetDms = await Self.app.models.Dms.findById(dmsId); - let trashDmsType = await Self.app.models.DmsType.findOne({where: {code: 'trash'}}); - - await targetClientDms.destroy(); - await targetDms.updateAttribute('dmsTypeFk', trashDmsType.id); - }; -}; - diff --git a/modules/client/back/methods/dms/removes.spec.js b/modules/client/back/methods/dms/removes.spec.js deleted file mode 100644 index a5d653f7e..000000000 --- a/modules/client/back/methods/dms/removes.spec.js +++ /dev/null @@ -1,29 +0,0 @@ -const app = require('vn-loopback/server/server'); - -describe('Client dms stuff', () => { - let dmsToRestore; - let dmsTypeToRestore; - afterAll(async done => { - await app.models.ClientDms.create(dmsToRestore); - await dmsTypeToRestore.save(); - - done(); - }); - - - it('should delete a dms from a client and update the dmsType to trash', async() => { - const dmsId = 2; - - dmsToRestore = await app.models.ClientDms.findOne({where: {dmsFk: dmsId}}); - dmsTypeToRestore = await app.models.Dms.findById(dmsToRestore.dmsFk); - - await app.models.ClientDms.removes(dmsId); - - let destroyedDms = await app.models.ClientDms.findOne({where: {dmsFk: dmsId}}); - - let alteredDmsType = await app.models.Dms.findById(dmsId); - - expect(destroyedDms).toBeNull(); - expect(alteredDmsType.dmsTypeFk).toEqual(13); - }); -}); diff --git a/modules/client/back/models/client-dms.js b/modules/client/back/models/client-dms.js index ef74b3c39..9e5da9132 100644 --- a/modules/client/back/models/client-dms.js +++ b/modules/client/back/models/client-dms.js @@ -1,3 +1,3 @@ module.exports = Self => { - require('../methods/dms/removes')(Self); + require('../methods/client-dms/removeFile')(Self); }; diff --git a/modules/client/back/models/client.js b/modules/client/back/models/client.js index 66c875aa5..dc60d3111 100644 --- a/modules/client/back/models/client.js +++ b/modules/client/back/models/client.js @@ -20,6 +20,7 @@ module.exports = Self => { require('../methods/client/getTransactions')(Self); require('../methods/client/confirmTransaction')(Self); require('../methods/client/canBeInvoiced')(Self); + require('../methods/client/uploadFile')(Self); // Validations diff --git a/modules/client/front/dms/create/index.html b/modules/client/front/dms/create/index.html new file mode 100644 index 000000000..3fa96a9da --- /dev/null +++ b/modules/client/front/dms/create/index.html @@ -0,0 +1,64 @@ + + + +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
diff --git a/modules/client/front/dms/create/index.js b/modules/client/front/dms/create/index.js new file mode 100644 index 000000000..a3b32228f --- /dev/null +++ b/modules/client/front/dms/create/index.js @@ -0,0 +1,97 @@ +import ngModule from '../../module'; +import './style.scss'; + +class Controller { + constructor($scope, $http, $state, $translate, vnApp) { + this.$ = $scope; + this.$http = $http; + this.$state = $state; + this.$translate = $translate; + this.vnApp = vnApp; + this.dms = { + files: [], + hasFile: false + }; + } + + get client() { + return this._client; + } + + set client(value) { + this._client = value; + + if (value) + this.setDefaultParams(); + } + + setDefaultParams() { + const params = {filter: { + where: {code: 'paymentsLaw'} + }}; + this.$http.get('/api/DmsTypes/findOne', {params}).then(res => { + const dmsType = res.data && res.data; + const companyId = window.localStorage.defaultCompanyFk; + const warehouseId = window.localStorage.defaultWarehouseFk; + const defaultParams = { + reference: this.client.id, + warehouseId: warehouseId, + companyId: companyId, + dmsTypeId: dmsType.id, + description: this.$translate.instant('ClientFileDescription', { + dmsTypeName: dmsType.name, + clientId: this.client.id, + clientName: this.client.name + }).toUpperCase() + }; + + this.dms = Object.assign(this.dms, defaultParams); + }); + } + + onSubmit() { + const query = `/api/clients/${this.client.id}/uploadFile`; + const options = { + method: 'POST', + url: query, + params: this.dms, + headers: { + 'Content-Type': undefined + }, + transformRequest: files => { + const formData = new FormData(); + + for (let i = 0; i < files.length; i++) + formData.append(files[i].name, files[i]); + + return formData; + }, + data: this.dms.files + }; + this.$http(options).then(res => { + if (res) { + this.vnApp.showSuccess(this.$translate.instant('Data saved!')); + this.$.watcher.updateOriginalData(); + this.$state.go('client.card.dms.index'); + } + }); + } + + onFileChange(files) { + if (files.length > 0) { + this.$.$applyAsync(() => { + this.dms.hasFile = true; + }); + } + } +} + +Controller.$inject = ['$scope', '$http', '$state', '$translate', 'vnApp']; + +ngModule.component('vnClientDmsCreate', { + template: require('./index.html'), + controller: Controller, + bindings: { + client: '<' + } +}); diff --git a/modules/client/front/dms/create/index.spec.js b/modules/client/front/dms/create/index.spec.js new file mode 100644 index 000000000..2825d4d25 --- /dev/null +++ b/modules/client/front/dms/create/index.spec.js @@ -0,0 +1,60 @@ +import './index'; + +describe('Client', () => { + describe('Component vnClientDmsCreate', () => { + let controller; + let $scope; + let $httpBackend; + let $httpParamSerializer; + + beforeEach(ngModule('client')); + + beforeEach(angular.mock.inject(($componentController, $rootScope, _$httpBackend_, _$httpParamSerializer_) => { + $scope = $rootScope.$new(); + $httpBackend = _$httpBackend_; + $httpParamSerializer = _$httpParamSerializer_; + controller = $componentController('vnClientDmsCreate', {$scope}); + controller._client = {id: 101, name: 'Bruce wayne'}; + })); + + describe('client() setter', () => { + it('should set the client data and then call setDefaultParams()', () => { + spyOn(controller, 'setDefaultParams'); + controller.client = { + id: 15, + name: 'Bruce wayne' + }; + + expect(controller.client).toBeDefined(); + expect(controller.setDefaultParams).toHaveBeenCalledWith(); + }); + }); + + describe('setDefaultParams()', () => { + it('should perform a GET query and define the dms property on controller', () => { + const params = {filter: { + where: {code: 'paymentsLaw'} + }}; + let serializedParams = $httpParamSerializer(params); + $httpBackend.when('GET', `/api/DmsTypes/findOne?${serializedParams}`).respond({id: 12, code: 'paymentsLaw'}); + $httpBackend.expect('GET', `/api/DmsTypes/findOne?${serializedParams}`); + controller.setDefaultParams(); + $httpBackend.flush(); + + expect(controller.dms).toBeDefined(); + expect(controller.dms.reference).toEqual(101); + expect(controller.dms.dmsTypeId).toEqual(12); + }); + }); + + describe('onFileChange()', () => { + it('should set dms hasFile property to true if has any files', () => { + const files = [{id: 1, name: 'MyFile'}]; + controller.onFileChange(files); + $scope.$apply(); + + expect(controller.dms.hasFile).toBeTruthy(); + }); + }); + }); +}); diff --git a/modules/client/front/dms/create/locale/en.yml b/modules/client/front/dms/create/locale/en.yml new file mode 100644 index 000000000..56f6a658b --- /dev/null +++ b/modules/client/front/dms/create/locale/en.yml @@ -0,0 +1 @@ +ClientFileDescription: "{{dmsTypeName}} from client {{clientName}} id {{clientId}}" \ No newline at end of file diff --git a/modules/client/front/dms/create/locale/es.yml b/modules/client/front/dms/create/locale/es.yml new file mode 100644 index 000000000..2de9e68d3 --- /dev/null +++ b/modules/client/front/dms/create/locale/es.yml @@ -0,0 +1,5 @@ +Upload file: Subir fichero +Upload: Subir +File: Fichero +ClientFileDescription: "{{dmsTypeName}} del cliente {{clientName}} id {{clientId}}" +Attached file: Fichero adjunto \ No newline at end of file diff --git a/modules/client/front/dms/create/style.scss b/modules/client/front/dms/create/style.scss new file mode 100644 index 000000000..b47544b12 --- /dev/null +++ b/modules/client/front/dms/create/style.scss @@ -0,0 +1,7 @@ +vn-ticket-request { + vn-textfield { + margin: 0!important; + max-width: 100px; + } +} + diff --git a/modules/client/front/dms/index.html b/modules/client/front/dms/index.html deleted file mode 100644 index df8bcfcb6..000000000 --- a/modules/client/front/dms/index.html +++ /dev/null @@ -1,74 +0,0 @@ - - - - - - - - - File - Description - Reference - Hard copy - Worker - Created - - - - - - - {{::document.dmsFk}} - - {{::document.dms.description}} - - {{::document.dms.reference}} - {{::document.dms.hardCopyNumber | dashIfEmpty}} - - - {{::document.dms.worker.user.nickname | dashIfEmpty}} - - - {{::document.dms.created | dateTime:'dd/MM/yyyy HH:mm'}} - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/modules/client/front/dms/index/index.html b/modules/client/front/dms/index/index.html new file mode 100644 index 000000000..40f01c4e0 --- /dev/null +++ b/modules/client/front/dms/index/index.html @@ -0,0 +1,95 @@ + + + + + + + + + Id + Type + Reference + Description + Attached file + File + Employee + Created + + + + + + {{::document.dmsFk}} + + + {{::document.dms.dmsType.name}} + + + + + {{::document.dms.reference}} + + + + + {{::document.dms.description}} + + + + + + + {{::document.dms.file}} + + + {{::document.dms.worker.user.nickname | dashIfEmpty}} + + + {{::document.dms.created | dateTime:'dd/MM/yyyy HH:mm'}} + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/modules/client/front/dms/index.js b/modules/client/front/dms/index/index.js similarity index 72% rename from modules/client/front/dms/index.js rename to modules/client/front/dms/index/index.js index 9a3c274b7..2a5da4072 100644 --- a/modules/client/front/dms/index.js +++ b/modules/client/front/dms/index/index.js @@ -1,4 +1,4 @@ -import ngModule from '../module'; +import ngModule from '../../module'; import './style.scss'; class Controller { @@ -13,7 +13,15 @@ class Controller { include: { relation: 'dms', scope: { - fields: ['dmsTypeFk', 'workerFk', 'file', 'created', 'description', 'reference', 'hardCopyNumber'], + fields: [ + 'dmsTypeFk', + 'workerFk', + 'reference', + 'description', + 'hasFile', + 'file', + 'created', + ], include: [{ relation: 'dmsType', scope: { @@ -45,19 +53,18 @@ class Controller { this.$.workerDescriptor.show(); } - showDeleteConfirm(dmsId) { - this.selectedDms = dmsId; + showDeleteConfirm(index) { + this.dmsIndex = index; this.$.confirm.show(); } deleteDms(response) { if (response === 'ACCEPT') { - let dmsId = this.selectedDms; - let query = `/client/api/ClientDms/removes`; - - this.$http.post(query, {dmsId}).then(() => { + const dmsFk = this.clientDms[this.dmsIndex].dmsFk; + const query = `/api/ClientDms/${dmsFk}/removeFile`; + this.$http.post(query).then(() => { + this.$.model.remove(this.dmsIndex); this.vnApp.showSuccess(this.$translate.instant('Data saved!')); - this.$.model.refresh(); }); } } @@ -65,7 +72,7 @@ class Controller { Controller.$inject = ['$stateParams', '$scope', 'vnToken', '$http', 'vnApp', '$translate']; -ngModule.component('vnClientDms', { +ngModule.component('vnClientDmsIndex', { template: require('./index.html'), controller: Controller, }); diff --git a/modules/client/front/dms/locale/es.yml b/modules/client/front/dms/index/locale/es.yml similarity index 100% rename from modules/client/front/dms/locale/es.yml rename to modules/client/front/dms/index/locale/es.yml diff --git a/modules/client/front/dms/style.scss b/modules/client/front/dms/index/style.scss similarity index 100% rename from modules/client/front/dms/style.scss rename to modules/client/front/dms/index/style.scss diff --git a/modules/client/front/index.js b/modules/client/front/index.js index b124aab7b..f7e4368db 100644 --- a/modules/client/front/index.js +++ b/modules/client/front/index.js @@ -35,5 +35,5 @@ import './sample/create'; import './web-payment'; import './log'; import './sms'; -import './dms'; - +import './dms/index'; +import './dms/create'; diff --git a/modules/client/front/routes.json b/modules/client/front/routes.json index 8f01f5771..c0e042086 100644 --- a/modules/client/front/routes.json +++ b/modules/client/front/routes.json @@ -25,7 +25,7 @@ {"state": "client.card.contact", "icon": "contact_phone"}, {"state": "client.card.sample.index", "icon": "mail"}, {"state": "client.card.webPayment", "icon": "icon-onlinepayment"}, - {"state": "client.card.dms", "icon": "cloud_download"} + {"state": "client.card.dms.index", "icon": "cloud_upload"} ] } ], @@ -319,8 +319,23 @@ { "url": "/dms", "state": "client.card.dms", - "component": "vn-client-dms", + "abstract": true, + "component": "ui-view" + }, + { + "url": "/index", + "state": "client.card.dms.index", + "component": "vn-client-dms-index", "description": "File management" + }, + { + "url": "/create", + "state": "client.card.dms.create", + "component": "vn-client-dms-create", + "description": "Upload file", + "params": { + "client": "$ctrl.client" + } } ] } diff --git a/modules/ticket/back/methods/ticket-dms/removeFile.js b/modules/ticket/back/methods/ticket-dms/removeFile.js new file mode 100644 index 000000000..52a0c524f --- /dev/null +++ b/modules/ticket/back/methods/ticket-dms/removeFile.js @@ -0,0 +1,33 @@ +module.exports = Self => { + Self.remoteMethodCtx('removeFile', { + description: 'Removes a ticket document', + accessType: 'WRITE', + accepts: { + arg: 'id', + type: 'Number', + description: 'The document id', + http: {source: 'path'} + }, + returns: { + type: 'Object', + root: true + }, + http: { + path: `/:id/removeFile`, + verb: 'POST' + } + }); + + Self.removeFile = async(ctx, id) => { + const models = Self.app.models; + const targetTicketDms = await models.TicketDms.findById(id); + const targetDms = await models.Dms.findById(targetTicketDms.dmsFk); + const trashDmsType = await models.DmsType.findOne({where: {code: 'trash'}}); + + await models.Dms.removeFile(ctx, targetTicketDms.dmsFk); + await targetTicketDms.destroy(); + + return targetDms.updateAttribute('dmsTypeFk', trashDmsType.id); + }; +}; + diff --git a/modules/ticket/back/methods/ticket-dms/specs/removeFile.spec.js b/modules/ticket/back/methods/ticket-dms/specs/removeFile.spec.js new file mode 100644 index 000000000..b1cd5686a --- /dev/null +++ b/modules/ticket/back/methods/ticket-dms/specs/removeFile.spec.js @@ -0,0 +1,18 @@ +const app = require('vn-loopback/server/server'); + +describe('TicketDms removeFile()', () => { + const ticketDmsId = 1; + it(`should return an error for a user without enough privileges`, async() => { + let clientId = 101; + let ctx = {req: {accessToken: {userId: clientId}}}; + + let error; + await app.models.TicketDms.removeFile(ctx, ticketDmsId).catch(e => { + error = e; + }).finally(() => { + expect(error.message).toEqual(`You don't have enough privileges`); + }); + + expect(error).toBeDefined(); + }); +}); diff --git a/modules/ticket/back/methods/ticket/specs/uploadFile.spec.js b/modules/ticket/back/methods/ticket/specs/uploadFile.spec.js new file mode 100644 index 000000000..78b935ab5 --- /dev/null +++ b/modules/ticket/back/methods/ticket/specs/uploadFile.spec.js @@ -0,0 +1,19 @@ +const app = require('vn-loopback/server/server'); + +describe('Ticket uploadFile()', () => { + it(`should return an error for a user without enough privileges`, async() => { + let ticketId = 15; + let currentUserId = 101; + let ticketTypeId = 14; + let ctx = {req: {accessToken: {userId: currentUserId}}, args: {dmsTypeId: ticketTypeId}}; + + let error; + await app.models.Ticket.uploadFile(ctx, ticketId).catch(e => { + error = e; + }).finally(() => { + expect(error.message).toEqual(`You don't have enough privileges`); + }); + + expect(error).toBeDefined(); + }); +}); diff --git a/modules/ticket/back/methods/ticket/uploadFile.js b/modules/ticket/back/methods/ticket/uploadFile.js new file mode 100644 index 000000000..6af042c8a --- /dev/null +++ b/modules/ticket/back/methods/ticket/uploadFile.js @@ -0,0 +1,77 @@ +module.exports = Self => { + Self.remoteMethodCtx('uploadFile', { + description: 'Upload and attach a document', + accessType: 'WRITE', + accepts: [{ + arg: 'id', + type: 'Number', + description: 'The ticket id', + http: {source: 'path'} + }, + { + arg: 'warehouseId', + type: 'Number', + description: '' + }, + { + arg: 'companyId', + type: 'Number', + description: '' + }, + { + arg: 'dmsTypeId', + type: 'Number', + description: '' + }, + { + arg: 'reference', + type: 'String', + description: '' + }, + { + arg: 'description', + type: 'String', + description: '' + }, + { + arg: 'hasFile', + type: 'Boolean', + description: '' + }], + returns: { + type: 'Object', + root: true + }, + http: { + path: `/:id/uploadFile`, + verb: 'POST' + } + }); + + Self.uploadFile = async(ctx, id) => { + const models = Self.app.models; + const transaction = await Self.beginTransaction({}); + const options = {transaction}; + const promises = []; + + try { + const uploadedFiles = await models.Dms.uploadFile(ctx, options); + uploadedFiles.forEach(dms => { + const newTicketDms = models.TicketDms.create({ + ticketFk: id, + dmsFk: dms.id + }, options); + + promises.push(newTicketDms); + }); + const resolvedPromises = await Promise.all(promises); + + await transaction.commit(); + + return resolvedPromises; + } catch (err) { + await transaction.rollback(); + throw err; + } + }; +}; diff --git a/modules/ticket/back/models/ticket-dms.js b/modules/ticket/back/models/ticket-dms.js new file mode 100644 index 000000000..ddb338632 --- /dev/null +++ b/modules/ticket/back/models/ticket-dms.js @@ -0,0 +1,3 @@ +module.exports = Self => { + require('../methods/ticket-dms/removeFile')(Self); +}; diff --git a/modules/ticket/back/models/ticket-dms.json b/modules/ticket/back/models/ticket-dms.json index b725d47cd..b7b24dce0 100644 --- a/modules/ticket/back/models/ticket-dms.json +++ b/modules/ticket/back/models/ticket-dms.json @@ -3,8 +3,7 @@ "base": "Loggable", "log": { "model": "TicketLog", - "relation": "ticket", - "showField": "dmsFk" + "relation": "ticket" }, "options": { "mysql": { @@ -12,11 +11,6 @@ } }, "properties": { - "ticketFk": { - "type": "Number", - "id": true, - "required": true - }, "dmsFk": { "type": "Number", "id": true, diff --git a/modules/ticket/back/models/ticket.js b/modules/ticket/back/models/ticket.js index 7cae2bc1d..19ee0a9fe 100644 --- a/modules/ticket/back/models/ticket.js +++ b/modules/ticket/back/models/ticket.js @@ -23,4 +23,5 @@ module.exports = Self => { require('../methods/ticket/updateEditableTicket')(Self); require('../methods/ticket/checkEmptiness')(Self); require('../methods/ticket/updateDiscount')(Self); + require('../methods/ticket/uploadFile')(Self); }; diff --git a/modules/ticket/front/dms/create/index.html b/modules/ticket/front/dms/create/index.html new file mode 100644 index 000000000..527c6030d --- /dev/null +++ b/modules/ticket/front/dms/create/index.html @@ -0,0 +1,63 @@ + + +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
diff --git a/modules/ticket/front/dms/create/index.js b/modules/ticket/front/dms/create/index.js new file mode 100644 index 000000000..1b28b2417 --- /dev/null +++ b/modules/ticket/front/dms/create/index.js @@ -0,0 +1,95 @@ +import ngModule from '../../module'; +import './style.scss'; + +class Controller { + constructor($scope, $http, $state, $translate, vnApp) { + this.$ = $scope; + this.$http = $http; + this.$state = $state; + this.$translate = $translate; + this.vnApp = vnApp; + this.dms = { + files: [], + hasFile: false + }; + } + + get ticket() { + return this._ticket; + } + + set ticket(value) { + this._ticket = value; + + if (value) + this.setDefaultParams(); + } + + setDefaultParams() { + const params = {filter: { + where: {code: 'ticket'} + }}; + this.$http.get('/api/DmsTypes/findOne', {params}).then(res => { + const dmsTypeId = res.data && res.data.id; + const defaultParams = { + reference: this.ticket.id, + warehouseId: this.ticket.warehouseFk, + companyId: this.ticket.companyFk, + dmsTypeId: dmsTypeId, + description: this.$translate.instant('FileDescription', { + ticketId: this.ticket.id, + clientId: this.ticket.client.id, + clientName: this.ticket.client.name + }).toUpperCase() + }; + + this.dms = Object.assign(this.dms, defaultParams); + }); + } + + onSubmit() { + const query = `/api/tickets/${this.ticket.id}/uploadFile`; + const options = { + method: 'POST', + url: query, + params: this.dms, + headers: { + 'Content-Type': undefined + }, + transformRequest: files => { + const formData = new FormData(); + + for (let i = 0; i < files.length; i++) + formData.append(files[i].name, files[i]); + + return formData; + }, + data: this.dms.files + }; + this.$http(options).then(res => { + if (res) { + this.vnApp.showSuccess(this.$translate.instant('Data saved!')); + this.$.watcher.updateOriginalData(); + this.$state.go('ticket.card.dms.index'); + } + }); + } + + onFileChange(files) { + if (files.length > 0) { + this.$.$applyAsync(() => { + this.dms.hasFile = true; + }); + } + } +} + +Controller.$inject = ['$scope', '$http', '$state', '$translate', 'vnApp']; + +ngModule.component('vnTicketDmsCreate', { + template: require('./index.html'), + controller: Controller, + bindings: { + ticket: '<' + } +}); diff --git a/modules/ticket/front/dms/create/index.spec.js b/modules/ticket/front/dms/create/index.spec.js new file mode 100644 index 000000000..1b8cdadfa --- /dev/null +++ b/modules/ticket/front/dms/create/index.spec.js @@ -0,0 +1,65 @@ +import './index'; + +describe('Ticket', () => { + describe('Component vnTicketDmsCreate', () => { + let controller; + let $scope; + let $httpBackend; + let $httpParamSerializer; + + beforeEach(ngModule('ticket')); + + beforeEach(angular.mock.inject(($componentController, $rootScope, _$httpBackend_, _$httpParamSerializer_) => { + $scope = $rootScope.$new(); + $httpBackend = _$httpBackend_; + $httpParamSerializer = _$httpParamSerializer_; + controller = $componentController('vnTicketDmsCreate', {$scope}); + controller._ticket = { + id: 15, + client: {id: 101, name: 'Bruce wayne'}, + warehouseFk: 1, + companyFk: 1 + }; + })); + + describe('client() setter', () => { + it('should set the ticket data and then call setDefaultParams()', () => { + spyOn(controller, 'setDefaultParams'); + controller.ticket = { + id: 15, + name: 'Bruce wayne' + }; + + expect(controller.ticket).toBeDefined(); + expect(controller.setDefaultParams).toHaveBeenCalledWith(); + }); + }); + + describe('setDefaultParams()', () => { + it('should perform a GET query and define the dms property on controller', () => { + const params = {filter: { + where: {code: 'ticket'} + }}; + let serializedParams = $httpParamSerializer(params); + $httpBackend.when('GET', `/api/DmsTypes/findOne?${serializedParams}`).respond({id: 14, code: 'ticket'}); + $httpBackend.expect('GET', `/api/DmsTypes/findOne?${serializedParams}`); + controller.setDefaultParams(); + $httpBackend.flush(); + + expect(controller.dms).toBeDefined(); + expect(controller.dms.reference).toEqual(15); + expect(controller.dms.dmsTypeId).toEqual(14); + }); + }); + + describe('onFileChange()', () => { + it('should set dms hasFile property to true if has any files', () => { + const files = [{id: 1, name: 'MyFile'}]; + controller.onFileChange(files); + $scope.$apply(); + + expect(controller.dms.hasFile).toBeTruthy(); + }); + }); + }); +}); diff --git a/modules/ticket/front/dms/create/locale/en.yml b/modules/ticket/front/dms/create/locale/en.yml new file mode 100644 index 000000000..9f4c026a4 --- /dev/null +++ b/modules/ticket/front/dms/create/locale/en.yml @@ -0,0 +1 @@ +FileDescription: Ticket id {{ticketId}} from client {{clientName}} id {{clientId}} \ No newline at end of file diff --git a/modules/ticket/front/dms/create/locale/es.yml b/modules/ticket/front/dms/create/locale/es.yml new file mode 100644 index 000000000..e074da48b --- /dev/null +++ b/modules/ticket/front/dms/create/locale/es.yml @@ -0,0 +1,5 @@ +Upload file: Subir fichero +Upload: Subir +File: Fichero +FileDescription: Ticket id {{ticketId}} del cliente {{clientName}} id {{clientId}} +Attached file: Fichero adjunto \ No newline at end of file diff --git a/modules/ticket/front/dms/create/style.scss b/modules/ticket/front/dms/create/style.scss new file mode 100644 index 000000000..b47544b12 --- /dev/null +++ b/modules/ticket/front/dms/create/style.scss @@ -0,0 +1,7 @@ +vn-ticket-request { + vn-textfield { + margin: 0!important; + max-width: 100px; + } +} + diff --git a/modules/ticket/front/dms/index.html b/modules/ticket/front/dms/index.html deleted file mode 100644 index 878fd3277..000000000 --- a/modules/ticket/front/dms/index.html +++ /dev/null @@ -1,51 +0,0 @@ - - - - - - - - - Id - Type - Employee - Created - - - - - - {{::document.dmsFk}} - {{::document.dms.dmsType.name}} - - - {{::document.dms.worker.user.nickname | dashIfEmpty}} - - {{::document.dms.created | dateTime:'dd/MM/yyyy HH:mm'}} - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/modules/ticket/front/dms/index/index.html b/modules/ticket/front/dms/index/index.html new file mode 100644 index 000000000..9000ca91e --- /dev/null +++ b/modules/ticket/front/dms/index/index.html @@ -0,0 +1,95 @@ + + + + + + + + + Id + Type + Reference + Description + Attached file + File + Employee + Created + + + + + + {{::document.dmsFk}} + + + {{::document.dms.dmsType.name}} + + + + + {{::document.dms.reference}} + + + + + {{::document.dms.description}} + + + + + + + {{::document.dms.file}} + + + {{::document.dms.worker.user.nickname | dashIfEmpty}} + + + {{::document.dms.created | dateTime:'dd/MM/yyyy HH:mm'}} + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/modules/ticket/front/dms/index.js b/modules/ticket/front/dms/index/index.js similarity index 53% rename from modules/ticket/front/dms/index.js rename to modules/ticket/front/dms/index/index.js index ab6456b5f..f2c65be26 100644 --- a/modules/ticket/front/dms/index.js +++ b/modules/ticket/front/dms/index/index.js @@ -1,16 +1,27 @@ -import ngModule from '../module'; +import ngModule from '../../module'; import './style.scss'; class Controller { - constructor($stateParams, $scope, vnToken) { + constructor($stateParams, $scope, $http, $translate, vnToken, vnApp) { this.$stateParams = $stateParams; this.$ = $scope; + this.$http = $http; + this.$translate = $translate; this.accessToken = vnToken.token; + this.vnApp = vnApp; this.filter = { include: { relation: 'dms', scope: { - fields: ['dmsTypeFk', 'workerFk', 'file', 'created'], + fields: [ + 'dmsTypeFk', + 'workerFk', + 'reference', + 'description', + 'hasFile', + 'file', + 'created', + ], include: [{ relation: 'dmsType', scope: { @@ -41,11 +52,27 @@ class Controller { this.$.workerDescriptor.workerFk = workerFk; this.$.workerDescriptor.show(); } + + showDeleteConfirm(index) { + this.dmsIndex = index; + this.$.confirm.show(); + } + + deleteDms(response) { + if (response === 'ACCEPT') { + const dmsFk = this.ticketDms[this.dmsIndex].dmsFk; + const query = `/api/TicketDms/${dmsFk}/removeFile`; + this.$http.post(query).then(() => { + this.$.model.remove(this.dmsIndex); + this.vnApp.showSuccess(this.$translate.instant('Data saved!')); + }); + } + } } -Controller.$inject = ['$stateParams', '$scope', 'vnToken']; +Controller.$inject = ['$stateParams', '$scope', '$http', '$translate', 'vnToken', 'vnApp']; -ngModule.component('vnTicketDms', { +ngModule.component('vnTicketDmsIndex', { template: require('./index.html'), controller: Controller, }); diff --git a/modules/ticket/front/dms/index.spec.js b/modules/ticket/front/dms/index/index.spec.js similarity index 100% rename from modules/ticket/front/dms/index.spec.js rename to modules/ticket/front/dms/index/index.spec.js diff --git a/modules/ticket/front/dms/index/locale/es.yml b/modules/ticket/front/dms/index/locale/es.yml new file mode 100644 index 000000000..c6742ef3c --- /dev/null +++ b/modules/ticket/front/dms/index/locale/es.yml @@ -0,0 +1,3 @@ +Type: Tipo +File management: Gestión documental +Are you sure you want to continue?: ¿Seguro que quieres continuar? \ No newline at end of file diff --git a/modules/ticket/front/dms/style.scss b/modules/ticket/front/dms/index/style.scss similarity index 100% rename from modules/ticket/front/dms/style.scss rename to modules/ticket/front/dms/index/style.scss diff --git a/modules/ticket/front/dms/locale/es.yml b/modules/ticket/front/dms/locale/es.yml deleted file mode 100644 index 6422279d9..000000000 --- a/modules/ticket/front/dms/locale/es.yml +++ /dev/null @@ -1,2 +0,0 @@ -Type: Tipo -File management: Gestión documental \ No newline at end of file diff --git a/modules/ticket/front/index.js b/modules/ticket/front/index.js index 027cb424b..a9a987b56 100644 --- a/modules/ticket/front/index.js +++ b/modules/ticket/front/index.js @@ -31,5 +31,5 @@ import './request/index'; import './request/create'; import './log'; import './weekly'; -import './dms'; - +import './dms/index'; +import './dms/create'; diff --git a/modules/ticket/front/routes.json b/modules/ticket/front/routes.json index 5c47901f3..431c4187b 100644 --- a/modules/ticket/front/routes.json +++ b/modules/ticket/front/routes.json @@ -19,7 +19,7 @@ {"state": "ticket.card.picture", "icon": "image"}, {"state": "ticket.card.log", "icon": "history"}, {"state": "ticket.card.request.index", "icon": "icon-100"}, - {"state": "ticket.card.dms", "icon": "cloud_download"} + {"state": "ticket.card.dms.index", "icon": "cloud_download"} ], "keybindings": [ {"key": "t", "state": "ticket.index"} @@ -216,8 +216,23 @@ { "url": "/dms", "state": "ticket.card.dms", - "component": "vn-ticket-dms", + "abstract": true, + "component": "ui-view" + }, + { + "url": "/index", + "state": "ticket.card.dms.index", + "component": "vn-ticket-dms-index", "description": "File management" + }, + { + "url": "/create", + "state": "ticket.card.dms.create", + "component": "vn-ticket-dms-create", + "description": "Upload file", + "params": { + "ticket": "$ctrl.ticket" + } } ] } \ No newline at end of file diff --git a/package-lock.json b/package-lock.json index 9652653a6..86b5df67c 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1243,8 +1243,7 @@ "ansi-regex": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz", - "integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8=", - "dev": true + "integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8=" }, "ansi-styles": { "version": "3.2.1", @@ -1469,6 +1468,15 @@ "integrity": "sha1-iYUI2iIm84DfkEcoRWhJwVAaSw0=", "dev": true }, + "ascli": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/ascli/-/ascli-0.3.0.tgz", + "integrity": "sha1-XmYjDlIZ/j6JUqTvtPIPrllqgTo=", + "requires": { + "colour": "^0.7.1", + "optjs": "^3.2.2" + } + }, "asn1": { "version": "0.2.4", "resolved": "https://registry.npmjs.org/asn1/-/asn1-0.2.4.tgz", @@ -1600,6 +1608,48 @@ "integrity": "sha512-Wm6ukoaOGJi/73p/cl2GvLjTI5JM1k/O14isD73YML8StrH/7/lRFgmg8nICZgD3bZZvjwCGxtMOD3wWNAu8cg==", "dev": true }, + "aws-sdk": { + "version": "2.461.0", + "resolved": "https://registry.npmjs.org/aws-sdk/-/aws-sdk-2.461.0.tgz", + "integrity": "sha512-nqRqlOaM92P6BTx/huq8FuowWNPiRRcpEKHvAQ2XTWTQUADx9HIP9KtbEzLpauxE4Er2reM0UYz9Kbtyke/3EQ==", + "requires": { + "buffer": "4.9.1", + "events": "1.1.1", + "ieee754": "1.1.8", + "jmespath": "0.15.0", + "querystring": "0.2.0", + "sax": "1.2.1", + "url": "0.10.3", + "uuid": "3.3.2", + "xml2js": "0.4.19" + }, + "dependencies": { + "events": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/events/-/events-1.1.1.tgz", + "integrity": "sha1-nr23Y1rQmccNzEwqH1AEKI6L2SQ=" + }, + "ieee754": { + "version": "1.1.8", + "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.1.8.tgz", + "integrity": "sha1-vjPUCsEO8ZJnAfbwii2G+/0a0+Q=" + }, + "sax": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/sax/-/sax-1.2.1.tgz", + "integrity": "sha1-e45lYZCyKOgaZq6nSEgNgozS03o=" + }, + "url": { + "version": "0.10.3", + "resolved": "https://registry.npmjs.org/url/-/url-0.10.3.tgz", + "integrity": "sha1-Ah5NnHcF8hu/N9A861h2dAJ3TGQ=", + "requires": { + "punycode": "1.3.2", + "querystring": "0.2.0" + } + } + } + }, "aws-sign2": { "version": "0.7.0", "resolved": "https://registry.npmjs.org/aws-sign2/-/aws-sign2-0.7.0.tgz", @@ -1802,6 +1852,96 @@ "integrity": "sha1-R2iMuZu2gE8OBtPnY7HDLlfY5rY=", "dev": true }, + "base64url": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/base64url/-/base64url-1.0.6.tgz", + "integrity": "sha1-1k03XWinxkDZEuI1jRcNylu1RoE=", + "requires": { + "concat-stream": "~1.4.7", + "meow": "~2.0.0" + }, + "dependencies": { + "camelcase": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-1.2.1.tgz", + "integrity": "sha1-m7UwTS4LVmmLLHWLCKPqqdqlijk=" + }, + "camelcase-keys": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/camelcase-keys/-/camelcase-keys-1.0.0.tgz", + "integrity": "sha1-vRoRv5sxoc5JNJOpMN4aC69K1+w=", + "requires": { + "camelcase": "^1.0.1", + "map-obj": "^1.0.0" + } + }, + "concat-stream": { + "version": "1.4.11", + "resolved": "https://registry.npmjs.org/concat-stream/-/concat-stream-1.4.11.tgz", + "integrity": "sha512-X3JMh8+4je3U1cQpG87+f9lXHDrqcb2MVLg9L7o8b1UZ0DzhRrUpdn65ttzu10PpJPPI3MQNkis+oha6TSA9Mw==", + "requires": { + "inherits": "~2.0.1", + "readable-stream": "~1.1.9", + "typedarray": "~0.0.5" + } + }, + "indent-string": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/indent-string/-/indent-string-1.2.2.tgz", + "integrity": "sha1-25m8xYPrarux5I3LsZmamGBBy2s=", + "requires": { + "get-stdin": "^4.0.1", + "minimist": "^1.1.0", + "repeating": "^1.1.0" + } + }, + "isarray": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz", + "integrity": "sha1-ihis/Kmo9Bd+Cav8YDiTmwXR7t8=" + }, + "meow": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/meow/-/meow-2.0.0.tgz", + "integrity": "sha1-j1MKjs9dQNP0tN+Tw0cpAPuiqPE=", + "requires": { + "camelcase-keys": "^1.0.0", + "indent-string": "^1.1.0", + "minimist": "^1.1.0", + "object-assign": "^1.0.0" + } + }, + "object-assign": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-1.0.0.tgz", + "integrity": "sha1-5l3Idm07R7S4MHRlyDEdoDCwcKY=" + }, + "readable-stream": { + "version": "1.1.14", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-1.1.14.tgz", + "integrity": "sha1-fPTFTvZI44EwhMY23SB54WbAgdk=", + "requires": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.1", + "isarray": "0.0.1", + "string_decoder": "~0.10.x" + } + }, + "repeating": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/repeating/-/repeating-1.1.3.tgz", + "integrity": "sha1-PUEUIYh3U3SU+X93+Xhfq4EPpKw=", + "requires": { + "is-finite": "^1.0.0" + } + }, + "string_decoder": { + "version": "0.10.31", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-0.10.31.tgz", + "integrity": "sha1-YuIDvEF2bGwoyfyEMB2rHFMQ+pQ=" + } + } + }, "batch": { "version": "0.6.1", "resolved": "https://registry.npmjs.org/batch/-/batch-0.6.1.tgz", @@ -2149,7 +2289,6 @@ "version": "4.9.1", "resolved": "http://registry.npmjs.org/buffer/-/buffer-4.9.1.tgz", "integrity": "sha1-bRu2AbB6TvztlwlBMgkwJ8lbwpg=", - "dev": true, "requires": { "base64-js": "^1.0.2", "ieee754": "^1.1.4", @@ -2178,6 +2317,11 @@ "integrity": "sha1-WWFrSYME1Var1GaWayLu2j7KX74=", "dev": true }, + "buffer-equal-constant-time": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/buffer-equal-constant-time/-/buffer-equal-constant-time-1.0.1.tgz", + "integrity": "sha1-+OcRMvf/5uAaXJaXpMbz5I1cyBk=" + }, "buffer-fill": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/buffer-fill/-/buffer-fill-1.0.0.tgz", @@ -2236,6 +2380,11 @@ } } }, + "bufferview": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/bufferview/-/bufferview-1.0.1.tgz", + "integrity": "sha1-ev10pF+Tf6QiodM4wIu/3HbNcl0=" + }, "builtin-modules": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/builtin-modules/-/builtin-modules-1.1.1.tgz", @@ -2248,6 +2397,22 @@ "integrity": "sha1-hZgoeOIbmOHGZCXgPQF0eI9Wnug=", "dev": true }, + "bytebuffer": { + "version": "3.5.5", + "resolved": "https://registry.npmjs.org/bytebuffer/-/bytebuffer-3.5.5.tgz", + "integrity": "sha1-em+vGhNRSwg/H8+VQcTJv75+f9M=", + "requires": { + "bufferview": "~1", + "long": "~2 >=2.2.3" + }, + "dependencies": { + "long": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/long/-/long-2.4.0.tgz", + "integrity": "sha1-n6GAux2VAM3CnEFWdmoZleH0Uk8=" + } + } + }, "bytes": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.0.0.tgz", @@ -2563,7 +2728,6 @@ "version": "3.2.0", "resolved": "https://registry.npmjs.org/cliui/-/cliui-3.2.0.tgz", "integrity": "sha1-EgYBU3qRbSmUD5NNo7SNWFo5IT0=", - "dev": true, "requires": { "string-width": "^1.0.1", "strip-ansi": "^3.0.1", @@ -2574,7 +2738,6 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-1.0.0.tgz", "integrity": "sha1-754xOG8DGn8NZDr4L95QxFfvAMs=", - "dev": true, "requires": { "number-is-nan": "^1.0.0" } @@ -2583,7 +2746,6 @@ "version": "1.0.2", "resolved": "http://registry.npmjs.org/string-width/-/string-width-1.0.2.tgz", "integrity": "sha1-EYvfW4zcUaKn5w0hHgfisLmxB9M=", - "dev": true, "requires": { "code-point-at": "^1.0.0", "is-fullwidth-code-point": "^1.0.0", @@ -2678,8 +2840,7 @@ "code-point-at": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/code-point-at/-/code-point-at-1.1.0.tgz", - "integrity": "sha1-DQcLTQQ6W+ozovGkDi7bPZpMz3c=", - "dev": true + "integrity": "sha1-DQcLTQQ6W+ozovGkDi7bPZpMz3c=" }, "coffee-script": { "version": "1.12.7", @@ -2734,6 +2895,11 @@ "integrity": "sha1-FopHAXVran9RoSzgyXv6KMCE7WM=", "dev": true }, + "colour": { + "version": "0.7.1", + "resolved": "https://registry.npmjs.org/colour/-/colour-0.7.1.tgz", + "integrity": "sha1-nLFpkX7F0SwHNtPoaFdG3xyt93g=" + }, "combined-stream": { "version": "1.0.7", "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.7.tgz", @@ -3316,8 +3482,7 @@ "decamelize": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-1.2.0.tgz", - "integrity": "sha1-9lNNFRSCabIDUue+4m9QH5oZEpA=", - "dev": true + "integrity": "sha1-9lNNFRSCabIDUue+4m9QH5oZEpA=" }, "decode-uri-component": { "version": "0.2.0", @@ -3564,6 +3729,11 @@ "integrity": "sha1-gGZJMmzqp8qjMG112YXqJ0i6kTw=", "dev": true }, + "diff": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/diff/-/diff-1.4.0.tgz", + "integrity": "sha1-fyjS657nsVqX79ic5j3P2qPMur8=" + }, "diffie-hellman": { "version": "5.0.3", "resolved": "http://registry.npmjs.org/diffie-hellman/-/diffie-hellman-5.0.3.tgz", @@ -3748,7 +3918,6 @@ "version": "3.6.1", "resolved": "https://registry.npmjs.org/duplexify/-/duplexify-3.6.1.tgz", "integrity": "sha512-vM58DwdnKmty+FSPzT14K9JXb90H+j5emaR4KYbr2KTIz00WHGbWOe5ghQTx233ZCLZtrGDALzKwcjEtSt35mA==", - "dev": true, "requires": { "end-of-stream": "^1.0.0", "inherits": "^2.0.1", @@ -3760,7 +3929,6 @@ "version": "1.4.1", "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.1.tgz", "integrity": "sha512-1MkrZNvWTKCaigbn+W15elq2BB/L22nqrSY5DKlo3X6+vclJm8Bb5djXJBmEX6fS3+zCh/F4VBK5Z2KxJt4s2Q==", - "dev": true, "requires": { "once": "^1.4.0" } @@ -3794,6 +3962,14 @@ "safer-buffer": "^2.1.0" } }, + "ecdsa-sig-formatter": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/ecdsa-sig-formatter/-/ecdsa-sig-formatter-1.0.11.tgz", + "integrity": "sha512-nagl3RYrbNv6kQkeJIpt6NJZy8twLB/2vtz6yN9Z4vRKHN4/QZJIEbqohALSgwKdnksuY3k5Addp5lg8sVoVcQ==", + "requires": { + "safe-buffer": "^5.0.1" + } + }, "editions": { "version": "1.3.3", "resolved": "https://registry.npmjs.org/editions/-/editions-1.3.3.tgz", @@ -4071,6 +4247,11 @@ "is-arrayish": "^0.2.1" } }, + "errs": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/errs/-/errs-0.3.2.tgz", + "integrity": "sha1-eYCZstvTfKK8dJ5TinwTB9C1BJk=" + }, "es-abstract": { "version": "1.12.0", "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.12.0.tgz", @@ -4852,6 +5033,14 @@ "schema-utils": "^0.4.5" } }, + "filed": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/filed/-/filed-0.1.0.tgz", + "integrity": "sha1-sPYmRyojZtwRlFN6Tup+eonzxzU=", + "requires": { + "mime": ">= 1.2.6" + } + }, "fill-range": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-4.0.0.tgz", @@ -5072,6 +5261,11 @@ } } }, + "formidable": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/formidable/-/formidable-1.2.1.tgz", + "integrity": "sha512-Fs9VRguL0gqGHkXS5GQiMCr1VhZBxz0JnJs4JmMp/2jL18Fmbzvv7vOFRU+U8TBkHEE/CX1qDXzJplVULgsLeg==" + }, "forwarded": { "version": "0.1.2", "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.1.2.tgz", @@ -5181,8 +5375,7 @@ "ansi-regex": { "version": "2.1.1", "bundled": true, - "dev": true, - "optional": true + "dev": true }, "aproba": { "version": "1.2.0", @@ -5225,8 +5418,7 @@ "code-point-at": { "version": "1.1.0", "bundled": true, - "dev": true, - "optional": true + "dev": true }, "concat-map": { "version": "0.0.1", @@ -5237,8 +5429,7 @@ "console-control-strings": { "version": "1.1.0", "bundled": true, - "dev": true, - "optional": true + "dev": true }, "core-util-is": { "version": "1.0.2", @@ -5355,8 +5546,7 @@ "inherits": { "version": "2.0.3", "bundled": true, - "dev": true, - "optional": true + "dev": true }, "ini": { "version": "1.3.5", @@ -5368,7 +5558,6 @@ "version": "1.0.0", "bundled": true, "dev": true, - "optional": true, "requires": { "number-is-nan": "^1.0.0" } @@ -5391,14 +5580,12 @@ "minimist": { "version": "0.0.8", "bundled": true, - "dev": true, - "optional": true + "dev": true }, "minipass": { "version": "2.3.5", "bundled": true, "dev": true, - "optional": true, "requires": { "safe-buffer": "^5.1.2", "yallist": "^3.0.0" @@ -5417,7 +5604,6 @@ "version": "0.5.1", "bundled": true, "dev": true, - "optional": true, "requires": { "minimist": "0.0.8" } @@ -5511,7 +5697,6 @@ "version": "1.4.0", "bundled": true, "dev": true, - "optional": true, "requires": { "wrappy": "1" } @@ -5597,8 +5782,7 @@ "safe-buffer": { "version": "5.1.2", "bundled": true, - "dev": true, - "optional": true + "dev": true }, "safer-buffer": { "version": "2.1.2", @@ -5634,7 +5818,6 @@ "version": "1.0.2", "bundled": true, "dev": true, - "optional": true, "requires": { "code-point-at": "^1.0.0", "is-fullwidth-code-point": "^1.0.0", @@ -5654,7 +5837,6 @@ "version": "3.0.1", "bundled": true, "dev": true, - "optional": true, "requires": { "ansi-regex": "^2.0.0" } @@ -5698,14 +5880,12 @@ "wrappy": { "version": "1.0.2", "bundled": true, - "dev": true, - "optional": true + "dev": true }, "yallist": { "version": "3.0.3", "bundled": true, - "dev": true, - "optional": true + "dev": true } } }, @@ -5747,6 +5927,15 @@ "swagger-client": "^3.3.1" } }, + "gapitoken": { + "version": "0.1.5", + "resolved": "https://registry.npmjs.org/gapitoken/-/gapitoken-0.1.5.tgz", + "integrity": "sha1-NXf8+1Qmvjp7jrrakmcSKdjMgc4=", + "requires": { + "jws": "~3.0.0", + "request": "^2.54.0" + } + }, "gauge": { "version": "2.7.4", "resolved": "https://registry.npmjs.org/gauge/-/gauge-2.7.4.tgz", @@ -5785,6 +5974,63 @@ } } }, + "gcloud": { + "version": "0.10.0", + "resolved": "https://registry.npmjs.org/gcloud/-/gcloud-0.10.0.tgz", + "integrity": "sha1-hVoms1Mdx7B5FRP/+4n8ZZIfQ+4=", + "requires": { + "duplexify": "^3.1.2", + "extend": "^1.3.0", + "gapitoken": "^0.1.3", + "node-uuid": "^1.4.1", + "protobufjs": "^3.4.0", + "request": "^2.39.0", + "stream-events": "^1.0.1", + "through2": "^0.6.3" + }, + "dependencies": { + "extend": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/extend/-/extend-1.3.0.tgz", + "integrity": "sha1-0VFvsP9WJNLr+RI+odrFoZlABPg=" + }, + "isarray": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz", + "integrity": "sha1-ihis/Kmo9Bd+Cav8YDiTmwXR7t8=" + }, + "node-uuid": { + "version": "1.4.8", + "resolved": "https://registry.npmjs.org/node-uuid/-/node-uuid-1.4.8.tgz", + "integrity": "sha1-sEDrCSOWivq/jTL7HxfxFn/auQc=" + }, + "readable-stream": { + "version": "1.0.34", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-1.0.34.tgz", + "integrity": "sha1-Elgg40vIQtLyqq+v5MKRbuMsFXw=", + "requires": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.1", + "isarray": "0.0.1", + "string_decoder": "~0.10.x" + } + }, + "string_decoder": { + "version": "0.10.31", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-0.10.31.tgz", + "integrity": "sha1-YuIDvEF2bGwoyfyEMB2rHFMQ+pQ=" + }, + "through2": { + "version": "0.6.5", + "resolved": "https://registry.npmjs.org/through2/-/through2-0.6.5.tgz", + "integrity": "sha1-QaucZ7KdVyCQcUEOHXp6lozTrUg=", + "requires": { + "readable-stream": ">=1.0.33-1 <1.1.0-0", + "xtend": ">=4.0.0 <4.1.0-0" + } + } + } + }, "generate-function": { "version": "2.3.1", "resolved": "https://registry.npmjs.org/generate-function/-/generate-function-2.3.1.tgz", @@ -5803,8 +6049,7 @@ "get-stdin": { "version": "4.0.1", "resolved": "https://registry.npmjs.org/get-stdin/-/get-stdin-4.0.1.tgz", - "integrity": "sha1-uWjGsKBDhDJJAui/Gl3zJXmkUP4=", - "dev": true + "integrity": "sha1-uWjGsKBDhDJJAui/Gl3zJXmkUP4=" }, "get-stream": { "version": "3.0.0", @@ -6011,6 +6256,510 @@ "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.1.11.tgz", "integrity": "sha1-Dovf5NHduIVNZOBOp8AOKgJuVlg=" }, + "growl": { + "version": "1.9.2", + "resolved": "https://registry.npmjs.org/growl/-/growl-1.9.2.tgz", + "integrity": "sha1-Dqd0NxXbjY3ixe3hd14bRayFwC8=" + }, + "grpc": { + "version": "1.20.3", + "resolved": "https://registry.npmjs.org/grpc/-/grpc-1.20.3.tgz", + "integrity": "sha512-GsEsi0NVj6usS/xor8pF/xDbDiwZQR59aZl5NUZ59Sy2bdPQFZ3UePr5wevZjHboirRCIQCKRI1cCgvSWUe2ag==", + "requires": { + "lodash.camelcase": "^4.3.0", + "lodash.clone": "^4.5.0", + "nan": "^2.13.2", + "node-pre-gyp": "^0.13.0", + "protobufjs": "^5.0.3" + }, + "dependencies": { + "abbrev": { + "version": "1.1.1", + "bundled": true + }, + "ansi-regex": { + "version": "2.1.1", + "bundled": true + }, + "aproba": { + "version": "1.2.0", + "bundled": true + }, + "are-we-there-yet": { + "version": "1.1.5", + "bundled": true, + "requires": { + "delegates": "^1.0.0", + "readable-stream": "^2.0.6" + } + }, + "ascli": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/ascli/-/ascli-1.0.1.tgz", + "integrity": "sha1-vPpZdKYvGOgcq660lzKrSoj5Brw=", + "requires": { + "colour": "~0.7.1", + "optjs": "~3.2.2" + } + }, + "balanced-match": { + "version": "1.0.0", + "bundled": true + }, + "brace-expansion": { + "version": "1.1.11", + "bundled": true, + "requires": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "bytebuffer": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/bytebuffer/-/bytebuffer-5.0.1.tgz", + "integrity": "sha1-WC7qSxqHO20CCkjVjfhfC7ps/d0=", + "requires": { + "long": "~3" + } + }, + "camelcase": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-2.1.1.tgz", + "integrity": "sha1-fB0W1nmhu+WcoCys7PsBHiAfWh8=" + }, + "chownr": { + "version": "1.1.1", + "bundled": true + }, + "code-point-at": { + "version": "1.1.0", + "bundled": true + }, + "concat-map": { + "version": "0.0.1", + "bundled": true + }, + "console-control-strings": { + "version": "1.1.0", + "bundled": true + }, + "core-util-is": { + "version": "1.0.2", + "bundled": true + }, + "deep-extend": { + "version": "0.6.0", + "bundled": true + }, + "delegates": { + "version": "1.0.0", + "bundled": true + }, + "detect-libc": { + "version": "1.0.3", + "bundled": true + }, + "fs-minipass": { + "version": "1.2.5", + "bundled": true, + "requires": { + "minipass": "^2.2.1" + } + }, + "fs.realpath": { + "version": "1.0.0", + "bundled": true + }, + "gauge": { + "version": "2.7.4", + "bundled": true, + "requires": { + "aproba": "^1.0.3", + "console-control-strings": "^1.0.0", + "has-unicode": "^2.0.0", + "object-assign": "^4.1.0", + "signal-exit": "^3.0.0", + "string-width": "^1.0.1", + "strip-ansi": "^3.0.1", + "wide-align": "^1.1.0" + } + }, + "glob": { + "version": "7.1.4", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.4.tgz", + "integrity": "sha512-hkLPepehmnKk41pUGm3sYxoFs/umurYfYJCerbXEyFIWcAzvpipAgVkBqqT9RBKMGjnq6kMuyYwha6csxbiM1A==", + "requires": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.0.4", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + } + }, + "has-unicode": { + "version": "2.0.1", + "bundled": true + }, + "iconv-lite": { + "version": "0.4.23", + "bundled": true, + "requires": { + "safer-buffer": ">= 2.1.2 < 3" + } + }, + "ignore-walk": { + "version": "3.0.1", + "bundled": true, + "requires": { + "minimatch": "^3.0.4" + } + }, + "inflight": { + "version": "1.0.6", + "bundled": true, + "requires": { + "once": "^1.3.0", + "wrappy": "1" + } + }, + "inherits": { + "version": "2.0.3", + "bundled": true + }, + "ini": { + "version": "1.3.5", + "bundled": true + }, + "is-fullwidth-code-point": { + "version": "1.0.0", + "bundled": true, + "requires": { + "number-is-nan": "^1.0.0" + } + }, + "isarray": { + "version": "1.0.0", + "bundled": true + }, + "long": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/long/-/long-3.2.0.tgz", + "integrity": "sha1-2CG3E4yhy1gcFymQ7xTbIAtcR0s=" + }, + "minimatch": { + "version": "3.0.4", + "bundled": true, + "requires": { + "brace-expansion": "^1.1.7" + } + }, + "minimist": { + "version": "1.2.0", + "bundled": true + }, + "minipass": { + "version": "2.3.5", + "bundled": true, + "requires": { + "safe-buffer": "^5.1.2", + "yallist": "^3.0.0" + } + }, + "minizlib": { + "version": "1.2.1", + "bundled": true, + "requires": { + "minipass": "^2.2.1" + } + }, + "mkdirp": { + "version": "0.5.1", + "bundled": true, + "requires": { + "minimist": "0.0.8" + }, + "dependencies": { + "minimist": { + "version": "0.0.8", + "bundled": true + } + } + }, + "nan": { + "version": "2.14.0", + "resolved": "https://registry.npmjs.org/nan/-/nan-2.14.0.tgz", + "integrity": "sha512-INOFj37C7k3AfaNTtX8RhsTw7qRy7eLET14cROi9+5HAVbbHuIWUHEauBv5qT4Av2tWasiTY1Jw6puUNqRJXQg==" + }, + "needle": { + "version": "2.3.1", + "bundled": true, + "requires": { + "debug": "^4.1.0", + "iconv-lite": "^0.4.4", + "sax": "^1.2.4" + }, + "dependencies": { + "debug": { + "version": "4.1.1", + "bundled": true, + "requires": { + "ms": "^2.1.1" + } + }, + "ms": { + "version": "2.1.1", + "bundled": true + } + } + }, + "node-pre-gyp": { + "version": "0.13.0", + "bundled": true, + "requires": { + "detect-libc": "^1.0.2", + "mkdirp": "^0.5.1", + "needle": "^2.2.1", + "nopt": "^4.0.1", + "npm-packlist": "^1.1.6", + "npmlog": "^4.0.2", + "rc": "^1.2.7", + "rimraf": "^2.6.1", + "semver": "^5.3.0", + "tar": "^4" + } + }, + "nopt": { + "version": "4.0.1", + "bundled": true, + "requires": { + "abbrev": "1", + "osenv": "^0.1.4" + } + }, + "npm-bundled": { + "version": "1.0.6", + "bundled": true + }, + "npm-packlist": { + "version": "1.4.1", + "bundled": true, + "requires": { + "ignore-walk": "^3.0.1", + "npm-bundled": "^1.0.1" + } + }, + "npmlog": { + "version": "4.1.2", + "bundled": true, + "requires": { + "are-we-there-yet": "~1.1.2", + "console-control-strings": "~1.1.0", + "gauge": "~2.7.3", + "set-blocking": "~2.0.0" + } + }, + "number-is-nan": { + "version": "1.0.1", + "bundled": true + }, + "object-assign": { + "version": "4.1.1", + "bundled": true + }, + "once": { + "version": "1.4.0", + "bundled": true, + "requires": { + "wrappy": "1" + } + }, + "os-homedir": { + "version": "1.0.2", + "bundled": true + }, + "os-locale": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/os-locale/-/os-locale-1.4.0.tgz", + "integrity": "sha1-IPnxeuKe00XoveWDsT0gCYA8FNk=", + "requires": { + "lcid": "^1.0.0" + } + }, + "os-tmpdir": { + "version": "1.0.2", + "bundled": true + }, + "osenv": { + "version": "0.1.5", + "bundled": true, + "requires": { + "os-homedir": "^1.0.0", + "os-tmpdir": "^1.0.0" + } + }, + "path-is-absolute": { + "version": "1.0.1", + "bundled": true + }, + "process-nextick-args": { + "version": "2.0.0", + "bundled": true + }, + "protobufjs": { + "version": "5.0.3", + "resolved": "https://registry.npmjs.org/protobufjs/-/protobufjs-5.0.3.tgz", + "integrity": "sha512-55Kcx1MhPZX0zTbVosMQEO5R6/rikNXd9b6RQK4KSPcrSIIwoXTtebIczUrXlwaSrbz4x8XUVThGPob1n8I4QA==", + "requires": { + "ascli": "~1", + "bytebuffer": "~5", + "glob": "^7.0.5", + "yargs": "^3.10.0" + } + }, + "rc": { + "version": "1.2.8", + "bundled": true, + "requires": { + "deep-extend": "^0.6.0", + "ini": "~1.3.0", + "minimist": "^1.2.0", + "strip-json-comments": "~2.0.1" + } + }, + "readable-stream": { + "version": "2.3.6", + "bundled": true, + "requires": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, + "rimraf": { + "version": "2.6.3", + "bundled": true, + "requires": { + "glob": "^7.1.3" + }, + "dependencies": { + "glob": { + "version": "7.1.3", + "bundled": true, + "requires": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.0.4", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + } + } + } + }, + "safe-buffer": { + "version": "5.1.2", + "bundled": true + }, + "safer-buffer": { + "version": "2.1.2", + "bundled": true + }, + "sax": { + "version": "1.2.4", + "bundled": true + }, + "semver": { + "version": "5.7.0", + "bundled": true + }, + "set-blocking": { + "version": "2.0.0", + "bundled": true + }, + "signal-exit": { + "version": "3.0.2", + "bundled": true + }, + "string-width": { + "version": "1.0.2", + "bundled": true, + "requires": { + "code-point-at": "^1.0.0", + "is-fullwidth-code-point": "^1.0.0", + "strip-ansi": "^3.0.0" + } + }, + "string_decoder": { + "version": "1.1.1", + "bundled": true, + "requires": { + "safe-buffer": "~5.1.0" + } + }, + "strip-ansi": { + "version": "3.0.1", + "bundled": true, + "requires": { + "ansi-regex": "^2.0.0" + } + }, + "strip-json-comments": { + "version": "2.0.1", + "bundled": true + }, + "tar": { + "version": "4.4.8", + "bundled": true, + "requires": { + "chownr": "^1.1.1", + "fs-minipass": "^1.2.5", + "minipass": "^2.3.4", + "minizlib": "^1.1.1", + "mkdirp": "^0.5.0", + "safe-buffer": "^5.1.2", + "yallist": "^3.0.2" + } + }, + "util-deprecate": { + "version": "1.0.2", + "bundled": true + }, + "wide-align": { + "version": "1.1.3", + "bundled": true, + "requires": { + "string-width": "^1.0.2 || 2" + } + }, + "wrappy": { + "version": "1.0.2", + "bundled": true + }, + "yallist": { + "version": "3.0.3", + "bundled": true + }, + "yargs": { + "version": "3.32.0", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-3.32.0.tgz", + "integrity": "sha1-AwiOnr+edWtpdRYR0qXvWRSCyZU=", + "requires": { + "camelcase": "^2.0.1", + "cliui": "^3.0.3", + "decamelize": "^1.1.1", + "os-locale": "^1.4.0", + "string-width": "^1.0.1", + "window-size": "^0.1.4", + "y18n": "^3.2.0" + } + } + } + }, "gulp": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/gulp/-/gulp-4.0.0.tgz", @@ -7214,8 +7963,7 @@ "ip": { "version": "1.1.5", "resolved": "https://registry.npmjs.org/ip/-/ip-1.1.5.tgz", - "integrity": "sha1-vd7XARQpCCjAoDnnLvJfWq7ENUo=", - "dev": true + "integrity": "sha1-vd7XARQpCCjAoDnnLvJfWq7ENUo=" }, "ip-regex": { "version": "2.1.0", @@ -7363,7 +8111,6 @@ "version": "1.0.2", "resolved": "https://registry.npmjs.org/is-finite/-/is-finite-1.0.2.tgz", "integrity": "sha1-zGZ3aVYCvlUO8R6LSqYwU0K20Ko=", - "dev": true, "requires": { "number-is-nan": "^1.0.0" } @@ -7599,6 +8346,27 @@ "resolved": "https://registry.npmjs.org/isstream/-/isstream-0.1.2.tgz", "integrity": "sha1-R+Y/evVa+m+S4VAOaQ64uFKcCZo=" }, + "jade": { + "version": "0.26.3", + "resolved": "https://registry.npmjs.org/jade/-/jade-0.26.3.tgz", + "integrity": "sha1-jxDXl32NefL2/4YqgbBRPMslaGw=", + "requires": { + "commander": "0.6.1", + "mkdirp": "0.3.0" + }, + "dependencies": { + "commander": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/commander/-/commander-0.6.1.tgz", + "integrity": "sha1-+mihT2qUXVTbvlDYzbMyDp47GgY=" + }, + "mkdirp": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.3.0.tgz", + "integrity": "sha1-G79asbqCevI1dRQ0kEJkVfSB/h4=" + } + } + }, "jasmine": { "version": "2.99.0", "resolved": "https://registry.npmjs.org/jasmine/-/jasmine-2.99.0.tgz", @@ -7684,6 +8452,11 @@ "uuid": "^3.2.1" } }, + "jmespath": { + "version": "0.15.0", + "resolved": "https://registry.npmjs.org/jmespath/-/jmespath-0.15.0.tgz", + "integrity": "sha1-o/Iiqarp+Wb10nx5ZRDigJF2Qhc=" + }, "js-base64": { "version": "2.5.0", "resolved": "https://registry.npmjs.org/js-base64/-/js-base64-2.5.0.tgz", @@ -7814,6 +8587,32 @@ "integrity": "sha1-h/zPrv/AtozRnVX2cilD+SnqNeo=", "dev": true }, + "jwa": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/jwa/-/jwa-1.0.2.tgz", + "integrity": "sha1-/Xlgnx53Limdzo3bdtAGWd2DUR8=", + "requires": { + "base64url": "~0.0.4", + "buffer-equal-constant-time": "^1.0.1", + "ecdsa-sig-formatter": "^1.0.0" + }, + "dependencies": { + "base64url": { + "version": "0.0.6", + "resolved": "https://registry.npmjs.org/base64url/-/base64url-0.0.6.tgz", + "integrity": "sha1-lZezazMNscQkdzIuqH6oAnSZuCs=" + } + } + }, + "jws": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/jws/-/jws-3.0.0.tgz", + "integrity": "sha1-2l8meJfdTpz4E3l52zP8VKPAVBg=", + "requires": { + "base64url": "~1.0.4", + "jwa": "~1.0.0" + } + }, "karma": { "version": "4.0.1", "resolved": "https://registry.npmjs.org/karma/-/karma-4.0.1.tgz", @@ -8052,6 +8851,15 @@ "type-check": "~0.3.2" } }, + "liboneandone": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/liboneandone/-/liboneandone-1.2.0.tgz", + "integrity": "sha512-EB6Ak9qw+U4HAOnKqPtatxQ9pLclvtsBsggrvOuD4zclJ5xOeEASojsLKEC3O8KJ1Q4obE2JHhOeDuqWXvkoUQ==", + "requires": { + "mocha": "^2.5.3", + "request": "^2.74.0" + } + }, "liftoff": { "version": "2.5.0", "resolved": "https://registry.npmjs.org/liftoff/-/liftoff-2.5.0.tgz", @@ -8186,6 +8994,16 @@ "integrity": "sha1-+6HEUkwZ7ppfgTa0YJ8BfPTe1pI=", "dev": true }, + "lodash.camelcase": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/lodash.camelcase/-/lodash.camelcase-4.3.0.tgz", + "integrity": "sha1-soqmKIorn8ZRA1x3EfZathkDMaY=" + }, + "lodash.clone": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/lodash.clone/-/lodash.clone-4.5.0.tgz", + "integrity": "sha1-GVhwRQ9aExkkeN9Lw9I9LeoZB7Y=" + }, "lodash.clonedeep": { "version": "4.5.0", "resolved": "https://registry.npmjs.org/lodash.clonedeep/-/lodash.clonedeep-4.5.0.tgz", @@ -8688,6 +9506,152 @@ } } }, + "loopback-component-storage": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/loopback-component-storage/-/loopback-component-storage-3.6.0.tgz", + "integrity": "sha512-BC6iUhgxeDXrBqp4x7Y4Im4WKds1JSN++wRczyXqoq87ceMDFZ9zqHlElZ9lcO9rklqIaixACwEUWI3HiAMfYg==", + "requires": { + "async": "^2.6.1", + "debug": "^3.1.0", + "formidable": "^1.2.1", + "pkgcloud": "^1.5.0", + "strong-globalize": "^4.1.1", + "uuid": "^3.2.1" + }, + "dependencies": { + "async": { + "version": "2.6.2", + "resolved": "https://registry.npmjs.org/async/-/async-2.6.2.tgz", + "integrity": "sha512-H1qVYh1MYhEEFLsP97cVKqCGo7KfCyTt6uEWqsTBr9SO84oK9Uwbyd/yCW+6rKJLHksBNUVWZDAjfS+Ccx0Bbg==", + "requires": { + "lodash": "^4.17.11" + } + }, + "cross-spawn": { + "version": "6.0.5", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-6.0.5.tgz", + "integrity": "sha512-eTVLrBSt7fjbDygz805pMnstIs2VTBNkRm0qxZd+M7A5XDdxVRWO5MxGBXZhjY4cqLYLdtrGqRf8mBPmzwSpWQ==", + "requires": { + "nice-try": "^1.0.4", + "path-key": "^2.0.1", + "semver": "^5.5.0", + "shebang-command": "^1.2.0", + "which": "^1.2.9" + } + }, + "debug": { + "version": "3.2.6", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.6.tgz", + "integrity": "sha512-mel+jf7nrtEl5Pn1Qx46zARXKDpBbvzezse7p7LqINmdoIk8PYP5SySaxEmYv6TZ0JyEKA1hsCId6DIhgITtWQ==", + "requires": { + "ms": "^2.1.1" + } + }, + "execa": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/execa/-/execa-1.0.0.tgz", + "integrity": "sha512-adbxcyWV46qiHyvSp50TKt05tB4tK3HcmF7/nxfAdhnox83seTDbwnaqKO4sXRy7roHAIFqJP/Rw/AuEbX61LA==", + "requires": { + "cross-spawn": "^6.0.0", + "get-stream": "^4.0.0", + "is-stream": "^1.1.0", + "npm-run-path": "^2.0.0", + "p-finally": "^1.0.0", + "signal-exit": "^3.0.0", + "strip-eof": "^1.0.0" + } + }, + "get-stream": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-4.1.0.tgz", + "integrity": "sha512-GMat4EJ5161kIy2HevLlr4luNjBgvmj413KaQA7jt4V8B4RDsfpHk7WQ9GVqfYyyx8OS/L66Kox+rJRNklLK7w==", + "requires": { + "pump": "^3.0.0" + } + }, + "globalize": { + "version": "1.4.2", + "resolved": "https://registry.npmjs.org/globalize/-/globalize-1.4.2.tgz", + "integrity": "sha512-IfKeYI5mAITBmT5EnH8kSQB5uGson4Fkj2XtTpyEbIS7IHNfLHoeTyLJ6tfjiKC6cJXng3IhVurDk5C7ORqFhQ==", + "requires": { + "cldrjs": "^0.5.0" + } + }, + "invert-kv": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/invert-kv/-/invert-kv-2.0.0.tgz", + "integrity": "sha512-wPVv/y/QQ/Uiirj/vh3oP+1Ww+AWehmi1g5fFWGPF6IpCBCDVrhgHRMvrLfdYcwDh3QJbGXDW4JAuzxElLSqKA==" + }, + "lcid": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/lcid/-/lcid-2.0.0.tgz", + "integrity": "sha512-avPEb8P8EGnwXKClwsNUgryVjllcRqtMYa49NTsbQagYuT1DcXnl1915oxWjoyGrXR6zH/Y0Zc96xWsPcoDKeA==", + "requires": { + "invert-kv": "^2.0.0" + } + }, + "mem": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/mem/-/mem-4.3.0.tgz", + "integrity": "sha512-qX2bG48pTqYRVmDB37rn/6PT7LcR8T7oAX3bf99u1Tt1nzxYfxkgqDwUwolPlXweM0XzBOBFzSx4kfp7KP1s/w==", + "requires": { + "map-age-cleaner": "^0.1.1", + "mimic-fn": "^2.0.0", + "p-is-promise": "^2.0.0" + } + }, + "mimic-fn": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz", + "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==" + }, + "ms": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.1.tgz", + "integrity": "sha512-tgp+dl5cGk28utYktBsrFqA7HKgrhgPsg6Z/EfhWI4gl1Hwq8B/GmY/0oXZ6nF8hDVesS/FpnYaD/kOWhYQvyg==" + }, + "os-locale": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/os-locale/-/os-locale-3.1.0.tgz", + "integrity": "sha512-Z8l3R4wYWM40/52Z+S265okfFj8Kt2cC2MKY+xNi3kFs+XGI7WXu/I309QQQYbRW4ijiZ+yxs9pqEhJh0DqW3Q==", + "requires": { + "execa": "^1.0.0", + "lcid": "^2.0.0", + "mem": "^4.0.0" + } + }, + "p-is-promise": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/p-is-promise/-/p-is-promise-2.1.0.tgz", + "integrity": "sha512-Y3W0wlRPK8ZMRbNq97l4M5otioeA5lm1z7bkNkxCka8HSPjR0xRWmpCmc9utiaLP9Jb1eD8BgeIxTW4AIF45Pg==" + }, + "strong-globalize": { + "version": "4.1.3", + "resolved": "https://registry.npmjs.org/strong-globalize/-/strong-globalize-4.1.3.tgz", + "integrity": "sha512-SJegV7w5D4AodEspZJtJ7rls3fmi+Zc0PdyJCqBsg4RN9B8TC80/uAI2fikC+s1Jp9FLvr2vDX8f0Fqc62M4OA==", + "requires": { + "accept-language": "^3.0.18", + "debug": "^4.1.1", + "globalize": "^1.4.2", + "lodash": "^4.17.4", + "md5": "^2.2.1", + "mkdirp": "^0.5.1", + "os-locale": "^3.1.0", + "yamljs": "^0.3.0" + }, + "dependencies": { + "debug": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz", + "integrity": "sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==", + "requires": { + "ms": "^2.1.1" + } + } + } + } + } + }, "loopback-connector": { "version": "4.5.1", "resolved": "https://registry.npmjs.org/loopback-connector/-/loopback-connector-4.5.1.tgz", @@ -9475,8 +10439,7 @@ "map-obj": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/map-obj/-/map-obj-1.0.1.tgz", - "integrity": "sha1-2TPOuSBdgr3PSIb2dCvcK03qFG0=", - "dev": true + "integrity": "sha1-2TPOuSBdgr3PSIb2dCvcK03qFG0=" }, "map-stream": { "version": "0.0.7", @@ -9800,6 +10763,76 @@ } } }, + "mocha": { + "version": "2.5.3", + "resolved": "https://registry.npmjs.org/mocha/-/mocha-2.5.3.tgz", + "integrity": "sha1-FhvlvetJZ3HrmzV0UFC2IrWu/Fg=", + "requires": { + "commander": "2.3.0", + "debug": "2.2.0", + "diff": "1.4.0", + "escape-string-regexp": "1.0.2", + "glob": "3.2.11", + "growl": "1.9.2", + "jade": "0.26.3", + "mkdirp": "0.5.1", + "supports-color": "1.2.0", + "to-iso-string": "0.0.2" + }, + "dependencies": { + "commander": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-2.3.0.tgz", + "integrity": "sha1-/UMOiJgy7DU7ms0d4hfBHLPu+HM=" + }, + "debug": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.2.0.tgz", + "integrity": "sha1-+HBX6ZWxofauaklgZkE3vFbwOdo=", + "requires": { + "ms": "0.7.1" + } + }, + "escape-string-regexp": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.2.tgz", + "integrity": "sha1-Tbwv5nTnGUnK8/smlc5/LcHZqNE=" + }, + "glob": { + "version": "3.2.11", + "resolved": "https://registry.npmjs.org/glob/-/glob-3.2.11.tgz", + "integrity": "sha1-Spc/Y1uRkPcV0QmH1cAP0oFevj0=", + "requires": { + "inherits": "2", + "minimatch": "0.3" + } + }, + "lru-cache": { + "version": "2.7.3", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-2.7.3.tgz", + "integrity": "sha1-bUUk6LlV+V1PW1iFHOId1y+06VI=" + }, + "minimatch": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-0.3.0.tgz", + "integrity": "sha1-J12O2qxPG7MyZHIInnlJyDlGmd0=", + "requires": { + "lru-cache": "2", + "sigmund": "~1.0.0" + } + }, + "ms": { + "version": "0.7.1", + "resolved": "https://registry.npmjs.org/ms/-/ms-0.7.1.tgz", + "integrity": "sha1-nNE8A62/8ltl7/3nzoZO6VIBcJg=" + }, + "supports-color": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-1.2.0.tgz", + "integrity": "sha1-/x7R5hFp0Gs88tWI4YixjYhH4X4=" + } + } + }, "move-concurrently": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/move-concurrently/-/move-concurrently-1.0.1.tgz", @@ -10848,8 +11881,7 @@ "console-control-strings": { "version": "1.1.0", "bundled": true, - "dev": true, - "optional": true + "dev": true }, "core-util-is": { "version": "1.0.2", @@ -11009,7 +12041,6 @@ "version": "2.3.5", "bundled": true, "dev": true, - "optional": true, "requires": { "safe-buffer": "^5.1.2", "yallist": "^3.0.0" @@ -11028,7 +12059,6 @@ "version": "0.5.1", "bundled": true, "dev": true, - "optional": true, "requires": { "minimist": "0.0.8" } @@ -11122,7 +12152,6 @@ "version": "1.4.0", "bundled": true, "dev": true, - "optional": true, "requires": { "wrappy": "1" } @@ -11208,8 +12237,7 @@ "safe-buffer": { "version": "5.1.2", "bundled": true, - "dev": true, - "optional": true + "dev": true }, "safer-buffer": { "version": "2.1.2", @@ -11245,7 +12273,6 @@ "version": "1.0.2", "bundled": true, "dev": true, - "optional": true, "requires": { "code-point-at": "^1.0.0", "is-fullwidth-code-point": "^1.0.0", @@ -11309,14 +12336,12 @@ "wrappy": { "version": "1.0.2", "bundled": true, - "dev": true, - "optional": true + "dev": true }, "yallist": { "version": "3.0.3", "bundled": true, - "dev": true, - "optional": true + "dev": true } } }, @@ -11434,8 +12459,7 @@ "number-is-nan": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/number-is-nan/-/number-is-nan-1.0.1.tgz", - "integrity": "sha1-CXtgK1NCKlIsGvuHkDGDNpQaAR0=", - "dev": true + "integrity": "sha1-CXtgK1NCKlIsGvuHkDGDNpQaAR0=" }, "oauth-sign": { "version": "0.9.0", @@ -11651,6 +12675,11 @@ "resolved": "https://registry.npmjs.org/options/-/options-0.0.6.tgz", "integrity": "sha1-7CLTEoBrtT5zF3Pnza788cZDEo8=" }, + "optjs": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/optjs/-/optjs-3.2.2.tgz", + "integrity": "sha1-aabOicRCpEQDFBrS+bNwvVu29O4=" + }, "ordered-read-streams": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/ordered-read-streams/-/ordered-read-streams-1.0.1.tgz", @@ -12022,6 +13051,93 @@ "find-up": "^2.1.0" } }, + "pkgcloud": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/pkgcloud/-/pkgcloud-1.7.0.tgz", + "integrity": "sha512-F4zfjozUAPeKgUKxuVvdGHjFMXEU971tiqtxGNgjQBuNtBNIOwUE0yWpZ/MgwAZEjI8xeQaIcT8JMxYkcHM3VA==", + "requires": { + "async": "^2.6.1", + "aws-sdk": "^2.382.0", + "errs": "^0.3.2", + "eventemitter2": "^5.0.1", + "fast-json-patch": "0.5.x", + "filed": "^0.1.0", + "gcloud": "^0.10.0", + "grpc": "^1.14.1", + "ip": "^1.1.5", + "liboneandone": "^1.2.0", + "lodash": "^4.17.10", + "mime": "1.4.1", + "qs": "^6.5.2", + "request": "^2.88.0", + "through2": "0.6.x", + "url-join": "0.0.x", + "xml2js": "0.1.x" + }, + "dependencies": { + "async": { + "version": "2.6.2", + "resolved": "https://registry.npmjs.org/async/-/async-2.6.2.tgz", + "integrity": "sha512-H1qVYh1MYhEEFLsP97cVKqCGo7KfCyTt6uEWqsTBr9SO84oK9Uwbyd/yCW+6rKJLHksBNUVWZDAjfS+Ccx0Bbg==", + "requires": { + "lodash": "^4.17.11" + } + }, + "fast-json-patch": { + "version": "0.5.7", + "resolved": "https://registry.npmjs.org/fast-json-patch/-/fast-json-patch-0.5.7.tgz", + "integrity": "sha1-taj0nSWWJFlu+YuHLz/aiVtNhmU=" + }, + "isarray": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz", + "integrity": "sha1-ihis/Kmo9Bd+Cav8YDiTmwXR7t8=" + }, + "qs": { + "version": "6.7.0", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.7.0.tgz", + "integrity": "sha512-VCdBRNFTX1fyE7Nb6FYoURo/SPe62QCaAyzJvUjwRaIsc+NePBEniHlvxFmmX56+HZphIGtV0XeCirBtpDrTyQ==" + }, + "readable-stream": { + "version": "1.0.34", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-1.0.34.tgz", + "integrity": "sha1-Elgg40vIQtLyqq+v5MKRbuMsFXw=", + "requires": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.1", + "isarray": "0.0.1", + "string_decoder": "~0.10.x" + } + }, + "string_decoder": { + "version": "0.10.31", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-0.10.31.tgz", + "integrity": "sha1-YuIDvEF2bGwoyfyEMB2rHFMQ+pQ=" + }, + "through2": { + "version": "0.6.5", + "resolved": "https://registry.npmjs.org/through2/-/through2-0.6.5.tgz", + "integrity": "sha1-QaucZ7KdVyCQcUEOHXp6lozTrUg=", + "requires": { + "readable-stream": ">=1.0.33-1 <1.1.0-0", + "xtend": ">=4.0.0 <4.1.0-0" + } + }, + "url-join": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/url-join/-/url-join-0.0.1.tgz", + "integrity": "sha1-HbSK1CLTQCRpqH99l73r/k+x48g=" + }, + "xml2js": { + "version": "0.1.14", + "resolved": "https://registry.npmjs.org/xml2js/-/xml2js-0.1.14.tgz", + "integrity": "sha1-UnTmf1pkxfkpdM2FE54DMq3GuQw=", + "requires": { + "sax": ">=0.1.1" + } + } + } + }, "platform": { "version": "1.3.5", "resolved": "https://registry.npmjs.org/platform/-/platform-1.3.5.tgz", @@ -12285,6 +13401,15 @@ "integrity": "sha1-mEcocL8igTL8vdhoEputEsPAKeM=", "dev": true }, + "protobufjs": { + "version": "3.8.2", + "resolved": "https://registry.npmjs.org/protobufjs/-/protobufjs-3.8.2.tgz", + "integrity": "sha1-vIJuNMOvRpfo0K96Zp5NYSrtzRc=", + "requires": { + "ascli": "~0.3", + "bytebuffer": "~3 >=3.5" + } + }, "proxy-addr": { "version": "2.0.4", "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.4.tgz", @@ -13502,6 +14627,11 @@ "nanoid": "^2.0.0" } }, + "sigmund": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/sigmund/-/sigmund-1.0.1.tgz", + "integrity": "sha1-P/IfGYytIXX587eBhT/ZTQ0ZtZA=" + }, "signal-exit": { "version": "3.0.2", "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.2.tgz", @@ -14152,6 +15282,14 @@ "stream-shift": "^1.0.0" } }, + "stream-events": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/stream-events/-/stream-events-1.0.5.tgz", + "integrity": "sha512-E1GUzBSgvct8Jsb3v2X15pjzN1tYebtbLaMg+eBOUOAxgbLoSbT2NS91ckc5lJD1KfLjId+jXJRgo0qnV5Nerg==", + "requires": { + "stubs": "^3.0.0" + } + }, "stream-exhaust": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/stream-exhaust/-/stream-exhaust-1.0.2.tgz", @@ -14211,8 +15349,7 @@ "stream-shift": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/stream-shift/-/stream-shift-1.0.0.tgz", - "integrity": "sha1-1cdSgl5TZ+eG944Y5EXqIjoVWVI=", - "dev": true + "integrity": "sha1-1cdSgl5TZ+eG944Y5EXqIjoVWVI=" }, "streamroller": { "version": "1.0.3", @@ -14303,7 +15440,6 @@ "version": "3.0.1", "resolved": "http://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", "integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=", - "dev": true, "requires": { "ansi-regex": "^2.0.0" } @@ -14571,6 +15707,11 @@ } } }, + "stubs": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/stubs/-/stubs-3.0.0.tgz", + "integrity": "sha1-6NK6H6nJBXAwPAMLaQD31fiavls=" + }, "style-loader": { "version": "0.23.1", "resolved": "https://registry.npmjs.org/style-loader/-/style-loader-0.23.1.tgz", @@ -15015,6 +16156,11 @@ "integrity": "sha1-3F5pjL0HkmW8c+A3doGk5Og/YW4=", "dev": true }, + "to-iso-string": { + "version": "0.0.2", + "resolved": "https://registry.npmjs.org/to-iso-string/-/to-iso-string-0.0.2.tgz", + "integrity": "sha1-TcGeZk38y+Jb2NtQiwDG2hWCVdE=" + }, "to-object-path": { "version": "0.3.0", "resolved": "https://registry.npmjs.org/to-object-path/-/to-object-path-0.3.0.tgz", @@ -16622,6 +17768,11 @@ "string-width": "^2.1.1" } }, + "window-size": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/window-size/-/window-size-0.1.4.tgz", + "integrity": "sha1-+OGqHuWlPsW/FR/6CXQqatdpeHY=" + }, "word-count": { "version": "0.2.2", "resolved": "https://registry.npmjs.org/word-count/-/word-count-0.2.2.tgz", @@ -16646,7 +17797,6 @@ "version": "2.1.0", "resolved": "http://registry.npmjs.org/wrap-ansi/-/wrap-ansi-2.1.0.tgz", "integrity": "sha1-2Pw9KE3QV5T+hJc8rs3Rz4JP3YU=", - "dev": true, "requires": { "string-width": "^1.0.1", "strip-ansi": "^3.0.1" @@ -16656,7 +17806,6 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-1.0.0.tgz", "integrity": "sha1-754xOG8DGn8NZDr4L95QxFfvAMs=", - "dev": true, "requires": { "number-is-nan": "^1.0.0" } @@ -16665,7 +17814,6 @@ "version": "1.0.2", "resolved": "http://registry.npmjs.org/string-width/-/string-width-1.0.2.tgz", "integrity": "sha1-EYvfW4zcUaKn5w0hHgfisLmxB9M=", - "dev": true, "requires": { "code-point-at": "^1.0.0", "is-fullwidth-code-point": "^1.0.0", @@ -16786,8 +17934,7 @@ "y18n": { "version": "3.2.1", "resolved": "https://registry.npmjs.org/y18n/-/y18n-3.2.1.tgz", - "integrity": "sha1-bRX7qITAhnnA136I53WegR4H+kE=", - "dev": true + "integrity": "sha1-bRX7qITAhnnA136I53WegR4H+kE=" }, "yallist": { "version": "2.1.2", diff --git a/package.json b/package.json index fa63013f7..ed5f51057 100644 --- a/package.json +++ b/package.json @@ -16,6 +16,7 @@ "loopback": "^3.25.0", "loopback-boot": "^2.27.1", "loopback-component-explorer": "^6.3.1", + "loopback-component-storage": "^3.6.0", "loopback-connector-mysql": "^5.3.1", "loopback-connector-remote": "^3.4.1", "loopback-context": "^3.4.0",