UNPKG

unleash-server

Version:

Unleash is an enterprise ready feature flag service. It provides different strategies for handling feature flags.

263 lines • 9.02 kB
import NotFoundError from '../error/notfound-error.js'; import Group from '../types/group.js'; import { BadDataError, FOREIGN_KEY_VIOLATION } from '../error/index.js'; import { PROJECT_ROLE_TYPES } from '../util/index.js'; const T = { GROUPS: 'groups', GROUP_USER: 'group_user', GROUP_ROLE: 'group_role', USERS: 'users', PROJECTS: 'projects', ROLES: 'roles', }; const GROUP_COLUMNS = [ 'id', 'name', 'description', 'mappings_sso', 'created_at', 'created_by', 'root_role_id', 'scim_id', ]; export const SSO_SYNC_USER = 'SSO'; const rowToGroup = (row) => { if (!row) { throw new NotFoundError('No group found'); } return new Group({ id: row.id, name: row.name, description: row.description, mappingsSSO: row.mappings_sso, createdAt: row.created_at, createdBy: row.created_by, rootRole: row.root_role_id, scimId: row.scim_id, }); }; const rowToGroupUser = (row) => { if (!row) { throw new NotFoundError('No group user found'); } return { userId: row.user_id, groupId: row.group_id, joinedAt: row.created_at, createdBy: row.created_by, rootRoleId: row.root_role_id, }; }; const groupToRow = (group) => ({ name: group.name, description: group.description, mappings_sso: JSON.stringify(group.mappingsSSO), root_role_id: group.rootRole || null, }); export default class GroupStore { constructor(db) { this.db = db; } async getAllWithId(ids) { const groups = await this.db .select(GROUP_COLUMNS) .from(T.GROUPS) .whereIn('id', ids); return groups.map(rowToGroup); } async update(group) { try { const rows = await this.db(T.GROUPS) .where({ id: group.id }) .update(groupToRow(group)) .returning(GROUP_COLUMNS); return rowToGroup(rows[0]); } catch (error) { if (error.code === FOREIGN_KEY_VIOLATION && error.constraint === 'fk_group_role_id') { throw new BadDataError(`Incorrect role id ${group.rootRole}`); } throw error; } } async getProjectGroupRoles(projectId) { const rows = await this.db .select('gr.group_id', 'gr.role_id', 'gr.created_at', 'r.name') .from(`${T.GROUP_ROLE} as gr`) .innerJoin(`${T.ROLES} as r`, 'gr.role_id', 'r.id') .where('project', projectId); return rows.map((r) => { return { groupId: r.group_id, roleId: r.role_id, createdAt: r.created_at, name: r.name, }; }); } async getProjectGroups(projectId) { const rows = await this.db .select(['gr.group_id', 'gr.created_at', 'gr.role_id']) .from(`${T.GROUP_ROLE} AS gr`) .join(`${T.ROLES} as r`, 'gr.role_id', 'r.id') .whereIn('r.type', PROJECT_ROLE_TYPES) .andWhere('project', projectId); return rows.reduce((acc, row) => { const existingGroup = acc.find((group) => group.id === row.group_id); if (existingGroup) { existingGroup.roles.push(row.role_id); } else { acc.push({ id: row.group_id, addedAt: row.created_at, roleId: row.role_id, roles: [row.role_id], }); } return acc; }, []); } async getGroupProjects(groupIds) { const rows = await this.db .select('group_id', 'project') .from(T.GROUP_ROLE) .whereIn('group_id', groupIds) .distinct(); return rows.map((r) => { return { groupId: r.group_id, project: r.project, }; }); } async getAllUsersByGroups(groupIds) { const rows = await this.db .select('gu.group_id', 'u.id as user_id', 'gu.created_at', 'gu.created_by', 'g.root_role_id') .from(`${T.GROUP_USER} AS gu`) .join(`${T.USERS} AS u`, 'u.id', 'gu.user_id') .join(`${T.GROUPS} AS g`, 'g.id', 'gu.group_id') .whereIn('gu.group_id', groupIds); return rows.map(rowToGroupUser); } async getAll() { const groups = await this.db.select(GROUP_COLUMNS).from(T.GROUPS); return groups.map(rowToGroup); } async delete(id) { return this.db(T.GROUPS).where({ id }).del(); } async deleteAll() { await this.db(T.GROUPS).del(); } destroy() { } async exists(id) { const result = await this.db.raw(`SELECT EXISTS(SELECT 1 FROM ${T.GROUPS} WHERE id = ?) AS present`, [id]); const { present } = result.rows[0]; return present; } async existsWithName(name) { const result = await this.db.raw(`SELECT EXISTS(SELECT 1 FROM ${T.GROUPS} WHERE name = ?) AS present`, [name]); const { present } = result.rows[0]; return present; } async get(id) { const row = await this.db(T.GROUPS).where({ id }).first(); return rowToGroup(row); } async create(group) { try { const row = await this.db(T.GROUPS) .insert(groupToRow(group)) .returning('*'); return rowToGroup(row[0]); } catch (error) { if (error.code === FOREIGN_KEY_VIOLATION && error.constraint === 'fk_group_role_id') { throw new BadDataError(`Incorrect role id ${group.rootRole}`); } throw error; } } async count() { return this.db(T.GROUPS) .count('*') .then((res) => Number(res[0].count)); } async addUsersToGroup(groupId, users, userName) { try { const rows = (users || []).map((user) => { return { group_id: groupId, user_id: user.user.id, created_by: userName, }; }); return await this.db.batchInsert(T.GROUP_USER, rows); } catch (error) { if (error.code === FOREIGN_KEY_VIOLATION && error.constraint === 'group_user_user_id_fkey') { throw new BadDataError('Incorrect user id in the users group'); } throw error; } } async deleteUsersFromGroup(deletableUsers) { return this.db(T.GROUP_USER) .whereIn(['group_id', 'user_id'], deletableUsers.map((user) => [user.groupId, user.userId])) .delete(); } async updateGroupUsers(groupId, newUsers, deletableUsers, userName) { await this.addUsersToGroup(groupId, newUsers, userName); await this.deleteUsersFromGroup(deletableUsers); } async getNewGroupsForExternalUser(userId, externalGroups) { const rows = await this.db(`${T.GROUPS} as g`) .leftJoin(`${T.GROUP_USER} as gs`, function () { this.on('g.id', 'gs.group_id').andOnVal('gs.user_id', '=', userId); }) .where('gs.user_id', null) .whereRaw('mappings_sso \\?| :groups', { groups: externalGroups }); return rows.map(rowToGroup); } async addUserToGroups(userId, groupIds, createdBy) { const rows = groupIds.map((groupId) => { return { group_id: groupId, user_id: userId, created_by: createdBy, }; }); return this.db.batchInsert(T.GROUP_USER, rows); } async getOldGroupsForExternalUser(userId, externalGroups) { const rows = await this.db(`${T.GROUP_USER} as gu`) .leftJoin(`${T.GROUPS} as g`, 'g.id', 'gu.group_id') .whereNotIn('g.id', this.db(T.GROUPS) .select('id') .whereRaw('mappings_sso \\?| :groups', { groups: externalGroups, }) .orWhereRaw('jsonb_array_length(mappings_sso) = 0')) .where({ 'gu.user_id': userId, 'gu.created_by': SSO_SYNC_USER }); return rows.map(rowToGroupUser); } async getGroupsForUser(userId) { const rows = await this.db(T.GROUPS) .leftJoin(T.GROUP_USER, 'groups.id', 'group_user.group_id') .where('user_id', userId); return rows.map(rowToGroup); } async hasProjectRole(groupId) { const result = await this.db.raw(`SELECT EXISTS(SELECT 1 FROM ${T.GROUP_ROLE} WHERE group_id = ?) AS present`, [groupId]); const { present } = result.rows[0]; return present; } async deleteScimGroups() { await this.db(T.GROUPS).whereNotNull('scim_id').del(); } } //# sourceMappingURL=group-store.js.map