2177-accountModule #373
|
@ -0,0 +1,64 @@
|
||||||
|
|
||||||
|
module.exports = Self => {
|
||||||
|
Self.remoteMethod('changePassword', {
|
||||||
|
description: 'Changes the user password',
|
||||||
|
accepts: [
|
||||||
|
{
|
||||||
|
arg: 'ctx',
|
||||||
|
type: 'Object',
|
||||||
|
http: {source: 'context'}
|
||||||
|
}, {
|
||||||
|
arg: 'id',
|
||||||
|
type: 'Number',
|
||||||
|
description: 'The user id',
|
||||||
|
http: {source: 'path'}
|
||||||
|
}, {
|
||||||
|
arg: 'oldPassword',
|
||||||
|
type: 'String',
|
||||||
|
description: 'The old password',
|
||||||
|
required: true
|
||||||
|
}, {
|
||||||
|
arg: 'newPassword',
|
||||||
|
type: 'String',
|
||||||
|
description: 'The new password',
|
||||||
|
required: true
|
||||||
|
}
|
||||||
|
],
|
||||||
|
returns: {
|
||||||
|
type: 'Boolean',
|
||||||
|
root: true
|
||||||
|
},
|
||||||
|
http: {
|
||||||
|
path: `/:id/changePassword`,
|
||||||
|
verb: 'PATCH'
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
Self.changePassword = async function(ctx, id, oldPassword, newPassword) {
|
||||||
|
let params = [id, oldPassword, newPassword];
|
||||||
|
await Self.rawSql(`CALL account.user_changePassword(?, ?, ?)`, params);
|
||||||
|
|
||||||
|
/*
|
||||||
|
const ldap = require('ldapjs');
|
||||||
|
|
||||||
|
let ldapConf = {
|
||||||
|
url: 'ldap://domain.local:389',
|
||||||
|
dn: 'cn=admin,dc=domain,dc=local',
|
||||||
|
password: '123456'
|
||||||
|
};
|
||||||
|
|
||||||
|
await new Promise((reject, resolve) => {
|
||||||
|
let client = ldap.createClient({url: ldapConf.url});
|
||||||
|
|
||||||
|
client.bind(ldapConf.dn, ldapConf.password, err => {
|
||||||
|
if (err)
|
||||||
|
reject(err);
|
||||||
|
else
|
||||||
|
resolve();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
*/
|
||||||
|
|
||||||
|
return true;
|
||||||
|
};
|
||||||
|
};
|
|
@ -0,0 +1,33 @@
|
||||||
|
|
||||||
|
module.exports = Self => {
|
||||||
|
Self.remoteMethod('setPassword', {
|
||||||
|
description: 'Sets the user password',
|
||||||
|
accepts: [
|
||||||
|
{
|
||||||
|
arg: 'id',
|
||||||
|
type: 'Number',
|
||||||
|
description: 'The user id',
|
||||||
|
http: {source: 'path'}
|
||||||
|
}, {
|
||||||
|
arg: 'newPassword',
|
||||||
|
type: 'String',
|
||||||
|
description: 'The new password',
|
||||||
|
required: true
|
||||||
|
}
|
||||||
|
],
|
||||||
|
returns: {
|
||||||
|
type: 'Boolean',
|
||||||
|
root: true
|
||||||
|
},
|
||||||
|
http: {
|
||||||
|
path: `/:id/setPassword`,
|
||||||
|
verb: 'PATCH'
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
Self.setPassword = async function(id, newPassword) {
|
||||||
|
let params = [id, newPassword];
|
||||||
|
await Self.rawSql(`CALL account.user_setPassword(?, ?)`, params);
|
||||||
|
return true;
|
||||||
|
};
|
||||||
|
};
|
|
@ -0,0 +1,9 @@
|
||||||
|
const app = require('vn-loopback/server/server');
|
||||||
|
|
||||||
|
describe('account changePassword()', () => {
|
||||||
|
it('should throw an error when old password is wrong', async() => {
|
||||||
|
let req = app.models.Account.changePassword(null, 1, 'wrongOldPass', 'newPass');
|
||||||
|
|
||||||
|
await expectAsync(req).toBeRejected();
|
||||||
|
});
|
||||||
|
});
|
|
@ -0,0 +1,15 @@
|
||||||
|
const app = require('vn-loopback/server/server');
|
||||||
|
|
||||||
|
describe('account changePassword()', () => {
|
||||||
|
it('should throw an error when password does not meet requirements', async() => {
|
||||||
|
let req = app.models.Account.setPassword(1, 'insecurePass');
|
||||||
|
|
||||||
|
await expectAsync(req).toBeRejected();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should update password when it passes requirements', async() => {
|
||||||
|
let req = app.models.Account.setPassword(1, 'Very$ecurePa22.');
|
||||||
|
|
||||||
|
await expectAsync(req).toBeResolved();
|
||||||
|
});
|
||||||
|
});
|
|
@ -38,6 +38,9 @@
|
||||||
"ImageCollectionSize": {
|
"ImageCollectionSize": {
|
||||||
"dataSource": "vn"
|
"dataSource": "vn"
|
||||||
},
|
},
|
||||||
|
"Language": {
|
||||||
|
"dataSource": "vn"
|
||||||
|
},
|
||||||
"Province": {
|
"Province": {
|
||||||
"dataSource": "vn"
|
"dataSource": "vn"
|
||||||
},
|
},
|
||||||
|
|
|
@ -4,6 +4,8 @@ module.exports = Self => {
|
||||||
require('../methods/account/login')(Self);
|
require('../methods/account/login')(Self);
|
||||||
require('../methods/account/logout')(Self);
|
require('../methods/account/logout')(Self);
|
||||||
require('../methods/account/acl')(Self);
|
require('../methods/account/acl')(Self);
|
||||||
|
require('../methods/account/change-password')(Self);
|
||||||
|
require('../methods/account/set-password')(Self);
|
||||||
require('../methods/account/validate-token')(Self);
|
require('../methods/account/validate-token')(Self);
|
||||||
|
|
||||||
// Validations
|
// Validations
|
||||||
|
|
|
@ -24,6 +24,9 @@
|
||||||
"nickname": {
|
"nickname": {
|
||||||
"type": "string"
|
"type": "string"
|
||||||
},
|
},
|
||||||
|
"lang": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
"password": {
|
"password": {
|
||||||
"type": "string",
|
"type": "string",
|
||||||
"required": true
|
"required": true
|
||||||
|
|
|
@ -0,0 +1,34 @@
|
||||||
|
{
|
||||||
|
"name": "Language",
|
||||||
|
"base": "VnModel",
|
||||||
|
"options": {
|
||||||
|
"mysql": {
|
||||||
|
"table": "hedera.language"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"properties": {
|
||||||
|
"code": {
|
||||||
|
"type": "string",
|
||||||
|
"id": true
|
||||||
|
},
|
||||||
|
"name": {
|
||||||
|
"type": "string",
|
||||||
|
"required": true
|
||||||
|
},
|
||||||
|
"orgName": {
|
||||||
|
"type": "string",
|
||||||
|
"required": true
|
||||||
|
},
|
||||||
|
"isActive": {
|
||||||
|
"type": "boolean"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"acls": [
|
||||||
|
{
|
||||||
|
"accessType": "READ",
|
||||||
|
"principalType": "ROLE",
|
||||||
|
"principalId": "$everyone",
|
||||||
|
"permission": "ALLOW"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
|
@ -0,0 +1,47 @@
|
||||||
|
|
||||||
|
ALTER TABLE `account`.`role`
|
||||||
|
MODIFY COLUMN `hasLogin` tinyint(3) unsigned DEFAULT 1 NOT NULL;
|
||||||
|
|
||||||
|
ALTER TABLE `account`.`roleInherit`
|
||||||
|
ADD UNIQUE( `role`, `inheritsFrom`);
|
||||||
|
|
||||||
|
ALTER TABLE `account`.`roleInherit`
|
||||||
|
DROP PRIMARY KEY;
|
||||||
|
|
||||||
|
ALTER TABLE `account`.`roleInherit`
|
||||||
|
ADD `id` INT UNSIGNED NOT NULL AUTO_INCREMENT FIRST,
|
||||||
|
ADD PRIMARY KEY (`id`);
|
||||||
|
|
||||||
|
ALTER TABLE `account`.`mailAlias`
|
||||||
|
ADD `description` VARCHAR(255) NULL AFTER `alias`;
|
||||||
|
|
||||||
|
ALTER TABLE `account`.`mailAliasAccount`
|
||||||
|
ADD UNIQUE( `mailAlias`, `account`);
|
||||||
|
|
||||||
|
ALTER TABLE `account`.`mailAliasAccount`
|
||||||
|
DROP PRIMARY KEY;
|
||||||
|
|
||||||
|
ALTER TABLE `account`.`mailAliasAccount`
|
||||||
|
ADD `id` INT UNSIGNED NOT NULL AUTO_INCREMENT FIRST,
|
||||||
|
ADD PRIMARY KEY (`id`);
|
||||||
|
|
||||||
|
|
||||||
|
USE account;
|
||||||
|
|
||||||
|
DELIMITER $$
|
||||||
|
|
||||||
|
CREATE TRIGGER role_beforeInsert
|
||||||
|
BEFORE INSERT ON `role` FOR EACH ROW
|
||||||
|
BEGIN
|
||||||
|
CALL role_checkName(NEW.`name`);
|
||||||
|
END$$
|
||||||
|
|
||||||
|
CREATE TRIGGER role_beforeUpdate
|
||||||
|
BEFORE UPDATE ON `role` FOR EACH ROW
|
||||||
|
BEGIN
|
||||||
|
IF !(NEW.`name` <=> OLD.`name`) THEN
|
||||||
|
CALL role_checkName (NEW.`name`);
|
||||||
|
END IF;
|
||||||
|
END$$
|
||||||
|
|
||||||
|
DELIMITER ;
|
|
@ -0,0 +1,13 @@
|
||||||
|
DROP PROCEDURE IF EXISTS account.myUserChangePassword;
|
||||||
|
|
||||||
|
DELIMITER $$
|
||||||
|
CREATE DEFINER=`root`@`%` PROCEDURE `account`.`myUserChangePassword`(vOldPassword VARCHAR(255), vPassword VARCHAR(255))
|
||||||
|
BEGIN
|
||||||
|
/**
|
||||||
|
* @deprecated Use myUser_changePassword()
|
||||||
|
*/
|
||||||
|
CALL myUser_changePassword(vOldPassword, vPassword);
|
||||||
|
END$$
|
||||||
|
DELIMITER ;
|
||||||
|
|
||||||
|
GRANT EXECUTE ON PROCEDURE account.myUserChangePassword TO account@localhost;
|
|
@ -0,0 +1,15 @@
|
||||||
|
DROP FUNCTION IF EXISTS account.myUserCheckLogin;
|
||||||
|
|
||||||
|
DELIMITER $$
|
||||||
|
CREATE DEFINER=`root`@`%` FUNCTION `account`.`myUserCheckLogin`() RETURNS tinyint(1)
|
||||||
|
READS SQL DATA
|
||||||
|
DETERMINISTIC
|
||||||
|
BEGIN
|
||||||
|
/**
|
||||||
|
* @deprecated Use myUser_checkLogin()
|
||||||
|
*/
|
||||||
|
RETURN myUser_checkLogin();
|
||||||
|
END$$
|
||||||
|
DELIMITER ;
|
||||||
|
|
||||||
|
GRANT EXECUTE ON FUNCTION account.myUserCheckLogin TO guest@localhost;
|
|
@ -0,0 +1,15 @@
|
||||||
|
DROP FUNCTION IF EXISTS account.myUserGetId;
|
||||||
|
|
||||||
|
DELIMITER $$
|
||||||
|
CREATE DEFINER=`root`@`%` FUNCTION `account`.`myUserGetId`() RETURNS int(11)
|
||||||
|
READS SQL DATA
|
||||||
|
DETERMINISTIC
|
||||||
|
BEGIN
|
||||||
|
/**
|
||||||
|
* @deprecated Use myUser_getId()
|
||||||
|
*/
|
||||||
|
RETURN myUser_getId();
|
||||||
|
END$$
|
||||||
|
DELIMITER ;
|
||||||
|
|
||||||
|
GRANT EXECUTE ON FUNCTION account.myUserGetId TO guest@localhost;
|
|
@ -0,0 +1,15 @@
|
||||||
|
DROP FUNCTION IF EXISTS account.myUserGetName;
|
||||||
|
|
||||||
|
DELIMITER $$
|
||||||
|
CREATE DEFINER=`root`@`%` FUNCTION `account`.`myUserGetName`() RETURNS varchar(30) CHARSET utf8
|
||||||
|
NO SQL
|
||||||
|
DETERMINISTIC
|
||||||
|
BEGIN
|
||||||
|
/**
|
||||||
|
* @deprecated Use myUser_getName()
|
||||||
|
*/
|
||||||
|
RETURN myUser_getName();
|
||||||
|
END$$
|
||||||
|
DELIMITER ;
|
||||||
|
|
||||||
|
GRANT EXECUTE ON FUNCTION account.myUserGetName TO guest@localhost;
|
|
@ -0,0 +1,14 @@
|
||||||
|
DROP FUNCTION IF EXISTS account.myUserHasRole;
|
||||||
|
|
||||||
|
DELIMITER $$
|
||||||
|
CREATE DEFINER=`root`@`%` FUNCTION `account`.`myUserHasRole`(vRoleName VARCHAR(255)) RETURNS tinyint(1)
|
||||||
|
DETERMINISTIC
|
||||||
|
BEGIN
|
||||||
|
/**
|
||||||
|
* @deprecated Use myUser_hasRole()
|
||||||
|
*/
|
||||||
|
RETURN myUser_hasRole(vRoleName);
|
||||||
|
END$$
|
||||||
|
DELIMITER ;
|
||||||
|
|
||||||
|
GRANT EXECUTE ON FUNCTION account.myUserHasRole TO guest@localhost;
|
|
@ -0,0 +1,14 @@
|
||||||
|
DROP FUNCTION IF EXISTS account.myUserHasRoleId;
|
||||||
|
|
||||||
|
DELIMITER $$
|
||||||
|
CREATE DEFINER=`root`@`%` FUNCTION `account`.`myUserHasRoleId`(vRoleId INT) RETURNS tinyint(1)
|
||||||
|
DETERMINISTIC
|
||||||
|
BEGIN
|
||||||
|
/**
|
||||||
|
* @deprecated Use myUser_hasRoleId()
|
||||||
|
*/
|
||||||
|
RETURN myUser_hasRoleId(vRoleId);
|
||||||
|
END$$
|
||||||
|
DELIMITER ;
|
||||||
|
|
||||||
|
GRANT EXECUTE ON FUNCTION account.myUserHasRoleId TO guest@localhost;
|
|
@ -0,0 +1,17 @@
|
||||||
|
DROP PROCEDURE IF EXISTS account.myUser_changePassword;
|
||||||
|
|
||||||
|
DELIMITER $$
|
||||||
|
CREATE DEFINER=`root`@`%` PROCEDURE `account`.`myUser_changePassword`(vOldPassword VARCHAR(255), vPassword VARCHAR(255))
|
||||||
|
BEGIN
|
||||||
|
/**
|
||||||
|
* Changes the current user password, if user is in recovery mode ignores the
|
||||||
|
* current password.
|
||||||
|
*
|
||||||
|
* @param vOldPassword The current password
|
||||||
|
* @param vPassword The new password
|
||||||
|
*/
|
||||||
|
CALL user_changePassword(myUser_getId(), vOldPassword, vPassword);
|
||||||
|
END$$
|
||||||
|
DELIMITER ;
|
||||||
|
|
||||||
|
GRANT EXECUTE ON PROCEDURE account.myUser_changePassword TO account@localhost;
|
|
@ -0,0 +1,29 @@
|
||||||
|
DROP FUNCTION IF EXISTS account.myUser_checkLogin;
|
||||||
|
|
||||||
|
DELIMITER $$
|
||||||
|
CREATE DEFINER=`root`@`%` FUNCTION `account`.`myUser_checkLogin`() RETURNS tinyint(1)
|
||||||
|
READS SQL DATA
|
||||||
|
DETERMINISTIC
|
||||||
|
BEGIN
|
||||||
|
/**
|
||||||
|
* Checks that variables @userId and @userName haven't been altered.
|
||||||
|
*
|
||||||
|
* @return %TRUE if they are unaltered or unset, otherwise %FALSE
|
||||||
|
*/
|
||||||
|
DECLARE vSignature VARCHAR(128);
|
||||||
|
DECLARE vKey VARCHAR(255);
|
||||||
|
|
||||||
|
IF @userId IS NOT NULL
|
||||||
|
AND @userName IS NOT NULL
|
||||||
|
AND @userSignature IS NOT NULL
|
||||||
|
THEN
|
||||||
|
SELECT loginKey INTO vKey FROM userConfig;
|
||||||
|
SET vSignature = util.hmacSha2(256, CONCAT_WS('/', @userId, @userName), vKey);
|
||||||
|
RETURN vSignature = @userSignature;
|
||||||
|
END IF;
|
||||||
|
|
||||||
|
RETURN FALSE;
|
||||||
|
END$$
|
||||||
|
DELIMITER ;
|
||||||
|
|
||||||
|
GRANT EXECUTE ON FUNCTION account.myUser_checkLogin TO guest@localhost;
|
|
@ -0,0 +1,27 @@
|
||||||
|
DROP FUNCTION IF EXISTS account.myUser_getId;
|
||||||
|
|
||||||
|
DELIMITER $$
|
||||||
|
CREATE DEFINER=`root`@`%` FUNCTION `account`.`myUser_getId`() RETURNS int(11)
|
||||||
|
READS SQL DATA
|
||||||
|
DETERMINISTIC
|
||||||
|
BEGIN
|
||||||
|
/**
|
||||||
|
* Returns the current user id.
|
||||||
|
*
|
||||||
|
* @return The user id
|
||||||
|
*/
|
||||||
|
DECLARE vUser INT DEFAULT NULL;
|
||||||
|
|
||||||
|
IF myUser_checkLogin()
|
||||||
|
THEN
|
||||||
|
SET vUser = @userId;
|
||||||
|
ELSE
|
||||||
|
SELECT id INTO vUser FROM user
|
||||||
|
WHERE name = LEFT(USER(), INSTR(USER(), '@') - 1);
|
||||||
|
END IF;
|
||||||
|
|
||||||
|
RETURN vUser;
|
||||||
|
END$$
|
||||||
|
DELIMITER ;
|
||||||
|
|
||||||
|
GRANT EXECUTE ON FUNCTION account.myUser_getId TO guest@localhost;
|
|
@ -0,0 +1,27 @@
|
||||||
|
DROP FUNCTION IF EXISTS account.myUser_getName;
|
||||||
|
|
||||||
|
DELIMITER $$
|
||||||
|
$$
|
||||||
|
CREATE DEFINER=`root`@`%` FUNCTION `account`.`myUser_getName`() RETURNS varchar(30) CHARSET utf8
|
||||||
|
NO SQL
|
||||||
|
DETERMINISTIC
|
||||||
|
BEGIN
|
||||||
|
/**
|
||||||
|
* Returns the current user name.
|
||||||
|
*
|
||||||
|
* @return The user name
|
||||||
|
*/
|
||||||
|
DECLARE vUser VARCHAR(30) DEFAULT NULL;
|
||||||
|
|
||||||
|
IF myUser_checkLogin()
|
||||||
|
THEN
|
||||||
|
SET vUser = @userName;
|
||||||
|
ELSE
|
||||||
|
SET vUser = LEFT(USER(), INSTR(USER(), '@') - 1);
|
||||||
|
END IF;
|
||||||
|
|
||||||
|
RETURN vUser;
|
||||||
|
END$$
|
||||||
|
DELIMITER ;
|
||||||
|
|
||||||
|
GRANT EXECUTE ON FUNCTION account.myUser_getName TO guest@localhost;
|
|
@ -0,0 +1,17 @@
|
||||||
|
DROP FUNCTION IF EXISTS account.myUser_hasRole;
|
||||||
|
|
||||||
|
DELIMITER $$
|
||||||
|
CREATE DEFINER=`root`@`%` FUNCTION `account`.`myUser_hasRole`(vRoleName VARCHAR(255)) RETURNS tinyint(1)
|
||||||
|
DETERMINISTIC
|
||||||
|
BEGIN
|
||||||
|
/**
|
||||||
|
* Checks if current user has/inherits a role.
|
||||||
|
*
|
||||||
|
* @param vRoleName Role to check
|
||||||
|
* @return %TRUE if it has role, %FALSE otherwise
|
||||||
|
*/
|
||||||
|
RETURN user_hasRole(myUser_getName(), vRoleName);
|
||||||
|
END$$
|
||||||
|
DELIMITER ;
|
||||||
|
|
||||||
|
GRANT EXECUTE ON FUNCTION account.myUser_hasRole TO guest@localhost;
|
|
@ -0,0 +1,17 @@
|
||||||
|
DROP FUNCTION IF EXISTS account.myUser_hasRoleId;
|
||||||
|
|
||||||
|
DELIMITER $$
|
||||||
|
CREATE DEFINER=`root`@`%` FUNCTION `account`.`myUser_hasRoleId`(vRoleId INT) RETURNS tinyint(1)
|
||||||
|
DETERMINISTIC
|
||||||
|
BEGIN
|
||||||
|
/**
|
||||||
|
* Checks if current user has/inherits a role.
|
||||||
|
*
|
||||||
|
* @param vRoleName Role id to check
|
||||||
|
* @return %TRUE if it has role, %FALSE otherwise
|
||||||
|
*/
|
||||||
|
RETURN user_hasRoleId(myUserGetName(), vRoleId);
|
||||||
|
END$$
|
||||||
|
DELIMITER ;
|
||||||
|
|
||||||
|
GRANT EXECUTE ON FUNCTION account.myUser_hasRoleId TO guest@localhost;
|
|
@ -0,0 +1,29 @@
|
||||||
|
DROP PROCEDURE IF EXISTS account.myUser_login;
|
||||||
|
|
||||||
|
DELIMITER $$
|
||||||
|
CREATE DEFINER=`root`@`%` PROCEDURE `account`.`myUser_login`(vUserName VARCHAR(255), vPassword VARCHAR(255))
|
||||||
|
READS SQL DATA
|
||||||
|
BEGIN
|
||||||
|
/**
|
||||||
|
* Logs in using the user credentials.
|
||||||
|
*
|
||||||
|
* @param vUserName The user name
|
||||||
|
* @param vPassword The user password
|
||||||
|
*/
|
||||||
|
DECLARE vAuthIsOk BOOLEAN DEFAULT FALSE;
|
||||||
|
|
||||||
|
SELECT COUNT(*) = 1 INTO vAuthIsOk FROM user
|
||||||
|
WHERE name = vUserName
|
||||||
|
AND password = MD5(vPassword)
|
||||||
|
AND active;
|
||||||
|
|
||||||
|
IF vAuthIsOk
|
||||||
|
THEN
|
||||||
|
CALL myUser_loginWithName (vUserName);
|
||||||
|
ELSE
|
||||||
|
CALL util.throw ('INVALID_CREDENTIALS');
|
||||||
|
END IF;
|
||||||
|
END$$
|
||||||
|
DELIMITER ;
|
||||||
|
|
||||||
|
GRANT EXECUTE ON PROCEDURE account.myUser_login TO guest@localhost;
|
|
@ -0,0 +1,25 @@
|
||||||
|
DROP PROCEDURE IF EXISTS account.myUser_loginWithKey;
|
||||||
|
|
||||||
|
DELIMITER $$
|
||||||
|
CREATE DEFINER=`root`@`%` PROCEDURE `account`.`myUser_loginWithKey`(vUserName VARCHAR(255), vKey VARCHAR(255))
|
||||||
|
READS SQL DATA
|
||||||
|
BEGIN
|
||||||
|
/**
|
||||||
|
* Logs in using the user name and MySQL master key.
|
||||||
|
*
|
||||||
|
* @param vUserName The user name
|
||||||
|
* @param vKey The MySQL master key
|
||||||
|
*/
|
||||||
|
DECLARE vLoginKey VARCHAR(255);
|
||||||
|
|
||||||
|
SELECT loginKey INTO vLoginKey FROM userConfig;
|
||||||
|
|
||||||
|
IF vLoginKey = vKey THEN
|
||||||
|
CALL user_loginWithName(vUserName);
|
||||||
|
ELSE
|
||||||
|
CALL util.throw('INVALID_KEY');
|
||||||
|
END IF;
|
||||||
|
END$$
|
||||||
|
DELIMITER ;
|
||||||
|
|
||||||
|
GRANT EXECUTE ON PROCEDURE account.myUser_loginWithKey TO guest@localhost;
|
|
@ -0,0 +1,26 @@
|
||||||
|
DROP PROCEDURE IF EXISTS account.myUser_loginWithName;
|
||||||
|
|
||||||
|
DELIMITER $$
|
||||||
|
CREATE DEFINER=`root`@`%` PROCEDURE `account`.`myUser_loginWithName`(vUserName VARCHAR(255))
|
||||||
|
READS SQL DATA
|
||||||
|
BEGIN
|
||||||
|
/**
|
||||||
|
* Logs in using only the user name. This procedure is intended to be executed
|
||||||
|
* by users with a high level of privileges so that normal users should not have
|
||||||
|
* execute permissions on it.
|
||||||
|
*
|
||||||
|
* @param vUserName The user name
|
||||||
|
*/
|
||||||
|
DECLARE vUserId INT DEFAULT NULL;
|
||||||
|
DECLARE vKey VARCHAR(255);
|
||||||
|
|
||||||
|
SELECT id INTO vUserId FROM user
|
||||||
|
WHERE name = vUserName;
|
||||||
|
|
||||||
|
SELECT loginKey INTO vKey FROM userConfig;
|
||||||
|
|
||||||
|
SET @userId = vUserId;
|
||||||
|
SET @userName = vUserName;
|
||||||
|
SET @userSignature = util.hmacSha2(256, CONCAT_WS('/', vUserId, vUserName), vKey);
|
||||||
|
END$$
|
||||||
|
DELIMITER ;
|
|
@ -0,0 +1,15 @@
|
||||||
|
DROP PROCEDURE IF EXISTS account.myUser_logout;
|
||||||
|
|
||||||
|
DELIMITER $$
|
||||||
|
CREATE DEFINER=`root`@`%` PROCEDURE `account`.`myUser_logout`()
|
||||||
|
BEGIN
|
||||||
|
/**
|
||||||
|
* Logouts the user.
|
||||||
|
*/
|
||||||
|
SET @userId = NULL;
|
||||||
|
SET @userName = NULL;
|
||||||
|
SET @userSignature = NULL;
|
||||||
|
END$$
|
||||||
|
DELIMITER ;
|
||||||
|
|
||||||
|
GRANT EXECUTE ON PROCEDURE account.myUser_logout TO account@localhost;
|
|
@ -0,0 +1,52 @@
|
||||||
|
DROP FUNCTION IF EXISTS account.passwordGenerate;
|
||||||
|
|
||||||
|
DELIMITER $$
|
||||||
|
CREATE DEFINER=`root`@`%` FUNCTION `account`.`passwordGenerate`() RETURNS text CHARSET utf8
|
||||||
|
BEGIN
|
||||||
|
/**
|
||||||
|
* Generates a random password that meets the minimum requirements.
|
||||||
|
*
|
||||||
|
* @return Generated password
|
||||||
|
*/
|
||||||
|
DECLARE vMinLength TINYINT;
|
||||||
|
DECLARE vMinAlpha TINYINT;
|
||||||
|
DECLARE vMinUpper TINYINT;
|
||||||
|
DECLARE vMinDigits TINYINT;
|
||||||
|
DECLARE vMinPunct TINYINT;
|
||||||
|
DECLARE vAlpha TINYINT DEFAULT 0;
|
||||||
|
DECLARE vUpper TINYINT DEFAULT 0;
|
||||||
|
DECLARE vDigits TINYINT DEFAULT 0;
|
||||||
|
DECLARE vPunct TINYINT DEFAULT 0;
|
||||||
|
DECLARE vRandIndex INT;
|
||||||
|
DECLARE vPwd TEXT DEFAULT '';
|
||||||
|
|
||||||
|
DECLARE vAlphaChars TEXT DEFAULT 'abcdefghijklmnopqrstuvwxyz';
|
||||||
|
DECLARE vUpperChars TEXT DEFAULT 'ABCDEFGHIJKLMNOPQRSTUVWXYZ';
|
||||||
|
DECLARE vDigitChars TEXT DEFAULT '1234567890';
|
||||||
|
DECLARE vPunctChars TEXT DEFAULT '!$%&()=.';
|
||||||
|
|
||||||
|
SELECT length, nAlpha, nUpper, nDigits, nPunct
|
||||||
|
INTO vMinLength, vMinAlpha, vMinUpper, vMinDigits, vMinPunct FROM userPassword;
|
||||||
|
|
||||||
|
WHILE LENGTH(vPwd) < vMinLength OR vAlpha < vMinAlpha
|
||||||
|
OR vUpper < vMinUpper OR vDigits < vMinDigits OR vPunct < vMinPunct DO
|
||||||
|
SET vRandIndex = FLOOR((RAND() * 4) + 1);
|
||||||
|
|
||||||
|
CASE
|
||||||
|
WHEN vRandIndex = 1 THEN
|
||||||
|
SET vPwd = CONCAT(vPwd, SUBSTRING(vAlphaChars, FLOOR((RAND() * 26) + 1), 1));
|
||||||
|
SET vAlpha = vAlpha + 1;
|
||||||
|
WHEN vRandIndex = 2 THEN
|
||||||
|
SET vPwd = CONCAT(vPwd, SUBSTRING(vUpperChars, FLOOR((RAND() * 26) + 1), 1));
|
||||||
|
SET vUpper = vUpper + 1;
|
||||||
|
WHEN vRandIndex = 3 THEN
|
||||||
|
SET vPwd = CONCAT(vPwd, SUBSTRING(vDigitChars, FLOOR((RAND() * 10) + 1), 1));
|
||||||
|
SET vDigits = vDigits + 1;
|
||||||
|
WHEN vRandIndex = 4 THEN
|
||||||
|
SET vPwd = CONCAT(vPwd, SUBSTRING(vPunctChars, FLOOR((RAND() * LENGTH(vPunctChars)) + 1), 1));
|
||||||
|
SET vPunct = vPunct + 1;
|
||||||
|
END CASE;
|
||||||
|
END WHILE;
|
||||||
|
RETURN vPwd;
|
||||||
|
END$$
|
||||||
|
DELIMITER ;
|
|
@ -0,0 +1,18 @@
|
||||||
|
DROP PROCEDURE IF EXISTS account.role_checkName;
|
||||||
|
|
||||||
|
DELIMITER $$
|
||||||
|
CREATE PROCEDURE account.role_checkName(vRoleName VARCHAR(255))
|
||||||
|
BEGIN
|
||||||
|
/**
|
||||||
|
* Checks that role name meets the necessary syntax requirements, otherwise it
|
||||||
|
* throws an exception.
|
||||||
|
* Role name must be written in camelCase.
|
||||||
|
*
|
||||||
|
* @param vRoleName The role name
|
||||||
|
*/
|
||||||
|
IF BINARY vRoleName NOT REGEXP '^[a-z][a-zA-Z]+$' THEN
|
||||||
|
SIGNAL SQLSTATE '45000'
|
||||||
|
SET MESSAGE_TEXT = 'Role name must be written in camelCase';
|
||||||
|
END IF;
|
||||||
|
END$$
|
||||||
|
DELIMITER ;
|
|
@ -0,0 +1,66 @@
|
||||||
|
DROP PROCEDURE IF EXISTS account.role_getDescendents;
|
||||||
|
|
||||||
|
DELIMITER $$
|
||||||
|
CREATE DEFINER=`root`@`%` PROCEDURE `account`.`role_getDescendents`(vSelf INT)
|
||||||
|
BEGIN
|
||||||
|
/**
|
||||||
|
* Gets the identifiers of all the subroles implemented by a role (Including
|
||||||
|
* itself).
|
||||||
|
*
|
||||||
|
* @param vSelf The role identifier
|
||||||
|
* @table tmp.role Subroles implemented by the role
|
||||||
|
*/
|
||||||
|
DECLARE vIsRoot BOOL;
|
||||||
|
|
||||||
|
DROP TEMPORARY TABLE IF EXISTS
|
||||||
|
tmp.role, parents, childs;
|
||||||
|
|
||||||
|
CREATE TEMPORARY TABLE tmp.role
|
||||||
|
(UNIQUE (id))
|
||||||
|
ENGINE = MEMORY
|
||||||
|
SELECT vSelf AS id;
|
||||||
|
|
||||||
|
CREATE TEMPORARY TABLE parents
|
||||||
|
ENGINE = MEMORY
|
||||||
|
SELECT vSelf AS id;
|
||||||
|
|
||||||
|
CREATE TEMPORARY TABLE childs
|
||||||
|
LIKE parents;
|
||||||
|
|
||||||
|
REPEAT
|
||||||
|
DELETE FROM childs;
|
||||||
|
INSERT INTO childs
|
||||||
|
SELECT DISTINCT r.inheritsFrom id
|
||||||
|
FROM parents p
|
||||||
|
JOIN roleInherit r ON r.role = p.id
|
||||||
|
LEFT JOIN tmp.role t ON t.id = r.inheritsFrom
|
||||||
|
WHERE t.id IS NULL;
|
||||||
|
|
||||||
|
DELETE FROM parents;
|
||||||
|
INSERT INTO parents
|
||||||
|
SELECT * FROM childs;
|
||||||
|
|
||||||
|
INSERT INTO tmp.role
|
||||||
|
SELECT * FROM childs;
|
||||||
|
|
||||||
|
UNTIL ROW_COUNT() <= 0
|
||||||
|
END REPEAT;
|
||||||
|
|
||||||
|
-- If it is root all the roles are added
|
||||||
|
|
||||||
|
SELECT COUNT(*) > 0 INTO vIsRoot
|
||||||
|
FROM tmp.role t
|
||||||
|
JOIN role r ON r.id = t.id
|
||||||
|
WHERE r.`name` = 'root';
|
||||||
|
|
||||||
|
IF vIsRoot THEN
|
||||||
|
INSERT IGNORE INTO tmp.role (id)
|
||||||
|
SELECT id FROM role;
|
||||||
|
END IF;
|
||||||
|
|
||||||
|
-- Cleaning
|
||||||
|
|
||||||
|
DROP TEMPORARY TABLE
|
||||||
|
parents, childs;
|
||||||
|
END$$
|
||||||
|
DELIMITER ;
|
|
@ -0,0 +1,53 @@
|
||||||
|
DROP PROCEDURE IF EXISTS account.role_sync;
|
||||||
|
|
||||||
|
DELIMITER $$
|
||||||
|
CREATE DEFINER=`root`@`%` PROCEDURE `account`.`role_sync`()
|
||||||
|
BEGIN
|
||||||
|
/**
|
||||||
|
* Synchronize the @roleRole table with the current role hierarchy. This
|
||||||
|
* procedure must be called every time the @roleInherit table is modified so
|
||||||
|
* that the changes made on it are effective.
|
||||||
|
*/
|
||||||
|
DECLARE vRoleId INT;
|
||||||
|
DECLARE vDone BOOL;
|
||||||
|
|
||||||
|
DECLARE cur CURSOR FOR
|
||||||
|
SELECT id FROM role;
|
||||||
|
|
||||||
|
DECLARE CONTINUE HANDLER FOR NOT FOUND SET vDone = TRUE;
|
||||||
|
|
||||||
|
DROP TEMPORARY TABLE IF EXISTS tRoleRole;
|
||||||
|
CREATE TEMPORARY TABLE tRoleRole
|
||||||
|
ENGINE = MEMORY
|
||||||
|
SELECT * FROM roleRole LIMIT 0;
|
||||||
|
|
||||||
|
OPEN cur;
|
||||||
|
|
||||||
|
l: LOOP
|
||||||
|
SET vDone = FALSE;
|
||||||
|
FETCH cur INTO vRoleId;
|
||||||
|
|
||||||
|
IF vDone THEN
|
||||||
|
LEAVE l;
|
||||||
|
END IF;
|
||||||
|
|
||||||
|
CALL role_getDescendents(vRoleId);
|
||||||
|
|
||||||
|
INSERT INTO tRoleRole (role, inheritsFrom)
|
||||||
|
SELECT vRoleId, id FROM tmp.role;
|
||||||
|
|
||||||
|
DROP TEMPORARY TABLE tmp.role;
|
||||||
|
END LOOP;
|
||||||
|
|
||||||
|
CLOSE cur;
|
||||||
|
|
||||||
|
START TRANSACTION;
|
||||||
|
DELETE FROM roleRole;
|
||||||
|
INSERT INTO roleRole SELECT * FROM tRoleRole;
|
||||||
|
COMMIT;
|
||||||
|
|
||||||
|
DROP TEMPORARY TABLE tRoleRole;
|
||||||
|
|
||||||
|
CALL role_syncPrivileges;
|
||||||
|
END$$
|
||||||
|
DELIMITER ;
|
|
@ -0,0 +1,494 @@
|
||||||
|
DROP PROCEDURE IF EXISTS account.role_syncPrivileges;
|
||||||
|
|
||||||
|
DELIMITER $$
|
||||||
|
CREATE DEFINER=`root`@`%` PROCEDURE `account`.`role_syncPrivileges`()
|
||||||
|
BEGIN
|
||||||
|
/**
|
||||||
|
* Synchronizes permissions of MySQL role users based on role hierarchy.
|
||||||
|
* The computed role users of permission mix will be named according to
|
||||||
|
* pattern z-[role_name].
|
||||||
|
*
|
||||||
|
* If any@localhost user exists, it will be taken as a template for basic
|
||||||
|
* attributes.
|
||||||
|
*
|
||||||
|
* Warning! This procedure should only be called when MySQL privileges
|
||||||
|
* are modified. If role hierarchy is modified, you must call the role_sync()
|
||||||
|
* procedure wich calls this internally.
|
||||||
|
*/
|
||||||
|
DECLARE vIsMysql BOOL DEFAULT VERSION() NOT LIKE '%MariaDB%';
|
||||||
|
DECLARE vVersion INT DEFAULT SUBSTRING_INDEX(VERSION(), '.', 1);
|
||||||
|
DECLARE vTplUser VARCHAR(255) DEFAULT 'any';
|
||||||
|
DECLARE vTplHost VARCHAR(255) DEFAULT '%';
|
||||||
|
DECLARE vRoleHost VARCHAR(255) DEFAULT 'localhost';
|
||||||
|
DECLARE vAllHost VARCHAR(255) DEFAULT '%';
|
||||||
|
DECLARE vPrefix VARCHAR(2) DEFAULT 'z-';
|
||||||
|
DECLARE vPrefixedLike VARCHAR(255);
|
||||||
|
DECLARE vPassword VARCHAR(255) DEFAULT '';
|
||||||
|
|
||||||
|
-- Deletes computed role users
|
||||||
|
|
||||||
|
SET vPrefixedLike = CONCAT(vPrefix, '%');
|
||||||
|
|
||||||
|
DELETE FROM mysql.user
|
||||||
|
WHERE `User` LIKE vPrefixedLike;
|
||||||
|
|
||||||
|
DELETE FROM mysql.db
|
||||||
|
WHERE `User` LIKE vPrefixedLike;
|
||||||
|
|
||||||
|
DELETE FROM mysql.tables_priv
|
||||||
|
WHERE `User` LIKE vPrefixedLike;
|
||||||
|
|
||||||
|
DELETE FROM mysql.columns_priv
|
||||||
|
WHERE `User` LIKE vPrefixedLike;
|
||||||
|
|
||||||
|
DELETE FROM mysql.procs_priv
|
||||||
|
WHERE `User` LIKE vPrefixedLike;
|
||||||
|
|
||||||
|
DELETE FROM mysql.proxies_priv
|
||||||
|
WHERE `Proxied_user` LIKE vPrefixedLike;
|
||||||
|
|
||||||
|
-- Temporary tables
|
||||||
|
|
||||||
|
DROP TEMPORARY TABLE IF EXISTS tRole;
|
||||||
|
CREATE TEMPORARY TABLE tRole
|
||||||
|
(INDEX (id))
|
||||||
|
ENGINE = MEMORY
|
||||||
|
SELECT
|
||||||
|
id,
|
||||||
|
`name` role,
|
||||||
|
CONCAT(vPrefix, `name`) prefixedRole
|
||||||
|
FROM role
|
||||||
|
WHERE hasLogin;
|
||||||
|
|
||||||
|
DROP TEMPORARY TABLE IF EXISTS tRoleInherit;
|
||||||
|
CREATE TEMPORARY TABLE tRoleInherit
|
||||||
|
(INDEX (inheritsFrom))
|
||||||
|
ENGINE = MEMORY
|
||||||
|
SELECT
|
||||||
|
r.prefixedRole,
|
||||||
|
ri.`name` inheritsFrom
|
||||||
|
FROM tRole r
|
||||||
|
JOIN roleRole rr ON rr.role = r.id
|
||||||
|
JOIN role ri ON ri.id = rr.inheritsFrom;
|
||||||
|
|
||||||
|
-- Recreate role users
|
||||||
|
|
||||||
|
IF vIsMysql THEN
|
||||||
|
DROP TEMPORARY TABLE IF EXISTS tUser;
|
||||||
|
CREATE TEMPORARY TABLE tUser
|
||||||
|
SELECT
|
||||||
|
r.prefixedRole `User`,
|
||||||
|
vTplHost `Host`,
|
||||||
|
IFNULL(t.`authentication_string`,
|
||||||
|
'') `authentication_string`,
|
||||||
|
IFNULL(t.`plugin`,
|
||||||
|
'mysql_native_password') `plugin`,
|
||||||
|
IFNULL(IF('' != u.`ssl_type`,
|
||||||
|
u.`ssl_type`, t.`ssl_type`),
|
||||||
|
'') `ssl_type`,
|
||||||
|
IFNULL(IF('' != u.`ssl_cipher`,
|
||||||
|
u.`ssl_cipher`, t.`ssl_cipher`),
|
||||||
|
'') `ssl_cipher`,
|
||||||
|
IFNULL(IF('' != u.`x509_issuer`,
|
||||||
|
u.`x509_issuer`, t.`x509_issuer`),
|
||||||
|
'') `x509_issuer`,
|
||||||
|
IFNULL(IF('' != u.`x509_subject`,
|
||||||
|
u.`x509_subject`, t.`x509_subject`),
|
||||||
|
'') `x509_subject`,
|
||||||
|
IFNULL(IF(0 != u.`max_questions`,
|
||||||
|
u.`max_questions`, t.`max_questions`),
|
||||||
|
0) `max_questions`,
|
||||||
|
IFNULL(IF(0 != u.`max_updates`,
|
||||||
|
u.`max_updates`, t.`max_updates`),
|
||||||
|
0) `max_updates`,
|
||||||
|
IFNULL(IF(0 != u.`max_connections`,
|
||||||
|
u.`max_connections`, t.`max_connections`),
|
||||||
|
0) `max_connections`,
|
||||||
|
IFNULL(IF(0 != u.`max_user_connections`,
|
||||||
|
u.`max_user_connections`, t.`max_user_connections`),
|
||||||
|
0) `max_user_connections`
|
||||||
|
FROM tRole r
|
||||||
|
LEFT JOIN mysql.user t
|
||||||
|
ON t.`User` = vTplUser
|
||||||
|
AND t.`Host` = vRoleHost
|
||||||
|
LEFT JOIN mysql.user u
|
||||||
|
ON u.`User` = r.role
|
||||||
|
AND u.`Host` = vRoleHost;
|
||||||
|
|
||||||
|
IF vVersion <= 5 THEN
|
||||||
|
SELECT `Password` INTO vPassword
|
||||||
|
FROM mysql.user
|
||||||
|
WHERE `User` = vTplUser
|
||||||
|
AND `Host` = vRoleHost;
|
||||||
|
|
||||||
|
INSERT INTO mysql.user (
|
||||||
|
`User`,
|
||||||
|
`Host`,
|
||||||
|
`Password`,
|
||||||
|
`authentication_string`,
|
||||||
|
`plugin`,
|
||||||
|
`ssl_type`,
|
||||||
|
`ssl_cipher`,
|
||||||
|
`x509_issuer`,
|
||||||
|
`x509_subject`,
|
||||||
|
`max_questions`,
|
||||||
|
`max_updates`,
|
||||||
|
`max_connections`,
|
||||||
|
`max_user_connections`
|
||||||
|
)
|
||||||
|
SELECT
|
||||||
|
`User`,
|
||||||
|
`Host`,
|
||||||
|
vPassword,
|
||||||
|
`authentication_string`,
|
||||||
|
`plugin`,
|
||||||
|
`ssl_type`,
|
||||||
|
`ssl_cipher`,
|
||||||
|
`x509_issuer`,
|
||||||
|
`x509_subject`,
|
||||||
|
`max_questions`,
|
||||||
|
`max_updates`,
|
||||||
|
`max_connections`,
|
||||||
|
`max_user_connections`
|
||||||
|
FROM tUser;
|
||||||
|
ELSE
|
||||||
|
INSERT INTO mysql.user (
|
||||||
|
`User`,
|
||||||
|
`Host`,
|
||||||
|
`authentication_string`,
|
||||||
|
`plugin`,
|
||||||
|
`ssl_type`,
|
||||||
|
`ssl_cipher`,
|
||||||
|
`x509_issuer`,
|
||||||
|
`x509_subject`,
|
||||||
|
`max_questions`,
|
||||||
|
`max_updates`,
|
||||||
|
`max_connections`,
|
||||||
|
`max_user_connections`
|
||||||
|
)
|
||||||
|
SELECT
|
||||||
|
`User`,
|
||||||
|
`Host`,
|
||||||
|
`authentication_string`,
|
||||||
|
`plugin`,
|
||||||
|
`ssl_type`,
|
||||||
|
`ssl_cipher`,
|
||||||
|
`x509_issuer`,
|
||||||
|
`x509_subject`,
|
||||||
|
`max_questions`,
|
||||||
|
`max_updates`,
|
||||||
|
`max_connections`,
|
||||||
|
`max_user_connections`
|
||||||
|
FROM tUser;
|
||||||
|
END IF;
|
||||||
|
|
||||||
|
DROP TEMPORARY TABLE IF EXISTS tUser;
|
||||||
|
ELSE
|
||||||
|
INSERT INTO mysql.global_priv (
|
||||||
|
`User`,
|
||||||
|
`Host`,
|
||||||
|
`Priv`
|
||||||
|
)
|
||||||
|
SELECT
|
||||||
|
r.prefixedRole,
|
||||||
|
vTplHost,
|
||||||
|
JSON_MERGE_PATCH(
|
||||||
|
IFNULL(t.`Priv`, '{}'),
|
||||||
|
IFNULL(u.`Priv`, '{}')
|
||||||
|
)
|
||||||
|
FROM tRole r
|
||||||
|
LEFT JOIN mysql.global_priv t
|
||||||
|
ON t.`User` = vTplUser
|
||||||
|
AND t.`Host` = vRoleHost
|
||||||
|
LEFT JOIN mysql.global_priv u
|
||||||
|
ON u.`User` = r.role
|
||||||
|
AND u.`Host` = vRoleHost;
|
||||||
|
END IF;
|
||||||
|
|
||||||
|
INSERT INTO mysql.proxies_priv (
|
||||||
|
`User`,
|
||||||
|
`Host`,
|
||||||
|
`Proxied_user`,
|
||||||
|
`Proxied_host`,
|
||||||
|
`Grantor`
|
||||||
|
)
|
||||||
|
SELECT
|
||||||
|
'',
|
||||||
|
vAllHost,
|
||||||
|
prefixedRole,
|
||||||
|
vTplHost,
|
||||||
|
CONCAT(prefixedRole, '@', vTplHost)
|
||||||
|
FROM tRole;
|
||||||
|
|
||||||
|
-- Copies global privileges
|
||||||
|
|
||||||
|
DROP TEMPORARY TABLE IF EXISTS tUserPriv;
|
||||||
|
|
||||||
|
IF vIsMysql THEN
|
||||||
|
CREATE TEMPORARY TABLE tUserPriv
|
||||||
|
(INDEX (prefixedRole))
|
||||||
|
ENGINE = MEMORY
|
||||||
|
SELECT
|
||||||
|
r.prefixedRole,
|
||||||
|
MAX(u.`Select_priv`) `Select_priv`,
|
||||||
|
MAX(u.`Insert_priv`) `Insert_priv`,
|
||||||
|
MAX(u.`Update_priv`) `Update_priv`,
|
||||||
|
MAX(u.`Delete_priv`) `Delete_priv`,
|
||||||
|
MAX(u.`Create_priv`) `Create_priv`,
|
||||||
|
MAX(u.`Drop_priv`) `Drop_priv`,
|
||||||
|
MAX(u.`Reload_priv`) `Reload_priv`,
|
||||||
|
MAX(u.`Shutdown_priv`) `Shutdown_priv`,
|
||||||
|
MAX(u.`Process_priv`) `Process_priv`,
|
||||||
|
MAX(u.`File_priv`) `File_priv`,
|
||||||
|
MAX(u.`Grant_priv`) `Grant_priv`,
|
||||||
|
MAX(u.`References_priv`) `References_priv`,
|
||||||
|
MAX(u.`Index_priv`) `Index_priv`,
|
||||||
|
MAX(u.`Alter_priv`) `Alter_priv`,
|
||||||
|
MAX(u.`Show_db_priv`) `Show_db_priv`,
|
||||||
|
MAX(u.`Super_priv`) `Super_priv`,
|
||||||
|
MAX(u.`Create_tmp_table_priv`) `Create_tmp_table_priv`,
|
||||||
|
MAX(u.`Lock_tables_priv`) `Lock_tables_priv`,
|
||||||
|
MAX(u.`Execute_priv`) `Execute_priv`,
|
||||||
|
MAX(u.`Repl_slave_priv`) `Repl_slave_priv`,
|
||||||
|
MAX(u.`Repl_client_priv`) `Repl_client_priv`,
|
||||||
|
MAX(u.`Create_view_priv`) `Create_view_priv`,
|
||||||
|
MAX(u.`Show_view_priv`) `Show_view_priv`,
|
||||||
|
MAX(u.`Create_routine_priv`) `Create_routine_priv`,
|
||||||
|
MAX(u.`Alter_routine_priv`) `Alter_routine_priv`,
|
||||||
|
MAX(u.`Create_user_priv`) `Create_user_priv`,
|
||||||
|
MAX(u.`Event_priv`) `Event_priv`,
|
||||||
|
MAX(u.`Trigger_priv`) `Trigger_priv`,
|
||||||
|
MAX(u.`Create_tablespace_priv`) `Create_tablespace_priv`
|
||||||
|
FROM tRoleInherit r
|
||||||
|
JOIN mysql.user u
|
||||||
|
ON u.`User` = r.inheritsFrom
|
||||||
|
AND u.`Host`= vRoleHost
|
||||||
|
GROUP BY r.prefixedRole;
|
||||||
|
|
||||||
|
UPDATE mysql.user u
|
||||||
|
JOIN tUserPriv t
|
||||||
|
ON u.`User` = t.prefixedRole
|
||||||
|
AND u.`Host` = vTplHost
|
||||||
|
SET
|
||||||
|
u.`Select_priv`
|
||||||
|
= t.`Select_priv`,
|
||||||
|
u.`Insert_priv`
|
||||||
|
= t.`Insert_priv`,
|
||||||
|
u.`Update_priv`
|
||||||
|
= t.`Update_priv`,
|
||||||
|
u.`Delete_priv`
|
||||||
|
= t.`Delete_priv`,
|
||||||
|
u.`Create_priv`
|
||||||
|
= t.`Create_priv`,
|
||||||
|
u.`Drop_priv`
|
||||||
|
= t.`Drop_priv`,
|
||||||
|
u.`Reload_priv`
|
||||||
|
= t.`Reload_priv`,
|
||||||
|
u.`Shutdown_priv`
|
||||||
|
= t.`Shutdown_priv`,
|
||||||
|
u.`Process_priv`
|
||||||
|
= t.`Process_priv`,
|
||||||
|
u.`File_priv`
|
||||||
|
= t.`File_priv`,
|
||||||
|
u.`Grant_priv`
|
||||||
|
= t.`Grant_priv`,
|
||||||
|
u.`References_priv`
|
||||||
|
= t.`References_priv`,
|
||||||
|
u.`Index_priv`
|
||||||
|
= t.`Index_priv`,
|
||||||
|
u.`Alter_priv`
|
||||||
|
= t.`Alter_priv`,
|
||||||
|
u.`Show_db_priv`
|
||||||
|
= t.`Show_db_priv`,
|
||||||
|
u.`Super_priv`
|
||||||
|
= t.`Super_priv`,
|
||||||
|
u.`Create_tmp_table_priv`
|
||||||
|
= t.`Create_tmp_table_priv`,
|
||||||
|
u.`Lock_tables_priv`
|
||||||
|
= t.`Lock_tables_priv`,
|
||||||
|
u.`Execute_priv`
|
||||||
|
= t.`Execute_priv`,
|
||||||
|
u.`Repl_slave_priv`
|
||||||
|
= t.`Repl_slave_priv`,
|
||||||
|
u.`Repl_client_priv`
|
||||||
|
= t.`Repl_client_priv`,
|
||||||
|
u.`Create_view_priv`
|
||||||
|
= t.`Create_view_priv`,
|
||||||
|
u.`Show_view_priv`
|
||||||
|
= t.`Show_view_priv`,
|
||||||
|
u.`Create_routine_priv`
|
||||||
|
= t.`Create_routine_priv`,
|
||||||
|
u.`Alter_routine_priv`
|
||||||
|
= t.`Alter_routine_priv`,
|
||||||
|
u.`Create_user_priv`
|
||||||
|
= t.`Create_user_priv`,
|
||||||
|
u.`Event_priv`
|
||||||
|
= t.`Event_priv`,
|
||||||
|
u.`Trigger_priv`
|
||||||
|
= t.`Trigger_priv`,
|
||||||
|
u.`Create_tablespace_priv`
|
||||||
|
= t.`Create_tablespace_priv`;
|
||||||
|
ELSE
|
||||||
|
CREATE TEMPORARY TABLE tUserPriv
|
||||||
|
(INDEX (prefixedRole))
|
||||||
|
SELECT
|
||||||
|
r.prefixedRole,
|
||||||
|
BIT_OR(JSON_VALUE(p.`Priv`, '$.access')) access
|
||||||
|
FROM tRoleInherit r
|
||||||
|
JOIN mysql.global_priv p
|
||||||
|
ON p.`User` = r.inheritsFrom
|
||||||
|
AND p.`Host`= vRoleHost
|
||||||
|
GROUP BY r.prefixedRole;
|
||||||
|
|
||||||
|
UPDATE mysql.global_priv p
|
||||||
|
JOIN tUserPriv t
|
||||||
|
ON p.`User` = t.prefixedRole
|
||||||
|
AND p.`Host` = vTplHost
|
||||||
|
SET
|
||||||
|
p.`Priv` = JSON_SET(p.`Priv`, '$.access', t.access);
|
||||||
|
END IF;
|
||||||
|
|
||||||
|
DROP TEMPORARY TABLE tUserPriv;
|
||||||
|
|
||||||
|
-- Copy schema level privileges
|
||||||
|
|
||||||
|
INSERT INTO mysql.db (
|
||||||
|
`User`,
|
||||||
|
`Host`,
|
||||||
|
`Db`,
|
||||||
|
`Select_priv`,
|
||||||
|
`Insert_priv`,
|
||||||
|
`Update_priv`,
|
||||||
|
`Delete_priv`,
|
||||||
|
`Create_priv`,
|
||||||
|
`Drop_priv`,
|
||||||
|
`Grant_priv`,
|
||||||
|
`References_priv`,
|
||||||
|
`Index_priv`,
|
||||||
|
`Alter_priv`,
|
||||||
|
`Create_tmp_table_priv`,
|
||||||
|
`Lock_tables_priv`,
|
||||||
|
`Create_view_priv`,
|
||||||
|
`Show_view_priv`,
|
||||||
|
`Create_routine_priv`,
|
||||||
|
`Alter_routine_priv`,
|
||||||
|
`Execute_priv`,
|
||||||
|
`Event_priv`,
|
||||||
|
`Trigger_priv`
|
||||||
|
)
|
||||||
|
SELECT
|
||||||
|
r.prefixedRole,
|
||||||
|
vTplHost,
|
||||||
|
t.`Db`,
|
||||||
|
MAX(t.`Select_priv`),
|
||||||
|
MAX(t.`Insert_priv`),
|
||||||
|
MAX(t.`Update_priv`),
|
||||||
|
MAX(t.`Delete_priv`),
|
||||||
|
MAX(t.`Create_priv`),
|
||||||
|
MAX(t.`Drop_priv`),
|
||||||
|
MAX(t.`Grant_priv`),
|
||||||
|
MAX(t.`References_priv`),
|
||||||
|
MAX(t.`Index_priv`),
|
||||||
|
MAX(t.`Alter_priv`),
|
||||||
|
MAX(t.`Create_tmp_table_priv`),
|
||||||
|
MAX(t.`Lock_tables_priv`),
|
||||||
|
MAX(t.`Create_view_priv`),
|
||||||
|
MAX(t.`Show_view_priv`),
|
||||||
|
MAX(t.`Create_routine_priv`),
|
||||||
|
MAX(t.`Alter_routine_priv`),
|
||||||
|
MAX(t.`Execute_priv`),
|
||||||
|
MAX(t.`Event_priv`),
|
||||||
|
MAX(t.`Trigger_priv`)
|
||||||
|
FROM tRoleInherit r
|
||||||
|
JOIN mysql.db t
|
||||||
|
ON t.`User` = r.inheritsFrom
|
||||||
|
AND t.`Host`= vRoleHost
|
||||||
|
GROUP BY r.prefixedRole, t.`Db`;
|
||||||
|
|
||||||
|
-- Copy table level privileges
|
||||||
|
|
||||||
|
INSERT INTO mysql.tables_priv (
|
||||||
|
`User`,
|
||||||
|
`Host`,
|
||||||
|
`Db`,
|
||||||
|
`Table_name`,
|
||||||
|
`Grantor`,
|
||||||
|
`Timestamp`,
|
||||||
|
`Table_priv`,
|
||||||
|
`Column_priv`
|
||||||
|
)
|
||||||
|
SELECT
|
||||||
|
r.prefixedRole,
|
||||||
|
vTplHost,
|
||||||
|
t.`Db`,
|
||||||
|
t.`Table_name`,
|
||||||
|
t.`Grantor`,
|
||||||
|
MAX(t.`Timestamp`),
|
||||||
|
IFNULL(GROUP_CONCAT(NULLIF(t.`Table_priv`, '')), ''),
|
||||||
|
IFNULL(GROUP_CONCAT(NULLIF(t.`Column_priv`, '')), '')
|
||||||
|
FROM tRoleInherit r
|
||||||
|
JOIN mysql.tables_priv t
|
||||||
|
ON t.`User` = r.inheritsFrom
|
||||||
|
AND t.`Host`= vRoleHost
|
||||||
|
GROUP BY r.prefixedRole, t.`Db`, t.`Table_name`;
|
||||||
|
|
||||||
|
-- Copy column level privileges
|
||||||
|
|
||||||
|
INSERT INTO mysql.columns_priv (
|
||||||
|
`User`,
|
||||||
|
`Host`,
|
||||||
|
`Db`,
|
||||||
|
`Table_name`,
|
||||||
|
`Column_name`,
|
||||||
|
`Timestamp`,
|
||||||
|
`Column_priv`
|
||||||
|
)
|
||||||
|
SELECT
|
||||||
|
r.prefixedRole,
|
||||||
|
vTplHost,
|
||||||
|
t.`Db`,
|
||||||
|
t.`Table_name`,
|
||||||
|
t.`Column_name`,
|
||||||
|
MAX(t.`Timestamp`),
|
||||||
|
IFNULL(GROUP_CONCAT(NULLIF(t.`Column_priv`, '')), '')
|
||||||
|
FROM tRoleInherit r
|
||||||
|
JOIN mysql.columns_priv t
|
||||||
|
ON t.`User` = r.inheritsFrom
|
||||||
|
AND t.`Host`= vRoleHost
|
||||||
|
GROUP BY r.prefixedRole, t.`Db`, t.`Table_name`, t.`Column_name`;
|
||||||
|
|
||||||
|
-- Copy routine privileges
|
||||||
|
|
||||||
|
INSERT IGNORE INTO mysql.procs_priv (
|
||||||
|
`User`,
|
||||||
|
`Host`,
|
||||||
|
`Db`,
|
||||||
|
`Routine_name`,
|
||||||
|
`Routine_type`,
|
||||||
|
`Grantor`,
|
||||||
|
`Timestamp`,
|
||||||
|
`Proc_priv`
|
||||||
|
)
|
||||||
|
SELECT
|
||||||
|
r.prefixedRole,
|
||||||
|
vTplHost,
|
||||||
|
t.`Db`,
|
||||||
|
t.`Routine_name`,
|
||||||
|
t.`Routine_type`,
|
||||||
|
t.`Grantor`,
|
||||||
|
t.`Timestamp`,
|
||||||
|
t.`Proc_priv`
|
||||||
|
FROM tRoleInherit r
|
||||||
|
JOIN mysql.procs_priv t
|
||||||
|
ON t.`User` = r.inheritsFrom
|
||||||
|
AND t.`Host`= vRoleHost;
|
||||||
|
|
||||||
|
-- Free memory
|
||||||
|
|
||||||
|
DROP TEMPORARY TABLE
|
||||||
|
tRole,
|
||||||
|
tRoleInherit;
|
||||||
|
|
||||||
|
FLUSH PRIVILEGES;
|
||||||
|
END$$
|
||||||
|
DELIMITER ;
|
|
@ -0,0 +1,15 @@
|
||||||
|
DROP FUNCTION IF EXISTS account.userGetId;
|
||||||
|
|
||||||
|
DELIMITER $$
|
||||||
|
CREATE DEFINER=`root`@`%` FUNCTION `account`.`userGetId`() RETURNS int(11)
|
||||||
|
READS SQL DATA
|
||||||
|
DETERMINISTIC
|
||||||
|
BEGIN
|
||||||
|
/**
|
||||||
|
* @deprecated Use myUser_getId()
|
||||||
|
*/
|
||||||
|
RETURN myUser_getId();
|
||||||
|
END$$
|
||||||
|
DELIMITER ;
|
||||||
|
|
||||||
|
GRANT EXECUTE ON FUNCTION account.userGetId TO guest@localhost;
|
|
@ -0,0 +1,11 @@
|
||||||
|
DROP FUNCTION IF EXISTS account.userGetMysqlRole;
|
||||||
|
|
||||||
|
DELIMITER $$
|
||||||
|
CREATE DEFINER=`root`@`%` FUNCTION `account`.`userGetMysqlRole`(vUserName VARCHAR(255)) RETURNS varchar(255) CHARSET utf8
|
||||||
|
BEGIN
|
||||||
|
/**
|
||||||
|
* @deprecated Use user_getMysqlRole()
|
||||||
|
*/
|
||||||
|
RETURN user_getMysqlRole();
|
||||||
|
END$$
|
||||||
|
DELIMITER ;
|
|
@ -0,0 +1,15 @@
|
||||||
|
DROP FUNCTION IF EXISTS account.userGetName;
|
||||||
|
|
||||||
|
DELIMITER $$
|
||||||
|
CREATE DEFINER=`root`@`%` FUNCTION `account`.`userGetName`() RETURNS varchar(30) CHARSET utf8
|
||||||
|
NO SQL
|
||||||
|
DETERMINISTIC
|
||||||
|
BEGIN
|
||||||
|
/**
|
||||||
|
* @deprecated Use myUser_getName()
|
||||||
|
*/
|
||||||
|
RETURN myUser_getName();
|
||||||
|
END$$
|
||||||
|
DELIMITER ;
|
||||||
|
|
||||||
|
GRANT EXECUTE ON FUNCTION account.userGetName TO guest@localhost;
|
|
@ -0,0 +1,11 @@
|
||||||
|
DROP FUNCTION IF EXISTS account.userGetNameFromId;
|
||||||
|
|
||||||
|
DELIMITER $$
|
||||||
|
CREATE DEFINER=`root`@`%` FUNCTION `account`.`userGetNameFromId`(vSelf INT) RETURNS varchar(30) CHARSET utf8
|
||||||
|
BEGIN
|
||||||
|
/**
|
||||||
|
* @deprecated Use user_getNameFromId();
|
||||||
|
*/
|
||||||
|
RETURN user_getNameFromId(vSelf);
|
||||||
|
END$$
|
||||||
|
DELIMITER ;
|
|
@ -0,0 +1,12 @@
|
||||||
|
DROP FUNCTION IF EXISTS account.userHasRole;
|
||||||
|
|
||||||
|
DELIMITER $$
|
||||||
|
CREATE DEFINER=`root`@`%` FUNCTION `account`.`userHasRole`(vUserName VARCHAR(255), vRoleName VARCHAR(255)) RETURNS tinyint(1)
|
||||||
|
DETERMINISTIC
|
||||||
|
BEGIN
|
||||||
|
/**
|
||||||
|
* @deprecated Use user_hasRole()
|
||||||
|
*/
|
||||||
|
RETURN user_hasRole(vUserName, vRoleName);
|
||||||
|
END$$
|
||||||
|
DELIMITER ;
|
|
@ -0,0 +1,12 @@
|
||||||
|
DROP FUNCTION IF EXISTS account.userHasRoleId;
|
||||||
|
|
||||||
|
DELIMITER $$
|
||||||
|
CREATE DEFINER=`root`@`%` FUNCTION `account`.`userHasRoleId`(vUser VARCHAR(255), vRoleId INT) RETURNS tinyint(1)
|
||||||
|
DETERMINISTIC
|
||||||
|
BEGIN
|
||||||
|
/**
|
||||||
|
* @deprecated Use user_hasRoleId()
|
||||||
|
*/
|
||||||
|
RETURN user_hasRoleId(vUser, vRoleId);
|
||||||
|
END$$
|
||||||
|
DELIMITER ;
|
|
@ -0,0 +1,14 @@
|
||||||
|
DROP PROCEDURE IF EXISTS account.userLogin;
|
||||||
|
|
||||||
|
DELIMITER $$
|
||||||
|
CREATE DEFINER=`root`@`%` PROCEDURE `account`.`userLogin`(vUserName VARCHAR(255), vPassword VARCHAR(255))
|
||||||
|
READS SQL DATA
|
||||||
|
BEGIN
|
||||||
|
/**
|
||||||
|
* @deprecated Use myUser_login()
|
||||||
|
*/
|
||||||
|
CALL myUser_login(vUserName, vPassword);
|
||||||
|
END$$
|
||||||
|
DELIMITER ;
|
||||||
|
|
||||||
|
GRANT EXECUTE ON PROCEDURE account.userLogin TO guest@localhost;
|
|
@ -0,0 +1,14 @@
|
||||||
|
DROP PROCEDURE IF EXISTS account.userLoginWithKey;
|
||||||
|
|
||||||
|
DELIMITER $$
|
||||||
|
CREATE DEFINER=`root`@`%` PROCEDURE `account`.`userLoginWithKey`(vUserName VARCHAR(255), vKey VARCHAR(255))
|
||||||
|
READS SQL DATA
|
||||||
|
BEGIN
|
||||||
|
/**
|
||||||
|
* @deprecated Use myUser_loginWithKey()
|
||||||
|
*/
|
||||||
|
CALL myUser_loginWithKey(vUserName, vKey);
|
||||||
|
END$$
|
||||||
|
DELIMITER ;
|
||||||
|
|
||||||
|
GRANT EXECUTE ON PROCEDURE account.userLoginWithKey TO guest@localhost;
|
|
@ -0,0 +1,12 @@
|
||||||
|
DROP PROCEDURE IF EXISTS account.userLoginWithName;
|
||||||
|
|
||||||
|
DELIMITER $$
|
||||||
|
CREATE DEFINER=`root`@`%` PROCEDURE `account`.`userLoginWithName`(vUserName VARCHAR(255))
|
||||||
|
READS SQL DATA
|
||||||
|
BEGIN
|
||||||
|
/**
|
||||||
|
* @deprecated Use myUser_loginWithName()
|
||||||
|
*/
|
||||||
|
CALL myUser_loginWithName(vUserName);
|
||||||
|
END$$
|
||||||
|
DELIMITER ;
|
|
@ -0,0 +1,14 @@
|
||||||
|
DROP PROCEDURE IF EXISTS account.myUserLogout;
|
||||||
|
|
||||||
|
DELIMITER $$
|
||||||
|
$$
|
||||||
|
CREATE DEFINER=`root`@`%` PROCEDURE `account`.`myUserLogout`()
|
||||||
|
BEGIN
|
||||||
|
/**
|
||||||
|
* @deprecated Use myUser_Logout()
|
||||||
|
*/
|
||||||
|
CALL myUser_logout;
|
||||||
|
END$$
|
||||||
|
DELIMITER ;
|
||||||
|
|
||||||
|
GRANT EXECUTE ON PROCEDURE account.myUserLogout TO account@localhost;
|
|
@ -0,0 +1,16 @@
|
||||||
|
DROP PROCEDURE IF EXISTS account.userSetPassword;
|
||||||
|
|
||||||
|
DELIMITER $$
|
||||||
|
CREATE DEFINER=`root`@`%` PROCEDURE `account`.`userSetPassword`(vUserName VARCHAR(255), vPassword VARCHAR(255))
|
||||||
|
BEGIN
|
||||||
|
/**
|
||||||
|
* @deprecated Use user_setPassword()
|
||||||
|
*/
|
||||||
|
DECLARE vUserId INT;
|
||||||
|
|
||||||
|
SELECT id INTO vUserId
|
||||||
|
FROM user WHERE `name` = vUserName;
|
||||||
|
|
||||||
|
CALL user_setPassword(vUserId, vPassword);
|
||||||
|
END$$
|
||||||
|
DELIMITER ;
|
|
@ -0,0 +1,27 @@
|
||||||
|
DROP PROCEDURE IF EXISTS account.user_changePassword;
|
||||||
|
|
||||||
|
DELIMITER $$
|
||||||
|
CREATE DEFINER=`root`@`%` PROCEDURE account.user_changePassword(vSelf INT, vOldPassword VARCHAR(255), vPassword VARCHAR(255))
|
||||||
|
BEGIN
|
||||||
|
/**
|
||||||
|
* Changes the user password.
|
||||||
|
*
|
||||||
|
* @param vSelf The user id
|
||||||
|
* @param vOldPassword The current password
|
||||||
|
* @param vPassword The new password
|
||||||
|
*/
|
||||||
|
DECLARE vPasswordOk BOOL;
|
||||||
|
DECLARE vUserName VARCHAR(255);
|
||||||
|
|
||||||
|
SELECT `password` = MD5(vOldPassword), `name`
|
||||||
|
INTO vPasswordOk, vUserName
|
||||||
|
FROM user WHERE id = vSelf;
|
||||||
|
|
||||||
|
IF NOT vPasswordOk THEN
|
||||||
|
SIGNAL SQLSTATE '45000'
|
||||||
|
SET MESSAGE_TEXT = 'Invalid password';
|
||||||
|
END IF;
|
||||||
|
|
||||||
|
CALL user_setPassword(vSelf, vPassword);
|
||||||
|
END$$
|
||||||
|
DELIMITER ;
|
|
@ -0,0 +1,17 @@
|
||||||
|
DROP PROCEDURE IF EXISTS account.user_checkName;
|
||||||
|
|
||||||
|
DELIMITER $$
|
||||||
|
CREATE DEFINER=`root`@`%` PROCEDURE `account`.`user_checkName`(vUserName VARCHAR(255))
|
||||||
|
BEGIN
|
||||||
|
/**
|
||||||
|
* Checks that username meets the necessary syntax requirements, otherwise it
|
||||||
|
* throws an exception.
|
||||||
|
* The user name must only contain lowercase letters or, starting with second
|
||||||
|
* character, numbers or underscores.
|
||||||
|
*/
|
||||||
|
IF vUserName NOT REGEXP '^[a-z0-9_-]*$' THEN
|
||||||
|
SIGNAL SQLSTATE '45000'
|
||||||
|
SET MESSAGE_TEXT = 'INVALID_USER_NAME';
|
||||||
|
END IF;
|
||||||
|
END$$
|
||||||
|
DELIMITER ;
|
|
@ -0,0 +1,22 @@
|
||||||
|
DROP FUNCTION IF EXISTS account.user_getMysqlRole;
|
||||||
|
|
||||||
|
DELIMITER $$
|
||||||
|
CREATE DEFINER=`root`@`%` FUNCTION `account`.`user_getMysqlRole`(vUserName VARCHAR(255)) RETURNS varchar(255) CHARSET utf8
|
||||||
|
BEGIN
|
||||||
|
/**
|
||||||
|
* From a username, it returns the associated MySQL wich should be used when
|
||||||
|
* using external authentication systems.
|
||||||
|
*
|
||||||
|
* @param vUserName The user name
|
||||||
|
* @return The associated MySQL role
|
||||||
|
*/
|
||||||
|
DECLARE vRole VARCHAR(255);
|
||||||
|
|
||||||
|
SELECT CONCAT(IF(r.hasLogin, 'z-', ''), r.name) INTO vRole
|
||||||
|
FROM role r
|
||||||
|
JOIN user u ON u.role = r.id
|
||||||
|
WHERE u.name = vUserName;
|
||||||
|
|
||||||
|
RETURN vRole;
|
||||||
|
END$$
|
||||||
|
DELIMITER ;
|
|
@ -0,0 +1,20 @@
|
||||||
|
DROP FUNCTION IF EXISTS account.user_getNameFromId;
|
||||||
|
|
||||||
|
DELIMITER $$
|
||||||
|
CREATE DEFINER=`root`@`%` FUNCTION `account`.`user_getNameFromId`(vSelf INT) RETURNS varchar(30) CHARSET utf8
|
||||||
|
BEGIN
|
||||||
|
/**
|
||||||
|
* Gets user name from it's id.
|
||||||
|
*
|
||||||
|
* @param vSelf The user id
|
||||||
|
* @return The user name
|
||||||
|
*/
|
||||||
|
DECLARE vName VARCHAR(30);
|
||||||
|
|
||||||
|
SELECT `name` INTO vName
|
||||||
|
FROM user
|
||||||
|
WHERE id = vId;
|
||||||
|
|
||||||
|
RETURN vSelf;
|
||||||
|
END$$
|
||||||
|
DELIMITER ;
|
|
@ -0,0 +1,25 @@
|
||||||
|
DROP FUNCTION IF EXISTS account.user_hasRole;
|
||||||
|
|
||||||
|
DELIMITER $$
|
||||||
|
CREATE DEFINER=`root`@`%` FUNCTION `account`.`user_hasRole`(vUserName VARCHAR(255), vRoleName VARCHAR(255)) RETURNS tinyint(1)
|
||||||
|
DETERMINISTIC
|
||||||
|
BEGIN
|
||||||
|
/**
|
||||||
|
* Checks if user has/inherits a role.
|
||||||
|
*
|
||||||
|
* @param vUserName The user name
|
||||||
|
* @param vRoleName Role to check
|
||||||
|
* @return %TRUE if it has role, %FALSE otherwise
|
||||||
|
*/
|
||||||
|
DECLARE vHasRole BOOL DEFAULT FALSE;
|
||||||
|
|
||||||
|
SELECT COUNT(*) > 0 INTO vHasRole
|
||||||
|
FROM user u
|
||||||
|
JOIN roleRole rr ON rr.role = u.role
|
||||||
|
JOIN role r ON r.id = rr.inheritsFrom
|
||||||
|
WHERE u.`name` = vUserName
|
||||||
|
AND r.`name` = vRoleName COLLATE 'utf8_unicode_ci';
|
||||||
|
|
||||||
|
RETURN vHasRole;
|
||||||
|
END$$
|
||||||
|
DELIMITER ;
|
|
@ -0,0 +1,25 @@
|
||||||
|
DROP FUNCTION IF EXISTS account.user_hasRoleId;
|
||||||
|
|
||||||
|
DELIMITER $$
|
||||||
|
CREATE DEFINER=`root`@`%` FUNCTION `account`.`user_hasRoleId`(vUser VARCHAR(255), vRoleId INT) RETURNS tinyint(1)
|
||||||
|
DETERMINISTIC
|
||||||
|
BEGIN
|
||||||
|
/**
|
||||||
|
* Checks if user has/inherits a role.
|
||||||
|
*
|
||||||
|
* @param vUserName The user name
|
||||||
|
* @param vRoleId Role id to check
|
||||||
|
* @return %TRUE if it has role, %FALSE otherwise
|
||||||
|
*/
|
||||||
|
DECLARE vHasRole BOOL DEFAULT FALSE;
|
||||||
|
|
||||||
|
SELECT COUNT(*) > 0 INTO vHasRole
|
||||||
|
FROM user u
|
||||||
|
JOIN roleRole rr ON rr.role = u.role
|
||||||
|
JOIN role r ON r.id = rr.inheritsFrom
|
||||||
|
WHERE u.`name` = vUser
|
||||||
|
AND r.id = vRoleId;
|
||||||
|
|
||||||
|
RETURN vHasRole;
|
||||||
|
END$$
|
||||||
|
DELIMITER ;
|
|
@ -0,0 +1,23 @@
|
||||||
|
DROP PROCEDURE IF EXISTS account.user_setPassword;
|
||||||
|
|
||||||
|
DELIMITER $$
|
||||||
|
CREATE DEFINER=`root`@`%` PROCEDURE account.user_setPassword(vSelf INT, vPassword VARCHAR(255))
|
||||||
|
BEGIN
|
||||||
|
/**
|
||||||
|
* Change the password of the passed as a parameter. Only administrators should
|
||||||
|
* have execute privileges on the procedure since it does not request the user's
|
||||||
|
* current password.
|
||||||
|
*
|
||||||
|
* @param vSelf The user id
|
||||||
|
* @param vPassword New password
|
||||||
|
*/
|
||||||
|
CALL user_checkPassword(vPassword);
|
||||||
|
|
||||||
|
UPDATE user SET
|
||||||
|
`password` = MD5(vPassword),
|
||||||
|
`recoverPass` = FALSE
|
||||||
|
WHERE id = vSelf;
|
||||||
|
|
||||||
|
CALL user_syncPassword(vSelf, vPassword);
|
||||||
|
END$$
|
||||||
|
DELIMITER ;
|
|
@ -12,10 +12,6 @@ INSERT INTO `vn`.`ticketConfig` (`id`, `scopeDays`)
|
||||||
VALUES
|
VALUES
|
||||||
('1', '6');
|
('1', '6');
|
||||||
|
|
||||||
INSERT INTO `account`.`mailConfig` (`id`, `domain`)
|
|
||||||
VALUES
|
|
||||||
('1', 'verdnatura.es');
|
|
||||||
|
|
||||||
INSERT INTO `vn`.`bionicConfig` (`generalInflationCoeficient`, `minimumDensityVolumetricWeight`, `verdnaturaVolumeBox`, `itemCarryBox`)
|
INSERT INTO `vn`.`bionicConfig` (`generalInflationCoeficient`, `minimumDensityVolumetricWeight`, `verdnaturaVolumeBox`, `itemCarryBox`)
|
||||||
VALUES
|
VALUES
|
||||||
(1.30, 167.00, 138000, 71);
|
(1.30, 167.00, 138000, 71);
|
||||||
|
@ -33,6 +29,7 @@ INSERT INTO `vn`.`packagingConfig`(`upperGap`)
|
||||||
('10');
|
('10');
|
||||||
|
|
||||||
UPDATE `account`.`role` SET id = 100 WHERE id = 0;
|
UPDATE `account`.`role` SET id = 100 WHERE id = 0;
|
||||||
|
CALL `account`.`role_sync`;
|
||||||
|
|
||||||
INSERT INTO `account`.`user`(`id`,`name`, `nickname`, `password`,`role`,`active`,`email`, `lang`)
|
INSERT INTO `account`.`user`(`id`,`name`, `nickname`, `password`,`role`,`active`,`email`, `lang`)
|
||||||
SELECT id, name, CONCAT(name, 'Nick'),MD5('nightmare'), id, 1, CONCAT(name, '@mydomain.com'), 'en'
|
SELECT id, name, CONCAT(name, 'Nick'),MD5('nightmare'), id, 1, CONCAT(name, '@mydomain.com'), 'en'
|
||||||
|
@ -68,6 +65,36 @@ INSERT INTO `account`.`user`(`id`,`name`,`nickname`, `password`,`role`,`active`,
|
||||||
(111, 'Missing', 'Missing', 'ac754a330530832ba1bf7687f577da91', 2, 0, NULL, 'en'),
|
(111, 'Missing', 'Missing', 'ac754a330530832ba1bf7687f577da91', 2, 0, NULL, 'en'),
|
||||||
(112, 'Trash', 'Trash', 'ac754a330530832ba1bf7687f577da91', 2, 0, NULL, 'en');
|
(112, 'Trash', 'Trash', 'ac754a330530832ba1bf7687f577da91', 2, 0, NULL, 'en');
|
||||||
|
|
||||||
|
|
||||||
|
INSERT INTO `account`.`userPassword` (`id`, `length`, `nAlpha`, `nUpper`, `nDigits`, `nPunct`)
|
||||||
|
VALUES
|
||||||
|
(1, 8, 1, 1, 1, 1);
|
||||||
|
|
||||||
|
INSERT INTO `account`.`account`(`id`)
|
||||||
|
VALUES
|
||||||
|
(101),
|
||||||
|
(102);
|
||||||
|
|
||||||
|
INSERT INTO `account`.`mailConfig` (`id`, `domain`)
|
||||||
|
VALUES
|
||||||
|
(1, 'verdnatura.es');
|
||||||
|
|
||||||
|
INSERT INTO `account`.`mailAlias`(`id`, `alias`, `description`, `isPublic`)
|
||||||
|
VALUES
|
||||||
|
(1, 'general', 'General mailing list', FALSE),
|
||||||
|
(2, 'it' , 'IT department' , TRUE),
|
||||||
|
(3, 'sales' , 'Sales department' , TRUE);
|
||||||
|
|
||||||
|
INSERT INTO `account`.`mailAliasAccount`(`mailAlias`, `account`)
|
||||||
|
VALUES
|
||||||
|
(1, 101),
|
||||||
|
(1, 102),
|
||||||
|
(2, 101);
|
||||||
|
|
||||||
|
INSERT INTO `account`.`mailForward`(`account`, `forwardTo`)
|
||||||
|
VALUES
|
||||||
|
(101, 'employee@domain.local');
|
||||||
|
|
||||||
INSERT INTO `vn`.`worker`(`id`, `code`, `firstName`, `lastName`, `userFk`,`bossFk`, `phone`)
|
INSERT INTO `vn`.`worker`(`id`, `code`, `firstName`, `lastName`, `userFk`,`bossFk`, `phone`)
|
||||||
VALUES
|
VALUES
|
||||||
(106, 'LGN', 'David Charles', 'Haller', 106, 19, 432978106),
|
(106, 'LGN', 'David Charles', 'Haller', 106, 19, 432978106),
|
||||||
|
@ -88,6 +115,15 @@ INSERT INTO `vn`.`country`(`id`, `country`, `isUeeMember`, `code`, `currencyFk`,
|
||||||
(19,'Francia', 1, 'FR', 1, 27),
|
(19,'Francia', 1, 'FR', 1, 27),
|
||||||
(30,'Canarias', 1, 'IC', 1, 24);
|
(30,'Canarias', 1, 'IC', 1, 24);
|
||||||
|
|
||||||
|
INSERT INTO `hedera`.`language` (`code`, `name`, `orgName`, `isActive`)
|
||||||
|
VALUES
|
||||||
|
('ca', 'Català' , 'Catalan' , TRUE),
|
||||||
|
('en', 'English' , 'English' , TRUE),
|
||||||
|
('es', 'Español' , 'Spanish' , TRUE),
|
||||||
|
('fr', 'Français' , 'French' , TRUE),
|
||||||
|
('mn', 'Португалий', 'Mongolian' , TRUE),
|
||||||
|
('pt', 'Português' , 'Portuguese', TRUE);
|
||||||
|
|
||||||
INSERT INTO `vn`.`warehouseAlias`(`id`, `name`)
|
INSERT INTO `vn`.`warehouseAlias`(`id`, `name`)
|
||||||
VALUES
|
VALUES
|
||||||
(1, 'Main Warehouse'),
|
(1, 'Main Warehouse'),
|
||||||
|
|
|
@ -134,7 +134,7 @@ export default class CrudModel extends ModelProxy {
|
||||||
*/
|
*/
|
||||||
save() {
|
save() {
|
||||||
if (!this.isChanged)
|
if (!this.isChanged)
|
||||||
return null;
|
return this.$q.resolve();
|
||||||
|
|
||||||
let deletes = [];
|
let deletes = [];
|
||||||
let updates = [];
|
let updates = [];
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
<div ng-if="$ctrl.isReady">
|
<div ng-if="$ctrl.isReady">
|
||||||
<div ng-transclude></div>
|
<div ng-transclude></div>
|
||||||
<vn-pagination
|
<vn-pagination
|
||||||
|
ng-if="$ctrl.model"
|
||||||
model="$ctrl.model"
|
model="$ctrl.model"
|
||||||
class="vn-pt-md">
|
class="vn-pt-md">
|
||||||
</vn-pagination>
|
</vn-pagination>
|
||||||
|
|
|
@ -32,7 +32,6 @@ vn-list,
|
||||||
|
|
||||||
vn-item,
|
vn-item,
|
||||||
.vn-item {
|
.vn-item {
|
||||||
@extend %clickable;
|
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
color: inherit;
|
color: inherit;
|
||||||
|
@ -85,4 +84,6 @@ vn-item,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
a.vn-item {
|
||||||
|
@extend %clickable;
|
||||||
|
}
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
import ngModule from '../../module';
|
import ngModule from '../../module';
|
||||||
import Popover from '../popover';
|
import Popover from '../popover';
|
||||||
|
import './style.scss';
|
||||||
|
|
||||||
export default class Menu extends Popover {
|
export default class Menu extends Popover {
|
||||||
show(parent) {
|
show(parent) {
|
||||||
|
|
|
@ -0,0 +1,7 @@
|
||||||
|
@import "./effects";
|
||||||
|
|
||||||
|
.vn-menu {
|
||||||
|
vn-item, .vn-item {
|
||||||
|
@extend %clickable;
|
||||||
|
}
|
||||||
|
}
|
|
@ -107,15 +107,16 @@ export default class ModelProxy extends DataModel {
|
||||||
* Removes a row from the model and emits the 'rowRemove' event.
|
* Removes a row from the model and emits the 'rowRemove' event.
|
||||||
*
|
*
|
||||||
* @param {Number} index The row index
|
* @param {Number} index The row index
|
||||||
|
* @return {Promise} The save request promise
|
||||||
*/
|
*/
|
||||||
remove(index) {
|
remove(index) {
|
||||||
let [item] = this.data.splice(index, 1);
|
let [row] = this.data.splice(index, 1);
|
||||||
|
|
||||||
let proxiedIndex = this.proxiedData.indexOf(item);
|
let proxiedIndex = this.proxiedData.indexOf(row);
|
||||||
this.proxiedData.splice(proxiedIndex, 1);
|
this.proxiedData.splice(proxiedIndex, 1);
|
||||||
|
|
||||||
if (!item.$isNew)
|
if (!row.$isNew)
|
||||||
this.removed.push(item);
|
this.removed.push(row);
|
||||||
|
|
||||||
this.isChanged = true;
|
this.isChanged = true;
|
||||||
if (!this.data.length)
|
if (!this.data.length)
|
||||||
|
@ -125,7 +126,19 @@ export default class ModelProxy extends DataModel {
|
||||||
this.emit('dataUpdate');
|
this.emit('dataUpdate');
|
||||||
|
|
||||||
if (this.autoSave)
|
if (this.autoSave)
|
||||||
this.save();
|
return this.save();
|
||||||
|
else
|
||||||
|
return this.$q.resolve();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Removes a row from the model and emits the 'rowRemove' event.
|
||||||
|
*
|
||||||
|
* @param {Object} row The row object
|
||||||
|
* @return {Promise} The save request promise
|
||||||
|
*/
|
||||||
|
removeRow(row) {
|
||||||
|
return this.remove(this.data.indexOf(row));
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -1 +1 @@
|
||||||
Search by: Search by {{module | translate}}
|
Search for: Search {{module}}
|
|
@ -1 +1 @@
|
||||||
Search by: Buscar por {{module | translate}}
|
Search for: Buscar {{module}}
|
|
@ -16,6 +16,7 @@ import './style.scss';
|
||||||
* @property {Function} onSearch Function to call when search is submited
|
* @property {Function} onSearch Function to call when search is submited
|
||||||
* @property {CrudModel} model The model used for searching
|
* @property {CrudModel} model The model used for searching
|
||||||
* @property {Function} exprBuilder If defined, is used to build each non-null param expresion
|
* @property {Function} exprBuilder If defined, is used to build each non-null param expresion
|
||||||
|
* @property {String} baseState The base state for searchs
|
||||||
*/
|
*/
|
||||||
export default class Searchbar extends Component {
|
export default class Searchbar extends Component {
|
||||||
constructor($element, $) {
|
constructor($element, $) {
|
||||||
|
@ -23,6 +24,8 @@ export default class Searchbar extends Component {
|
||||||
this.searchState = '.';
|
this.searchState = '.';
|
||||||
this.placeholder = 'Search';
|
this.placeholder = 'Search';
|
||||||
this.autoState = true;
|
this.autoState = true;
|
||||||
|
this.separateIndex = true;
|
||||||
|
this.entityState = 'card.summary';
|
||||||
|
|
||||||
this.deregisterCallback = this.$transitions.onSuccess(
|
this.deregisterCallback = this.$transitions.onSuccess(
|
||||||
{}, transition => this.onStateChange(transition));
|
{}, transition => this.onStateChange(transition));
|
||||||
|
@ -33,11 +36,13 @@ export default class Searchbar extends Component {
|
||||||
if (!this.baseState) {
|
if (!this.baseState) {
|
||||||
let stateParts = this.$state.current.name.split('.');
|
let stateParts = this.$state.current.name.split('.');
|
||||||
this.baseState = stateParts[0];
|
this.baseState = stateParts[0];
|
||||||
}
|
this.searchState = `${this.baseState}.index`;
|
||||||
|
} else
|
||||||
|
this.searchState = this.baseState;
|
||||||
|
|
||||||
this.searchState = `${this.baseState}.index`;
|
let description = this.$state.get(this.baseState).description;
|
||||||
this.placeholder = this.$t('Search by', {
|
this.placeholder = this.$t('Search for', {
|
||||||
module: this.baseState
|
module: this.$t(description).toLowerCase()
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -205,7 +210,9 @@ export default class Searchbar extends Component {
|
||||||
&& source != 'state'
|
&& source != 'state'
|
||||||
&& !angular.equals(filter, {})
|
&& !angular.equals(filter, {})
|
||||||
&& data
|
&& data
|
||||||
&& data.length == 1;
|
&& data.length == 1
|
||||||
|
&& filter.search
|
||||||
|
&& Object.keys(filter).length == 1;
|
||||||
|
|
||||||
if (isOneResult) {
|
if (isOneResult) {
|
||||||
let baseDepth = this.baseState.split('.').length;
|
let baseDepth = this.baseState.split('.').length;
|
||||||
|
@ -222,7 +229,7 @@ export default class Searchbar extends Component {
|
||||||
subState += '.index';
|
subState += '.index';
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
subState = 'card.summary';
|
subState = this.entityState;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this.stateParams)
|
if (this.stateParams)
|
||||||
|
@ -292,8 +299,10 @@ ngModule.vnComponent('vnSearchbar', {
|
||||||
panel: '@',
|
panel: '@',
|
||||||
info: '@?',
|
info: '@?',
|
||||||
onSearch: '&?',
|
onSearch: '&?',
|
||||||
baseState: '@?',
|
|
||||||
autoState: '<?',
|
autoState: '<?',
|
||||||
|
baseState: '@?',
|
||||||
|
entityState: '@?',
|
||||||
|
separateIndex: '<?',
|
||||||
stateParams: '&?',
|
stateParams: '&?',
|
||||||
model: '<?',
|
model: '<?',
|
||||||
exprBuilder: '&?',
|
exprBuilder: '&?',
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
import ngModule from '../../module';
|
import ngModule from '../../module';
|
||||||
import Component from '../../lib/component';
|
import Component from '../../lib/component';
|
||||||
|
import {kebabToCamel} from '../../lib/string';
|
||||||
import './style.scss';
|
import './style.scss';
|
||||||
|
|
||||||
let positions = ['left', 'right', 'up', 'down'];
|
let positions = ['left', 'right', 'up', 'down'];
|
||||||
|
@ -206,8 +207,8 @@ ngModule.vnComponent('vnTooltip', {
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
directive.$inject = ['$document', '$compile', '$templateRequest'];
|
directive.$inject = ['$document', '$compile'];
|
||||||
export function directive($document, $compile, $templateRequest) {
|
export function directive($document, $compile) {
|
||||||
return {
|
return {
|
||||||
restrict: 'A',
|
restrict: 'A',
|
||||||
link: function($scope, $element, $attrs) {
|
link: function($scope, $element, $attrs) {
|
||||||
|
|
|
@ -1,70 +1,220 @@
|
||||||
import ngModule from '../../module';
|
import ngModule from '../../module';
|
||||||
import Component from '../../lib/component';
|
import Component from '../../lib/component';
|
||||||
import getModifiedData from '../../lib/modified';
|
import getModifiedData from '../../lib/modified';
|
||||||
import copyObject from '../../lib/copy';
|
|
||||||
import isEqual from '../../lib/equals';
|
import isEqual from '../../lib/equals';
|
||||||
import isFullEmpty from '../../lib/full-empty';
|
import isFullEmpty from '../../lib/full-empty';
|
||||||
import UserError from '../../lib/user-error';
|
import UserError from '../../lib/user-error';
|
||||||
|
import {mergeFilters} from 'vn-loopback/util/filter';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Component that checks for changes on a specific model property and
|
* Component that checks for changes on a specific model property and asks the
|
||||||
* asks the user to save or discard it when the state changes.
|
* user to save or discard it when the state changes.
|
||||||
* Also it can save the data to the server when the @url and @idField
|
* Also it can save the data to the server when the @url and @idField properties
|
||||||
* properties are provided.
|
* are provided.
|
||||||
|
*
|
||||||
|
* @property {String} idField The id field name, 'id' if not specified
|
||||||
|
* @property {*} idValue The id field value
|
||||||
|
* @property {Boolean} isNew Whether is a new instance
|
||||||
|
* @property {Boolean} insertMode Whether to enable insert mode
|
||||||
|
* @property {String} url The base HTTP request path
|
||||||
|
* @property {Boolean} get Whether to fetch initial data
|
||||||
*/
|
*/
|
||||||
export default class Watcher extends Component {
|
export default class Watcher extends Component {
|
||||||
constructor($element, $, $state, $stateParams, $transitions, $http, vnApp, $translate, $attrs, $q) {
|
constructor(...args) {
|
||||||
super($element);
|
super(...args);
|
||||||
Object.assign(this, {
|
this.idField = 'id';
|
||||||
$,
|
this.get = true;
|
||||||
$state,
|
this.insertMode = false;
|
||||||
$stateParams,
|
|
||||||
$http,
|
|
||||||
_: $translate,
|
|
||||||
$attrs,
|
|
||||||
vnApp,
|
|
||||||
$q
|
|
||||||
});
|
|
||||||
|
|
||||||
this.state = null;
|
this.state = null;
|
||||||
this.deregisterCallback = $transitions.onStart({},
|
this.deregisterCallback = this.$transitions.onStart({},
|
||||||
transition => this.callback(transition));
|
transition => this.callback(transition));
|
||||||
this.updateOriginalData();
|
this.snapshot();
|
||||||
}
|
}
|
||||||
|
|
||||||
$onInit() {
|
$onInit() {
|
||||||
if (this.get && this.url)
|
let fetch = !this.insertMode
|
||||||
this.fetchData();
|
&& this.get
|
||||||
else if (this.get && !this.url)
|
&& this.url
|
||||||
throw new Error('URL parameter ommitted');
|
&& this.idValue;
|
||||||
}
|
|
||||||
|
|
||||||
$onChanges() {
|
if (fetch)
|
||||||
if (this.data)
|
this.fetch();
|
||||||
this.updateOriginalData();
|
else {
|
||||||
|
this.isNew = !!this.insertMode;
|
||||||
|
this.snapshot();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
$onDestroy() {
|
$onDestroy() {
|
||||||
this.deregisterCallback();
|
this.deregisterCallback();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @type {Booelan} The computed instance HTTP path
|
||||||
|
*/
|
||||||
|
get instanceUrl() {
|
||||||
|
return `${this.url}/${this.idValue}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @type {Object} The instance data
|
||||||
|
*/
|
||||||
|
get data() {
|
||||||
|
return this._data;
|
||||||
|
}
|
||||||
|
|
||||||
|
set data(value) {
|
||||||
|
this._data = value;
|
||||||
|
this.isNew = !!this.insertMode;
|
||||||
|
this.snapshot();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @type {Booelan} Whether it's popullated with data
|
||||||
|
*/
|
||||||
|
get hasData() {
|
||||||
|
return !!this.data;
|
||||||
|
}
|
||||||
|
|
||||||
|
set hasData(value) {
|
||||||
|
if (value)
|
||||||
|
this.fill();
|
||||||
|
else
|
||||||
|
this.delete();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @type {Booelan} Whether instance data have been modified
|
||||||
|
*/
|
||||||
get dirty() {
|
get dirty() {
|
||||||
return this.form && this.form.$dirty || this.dataChanged();
|
return this.form && this.form.$dirty || this.dataChanged();
|
||||||
}
|
}
|
||||||
|
|
||||||
dataChanged() {
|
fetch() {
|
||||||
let data = this.copyInNewObject(this.data);
|
let filter = mergeFilters({
|
||||||
return !isEqual(data, this.orgData);
|
fields: this.fields,
|
||||||
|
where: this.where,
|
||||||
|
include: this.include
|
||||||
|
}, this.filter);
|
||||||
|
|
||||||
|
let params = filter ? {filter} : null;
|
||||||
|
|
||||||
|
return this.$http.get(this.instanceUrl, params)
|
||||||
|
.then(json => {
|
||||||
|
this.overwrite(json.data);
|
||||||
|
this.isNew = false;
|
||||||
|
this.snapshot();
|
||||||
|
})
|
||||||
|
.catch(err => {
|
||||||
|
if (!(err.name == 'HttpError' && err.status == 404))
|
||||||
|
throw err;
|
||||||
|
|
||||||
|
if (this.autoFill) {
|
||||||
|
this.insert();
|
||||||
|
this.snapshot();
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
fetchData() {
|
insert(data) {
|
||||||
let id = this.data[this.idField];
|
this.assign({[this.idField]: this.idValue}, data);
|
||||||
return this.$http.get(`${this.url}/${id}`).then(
|
this.isNew = true;
|
||||||
json => {
|
this.deleted = null;
|
||||||
this.data = copyObject(json.data);
|
}
|
||||||
this.updateOriginalData();
|
|
||||||
}
|
delete() {
|
||||||
);
|
if (!this.hasData) return;
|
||||||
|
this.deleted = this.makeSnapshot();
|
||||||
|
this.clear();
|
||||||
|
this.isNew = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
recover() {
|
||||||
|
if (!this.deleted) return;
|
||||||
|
this.restoreSnapshot(this.deleted);
|
||||||
|
}
|
||||||
|
|
||||||
|
fill() {
|
||||||
|
if (this.hasData)
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (this.deleted)
|
||||||
|
this.recover();
|
||||||
|
else if (this.original && this.original.data)
|
||||||
|
this.reset();
|
||||||
|
else
|
||||||
|
this.insert();
|
||||||
|
}
|
||||||
|
|
||||||
|
reset() {
|
||||||
|
this.restoreSnapshot(this.original);
|
||||||
|
this.setPristine();
|
||||||
|
}
|
||||||
|
|
||||||
|
snapshot() {
|
||||||
|
const snapshot = this.makeSnapshot();
|
||||||
|
|
||||||
|
if (snapshot.data) {
|
||||||
|
const idValue = snapshot.data[this.idField];
|
||||||
|
if (idValue) this.idValue = idValue;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.original = snapshot;
|
||||||
|
this.orgData = snapshot.data;
|
||||||
|
this.deleted = null;
|
||||||
|
this.setPristine();
|
||||||
|
}
|
||||||
|
|
||||||
|
makeSnapshot() {
|
||||||
|
return {
|
||||||
|
data: this.copyData(),
|
||||||
|
isNew: this.isNew,
|
||||||
|
ref: this.data
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
restoreSnapshot(snapshot) {
|
||||||
|
if (!snapshot) return;
|
||||||
|
this._data = snapshot.ref;
|
||||||
|
this.overwrite(snapshot.data);
|
||||||
|
this.isNew = snapshot.isNew;
|
||||||
|
this.deleted = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
writeData(res) {
|
||||||
|
if (this.hasData)
|
||||||
|
this.assign(res.data);
|
||||||
|
this.isNew = false;
|
||||||
|
this.snapshot();
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
|
||||||
|
clear() {
|
||||||
|
this._data = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
overwrite(data) {
|
||||||
|
if (data) {
|
||||||
|
if (!this.data) this._data = {};
|
||||||
|
overwrite(this.data, data);
|
||||||
|
} else
|
||||||
|
this._data = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
assign(...args) {
|
||||||
|
this._data = Object.assign(this.data || {}, ...args);
|
||||||
|
}
|
||||||
|
|
||||||
|
copyData() {
|
||||||
|
return copyObject(this.data);
|
||||||
|
}
|
||||||
|
|
||||||
|
refresh() {
|
||||||
|
return this.fetch();
|
||||||
|
}
|
||||||
|
|
||||||
|
dataChanged() {
|
||||||
|
return !isEqual(this.orgData, this.copyData());
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -100,9 +250,10 @@ export default class Watcher extends Component {
|
||||||
*/
|
*/
|
||||||
submit() {
|
submit() {
|
||||||
try {
|
try {
|
||||||
if (this.requestMethod() !== 'post')
|
if (this.isNew)
|
||||||
|
this.isInvalid();
|
||||||
|
else
|
||||||
this.check();
|
this.check();
|
||||||
else this.isInvalid();
|
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
return this.$q.reject(err);
|
return this.$q.reject(err);
|
||||||
}
|
}
|
||||||
|
@ -122,65 +273,42 @@ export default class Watcher extends Component {
|
||||||
if (this.form)
|
if (this.form)
|
||||||
this.form.$setSubmitted();
|
this.form.$setSubmitted();
|
||||||
|
|
||||||
const isPost = (this.requestMethod() === 'post');
|
if (!this.dataChanged() && !this.isNew) {
|
||||||
if (!this.dataChanged() && !isPost) {
|
this.snapshot();
|
||||||
this.updateOriginalData();
|
|
||||||
return this.$q.resolve();
|
return this.$q.resolve();
|
||||||
}
|
}
|
||||||
|
|
||||||
let changedData = isPost
|
let changedData = this.isNew
|
||||||
? this.data
|
? this.data
|
||||||
: getModifiedData(this.data, this.orgData);
|
: getModifiedData(this.data, this.orgData);
|
||||||
|
|
||||||
let id = this.idField ? this.orgData[this.idField] : null;
|
|
||||||
|
|
||||||
// If watcher is associated to mgCrud
|
// If watcher is associated to mgCrud
|
||||||
|
|
||||||
if (this.save && this.save.accept) {
|
if (this.save && this.save.accept) {
|
||||||
if (id)
|
|
||||||
changedData[this.idField] = id;
|
|
||||||
|
|
||||||
this.save.model = changedData;
|
this.save.model = changedData;
|
||||||
return this.$q((resolve, reject) => {
|
return this.save.accept()
|
||||||
this.save.accept().then(
|
.then(json => this.writeData({data: json}));
|
||||||
json => this.writeData({data: json}, resolve),
|
|
||||||
reject
|
|
||||||
);
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// When mgCrud is not used
|
// When mgCrud is not used
|
||||||
|
|
||||||
if (id) {
|
let req;
|
||||||
return this.$q((resolve, reject) => {
|
|
||||||
this.$http.patch(`${this.url}/${id}`, changedData).then(
|
|
||||||
json => this.writeData(json, resolve),
|
|
||||||
reject
|
|
||||||
);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
return this.$q((resolve, reject) => {
|
if (this.deleted)
|
||||||
this.$http.post(this.url, changedData).then(
|
req = this.$http.delete(this.instanceUrl);
|
||||||
json => this.writeData(json, resolve),
|
else if (this.isNew)
|
||||||
reject
|
req = this.$http.post(this.url, changedData);
|
||||||
);
|
else
|
||||||
});
|
req = this.$http.patch(this.instanceUrl, changedData);
|
||||||
}
|
|
||||||
/**
|
|
||||||
* return the request method.
|
|
||||||
*/
|
|
||||||
|
|
||||||
requestMethod() {
|
return req.then(res => this.writeData(res));
|
||||||
return this.$attrs.save && this.$attrs.save.toLowerCase();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Checks if data is ready to send.
|
* Checks if data is ready to send.
|
||||||
*/
|
*/
|
||||||
check() {
|
check() {
|
||||||
if (this.form && this.form.$invalid)
|
this.isInvalid();
|
||||||
throw new UserError('Some fields are invalid');
|
|
||||||
if (!this.dirty)
|
if (!this.dirty)
|
||||||
throw new UserError('No changes to save');
|
throw new UserError('No changes to save');
|
||||||
}
|
}
|
||||||
|
@ -220,62 +348,82 @@ export default class Watcher extends Component {
|
||||||
|
|
||||||
onConfirmResponse(response) {
|
onConfirmResponse(response) {
|
||||||
if (response === 'accept') {
|
if (response === 'accept') {
|
||||||
if (this.data)
|
this.reset();
|
||||||
Object.assign(this.data, this.orgData);
|
|
||||||
this.$state.go(this.state);
|
this.$state.go(this.state);
|
||||||
} else
|
} else
|
||||||
this.state = null;
|
this.state = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
writeData(json, resolve) {
|
/**
|
||||||
Object.assign(this.data, json.data);
|
* @deprecated Use reset()
|
||||||
this.updateOriginalData();
|
*/
|
||||||
resolve(json);
|
|
||||||
}
|
|
||||||
|
|
||||||
updateOriginalData() {
|
|
||||||
this.orgData = this.copyInNewObject(this.data);
|
|
||||||
this.setPristine();
|
|
||||||
}
|
|
||||||
|
|
||||||
loadOriginalData() {
|
loadOriginalData() {
|
||||||
const orgData = JSON.parse(JSON.stringify(this.orgData));
|
this.reset();
|
||||||
this.data = Object.assign(this.data, orgData);
|
|
||||||
this.setPristine();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
copyInNewObject(data) {
|
/**
|
||||||
let newCopy = {};
|
* @deprecated Use snapshot()
|
||||||
if (data && typeof data === 'object') {
|
*/
|
||||||
Object.keys(data).forEach(
|
updateOriginalData() {
|
||||||
key => {
|
this.snapshot();
|
||||||
let value = data[key];
|
|
||||||
if (value instanceof Date)
|
|
||||||
newCopy[key] = new Date(value.getTime());
|
|
||||||
else if (!isFullEmpty(value)) {
|
|
||||||
if (typeof value === 'object')
|
|
||||||
newCopy[key] = this.copyInNewObject(value);
|
|
||||||
else
|
|
||||||
newCopy[key] = value;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
return newCopy;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Watcher.$inject = ['$element', '$scope', '$state', '$stateParams', '$transitions', '$http', 'vnApp', '$translate', '$attrs', '$q'];
|
Watcher.$inject = ['$element', '$scope'];
|
||||||
|
|
||||||
|
function copyObject(data) {
|
||||||
|
let newCopy;
|
||||||
|
|
||||||
|
if (data && typeof data === 'object') {
|
||||||
|
newCopy = {};
|
||||||
|
Object.keys(data).forEach(
|
||||||
|
key => {
|
||||||
|
let value = data[key];
|
||||||
|
if (value instanceof Date)
|
||||||
|
newCopy[key] = new Date(value.getTime());
|
||||||
|
else if (!isFullEmpty(value)) {
|
||||||
|
if (typeof value === 'object')
|
||||||
|
newCopy[key] = copyObject(value);
|
||||||
|
else
|
||||||
|
newCopy[key] = value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
} else
|
||||||
|
newCopy = data;
|
||||||
|
|
||||||
|
return newCopy;
|
||||||
|
}
|
||||||
|
|
||||||
|
function clearObject(obj) {
|
||||||
|
if (!obj) return;
|
||||||
|
for (let key in obj) {
|
||||||
|
if (obj.hasOwnProperty(key))
|
||||||
|
delete obj[key];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function overwrite(obj, data) {
|
||||||
|
if (!obj) return;
|
||||||
|
clearObject(obj);
|
||||||
|
Object.assign(obj, data);
|
||||||
|
}
|
||||||
|
|
||||||
ngModule.vnComponent('vnWatcher', {
|
ngModule.vnComponent('vnWatcher', {
|
||||||
template: require('./watcher.html'),
|
template: require('./watcher.html'),
|
||||||
bindings: {
|
bindings: {
|
||||||
url: '@?',
|
url: '@?',
|
||||||
idField: '@?',
|
idField: '@?',
|
||||||
data: '<',
|
idValue: '<?',
|
||||||
|
data: '=',
|
||||||
form: '<',
|
form: '<',
|
||||||
save: '<',
|
save: '<?',
|
||||||
get: '<?'
|
get: '<?',
|
||||||
|
insertMode: '<?',
|
||||||
|
autoFill: '<?',
|
||||||
|
filter: '<?',
|
||||||
|
fields: '<?',
|
||||||
|
where: '<?',
|
||||||
|
include: '<?'
|
||||||
},
|
},
|
||||||
controller: Watcher
|
controller: Watcher
|
||||||
});
|
});
|
||||||
|
|
|
@ -1,5 +1,4 @@
|
||||||
import './watcher.js';
|
import './watcher.js';
|
||||||
import getModifiedData from '../../lib/modified';
|
|
||||||
|
|
||||||
describe('Component vnWatcher', () => {
|
describe('Component vnWatcher', () => {
|
||||||
let $scope;
|
let $scope;
|
||||||
|
@ -9,10 +8,16 @@ describe('Component vnWatcher', () => {
|
||||||
let controller;
|
let controller;
|
||||||
let $attrs;
|
let $attrs;
|
||||||
let $q;
|
let $q;
|
||||||
|
let data;
|
||||||
|
|
||||||
beforeEach(ngModule('vnCore'));
|
beforeEach(ngModule('vnCore'));
|
||||||
|
|
||||||
beforeEach(inject(($componentController, $rootScope, _$httpBackend_, _$state_, _$q_) => {
|
beforeEach(inject(($componentController, $rootScope, _$httpBackend_, _$state_, _$q_) => {
|
||||||
|
data = {
|
||||||
|
id: 1,
|
||||||
|
foo: 'bar'
|
||||||
|
};
|
||||||
|
|
||||||
$scope = $rootScope.$new();
|
$scope = $rootScope.$new();
|
||||||
$element = angular.element('<div></div>');
|
$element = angular.element('<div></div>');
|
||||||
$state = _$state_;
|
$state = _$state_;
|
||||||
|
@ -25,38 +30,49 @@ describe('Component vnWatcher', () => {
|
||||||
}));
|
}));
|
||||||
|
|
||||||
describe('$onInit()', () => {
|
describe('$onInit()', () => {
|
||||||
it('should call fetchData() if controllers get and url properties are defined', () => {
|
it('should set data empty by default', () => {
|
||||||
controller.get = () => {};
|
|
||||||
controller.url = 'test.com';
|
|
||||||
jest.spyOn(controller, 'fetchData').mockReturnThis();
|
|
||||||
controller.$onInit();
|
controller.$onInit();
|
||||||
|
|
||||||
expect(controller.fetchData).toHaveBeenCalledWith();
|
expect(controller.data).toBeUndefined();
|
||||||
});
|
});
|
||||||
|
|
||||||
it(`should throw an error if $onInit is called without url defined`, () => {
|
it('should set new data when insert mode is enabled', () => {
|
||||||
controller.get = () => {};
|
controller.insertMode = true;
|
||||||
|
controller.data = data;
|
||||||
|
|
||||||
expect(function() {
|
controller.$onInit();
|
||||||
controller.$onInit();
|
|
||||||
}).toThrowError(/parameter/);
|
expect(controller.orgData).toEqual(data);
|
||||||
|
expect(controller.orgData).toEqual(data);
|
||||||
|
expect(controller.isNew).toBeTruthy();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should call backend and fetch data if url and idValue properties are defined', () => {
|
||||||
|
controller.url = 'Foos';
|
||||||
|
controller.idValue = 1;
|
||||||
|
|
||||||
|
$httpBackend.expectGET('Foos/1').respond(data);
|
||||||
|
controller.$onInit();
|
||||||
|
$httpBackend.flush();
|
||||||
|
|
||||||
|
expect(controller.orgData).toEqual(data);
|
||||||
|
expect(controller.orgData).toEqual(data);
|
||||||
|
expect(controller.orgData).not.toBe(controller.data);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('fetchData()', () => {
|
describe('fetch()', () => {
|
||||||
it(`should perform a query then store the received data into controller.data and call updateOriginalData()`, () => {
|
it(`should perform a query then store the received data into data property and make an snapshot into orgData`, () => {
|
||||||
jest.spyOn(controller, 'updateOriginalData');
|
controller.url = 'Bars';
|
||||||
let json = {data: 'some data'};
|
controller.idValue = 1;
|
||||||
controller.data = [1];
|
|
||||||
controller.idField = 0;
|
$httpBackend.expectGET('Bars/1').respond(data);
|
||||||
controller.url = 'test.com';
|
controller.$onInit();
|
||||||
$httpBackend.whenGET('test.com/1').respond(json);
|
|
||||||
$httpBackend.expectGET('test.com/1');
|
|
||||||
controller.fetchData();
|
|
||||||
$httpBackend.flush();
|
$httpBackend.flush();
|
||||||
|
|
||||||
expect(controller.data).toEqual({data: 'some data'});
|
expect(controller.orgData).toEqual(data);
|
||||||
expect(controller.updateOriginalData).toHaveBeenCalledWith();
|
expect(controller.orgData).toEqual(data);
|
||||||
|
expect(controller.orgData).not.toBe(controller.data);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -95,14 +111,6 @@ describe('Component vnWatcher', () => {
|
||||||
controller.check();
|
controller.check();
|
||||||
}).toThrowError();
|
}).toThrowError();
|
||||||
});
|
});
|
||||||
|
|
||||||
it(`should throw error if controller.dirty is true`, () => {
|
|
||||||
controller.form = {$invalid: true};
|
|
||||||
|
|
||||||
expect(function() {
|
|
||||||
controller.check();
|
|
||||||
}).toThrowError();
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('realSubmit()', () => {
|
describe('realSubmit()', () => {
|
||||||
|
@ -130,59 +138,60 @@ describe('Component vnWatcher', () => {
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('when id is defined', () => {
|
describe('should perform a PATCH query and save the data', () => {
|
||||||
it(`should perform a query then call controller.writeData()`, done => {
|
it(`should perform a query then call controller.writeData()`, () => {
|
||||||
controller.dataChanged = () => {
|
controller.url = 'Foos';
|
||||||
return true;
|
controller.data = data;
|
||||||
};
|
|
||||||
controller.data = {id: 2};
|
const changedData = {baz: 'value'};
|
||||||
controller.orgData = {id: 1};
|
Object.assign(controller.data, changedData);
|
||||||
let changedData = getModifiedData(controller.data, controller.orgData);
|
|
||||||
controller.idField = 'id';
|
$httpBackend.expectPATCH('Foos/1', changedData).respond({newProp: 'some'});
|
||||||
controller.url = 'test.com';
|
controller.realSubmit();
|
||||||
let json = {data: 'some data'};
|
|
||||||
jest.spyOn(controller, 'writeData');
|
|
||||||
$httpBackend.whenPATCH(`${controller.url}/1`, changedData).respond(json);
|
|
||||||
$httpBackend.expectPATCH(`${controller.url}/1`);
|
|
||||||
controller.realSubmit()
|
|
||||||
.then(() => {
|
|
||||||
expect(controller.writeData).toHaveBeenCalledWith(jasmine.any(Object), jasmine.any(Function));
|
|
||||||
done();
|
|
||||||
}).catch(done.fail);
|
|
||||||
$httpBackend.flush();
|
$httpBackend.flush();
|
||||||
|
|
||||||
|
expect(controller.data).toEqual(Object.assign({}, data, changedData));
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
it(`should perform a POST query then call controller.writeData()`, done => {
|
it(`should perform a POST query and save the data`, () => {
|
||||||
controller.dataChanged = () => {
|
controller.insertMode = true;
|
||||||
return true;
|
controller.url = 'Foos';
|
||||||
};
|
controller.data = data;
|
||||||
controller.data = {id: 2};
|
|
||||||
controller.orgData = {id: 1};
|
const changedData = {baz: 'value'};
|
||||||
controller.url = 'test.com';
|
Object.assign(controller.data, changedData);
|
||||||
let json = {data: 'some data'};
|
|
||||||
jest.spyOn(controller, 'writeData');
|
$httpBackend.expectPOST('Foos', controller.data).respond({newProp: 'some'});
|
||||||
$httpBackend.whenPOST(`${controller.url}`, controller.data).respond(json);
|
controller.realSubmit();
|
||||||
$httpBackend.expectPOST(`${controller.url}`, controller.data);
|
|
||||||
controller.realSubmit()
|
|
||||||
.then(() => {
|
|
||||||
expect(controller.writeData).toHaveBeenCalledWith(jasmine.any(Object), jasmine.any(Function));
|
|
||||||
done();
|
|
||||||
}).catch(done.fail);
|
|
||||||
$httpBackend.flush();
|
$httpBackend.flush();
|
||||||
|
|
||||||
|
expect(controller.data).toEqual(Object.assign({}, data, changedData));
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('should perform a DELETE query and save empty data', () => {
|
||||||
|
it(`should perform a query then call controller.writeData()`, () => {
|
||||||
|
controller.url = 'Foos';
|
||||||
|
controller.data = data;
|
||||||
|
controller.delete();
|
||||||
|
|
||||||
|
$httpBackend.expectDELETE('Foos/1').respond();
|
||||||
|
controller.realSubmit();
|
||||||
|
$httpBackend.flush();
|
||||||
|
|
||||||
|
expect(controller.data).toBeNull();
|
||||||
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('writeData()', () => {
|
describe('writeData()', () => {
|
||||||
it(`should call Object.asssign() function over controllers.data with json.data, then call updateOriginalData function and finally call resolve() function`, () => {
|
it(`should save data into orgData`, () => {
|
||||||
jest.spyOn(controller, 'updateOriginalData');
|
controller.data = data;
|
||||||
controller.data = {};
|
Object.assign(controller.data, {baz: 'value'});
|
||||||
let json = {data: 'some data'};
|
|
||||||
let resolve = jasmine.createSpy('resolve');
|
|
||||||
controller.writeData(json, resolve);
|
|
||||||
|
|
||||||
expect(controller.updateOriginalData).toHaveBeenCalledWith();
|
controller.writeData({});
|
||||||
expect(resolve).toHaveBeenCalledWith(json);
|
|
||||||
|
expect(controller.data).toEqual(controller.orgData);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -224,37 +233,38 @@ describe('Component vnWatcher', () => {
|
||||||
|
|
||||||
describe(`onConfirmResponse()`, () => {
|
describe(`onConfirmResponse()`, () => {
|
||||||
describe(`when response is accept`, () => {
|
describe(`when response is accept`, () => {
|
||||||
it(`should call Object.assing on controlle.data with controller.orgData then call go() on state`, () => {
|
it(`should reset data them go to state`, () => {
|
||||||
let response = 'accept';
|
let data = {key: 'value'};
|
||||||
controller.data = {};
|
controller.data = data;
|
||||||
controller.orgData = {name: 'Batman'};
|
data.foo = 'bar';
|
||||||
controller.$state = {go: jasmine.createSpy('go')};
|
controller.$state = {go: jasmine.createSpy('go')};
|
||||||
controller.state = 'Batman';
|
controller.state = 'foo.bar';
|
||||||
controller.onConfirmResponse(response);
|
controller.onConfirmResponse('accept');
|
||||||
|
|
||||||
expect(controller.data).toEqual(controller.orgData);
|
expect(controller.data).toEqual({key: 'value'});
|
||||||
expect(controller.$state.go).toHaveBeenCalledWith(controller.state);
|
expect(controller.$state.go).toHaveBeenCalledWith(controller.state);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe(`when response is not accept`, () => {
|
describe(`when response is not accept`, () => {
|
||||||
it(`should set controller.state to null`, () => {
|
it(`should set controller.state to null`, () => {
|
||||||
let response = 'anything but accept';
|
|
||||||
controller.state = 'Batman';
|
controller.state = 'Batman';
|
||||||
controller.onConfirmResponse(response);
|
controller.onConfirmResponse('cancel');
|
||||||
|
|
||||||
expect(controller.state).toBeFalsy();
|
expect(controller.state).toBeFalsy();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe(`loadOriginalData()`, () => {
|
describe(`reset()`, () => {
|
||||||
it(`should iterate over the current data object, delete all properties then assign the ones from original data`, () => {
|
it(`should reset data as it was before changing it`, () => {
|
||||||
controller.data = {name: 'Bruce'};
|
let data = {key: 'value'};
|
||||||
controller.orgData = {name: 'Batman'};
|
|
||||||
controller.loadOriginalData();
|
|
||||||
|
|
||||||
expect(controller.data).toEqual(controller.orgData);
|
controller.data = data;
|
||||||
|
data.foo = 'bar';
|
||||||
|
controller.reset();
|
||||||
|
|
||||||
|
expect(data).toEqual({key: 'value'});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
|
@ -65,7 +65,7 @@ export function directive($translate, $window) {
|
||||||
};
|
};
|
||||||
|
|
||||||
if (form)
|
if (form)
|
||||||
$scope.$watch(form.$submitted, refreshError);
|
$scope.$watch(() => form.$submitted, refreshError);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
ngModule.directive('rule', directive);
|
ngModule.directive('rule', directive);
|
||||||
|
|
|
@ -25,4 +25,10 @@
|
||||||
%active {
|
%active {
|
||||||
background-color: $color-active;
|
background-color: $color-active;
|
||||||
color: $color-active-font;
|
color: $color-active-font;
|
||||||
|
|
||||||
|
&:hover,
|
||||||
|
&:focus {
|
||||||
|
background-color: $color-active;
|
||||||
|
color: $color-active-font;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
Binary file not shown.
|
@ -18,5 +18,6 @@ export default function moduleImport(moduleName) {
|
||||||
case 'invoiceOut' : return import('invoiceOut/front');
|
case 'invoiceOut' : return import('invoiceOut/front');
|
||||||
case 'route' : return import('route/front');
|
case 'route' : return import('route/front');
|
||||||
case 'entry' : return import('entry/front');
|
case 'entry' : return import('entry/front');
|
||||||
|
case 'account' : return import('account/front');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -8,13 +8,13 @@
|
||||||
<div class="header">
|
<div class="header">
|
||||||
<a
|
<a
|
||||||
translate-attr="{title: 'Go to module index'}"
|
translate-attr="{title: 'Go to module index'}"
|
||||||
ui-sref="{{::$ctrl.module}}.index"
|
ui-sref="{{::$ctrl.indexState}}"
|
||||||
name="goToModuleIndex">
|
name="goToModuleIndex">
|
||||||
<vn-icon icon="{{$ctrl.moduleMap[$ctrl.module].icon}}"></vn-icon>
|
<vn-icon icon="{{$ctrl.moduleMap[$ctrl.module].icon}}"></vn-icon>
|
||||||
</a>
|
</a>
|
||||||
<a
|
<a
|
||||||
translate-attr="{title: 'Preview'}"
|
translate-attr="{title: 'Preview'}"
|
||||||
ui-sref="{{::$ctrl.module}}.card.summary({id: $ctrl.descriptor.id})">
|
ui-sref="{{::$ctrl.summaryState}}({id: $ctrl.descriptor.id})">
|
||||||
<vn-icon icon="desktop_windows"></vn-icon>
|
<vn-icon icon="desktop_windows"></vn-icon>
|
||||||
</a>
|
</a>
|
||||||
<vn-icon-button
|
<vn-icon-button
|
||||||
|
|
|
@ -15,6 +15,8 @@ export default class Descriptor extends Component {
|
||||||
}
|
}
|
||||||
|
|
||||||
$postLink() {
|
$postLink() {
|
||||||
|
super.$postLink();
|
||||||
|
|
||||||
const content = this.element.querySelector('vn-descriptor-content');
|
const content = this.element.querySelector('vn-descriptor-content');
|
||||||
if (!content) throw new Error('Directive vnDescriptorContent not found');
|
if (!content) throw new Error('Directive vnDescriptorContent not found');
|
||||||
|
|
||||||
|
@ -104,6 +106,14 @@ export class DescriptorContent {
|
||||||
this.$transclude = $transclude;
|
this.$transclude = $transclude;
|
||||||
this.moduleMap = vnModules.getMap();
|
this.moduleMap = vnModules.getMap();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
get summaryState() {
|
||||||
|
return `${this.baseState || this.module}.card.summary`;
|
||||||
|
}
|
||||||
|
|
||||||
|
get indexState() {
|
||||||
|
return this.baseState || `${this.module}.index`;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
DescriptorContent.$inject = ['$transclude', 'vnModules'];
|
DescriptorContent.$inject = ['$transclude', 'vnModules'];
|
||||||
|
|
||||||
|
@ -112,6 +122,7 @@ ngModule.vnComponent('vnDescriptorContent', {
|
||||||
controller: DescriptorContent,
|
controller: DescriptorContent,
|
||||||
bindings: {
|
bindings: {
|
||||||
module: '@',
|
module: '@',
|
||||||
|
baseState: '@?',
|
||||||
description: '<',
|
description: '<',
|
||||||
descriptor: '<?'
|
descriptor: '<?'
|
||||||
},
|
},
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
<ul class="vn-list" ng-if="::$ctrl.items.length > 0">
|
<ul class="vn-list" ng-if="::$ctrl.items.length > 0">
|
||||||
<li ng-repeat="item in ::$ctrl.items" name="{{::item.description}}">
|
<li ng-repeat="item in ::$ctrl.items" name="{{::item.description}}">
|
||||||
<a ng-if="!item.external"
|
<a ng-if="::!item.external"
|
||||||
ui-sref="{{::item.state}}"
|
ui-sref="{{::item.state}}"
|
||||||
class="vn-item"
|
class="vn-item"
|
||||||
ng-class="{active: item.active && !item.childs, expanded: item.active}"
|
ng-class="{active: item.active && !item.childs, expanded: item.active}"
|
||||||
|
@ -15,7 +15,7 @@
|
||||||
<vn-icon icon="keyboard_arrow_down" ng-if="::item.childs.length > 0"></vn-icon>
|
<vn-icon icon="keyboard_arrow_down" ng-if="::item.childs.length > 0"></vn-icon>
|
||||||
</vn-item-section>
|
</vn-item-section>
|
||||||
</a>
|
</a>
|
||||||
<a ng-if="item.external"
|
<a ng-if="::item.external"
|
||||||
href="{{::item.url}}"
|
href="{{::item.url}}"
|
||||||
class="vn-item">
|
class="vn-item">
|
||||||
<vn-item-section avatar>
|
<vn-item-section avatar>
|
||||||
|
|
|
@ -32,73 +32,86 @@ export default class LeftMenu {
|
||||||
let moduleIndex = this.$state.current.data.moduleIndex;
|
let moduleIndex = this.$state.current.data.moduleIndex;
|
||||||
let moduleFile = window.routes[moduleIndex] || [];
|
let moduleFile = window.routes[moduleIndex] || [];
|
||||||
let menu = moduleFile.menus && moduleFile.menus[this.source] || [];
|
let menu = moduleFile.menus && moduleFile.menus[this.source] || [];
|
||||||
let items = [];
|
|
||||||
|
|
||||||
let addItem = (items, item) => {
|
let cloneItems = (items, parent) => {
|
||||||
let state = states[item.state];
|
let myItems = [];
|
||||||
if (state) {
|
|
||||||
state = state.self;
|
|
||||||
let acl = state.data.acl;
|
|
||||||
|
|
||||||
if (acl && !this.aclService.hasAny(acl))
|
for (let item of items) {
|
||||||
return;
|
let state = states[item.state];
|
||||||
} else if (!item.external) {
|
if (state) {
|
||||||
console.warn('wrong left-menu definition');
|
state = state.self;
|
||||||
return;
|
let acl = state.data.acl;
|
||||||
|
|
||||||
|
if (acl && !this.aclService.hasAny(acl))
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
let myItem = {
|
||||||
|
icon: item.icon,
|
||||||
|
description: item.description || state.description,
|
||||||
|
state: item.state,
|
||||||
|
external: item.external,
|
||||||
|
url: item.url,
|
||||||
|
parent
|
||||||
|
};
|
||||||
|
|
||||||
|
if (item.childs) {
|
||||||
|
let myChilds = cloneItems(item.childs, myItem);
|
||||||
|
|
||||||
|
if (myChilds.length > 0) {
|
||||||
|
myItem.childs = myChilds;
|
||||||
|
myItems.push(myItem);
|
||||||
|
}
|
||||||
|
} else
|
||||||
|
myItems.push(myItem);
|
||||||
}
|
}
|
||||||
|
|
||||||
items.push({
|
return myItems;
|
||||||
icon: item.icon,
|
|
||||||
description: item.description || state.description,
|
|
||||||
state: item.state,
|
|
||||||
external: item.external,
|
|
||||||
url: item.url
|
|
||||||
});
|
|
||||||
};
|
};
|
||||||
|
|
||||||
for (let item of menu) {
|
return cloneItems(menu);
|
||||||
if (item.state || item.external)
|
|
||||||
addItem(items, item);
|
|
||||||
else {
|
|
||||||
let childs = [];
|
|
||||||
|
|
||||||
for (let child of item.childs)
|
|
||||||
addItem(childs, child);
|
|
||||||
|
|
||||||
if (childs.length > 0) {
|
|
||||||
items.push({
|
|
||||||
icon: item.icon,
|
|
||||||
description: item.description,
|
|
||||||
childs: childs
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return items;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
activateItem() {
|
activateItem() {
|
||||||
let myState = this.$state.current.name
|
if (!this.items) return;
|
||||||
.split('.')
|
let currentState = this.$state.current.name;
|
||||||
.slice(0, this._depth)
|
let maxSpecificity = 0;
|
||||||
.join('.');
|
let selectedItem;
|
||||||
let re = new RegExp(`^${myState}(\\..*)?$`);
|
|
||||||
|
|
||||||
if (this.items) {
|
function isParentState(state, currentState) {
|
||||||
// Check items matching current path
|
if (!state) return 0;
|
||||||
for (let item of this.items) {
|
let match = state.match(/^(.*)\.index$/);
|
||||||
item.active = re.test(item.state);
|
if (match) state = match[1];
|
||||||
|
|
||||||
if (item.childs) {
|
let isParent =
|
||||||
for (let child of item.childs) {
|
currentState.startsWith(`${state}.`) ||
|
||||||
child.active = re.test(child.state);
|
currentState === state;
|
||||||
if (child.active)
|
|
||||||
item.active = child.active;
|
return isParent
|
||||||
}
|
? (state.match(/\./g) || []).length + 1
|
||||||
|
: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
function selectItem(items) {
|
||||||
|
if (!items) return;
|
||||||
|
for (let item of items) {
|
||||||
|
item.active = false;
|
||||||
|
let specificity = isParentState(item.state, currentState);
|
||||||
|
if (specificity > maxSpecificity) {
|
||||||
|
selectedItem = item;
|
||||||
|
maxSpecificity = specificity;
|
||||||
}
|
}
|
||||||
|
selectItem(item.childs);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Check items matching current path
|
||||||
|
selectItem(this.items);
|
||||||
|
|
||||||
|
while (selectedItem) {
|
||||||
|
selectedItem.active = true;
|
||||||
|
selectedItem = selectedItem.parent;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
setActive(item) {
|
setActive(item) {
|
||||||
|
|
|
@ -44,6 +44,7 @@ Routes: Rutas
|
||||||
Locator: Localizador
|
Locator: Localizador
|
||||||
Invoices out: Facturas emitidas
|
Invoices out: Facturas emitidas
|
||||||
Entries: Entradas
|
Entries: Entradas
|
||||||
|
Users: Usuarios
|
||||||
|
|
||||||
# Common
|
# Common
|
||||||
|
|
||||||
|
|
|
@ -134,5 +134,12 @@
|
||||||
"This ticket is deleted": "Este ticket está eliminado",
|
"This ticket is deleted": "Este ticket está eliminado",
|
||||||
"A travel with this data already exists": "Ya existe un travel con estos datos",
|
"A travel with this data already exists": "Ya existe un travel con estos datos",
|
||||||
"This thermograph id already exists": "La id del termógrafo ya existe",
|
"This thermograph id already exists": "La id del termógrafo ya existe",
|
||||||
"Choose a date range or days forward": "Selecciona un rango de fechas o días en adelante"
|
"Choose a date range or days forward": "Selecciona un rango de fechas o días en adelante",
|
||||||
|
"ORDER_ALREADY_CONFIRMED": "ORDER_ALREADY_CONFIRMED",
|
||||||
|
"Invalid password": "Invalid password",
|
||||||
|
"Password does not meet requirements": "Password does not meet requirements",
|
||||||
|
"Role already assigned": "Role already assigned",
|
||||||
|
"Invalid role name": "Invalid role name",
|
||||||
|
"Role name must be written in camelCase": "Role name must be written in camelCase",
|
||||||
|
"can't be set": "can't be set"
|
||||||
}
|
}
|
|
@ -0,0 +1,24 @@
|
||||||
|
{
|
||||||
|
"MailAlias": {
|
||||||
|
"dataSource": "vn"
|
||||||
|
},
|
||||||
|
"MailAliasAccount": {
|
||||||
|
"dataSource": "vn"
|
||||||
|
},
|
||||||
|
"MailForward": {
|
||||||
|
"dataSource": "vn"
|
||||||
|
},
|
||||||
|
"RoleInherit": {
|
||||||
|
"dataSource": "vn"
|
||||||
|
},
|
||||||
|
"RoleRole": {
|
||||||
|
"dataSource": "vn"
|
||||||
|
},
|
||||||
|
"UserAccount": {
|
||||||
|
"dataSource": "vn"
|
||||||
|
},
|
||||||
|
"UserPassword": {
|
||||||
|
"dataSource": "vn"
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,27 @@
|
||||||
|
{
|
||||||
|
"name": "MailAliasAccount",
|
||||||
|
"base": "VnModel",
|
||||||
|
"options": {
|
||||||
|
"mysql": {
|
||||||
|
"table": "account.mailAliasAccount"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"properties": {
|
||||||
|
"id": {
|
||||||
|
"type": "number",
|
||||||
|
"id": true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"relations": {
|
||||||
|
"alias": {
|
||||||
|
"type": "belongsTo",
|
||||||
|
"model": "MailAlias",
|
||||||
|
"foreignKey": "mailAlias"
|
||||||
|
},
|
||||||
|
"user": {
|
||||||
|
"type": "belongsTo",
|
||||||
|
"model": "Account",
|
||||||
|
"foreignKey": "account"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,33 @@
|
||||||
|
{
|
||||||
|
"name": "MailAlias",
|
||||||
|
"base": "VnModel",
|
||||||
|
"options": {
|
||||||
|
"mysql": {
|
||||||
|
"table": "account.mailAlias"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"properties": {
|
||||||
|
"id": {
|
||||||
|
"type": "number",
|
||||||
|
"id": true
|
||||||
|
},
|
||||||
|
"alias": {
|
||||||
|
"type": "string",
|
||||||
|
"required": true
|
||||||
|
},
|
||||||
|
"description": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"isPublic": {
|
||||||
|
"type": "boolean"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"relations": {
|
||||||
|
"accounts": {
|
||||||
|
"type": "hasMany",
|
||||||
|
"model": "MailAliasAccount",
|
||||||
|
"foreignKey": "mailAlias",
|
||||||
|
"property": "id"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,25 @@
|
||||||
|
{
|
||||||
|
"name": "MailForward",
|
||||||
|
"base": "VnModel",
|
||||||
|
"options": {
|
||||||
|
"mysql": {
|
||||||
|
"table": "account.mailForward"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"properties": {
|
||||||
|
"account": {
|
||||||
|
"id": true
|
||||||
|
},
|
||||||
|
"forwardTo": {
|
||||||
|
"type": "string",
|
||||||
|
"required": true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"relations": {
|
||||||
|
"user": {
|
||||||
|
"type": "belongsTo",
|
||||||
|
"model": "Account",
|
||||||
|
"foreignKey": "account"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,27 @@
|
||||||
|
{
|
||||||
|
"name": "RoleInherit",
|
||||||
|
"base": "VnModel",
|
||||||
|
"options": {
|
||||||
|
"mysql": {
|
||||||
|
"table": "account.roleInherit"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"properties": {
|
||||||
|
"id": {
|
||||||
|
"type": "number",
|
||||||
|
"id": true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"relations": {
|
||||||
|
"owner": {
|
||||||
|
"type": "belongsTo",
|
||||||
|
"model": "Role",
|
||||||
|
"foreignKey": "role"
|
||||||
|
},
|
||||||
|
"inherits": {
|
||||||
|
"type": "belongsTo",
|
||||||
|
"model": "Role",
|
||||||
|
"foreignKey": "inheritsFrom"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,20 @@
|
||||||
|
const app = require('vn-loopback/server/server');
|
||||||
|
|
||||||
|
module.exports = Self => {
|
||||||
|
app.on('started', function() {
|
||||||
|
let hooks = ['after save', 'after delete'];
|
||||||
|
for (let hook of hooks) {
|
||||||
|
app.models.RoleInherit.observe(hook, async() => {
|
||||||
|
try {
|
||||||
|
await Self.rawSql(`
|
||||||
|
CREATE EVENT account.role_sync
|
||||||
|
ON SCHEDULE AT CURRENT_TIMESTAMP + INTERVAL 5 SECOND
|
||||||
|
DO CALL role_sync;
|
||||||
|
`);
|
||||||
|
} catch (err) {
|
||||||
|
if (err.code != 'ER_EVENT_ALREADY_EXISTS') throw err;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
|
@ -0,0 +1,26 @@
|
||||||
|
{
|
||||||
|
"name": "RoleRole",
|
||||||
|
"base": "VnModel",
|
||||||
|
"options": {
|
||||||
|
"mysql": {
|
||||||
|
"table": "account.roleRole"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"properties": {
|
||||||
|
"role": {
|
||||||
|
"id": true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"relations": {
|
||||||
|
"owner": {
|
||||||
|
"type": "belongsTo",
|
||||||
|
"model": "Role",
|
||||||
|
"foreignKey": "role"
|
||||||
|
},
|
||||||
|
"inherits": {
|
||||||
|
"type": "belongsTo",
|
||||||
|
"model": "Role",
|
||||||
|
"foreignKey": "inheritsFrom"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,26 @@
|
||||||
|
{
|
||||||
|
"name": "UserAccount",
|
||||||
|
"base": "VnModel",
|
||||||
|
"options": {
|
||||||
|
"mysql": {
|
||||||
|
"table": "account.account"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"properties": {
|
||||||
|
"id": {
|
||||||
|
"id": true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"relations": {
|
||||||
|
"user": {
|
||||||
|
"type": "belongsTo",
|
||||||
|
"model": "Account",
|
||||||
|
"foreignKey": "id"
|
||||||
|
},
|
||||||
|
"aliases": {
|
||||||
|
"type": "hasMany",
|
||||||
|
"model": "MailAliasAccount",
|
||||||
|
"foreignKey": "account"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,34 @@
|
||||||
|
{
|
||||||
|
"name": "UserPassword",
|
||||||
|
"base": "VnModel",
|
||||||
|
"options": {
|
||||||
|
"mysql": {
|
||||||
|
"table": "account.userPassword"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"properties": {
|
||||||
|
"id": {
|
||||||
|
"id": true
|
||||||
|
},
|
||||||
|
"length": {
|
||||||
|
"type": "number",
|
||||||
|
"required": true
|
||||||
|
},
|
||||||
|
"nAlpha": {
|
||||||
|
"type": "number",
|
||||||
|
"required": true
|
||||||
|
},
|
||||||
|
"nUpper": {
|
||||||
|
"type": "number",
|
||||||
|
"required": true
|
||||||
|
},
|
||||||
|
"nDigits": {
|
||||||
|
"type": "number",
|
||||||
|
"required": true
|
||||||
|
},
|
||||||
|
"nPunct": {
|
||||||
|
"type": "number",
|
||||||
|
"required": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,65 @@
|
||||||
|
<vn-watcher
|
||||||
|
vn-id="watcher"
|
||||||
|
url="ACLs"
|
||||||
|
data="$ctrl.acl"
|
||||||
|
id-value="$ctrl.$params.id"
|
||||||
|
insert-mode="!$ctrl.$params.id"
|
||||||
|
form="form">
|
||||||
|
</vn-watcher>
|
||||||
|
<form
|
||||||
|
name="form"
|
||||||
|
vn-http-submit="watcher.submitGo('account.acl')"
|
||||||
|
class="vn-w-md">
|
||||||
|
<vn-card class="vn-pa-lg">
|
||||||
|
<vn-horizontal>
|
||||||
|
<vn-autocomplete
|
||||||
|
label="Role"
|
||||||
|
ng-model="$ctrl.acl.principalId"
|
||||||
|
url="Roles"
|
||||||
|
id-field="name"
|
||||||
|
value-field="name"
|
||||||
|
vn-focus>
|
||||||
|
</vn-autocomplete>
|
||||||
|
</vn-horizontal>
|
||||||
|
<vn-horizontal>
|
||||||
|
<vn-autocomplete
|
||||||
|
label="Model"
|
||||||
|
ng-model="$ctrl.acl.model"
|
||||||
|
data="$ctrl.models"
|
||||||
|
id-field="name"
|
||||||
|
value-field="name">
|
||||||
|
</vn-autocomplete>
|
||||||
|
</vn-horizontal>
|
||||||
|
<vn-horizontal>
|
||||||
|
<vn-textfield
|
||||||
|
label="Property"
|
||||||
|
ng-model="$ctrl.acl.property"
|
||||||
|
info="Use * to match all properties">
|
||||||
|
</vn-textfield>
|
||||||
|
</vn-horizontal>
|
||||||
|
<vn-horizontal>
|
||||||
|
<vn-autocomplete
|
||||||
|
label="Access type"
|
||||||
|
ng-model="$ctrl.acl.accessType"
|
||||||
|
data="$ctrl.accessTypes"
|
||||||
|
id-field="name"
|
||||||
|
value-field="name">
|
||||||
|
</vn-autocomplete>
|
||||||
|
</vn-horizontal>
|
||||||
|
<vn-horizontal>
|
||||||
|
<vn-autocomplete
|
||||||
|
label="Permission"
|
||||||
|
ng-model="$ctrl.acl.permission"
|
||||||
|
data="$ctrl.permissions"
|
||||||
|
id-field="name"
|
||||||
|
value-field="name">
|
||||||
|
</vn-autocomplete>
|
||||||
|
</vn-horizontal>
|
||||||
|
</vn-card>
|
||||||
|
<vn-submit
|
||||||
|
icon="check"
|
||||||
|
vn-tooltip="Create"
|
||||||
|
class="round"
|
||||||
|
fixed-bottom-right>
|
||||||
|
</vn-submit>
|
||||||
|
</form>
|
|
@ -0,0 +1,33 @@
|
||||||
|
import ngModule from '../../module';
|
||||||
|
import Section from 'salix/components/section';
|
||||||
|
|
||||||
|
export default class Controller extends Section {
|
||||||
|
constructor(...args) {
|
||||||
|
super(...args);
|
||||||
|
this.accessTypes = [
|
||||||
|
{name: '*'},
|
||||||
|
{name: 'READ'},
|
||||||
|
{name: 'WRITE'}
|
||||||
|
];
|
||||||
|
this.permissions = [
|
||||||
|
{name: 'ALLOW'},
|
||||||
|
{name: 'DENY'}
|
||||||
|
];
|
||||||
|
|
||||||
|
this.models = [];
|
||||||
|
for (let model in window.validations)
|
||||||
|
this.models.push({name: model});
|
||||||
|
|
||||||
|
this.acl = {
|
||||||
|
property: '*',
|
||||||
|
principalType: 'ROLE',
|
||||||
|
accessType: 'READ',
|
||||||
|
permission: 'ALLOW'
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ngModule.component('vnAclCreate', {
|
||||||
|
template: require('./index.html'),
|
||||||
|
controller: Controller
|
||||||
|
});
|
|
@ -0,0 +1,4 @@
|
||||||
|
import './main';
|
||||||
|
import './index/';
|
||||||
|
import './create';
|
||||||
|
import './search-panel';
|
|
@ -0,0 +1,51 @@
|
||||||
|
<vn-auto-search
|
||||||
|
model="model">
|
||||||
|
</vn-auto-search>
|
||||||
|
<vn-data-viewer
|
||||||
|
model="model"
|
||||||
|
class="vn-w-sm">
|
||||||
|
<vn-card>
|
||||||
|
<vn-list class="separated">
|
||||||
|
<a
|
||||||
|
ng-repeat="row in model.data track by row.id"
|
||||||
|
ui-sref="account.acl.edit(::{id: row.id})"
|
||||||
|
translate-attr="{title: 'Edit ACL'}"
|
||||||
|
class="vn-item search-result">
|
||||||
|
<vn-item-section>
|
||||||
|
<h6>{{::row.model}}.{{::row.property}}</h6>
|
||||||
|
<vn-label-value
|
||||||
|
label="Role"
|
||||||
|
value="{{::row.principalId}}">
|
||||||
|
</vn-label-value>
|
||||||
|
<vn-label-value
|
||||||
|
label="Access type"
|
||||||
|
value="{{::row.accessType}}">
|
||||||
|
</vn-label-value>
|
||||||
|
<vn-label-value
|
||||||
|
label="Permission"
|
||||||
|
value="{{::row.permission}}">
|
||||||
|
</vn-label-value>
|
||||||
|
</vn-item-section>
|
||||||
|
<vn-item-section side>
|
||||||
|
<vn-icon-button
|
||||||
|
vn-click-stop="deleteAcl.show(row)"
|
||||||
|
vn-tooltip="Delete"
|
||||||
|
icon="delete">
|
||||||
|
</vn-icon-button>
|
||||||
|
</vn-item-section>
|
||||||
|
</a>
|
||||||
|
</vn-list>
|
||||||
|
</vn-card>
|
||||||
|
</vn-data-viewer>
|
||||||
|
<a ui-sref="account.acl.create"
|
||||||
|
vn-tooltip="New ACL"
|
||||||
|
vn-bind="+"
|
||||||
|
fixed-bottom-right>
|
||||||
|
<vn-float-button icon="add"></vn-float-button>
|
||||||
|
</a>
|
||||||
|
<vn-confirm
|
||||||
|
vn-id="deleteAcl"
|
||||||
|
on-accept="$ctrl.onDelete($data)"
|
||||||
|
question="Are you sure you want to continue?"
|
||||||
|
message="ACL will be removed">
|
||||||
|
</vn-confirm>
|
|
@ -0,0 +1,15 @@
|
||||||
|
import ngModule from '../../module';
|
||||||
|
import Section from 'salix/components/section';
|
||||||
|
|
||||||
|
export default class Controller extends Section {
|
||||||
|
onDelete(row) {
|
||||||
|
return this.$http.delete(`ACLs/${row.id}`)
|
||||||
|
.then(() => this.$.model.refresh())
|
||||||
|
.then(() => this.vnApp.showSuccess(this.$t('ACL removed')));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ngModule.component('vnAclIndex', {
|
||||||
|
template: require('./index.html'),
|
||||||
|
controller: Controller
|
||||||
|
});
|
|
@ -0,0 +1,4 @@
|
||||||
|
New ACL: Nuevo ACL
|
||||||
|
Edit ACL: Editar ACL
|
||||||
|
ACL will be removed: El ACL será eliminado
|
||||||
|
ACL removed: ACL eliminado
|
|
@ -0,0 +1,4 @@
|
||||||
|
Model: Modelo
|
||||||
|
Property: Propiedad
|
||||||
|
Access type: Tipo de acceso
|
||||||
|
Permission: Permiso
|
|
@ -0,0 +1,20 @@
|
||||||
|
<vn-crud-model
|
||||||
|
vn-id="model"
|
||||||
|
url="ACLs"
|
||||||
|
limit="20"
|
||||||
|
expr-builder="$ctrl.exprBuilder(param, value)"
|
||||||
|
auto-save="true">
|
||||||
|
</vn-crud-model>
|
||||||
|
<vn-portal slot="topbar">
|
||||||
|
<vn-searchbar
|
||||||
|
info="Search ACL by model name"
|
||||||
|
panel="vn-acl-search-panel"
|
||||||
|
model="model"
|
||||||
|
expr-builder="$ctrl.exprBuilder(param, value)"
|
||||||
|
base-state="account.acl"
|
||||||
|
entity-state="edit">
|
||||||
|
</vn-searchbar>
|
||||||
|
</vn-portal>
|
||||||
|
<ui-view>
|
||||||
|
<vn-acl-index></vn-acl-index>
|
||||||
|
</ui-view>
|
|
@ -0,0 +1,18 @@
|
||||||
|
import ngModule from '../../module';
|
||||||
|
import ModuleMain from 'salix/components/module-main';
|
||||||
|
|
||||||
|
export default class ACL extends ModuleMain {
|
||||||
|
exprBuilder(param, value) {
|
||||||
|
switch (param) {
|
||||||
|
case 'search':
|
||||||
|
return {model: {like: `%${value}%`}};
|
||||||
|
default:
|
||||||
|
return {[param]: value};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ngModule.vnComponent('vnAclComponent', {
|
||||||
|
controller: ACL,
|
||||||
|
template: require('./index.html')
|
||||||
|
});
|
|
@ -0,0 +1,39 @@
|
||||||
|
<div class="search-panel">
|
||||||
|
<form ng-submit="$ctrl.onSearch()">
|
||||||
|
<vn-vertical>
|
||||||
|
<vn-autocomplete
|
||||||
|
label="Role"
|
||||||
|
ng-model="filter.principalId"
|
||||||
|
url="Roles"
|
||||||
|
value-field="name">
|
||||||
|
</vn-autocomplete>
|
||||||
|
<vn-autocomplete
|
||||||
|
label="Model"
|
||||||
|
ng-model="filter.model"
|
||||||
|
data="$ctrl.models"
|
||||||
|
id-field="name"
|
||||||
|
value-field="name">
|
||||||
|
</vn-autocomplete>
|
||||||
|
<vn-textfield
|
||||||
|
label="Property"
|
||||||
|
ng-model="filter.property">
|
||||||
|
</vn-textfield>
|
||||||
|
<vn-autocomplete
|
||||||
|
label="Access type"
|
||||||
|
ng-model="filter.accessType"
|
||||||
|
data="$ctrl.accessTypes"
|
||||||
|
value-field="name">
|
||||||
|
</vn-autocomplete>
|
||||||
|
<vn-autocomplete
|
||||||
|
label="Permission"
|
||||||
|
ng-model="$ctrl.acl.permission"
|
||||||
|
data="$ctrl.permissions"
|
||||||
|
value-field="name">
|
||||||
|
</vn-autocomplete>
|
||||||
|
<vn-submit
|
||||||
|
label="Search"
|
||||||
|
class="vn-mt-lg">
|
||||||
|
</vn-submit>
|
||||||
|
</vn-vertical>
|
||||||
|
</form>
|
||||||
|
</div>
|
|
@ -0,0 +1,26 @@
|
||||||
|
import ngModule from '../../module';
|
||||||
|
import SearchPanel from 'core/components/searchbar/search-panel';
|
||||||
|
|
||||||
|
export default class Controller extends SearchPanel {
|
||||||
|
constructor(...args) {
|
||||||
|
super(...args);
|
||||||
|
this.accessTypes = [
|
||||||
|
{name: '*'},
|
||||||
|
{name: 'READ'},
|
||||||
|
{name: 'WRITE'}
|
||||||
|
];
|
||||||
|
this.permissions = [
|
||||||
|
{name: 'ALLOW'},
|
||||||
|
{name: 'DENY'}
|
||||||
|
];
|
||||||
|
|
||||||
|
this.models = [];
|
||||||
|
for (let model in window.validations)
|
||||||
|
this.models.push({name: model});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ngModule.component('vnAclSearchPanel', {
|
||||||
|
template: require('./index.html'),
|
||||||
|
controller: Controller
|
||||||
|
});
|
|
@ -0,0 +1,44 @@
|
||||||
|
<vn-watcher
|
||||||
|
vn-id="watcher"
|
||||||
|
url="MailAliases"
|
||||||
|
data="$ctrl.alias"
|
||||||
|
form="form">
|
||||||
|
</vn-watcher>
|
||||||
|
<form
|
||||||
|
name="form"
|
||||||
|
ng-submit="watcher.submit()"
|
||||||
|
class="vn-w-md">
|
||||||
|
<vn-card class="vn-pa-lg">
|
||||||
|
<vn-vertical>
|
||||||
|
<vn-textfield
|
||||||
|
label="Name"
|
||||||
|
ng-model="$ctrl.alias.alias"
|
||||||
|
rule="MailAlias"
|
||||||
|
vn-focus>
|
||||||
|
</vn-textfield>
|
||||||
|
<vn-textfield
|
||||||
|
label="Description"
|
||||||
|
ng-model="$ctrl.alias.description"
|
||||||
|
rule="MailAlias">
|
||||||
|
</vn-textfield>
|
||||||
|
<vn-check
|
||||||
|
label="Public"
|
||||||
|
ng-model="$ctrl.alias.isPublic"
|
||||||
|
rule="MailAlias">
|
||||||
|
</vn-check>
|
||||||
|
</vn-vertical>
|
||||||
|
</vn-card>
|
||||||
|
<vn-button-bar>
|
||||||
|
<vn-button
|
||||||
|
label="Undo changes"
|
||||||
|
ng-if="watcher.dataChanged()"
|
||||||
|
ng-click="watcher.loadOriginalData()">
|
||||||
|
</vn-button>
|
||||||
|
</vn-button-bar>
|
||||||
|
<vn-submit
|
||||||
|
icon="save"
|
||||||
|
vn-tooltip="Save"
|
||||||
|
class="round"
|
||||||
|
fixed-bottom-right>
|
||||||
|
</vn-submit>
|
||||||
|
</form>
|
|
@ -0,0 +1,12 @@
|
||||||
|
import ngModule from '../../module';
|
||||||
|
import Section from 'salix/components/section';
|
||||||
|
|
||||||
|
export default class Controller extends Section {}
|
||||||
|
|
||||||
|
ngModule.component('vnAliasBasicData', {
|
||||||
|
template: require('./index.html'),
|
||||||
|
controller: Controller,
|
||||||
|
bindings: {
|
||||||
|
alias: '<'
|
||||||
|
}
|
||||||
|
});
|
|
@ -0,0 +1,5 @@
|
||||||
|
<vn-portal slot="menu">
|
||||||
|
<vn-alias-descriptor alias="$ctrl.alias"></vn-alias-descriptor>
|
||||||
|
<vn-left-menu source="alias"></vn-left-menu>
|
||||||
|
</vn-portal>
|
||||||
|
<ui-view></ui-view>
|
|
@ -0,0 +1,14 @@
|
||||||
|
import ngModule from '../../module';
|
||||||
|
import ModuleCard from 'salix/components/module-card';
|
||||||
|
|
||||||
|
class Controller extends ModuleCard {
|
||||||
|
reload() {
|
||||||
|
this.$http.get(`MailAliases/${this.$params.id}`)
|
||||||
|
.then(res => this.alias = res.data);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ngModule.vnComponent('vnAliasCard', {
|
||||||
|
template: require('./index.html'),
|
||||||
|
controller: Controller
|
||||||
|
});
|
|
@ -0,0 +1,33 @@
|
||||||
|
<vn-watcher
|
||||||
|
vn-id="watcher"
|
||||||
|
url="MailAliases"
|
||||||
|
data="$ctrl.alias"
|
||||||
|
insert-mode="true"
|
||||||
|
form="form">
|
||||||
|
</vn-watcher>
|
||||||
|
<form
|
||||||
|
name="form"
|
||||||
|
vn-http-submit="$ctrl.onSubmit()"
|
||||||
|
class="vn-w-md">
|
||||||
|
<vn-card class="vn-pa-lg">
|
||||||
|
<vn-vertical>
|
||||||
|
<vn-textfield
|
||||||
|
label="Name"
|
||||||
|
ng-model="$ctrl.alias.alias"
|
||||||
|
rule="MailAlias"
|
||||||
|
vn-focus>
|
||||||
|
</vn-textfield>
|
||||||
|
<vn-textfield
|
||||||
|
label="Description"
|
||||||
|
ng-model="$ctrl.alias.description"
|
||||||
|
rule="MailAlias">
|
||||||
|
</vn-textfield>
|
||||||
|
</vn-vertical>
|
||||||
|
</vn-card>
|
||||||
|
<vn-submit
|
||||||
|
icon="check"
|
||||||
|
vn-tooltip="Create"
|
||||||
|
class="round"
|
||||||
|
fixed-bottom-right>
|
||||||
|
</vn-submit>
|
||||||
|
</form>
|
|
@ -0,0 +1,15 @@
|
||||||
|
import ngModule from '../../module';
|
||||||
|
import Section from 'salix/components/section';
|
||||||
|
|
||||||
|
export default class Controller extends Section {
|
||||||
|
onSubmit() {
|
||||||
|
return this.$.watcher.submit().then(res =>
|
||||||
|
this.$state.go('account.alias.card.basicData', {id: res.data.id})
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ngModule.component('vnAliasCreate', {
|
||||||
|
template: require('./index.html'),
|
||||||
|
controller: Controller
|
||||||
|
});
|
|
@ -0,0 +1,27 @@
|
||||||
|
<vn-descriptor-content
|
||||||
|
module="account"
|
||||||
|
base-state="account.alias"
|
||||||
|
description="$ctrl.alias.alias">
|
||||||
|
<slot-menu>
|
||||||
|
<vn-item
|
||||||
|
ng-click="deleteAlias.show()"
|
||||||
|
name="deleteAlias"
|
||||||
|
translate>
|
||||||
|
Delete
|
||||||
|
</vn-item>
|
||||||
|
</slot-menu>
|
||||||
|
<slot-body>
|
||||||
|
<div class="attributes">
|
||||||
|
<vn-label-value
|
||||||
|
label="Description"
|
||||||
|
value="{{$ctrl.alias.description}}">
|
||||||
|
</vn-label-value>
|
||||||
|
</div>
|
||||||
|
</slot-body>
|
||||||
|
</vn-descriptor-content>
|
||||||
|
<vn-confirm
|
||||||
|
vn-id="delete-alias"
|
||||||
|
on-accept="$ctrl.onDelete()"
|
||||||
|
question="Are you sure you want to continue?"
|
||||||
|
message="Alias will be removed">
|
||||||
|
</vn-confirm>
|
|
@ -0,0 +1,26 @@
|
||||||
|
import ngModule from '../../module';
|
||||||
|
import Descriptor from 'salix/components/descriptor';
|
||||||
|
|
||||||
|
class Controller extends Descriptor {
|
||||||
|
get alias() {
|
||||||
|
return this.entity;
|
||||||
|
}
|
||||||
|
|
||||||
|
set alias(value) {
|
||||||
|
this.entity = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
onDelete() {
|
||||||
|
return this.$http.delete(`MailAliases/${this.id}`)
|
||||||
|
.then(() => this.$state.go('account.alias'))
|
||||||
|
.then(() => this.vnApp.showSuccess(this.$t('Alias removed')));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ngModule.component('vnAliasDescriptor', {
|
||||||
|
template: require('./index.html'),
|
||||||
|
controller: Controller,
|
||||||
|
bindings: {
|
||||||
|
alias: '<'
|
||||||
|
}
|
||||||
|
});
|
|
@ -0,0 +1,2 @@
|
||||||
|
Alias will be removed: El alias será eliminado
|
||||||
|
Alias removed: Alias eliminado
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue