diff --git a/db/changes/10510-december/00-mdbApp.sql b/db/changes/10510-december/00-mdbApp.sql new file mode 100644 index 000000000..3202e3f08 --- /dev/null +++ b/db/changes/10510-december/00-mdbApp.sql @@ -0,0 +1,11 @@ +CREATE TABLE `vn`.`mdbApp` ( + `app` varchar(100) COLLATE utf8mb3_unicode_ci NOT NULL, + `baselineBranchFk` varchar(255) COLLATE utf8mb3_unicode_ci DEFAULT NULL, + `userFk` int(10) unsigned DEFAULT NULL, + `locked` datetime DEFAULT NULL, + PRIMARY KEY (`app`), + KEY `mdbApp_FK` (`userFk`), + KEY `mdbApp_FK_1` (`baselineBranchFk`), + CONSTRAINT `mdbApp_FK` FOREIGN KEY (`userFk`) REFERENCES `account`.`user` (`id`) ON DELETE SET NULL ON UPDATE CASCADE, + CONSTRAINT `mdbApp_FK_1` FOREIGN KEY (`baselineBranchFk`) REFERENCES `mdbBranch` (`name`) ON UPDATE CASCADE +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb3 COLLATE=utf8mb3_unicode_ci diff --git a/db/changes/225001/00-aclMdbApp.sql b/db/changes/225001/00-aclMdbApp.sql new file mode 100644 index 000000000..b5b60546c --- /dev/null +++ b/db/changes/225001/00-aclMdbApp.sql @@ -0,0 +1,4 @@ +INSERT INTO `salix`.`ACL` (`model`, `property`, `accessType`, `permission`, `principalType`, `principalId`) + VALUES + ('MdbApp', 'lock', 'WRITE', 'ALLOW', 'ROLE', 'developer'), + ('MdbApp', 'unlock', 'WRITE', 'ALLOW', 'ROLE', 'developer'); diff --git a/db/dump/fixtures.sql b/db/dump/fixtures.sql index c4ce78658..485802a34 100644 --- a/db/dump/fixtures.sql +++ b/db/dump/fixtures.sql @@ -2745,6 +2745,10 @@ INSERT INTO `vn`.`osTicketConfig` (`id`, `host`, `user`, `password`, `oldStatus` VALUES (0, 'http://localhost:56596/scp', 'ostadmin', 'Admin1', 'open', 3, 60, 'Este CAU se ha cerrado automáticamente. Si el problema persiste responda a este mensaje.', 'localhost', 'osticket', 'osticket', 40003, 'reply', 1, 'all'); +INSERT INTO `vn`.`mdbApp` (`app`, `baselineBranchFk`, `userFk`, `locked`) + VALUES + ('foo', 'master', NULL, NULL), + ('bar', 'test', 9, util.VN_NOW()); INSERT INTO `vn`.`ticketLog` (`id`, `originFk`, `userFk`, `action`, `changedModel`, `oldInstance`, `newInstance`, `changedModelId`) VALUES (1, 1, 9, 'insert', 'Ticket', '{}', '{"clientFk":1, "nickname": "Bat cave"}', 1); diff --git a/loopback/locale/en.json b/loopback/locale/en.json index 4885e34ec..a406b55a5 100644 --- a/loopback/locale/en.json +++ b/loopback/locale/en.json @@ -143,6 +143,7 @@ "Email verify": "Email verify", "Ticket merged": "Ticket [{{originId}}]({{{originFullPath}}}) ({{{originDated}}}) merged with [{{destinationId}}]({{{destinationFullPath}}}) ({{{destinationDated}}})", "Sale(s) blocked, please contact production": "Sale(s) blocked, please contact production", + "App locked": "App locked by user {{userId}}", "The sales of the receiver ticket can't be modified": "The sales of the receiver ticket can't be modified", "Receipt's bank was not found": "Receipt's bank was not found", "This receipt was not compensated": "This receipt was not compensated", diff --git a/loopback/locale/es.json b/loopback/locale/es.json index a500ff550..ea83b36c4 100644 --- a/loopback/locale/es.json +++ b/loopback/locale/es.json @@ -246,9 +246,11 @@ "Already has this status": "Ya tiene este estado", "There aren't records for this week": "No existen registros para esta semana", "Empty data source": "Origen de datos vacio", + "App locked": "Aplicación bloqueada por el usuario {{userId}}", "Email verify": "Correo de verificación", "Landing cannot be lesser than shipment": "Landing cannot be lesser than shipment", - "Receipt's bank was not found": "No se encontró el banco del recibo", - "This receipt was not compensated": "Este recibo no ha sido compensado", - "Client's email was not found": "No se encontró el email del cliente" -} + "Receipt's bank was not found": "No se encontró el banco del recibo", + "This receipt was not compensated": "Este recibo no ha sido compensado", + "Client's email was not found": "No se encontró el email del cliente", + "Aplicación bloqueada por el usuario 9": "Aplicación bloqueada por el usuario 9" +} \ No newline at end of file diff --git a/modules/mdb/back/methods/mdbApp/lock.js b/modules/mdb/back/methods/mdbApp/lock.js new file mode 100644 index 000000000..98e61fb53 --- /dev/null +++ b/modules/mdb/back/methods/mdbApp/lock.js @@ -0,0 +1,66 @@ +const UserError = require('vn-loopback/util/user-error'); + +module.exports = Self => { + Self.remoteMethodCtx('lock', { + description: 'Lock an app for the user', + accessType: 'WRITE', + accepts: [ + { + arg: 'appName', + type: 'string', + required: true, + description: 'The app name', + http: {source: 'path'} + + } + ], + returns: { + type: ['object'], + root: true + }, + http: { + path: `/:appName/lock`, + verb: 'POST' + } + }); + + Self.lock = async(ctx, appName, options) => { + const models = Self.app.models; + const userId = ctx.req.accessToken.userId; + const myOptions = {}; + const $t = ctx.req.__; // $translate + + let tx; + + if (typeof options == 'object') + Object.assign(myOptions, options); + + if (!myOptions.transaction) { + tx = await Self.beginTransaction({}); + myOptions.transaction = tx; + } + + try { + const mdbApp = await models.MdbApp.findById(appName, {fields: ['app', 'locked', 'userFk']}, myOptions); + + if (mdbApp.locked) { + throw new UserError($t('App locked', { + userId: mdbApp.userFk + })); + } + + const updatedMdbApp = await mdbApp.updateAttributes({ + userFk: userId, + locked: new Date() + }, myOptions); + + if (tx) await tx.commit(); + + return updatedMdbApp; + } catch (e) { + if (tx) await tx.rollback(); + + throw e; + } + }; +}; diff --git a/modules/mdb/back/methods/mdbApp/specs/lock.spec.js b/modules/mdb/back/methods/mdbApp/specs/lock.spec.js new file mode 100644 index 000000000..162d0490a --- /dev/null +++ b/modules/mdb/back/methods/mdbApp/specs/lock.spec.js @@ -0,0 +1,51 @@ +const models = require('vn-loopback/server/server').models; + +describe('MdbApp lock()', () => { + it('should throw an error if the app is already locked', async() => { + const tx = await models.MdbApp.beginTransaction({}); + let error; + + try { + const options = {transaction: tx}; + const appName = 'bar'; + const developerId = 9; + const ctx = { + req: { + accessToken: {userId: developerId}, + __: () => {} + } + }; + + const result = await models.MdbApp.lock(ctx, appName, options); + + expect(result.locked).not.toBeNull(); + + await tx.rollback(); + } catch (e) { + error = e; + + await tx.rollback(); + } + + expect(error).toBeDefined(); + }); + + it(`should lock a mdb `, async() => { + const tx = await models.MdbApp.beginTransaction({}); + + try { + const options = {transaction: tx}; + const appName = 'foo'; + const developerId = 9; + const ctx = {req: {accessToken: {userId: developerId}}}; + + const result = await models.MdbApp.lock(ctx, appName, options); + + expect(result.locked).not.toBeNull(); + + await tx.rollback(); + } catch (e) { + await tx.rollback(); + } + }); +}); diff --git a/modules/mdb/back/methods/mdbApp/specs/unlock.spec.js b/modules/mdb/back/methods/mdbApp/specs/unlock.spec.js new file mode 100644 index 000000000..9f1678372 --- /dev/null +++ b/modules/mdb/back/methods/mdbApp/specs/unlock.spec.js @@ -0,0 +1,22 @@ +const models = require('vn-loopback/server/server').models; + +describe('MdbApp unlock()', () => { + it(`should unlock a mdb `, async() => { + const tx = await models.MdbApp.beginTransaction({}); + + try { + const options = {transaction: tx}; + const appName = 'bar'; + const developerId = 9; + const ctx = {req: {accessToken: {userId: developerId}}}; + + const result = await models.MdbApp.unlock(ctx, appName, options); + + expect(result.locked).toBeNull(); + + await tx.rollback(); + } catch (e) { + await tx.rollback(); + } + }); +}); diff --git a/modules/mdb/back/methods/mdbApp/unlock.js b/modules/mdb/back/methods/mdbApp/unlock.js new file mode 100644 index 000000000..6bf67ddf4 --- /dev/null +++ b/modules/mdb/back/methods/mdbApp/unlock.js @@ -0,0 +1,40 @@ +module.exports = Self => { + Self.remoteMethodCtx('unlock', { + description: 'Unlock an app for the user', + accessType: 'WRITE', + accepts: [ + { + arg: 'appName', + type: 'string', + required: true, + description: 'The app name', + http: {source: 'path'} + + } + ], + returns: { + type: ['object'], + root: true + }, + http: { + path: `/:appName/unlock`, + verb: 'POST' + } + }); + + Self.unlock = async(ctx, appName, options) => { + const models = Self.app.models; + const myOptions = {}; + + if (typeof options == 'object') + Object.assign(myOptions, options); + + const mdbApp = await models.MdbApp.findById(appName, null, myOptions); + const updatedMdbApp = await mdbApp.updateAttributes({ + userFk: null, + locked: null + }, myOptions); + + return updatedMdbApp; + }; +}; diff --git a/modules/mdb/back/methods/mdbVersion/upload.js b/modules/mdb/back/methods/mdbVersion/upload.js index 57df35ce7..5dfe5d3ef 100644 --- a/modules/mdb/back/methods/mdbVersion/upload.js +++ b/modules/mdb/back/methods/mdbVersion/upload.js @@ -23,6 +23,12 @@ module.exports = Self => { type: 'string', required: true, description: `The branch name` + }, + { + arg: 'unlock', + type: 'boolean', + required: false, + description: `It allows unlock the app` } ], returns: { @@ -35,9 +41,11 @@ module.exports = Self => { } }); - Self.upload = async(ctx, appName, newVersion, branch, options) => { + Self.upload = async(ctx, appName, newVersion, branch, unlock, options) => { const models = Self.app.models; + const userId = ctx.req.accessToken.userId; const myOptions = {}; + const $t = ctx.req.__; // $translate const TempContainer = models.TempContainer; const AccessContainer = models.AccessContainer; @@ -55,6 +63,14 @@ module.exports = Self => { let srcFile; try { + const mdbApp = await models.MdbApp.findById(appName, null, myOptions); + + if (mdbApp.locked && mdbApp.userFk != userId) { + throw new UserError($t('App locked', { + userId: mdbApp.userFk + })); + } + const tempContainer = await TempContainer.container('access'); const uploaded = await TempContainer.upload(tempContainer.name, ctx.req, ctx.result, fileOptions); const files = Object.values(uploaded.files).map(file => { @@ -79,7 +95,7 @@ module.exports = Self => { const existBranch = await models.MdbBranch.findOne({ where: {name: branch} - }); + }, myOptions); if (!existBranch) throw new UserError('Not exist this branch'); @@ -108,7 +124,9 @@ module.exports = Self => { app: appName, branchFk: branch, version: newVersion - }); + }, myOptions); + + if (unlock) await models.MdbApp.unlock(ctx, appName, myOptions); if (tx) await tx.commit(); } catch (e) { diff --git a/modules/mdb/back/model-config.json b/modules/mdb/back/model-config.json index d5be8de87..6107f8790 100644 --- a/modules/mdb/back/model-config.json +++ b/modules/mdb/back/model-config.json @@ -1,4 +1,7 @@ { + "MdbApp": { + "dataSource": "vn" + }, "MdbBranch": { "dataSource": "vn" }, diff --git a/modules/mdb/back/models/mdbApp.js b/modules/mdb/back/models/mdbApp.js new file mode 100644 index 000000000..dce715573 --- /dev/null +++ b/modules/mdb/back/models/mdbApp.js @@ -0,0 +1,4 @@ +module.exports = Self => { + require('../methods/mdbApp/lock')(Self); + require('../methods/mdbApp/unlock')(Self); +}; diff --git a/modules/mdb/back/models/mdbApp.json b/modules/mdb/back/models/mdbApp.json new file mode 100644 index 000000000..868f8c1d0 --- /dev/null +++ b/modules/mdb/back/models/mdbApp.json @@ -0,0 +1,31 @@ +{ + "name": "MdbApp", + "base": "VnModel", + "options": { + "mysql": { + "table": "mdbApp" + } + }, + "properties": { + "app": { + "type": "string", + "description": "The app name", + "id": true + }, + "locked": { + "type": "date" + } + }, + "relations": { + "branch": { + "type": "belongsTo", + "model": "MdbBranch", + "foreignKey": "baselineBranchFk" + }, + "user": { + "type": "belongsTo", + "model": "MdbBranch", + "foreignKey": "userFk" + } + } +}