Skip to content

Commit

Permalink
Setup migration path from default roles to groups (#2601)
Browse files Browse the repository at this point in the history
* Setup up version for migrating from old permissions style to groups

* Update accounts that match on migrate and add down method

* Switch logger to debug

* Setup searching for and creating custom group and setting users to custom

* Add comments to document flow of migration

* Prevent error on user without roles field in tests

* Change info logs to debug
  • Loading branch information
impactmass authored and spencern committed Jul 29, 2017
1 parent 64518de commit d8bbe37
Show file tree
Hide file tree
Showing 2 changed files with 141 additions and 0 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,140 @@
import { Meteor } from "meteor/meteor";
import { Roles } from "meteor/alanning:roles";
import { Migrations } from "/imports/plugins/core/versions";
import { Logger } from "/server/api";
import { Accounts, Groups, Shops } from "/lib/collections";

/**
* Migration file created for moving from previous admin permission management to permission groups
* On up, it creates the default groups for all shops in the app, then update accounts belonging to the
* default groups. It also creates custom groups for every unique set of permission and assigns accounts
* with such permissions to the custom group they belong.
*/
Migrations.add({
version: 5,
up() {
const shops = Shops.find({}).fetch();
Groups.remove({}); // needed to ensure restart in case of a migration that failed before finishing

if (shops && shops.length) {
shops.forEach((shop) => {
const defaultGroupAccounts = createDefaultGroupsForShop(shop);
// retrieves remaining permission sets that doesn't fit the default sets
const customPermissions = Meteor.users
.find({ _id: { $nin: defaultGroupAccounts } })
.fetch()
.map(user => user.roles && user.roles[shop._id]);
// sorts the array of permission sets to contain only unique sets to avoid creating groups with same permissions
const permissionsArray = sortUniqueArray(customPermissions);
permissionsArray.forEach((permissions, index) => {
if (!permissions) { return null; }
Logger.debug(`creating custom group for shop ${shop.name}`);
const groupId = Groups.insert({
name: `custom ${index + 1}`,
slug: `custom${index + 1}`,
permissions,
shopId: shop._id
});
updateAccountsInGroup({
shopId: shop._id,
permissions,
groupId
});
});
});
}

function createDefaultGroupsForShop(shop) {
let defaultGroupAccounts = [];
const { defaultRoles, defaultVisitorRole } = shop;
const roles = {
customer: defaultRoles || [ "guest", "account/profile", "product", "tag", "index", "cart/checkout", "cart/completed"],
guest: defaultVisitorRole || ["anonymous", "guest", "product", "tag", "index", "cart/checkout", "cart/completed"],
owner: Roles.getAllRoles().fetch().map((role) => role.name)
};

Object.keys(roles).forEach((groupKeys) => {
Logger.debug(`creating group ${groupKeys} for shop ${shop.name}`);
const groupId = Groups.insert({
name: groupKeys,
slug: groupKeys,
permissions: roles[groupKeys],
shopId: shop._id
});
Logger.debug(`new group "${groupKeys}" created with id "${groupId}"`);
const updatedAccounts = updateAccountsInGroup({
shopId: shop._id,
permissions: roles[groupKeys],
groupId
});
defaultGroupAccounts = defaultGroupAccounts.concat(updatedAccounts);
});
return defaultGroupAccounts;
}

// finds all accounts with a permission set and assigns them to matching group
function updateAccountsInGroup({ shopId, permissions = [], groupId }) {
const query = { [`roles.${shopId}`]: { $size: permissions.length, $all: permissions } };
const matchingUserIds = Meteor.users.find(query).fetch().map((user) => user._id);

if (matchingUserIds.length) {
Logger.debug(`updating following matching Accounts to new group: ${matchingUserIds}`);
}
Accounts.update(
{ _id: { $in: matchingUserIds }, shopId },
{ $addToSet: { groups: groupId } },
{ multi: true }
);
return matchingUserIds;
}
},
down() {
const shops = Shops.find({}).fetch();

if (shops && shops.length) {
shops.forEach((shop) => removeGroupsForShop(shop));
}
function removeGroupsForShop(shop) {
const shopGroups = Groups.find({ shopId: shop._id }).fetch();

const keys = {
customer: "defaultRoles",
guest: "defaultVisitorRole"
};

shopGroups.forEach((group) => {
const shopkey = keys[group.slug];
Shops.update({ _id: shop._id }, { $set: { [shopkey]: group.permissions } });
Accounts.update({ shopId: shop._id }, { $unset: { groups: "" } }, { multi: true });
});
}
}
});

/*
* helper func created to limit the permission sets available to unique values without duplicates.
* It takes a two dimentional array like this:
* [
* ["tag", "product"],
* ["product", "tag"],
* ["tag", "product", "shop"],
* ["tag", "shop"],
* ["shop", "tag"]
* ]
* and returns this: [["product", "tag"], ["product", "shop", "tag"], ["shop", "tag"]]
*/
function sortUniqueArray(multiArray) {
const sorted = multiArray.map(x => {
if (!x) { return []; }
return x.sort();
}).sort();
const uniqueArray = [];
uniqueArray.push(sorted[0]);

for (let i = 1; i < sorted.length; i++) {
if (JSON.stringify(sorted[i]) !== JSON.stringify(sorted[i - 1])) {
uniqueArray.push(sorted[i]);
}
}
return uniqueArray;
}
1 change: 1 addition & 0 deletions imports/plugins/core/versions/server/migrations/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,3 +2,4 @@ import "./1_rebuild_account_and_order_search_collections";
import "./2_add_key_to_search_ui";
import "./3_reset_package_registry";
import "./4_update_templates_priority";
import "./5_update_defaultRoles_to_groups";

0 comments on commit d8bbe37

Please sign in to comment.