refactor(core): Remove roleId indirection (no-changelog) (#8413)

This commit is contained in:
कारतोफ्फेलस्क्रिप्ट™
2024-01-24 13:38:57 +01:00
committed by GitHub
parent 1affebd85e
commit d6deceacde
139 changed files with 922 additions and 1684 deletions

View File

@@ -45,7 +45,7 @@ export class CredentialsRepository extends Repository<CredentialsEntity> {
type Select = Array<keyof CredentialsEntity>;
const defaultRelations = ['shared', 'shared.role', 'shared.user'];
const defaultRelations = ['shared', 'shared.user'];
const defaultSelect: Select = ['id', 'name', 'type', 'nodesAccess', 'createdAt', 'updatedAt'];
if (!listQueryOptions) return { select: defaultSelect, relations: defaultRelations };
@@ -81,7 +81,7 @@ export class CredentialsRepository extends Repository<CredentialsEntity> {
const findManyOptions: FindManyOptions<CredentialsEntity> = { where: { id: In(ids) } };
if (withSharings) {
findManyOptions.relations = ['shared', 'shared.user', 'shared.role'];
findManyOptions.relations = ['shared', 'shared.user'];
}
return await this.find(findManyOptions);

View File

@@ -1,42 +0,0 @@
import { Service } from 'typedi';
import { DataSource, In, Repository } from 'typeorm';
import type { RoleNames, RoleScopes } from '../entities/Role';
import { Role } from '../entities/Role';
import { User } from '../entities/User';
@Service()
export class RoleRepository extends Repository<Role> {
constructor(dataSource: DataSource) {
super(Role, dataSource.manager);
}
async findRole(scope: RoleScopes, name: RoleNames) {
return await this.findOne({ where: { scope, name } });
}
/**
* Counts the number of users in each role, e.g. `{ admin: 2, member: 6, owner: 1 }`
*/
async countUsersByRole() {
type Row = { role_name: string; count: number | string };
const rows: Row[] = await this.createQueryBuilder('role')
.select('role.name')
.addSelect('COUNT(user.id)', 'count')
.innerJoin(User, 'user', 'role.id = user.globalRoleId')
.groupBy('role.name')
.getRawMany();
return rows.reduce<Record<string, number>>((acc, item) => {
acc[item.role_name] = typeof item.count === 'number' ? item.count : parseInt(item.count, 10);
return acc;
}, {});
}
async getIdsInScopeWorkflowByNames(roleNames: RoleNames[]) {
return await this.find({
select: ['id'],
where: { name: In(roleNames), scope: 'workflow' },
}).then((role) => role.map(({ id }) => id));
}
}

View File

@@ -1,9 +1,8 @@
import { Service } from 'typedi';
import type { EntityManager, FindOptionsWhere } from 'typeorm';
import type { EntityManager } from 'typeorm';
import { DataSource, In, Not, Repository } from 'typeorm';
import { SharedCredentials } from '../entities/SharedCredentials';
import type { User } from '../entities/User';
import type { Role } from '../entities/Role';
@Service()
export class SharedCredentialsRepository extends Repository<SharedCredentials> {
@@ -26,15 +25,15 @@ export class SharedCredentialsRepository extends Repository<SharedCredentials> {
async findByCredentialIds(credentialIds: string[]) {
return await this.find({
relations: ['credentials', 'role', 'user'],
relations: ['credentials', 'user'],
where: {
credentialsId: In(credentialIds),
},
});
}
async makeOwnerOfAllCredentials(user: User, role: Role) {
return await this.update({ userId: Not(user.id), roleId: role.id }, { user });
async makeOwnerOfAllCredentials(user: User) {
return await this.update({ userId: Not(user.id), role: 'credential:owner' }, { user });
}
/**
@@ -42,23 +41,22 @@ export class SharedCredentialsRepository extends Repository<SharedCredentials> {
*/
async getAccessibleCredentials(userId: string) {
const sharings = await this.find({
relations: ['role'],
where: {
userId,
role: { name: In(['owner', 'user']), scope: 'credential' },
role: In(['credential:owner', 'credential:user']),
},
});
return sharings.map((s) => s.credentialsId);
}
async findSharings(userIds: string[], roleId?: string) {
const where: FindOptionsWhere<SharedCredentials> = { userId: In(userIds) };
// If credential sharing is not enabled, get only credentials owned by this user
if (roleId) where.roleId = roleId;
return await this.find({ where });
async findOwnedSharings(userIds: string[]) {
return await this.find({
where: {
userId: In(userIds),
role: 'credential:owner',
},
});
}
async deleteByIds(transaction: EntityManager, sharedCredentialsIds: string[], user?: User) {

View File

@@ -1,10 +1,9 @@
import { Service } from 'typedi';
import { DataSource, Repository, In, Not } from 'typeorm';
import type { EntityManager, FindOptionsSelect, FindOptionsWhere } from 'typeorm';
import { SharedWorkflow } from '../entities/SharedWorkflow';
import type { EntityManager, FindManyOptions, FindOptionsWhere } from 'typeorm';
import { SharedWorkflow, type WorkflowSharingRole } from '../entities/SharedWorkflow';
import { type User } from '../entities/User';
import type { Scope } from '@n8n/permissions';
import type { Role } from '../entities/Role';
import type { WorkflowEntity } from '../entities/WorkflowEntity';
@Service()
@@ -35,22 +34,29 @@ export class SharedWorkflowRepository extends Repository<SharedWorkflow> {
async findByWorkflowIds(workflowIds: string[]) {
return await this.find({
relations: ['role', 'user'],
relations: ['user'],
where: {
role: {
name: 'owner',
scope: 'workflow',
},
role: 'workflow:owner',
workflowId: In(workflowIds),
},
});
}
async findSharingRole(
userId: string,
workflowId: string,
): Promise<WorkflowSharingRole | undefined> {
return await this.findOne({
select: ['role'],
where: { workflowId, userId },
}).then((shared) => shared?.role);
}
async findSharing(
workflowId: string,
user: User,
scope: Scope,
{ roles, extraRelations }: { roles?: string[]; extraRelations?: string[] } = {},
{ roles, extraRelations }: { roles?: WorkflowSharingRole[]; extraRelations?: string[] } = {},
) {
const where: FindOptionsWhere<SharedWorkflow> = {
workflow: { id: workflowId },
@@ -61,18 +67,18 @@ export class SharedWorkflowRepository extends Repository<SharedWorkflow> {
}
if (roles) {
where.role = { name: In(roles) };
where.role = In(roles);
}
const relations = ['workflow', 'role'];
const relations = ['workflow'];
if (extraRelations) relations.push(...extraRelations);
return await this.findOne({ relations, where });
}
async makeOwnerOfAllWorkflows(user: User, role: Role) {
return await this.update({ userId: Not(user.id), roleId: role.id }, { user });
async makeOwnerOfAllWorkflows(user: User) {
return await this.update({ userId: Not(user.id), role: 'workflow:owner' }, { user });
}
async getSharing(
@@ -102,14 +108,14 @@ export class SharedWorkflowRepository extends Repository<SharedWorkflow> {
): Promise<SharedWorkflow[]> {
return await this.find({
where: {
...(!['owner', 'admin'].includes(user.globalRole.name) && { userId: user.id }),
...(!['global:owner', 'global:admin'].includes(user.role) && { userId: user.id }),
...(options.workflowIds && { workflowId: In(options.workflowIds) }),
},
...(options.relations && { relations: options.relations }),
});
}
async share(transaction: EntityManager, workflow: WorkflowEntity, users: User[], roleId: string) {
async share(transaction: EntityManager, workflow: WorkflowEntity, users: User[]) {
const newSharedWorkflows = users.reduce<SharedWorkflow[]>((acc, user) => {
if (user.isPending) {
return acc;
@@ -117,7 +123,7 @@ export class SharedWorkflowRepository extends Repository<SharedWorkflow> {
const entity: Partial<SharedWorkflow> = {
workflowId: workflow.id,
userId: user.id,
roleId,
role: 'workflow:editor',
};
acc.push(this.create(entity));
return acc;
@@ -126,12 +132,15 @@ export class SharedWorkflowRepository extends Repository<SharedWorkflow> {
return await transaction.save(newSharedWorkflows);
}
async findWithFields(workflowIds: string[], { fields }: { fields: string[] }) {
async findWithFields(
workflowIds: string[],
{ select }: Pick<FindManyOptions<SharedWorkflow>, 'select'>,
) {
return await this.find({
where: {
workflowId: In(workflowIds),
},
select: fields as FindOptionsSelect<SharedWorkflow>,
select,
});
}

View File

@@ -1,9 +1,9 @@
import { Service } from 'typedi';
import type { EntityManager, FindManyOptions } from 'typeorm';
import { DataSource, In, IsNull, Not, Repository } from 'typeorm';
import { User } from '../entities/User';
import type { ListQuery } from '@/requests';
import { type GlobalRole, User } from '../entities/User';
@Service()
export class UserRepository extends Repository<User> {
constructor(dataSource: DataSource) {
@@ -13,7 +13,6 @@ export class UserRepository extends Repository<User> {
async findManyByIds(userIds: string[]) {
return await this.find({
where: { id: In(userIds) },
relations: ['globalRole'],
});
}
@@ -28,7 +27,6 @@ export class UserRepository extends Repository<User> {
async findManyByEmail(emails: string[]) {
return await this.find({
where: { email: In(emails) },
relations: ['globalRole'],
select: ['email', 'password', 'id'],
});
}
@@ -43,15 +41,30 @@ export class UserRepository extends Repository<User> {
email,
password: Not(IsNull()),
},
relations: ['authIdentities', 'globalRole'],
relations: ['authIdentities'],
});
}
async toFindManyOptions(listQueryOptions?: ListQuery.Options, globalOwnerRoleId?: string) {
/** Counts the number of users in each role, e.g. `{ admin: 2, member: 6, owner: 1 }` */
async countUsersByRole() {
const rows = (await this.createQueryBuilder()
.select(['role', 'COUNT(role) as count'])
.groupBy('role')
.execute()) as Array<{ role: GlobalRole; count: string }>;
return rows.reduce(
(acc, row) => {
acc[row.role] = parseInt(row.count, 10);
return acc;
},
{} as Record<GlobalRole, number>,
);
}
async toFindManyOptions(listQueryOptions?: ListQuery.Options) {
const findManyOptions: FindManyOptions<User> = {};
if (!listQueryOptions) {
findManyOptions.relations = ['globalRole', 'authIdentities'];
findManyOptions.relations = ['authIdentities'];
return findManyOptions;
}
@@ -62,7 +75,7 @@ export class UserRepository extends Repository<User> {
if (skip) findManyOptions.skip = skip;
if (take && !select) {
findManyOptions.relations = ['globalRole', 'authIdentities'];
findManyOptions.relations = ['authIdentities'];
}
if (take && select && !select?.id) {
@@ -74,11 +87,8 @@ export class UserRepository extends Repository<User> {
findManyOptions.where = otherFilters;
if (isOwner !== undefined && globalOwnerRoleId) {
findManyOptions.relations = ['globalRole'];
findManyOptions.where.globalRole = {
id: isOwner ? globalOwnerRoleId : Not(globalOwnerRoleId),
};
if (isOwner !== undefined) {
findManyOptions.where.role = isOwner ? 'global:owner' : Not('global:owner');
}
}

View File

@@ -35,7 +35,7 @@ export class WorkflowRepository extends Repository<WorkflowEntity> {
async getAllActive() {
return await this.find({
where: { active: true },
relations: ['shared', 'shared.user', 'shared.user.globalRole', 'shared.role'],
relations: ['shared', 'shared.user'],
});
}
@@ -50,7 +50,7 @@ export class WorkflowRepository extends Repository<WorkflowEntity> {
async findById(workflowId: string) {
return await this.findOne({
where: { id: workflowId },
relations: ['shared', 'shared.user', 'shared.user.globalRole', 'shared.role'],
relations: ['shared', 'shared.user'],
});
}
@@ -135,7 +135,7 @@ export class WorkflowRepository extends Repository<WorkflowEntity> {
createdAt: true,
updatedAt: true,
versionId: true,
shared: { userId: true, roleId: true },
shared: { userId: true, role: true },
};
delete select?.ownedBy; // remove non-entity field, handled after query
@@ -152,7 +152,7 @@ export class WorkflowRepository extends Repository<WorkflowEntity> {
select.tags = { id: true, name: true };
}
if (isOwnedByIncluded) relations.push('shared', 'shared.role', 'shared.user');
if (isOwnedByIncluded) relations.push('shared', 'shared.user');
if (typeof where.name === 'string' && where.name !== '') {
where.name = Like(`%${where.name}%`);

View File

@@ -5,7 +5,6 @@ import { StatisticsNames, WorkflowStatistics } from '../entities/WorkflowStatist
import type { User } from '@/databases/entities/User';
import { WorkflowEntity } from '@/databases/entities/WorkflowEntity';
import { SharedWorkflow } from '@/databases/entities/SharedWorkflow';
import { Role } from '@/databases/entities/Role';
type StatisticsInsertResult = 'insert' | 'failed' | 'alreadyExists';
type StatisticsUpsertResult = StatisticsInsertResult | 'update';
@@ -110,12 +109,11 @@ export class WorkflowStatisticsRepository extends Repository<WorkflowStatistics>
'shared_workflow',
'shared_workflow.workflowId = workflow_statistics.workflowId',
)
.innerJoin(Role, 'role', 'role.id = shared_workflow.roleId')
.where('shared_workflow.userId = :userId', { userId })
.andWhere('workflow.active = :isActive', { isActive: true })
.andWhere('workflow_statistics.name = :name', { name: StatisticsNames.productionSuccess })
.andWhere('workflow_statistics.count >= 5')
.andWhere('role.name = :roleName', { roleName: 'owner' })
.andWhere('role = :roleName', { roleName: 'workflow:owner' })
.getCount();
}
}