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

@@ -4,16 +4,13 @@ import { type INode, type INodeCredentialsDetails } from 'n8n-workflow';
import { Logger } from '@/Logger';
import * as Db from '@/Db';
import { CredentialsRepository } from '@/databases/repositories/credentials.repository';
import { TagRepository } from '@/databases/repositories/tag.repository';
import { SharedWorkflow } from '@/databases/entities/SharedWorkflow';
import { RoleService } from '@/services/role.service';
import { CredentialsRepository } from '@db/repositories/credentials.repository';
import { TagRepository } from '@db/repositories/tag.repository';
import { SharedWorkflow } from '@db/entities/SharedWorkflow';
import { replaceInvalidCredentials } from '@/WorkflowHelpers';
import { WorkflowEntity } from '@/databases/entities/WorkflowEntity';
import { WorkflowTagMapping } from '@/databases/entities/WorkflowTagMapping';
import type { TagEntity } from '@/databases/entities/TagEntity';
import type { Role } from '@/databases/entities/Role';
import { WorkflowEntity } from '@db/entities/WorkflowEntity';
import { WorkflowTagMapping } from '@db/entities/WorkflowTagMapping';
import type { TagEntity } from '@db/entities/TagEntity';
import type { ICredentialsDb } from '@/Interfaces';
@Service()
@@ -22,19 +19,15 @@ export class ImportService {
private dbTags: TagEntity[] = [];
private workflowOwnerRole: Role;
constructor(
private readonly logger: Logger,
private readonly credentialsRepository: CredentialsRepository,
private readonly tagRepository: TagRepository,
private readonly roleService: RoleService,
) {}
async initRecords() {
this.dbCredentials = await this.credentialsRepository.find();
this.dbTags = await this.tagRepository.find();
this.workflowOwnerRole = await this.roleService.findWorkflowOwnerRole();
}
async importWorkflows(workflows: WorkflowEntity[], userId: string) {
@@ -64,7 +57,7 @@ export class ImportService {
const workflowId = upsertResult.identifiers.at(0)?.id as string;
await tx.upsert(SharedWorkflow, { workflowId, userId, roleId: this.workflowOwnerRole.id }, [
await tx.upsert(SharedWorkflow, { workflowId, userId, role: 'workflow:owner' }, [
'workflowId',
'userId',
]);

View File

@@ -2,17 +2,14 @@ import { Service } from 'typedi';
import { CacheService } from '@/services/cache/cache.service';
import { SharedWorkflowRepository } from '@db/repositories/sharedWorkflow.repository';
import type { User } from '@db/entities/User';
import { RoleService } from './role.service';
import { UserRepository } from '@/databases/repositories/user.repository';
import { UserRepository } from '@db/repositories/user.repository';
import type { ListQuery } from '@/requests';
import { ApplicationError } from 'n8n-workflow';
@Service()
export class OwnershipService {
constructor(
private cacheService: CacheService,
private userRepository: UserRepository,
private roleService: RoleService,
private sharedWorkflowRepository: SharedWorkflowRepository,
) {}
@@ -27,13 +24,9 @@ export class OwnershipService {
if (cachedValue) return this.userRepository.create(cachedValue);
const workflowOwnerRole = await this.roleService.findWorkflowOwnerRole();
if (!workflowOwnerRole) throw new ApplicationError('Failed to find workflow owner role');
const sharedWorkflow = await this.sharedWorkflowRepository.findOneOrFail({
where: { workflowId, roleId: workflowOwnerRole.id },
relations: ['user', 'user.globalRole'],
where: { workflowId, role: 'workflow:owner' },
relations: ['user'],
});
void this.cacheService.setHash('workflow-ownership', { [workflowId]: sharedWorkflow.user });
@@ -61,7 +54,7 @@ export class OwnershipService {
shared?.forEach(({ user, role }) => {
const { id, email, firstName, lastName } = user;
if (role.name === 'owner') {
if (role === 'credential:owner' || role === 'workflow:owner') {
entity.ownedBy = { id, email, firstName, lastName };
} else {
entity.sharedWith.push({ id, email, firstName, lastName });
@@ -72,11 +65,8 @@ export class OwnershipService {
}
async getInstanceOwner() {
const globalOwnerRole = await this.roleService.findGlobalOwnerRole();
return await this.userRepository.findOneOrFail({
where: { globalRoleId: globalOwnerRole.id },
relations: ['globalRole'],
where: { role: 'global:owner' },
});
}
}

View File

@@ -1,109 +0,0 @@
import { Service } from 'typedi';
import { RoleRepository } from '@db/repositories/role.repository';
import { SharedWorkflowRepository } from '@db/repositories/sharedWorkflow.repository';
import { CacheService } from '@/services/cache/cache.service';
import type { RoleNames, RoleScopes } from '@db/entities/Role';
import { InvalidRoleError } from '@/errors/invalid-role.error';
import { License } from '@/License';
@Service()
export class RoleService {
constructor(
private roleRepository: RoleRepository,
private sharedWorkflowRepository: SharedWorkflowRepository,
private cacheService: CacheService,
private readonly license: License,
) {
void this.populateCache();
}
async populateCache() {
const allRoles = await this.roleRepository.find({});
if (!allRoles) return;
void this.cacheService.setMany(allRoles.map((r) => [r.cacheKey, r]));
}
async findCached(scope: RoleScopes, name: RoleNames) {
const cacheKey = `role:${scope}:${name}`;
const cachedRole = await this.cacheService.get(cacheKey);
if (cachedRole) return this.roleRepository.create(cachedRole);
let dbRole = await this.roleRepository.findRole(scope, name);
if (dbRole === null) {
if (!this.isValid(scope, name)) {
throw new InvalidRoleError(`${scope}:${name} is not a valid role`);
}
const toSave = this.roleRepository.create({ scope, name });
dbRole = await this.roleRepository.save(toSave);
}
void this.cacheService.set(cacheKey, dbRole);
return dbRole;
}
private roles: Array<{ name: RoleNames; scope: RoleScopes }> = [
{ scope: 'global', name: 'owner' },
{ scope: 'global', name: 'member' },
{ scope: 'global', name: 'admin' },
{ scope: 'workflow', name: 'owner' },
{ scope: 'credential', name: 'owner' },
{ scope: 'credential', name: 'user' },
{ scope: 'workflow', name: 'editor' },
];
listRoles() {
return this.roles;
}
private isValid(scope: RoleScopes, name: RoleNames) {
return this.roles.some((r) => r.scope === scope && r.name === name);
}
async findGlobalOwnerRole() {
return await this.findCached('global', 'owner');
}
async findGlobalMemberRole() {
return await this.findCached('global', 'member');
}
async findGlobalAdminRole() {
return await this.findCached('global', 'admin');
}
async findWorkflowOwnerRole() {
return await this.findCached('workflow', 'owner');
}
async findWorkflowEditorRole() {
return await this.findCached('workflow', 'editor');
}
async findCredentialOwnerRole() {
return await this.findCached('credential', 'owner');
}
async findCredentialUserRole() {
return await this.findCached('credential', 'user');
}
async findRoleByUserAndWorkflow(userId: string, workflowId: string) {
return await this.sharedWorkflowRepository
.findOne({
where: { workflowId, userId },
relations: ['role'],
})
.then((shared) => shared?.role);
}
async findCredentialOwnerRoleId() {
return this.license.isSharingEnabled() ? undefined : (await this.findCredentialOwnerRole()).id;
}
}

View File

@@ -1,5 +1,5 @@
import { Container, Service } from 'typedi';
import { User } from '@db/entities/User';
import { type AssignableRole, User } from '@db/entities/User';
import type { IUserSettings } from 'n8n-workflow';
import { UserRepository } from '@db/repositories/user.repository';
import type { PublicUser } from '@/Interfaces';
@@ -10,7 +10,6 @@ import { Logger } from '@/Logger';
import { createPasswordSha } from '@/auth/jwt';
import { UserManagementMailer } from '@/UserManagement/email';
import { InternalHooks } from '@/InternalHooks';
import { RoleService } from '@/services/role.service';
import { UrlService } from '@/services/url.service';
import { ApplicationError, ErrorReporterProxy as ErrorReporter } from 'n8n-workflow';
import type { UserRequest } from '@/requests';
@@ -23,7 +22,6 @@ export class UserService {
private readonly userRepository: UserRepository,
private readonly jwtService: JwtService,
private readonly mailer: UserManagementMailer,
private readonly roleService: RoleService,
private readonly urlService: UrlService,
) {}
@@ -73,7 +71,7 @@ export class UserService {
const user = await this.userRepository.findOne({
where: { id: decodedToken.sub },
relations: ['authIdentities', 'globalRole'],
relations: ['authIdentities'],
});
if (!user) {
@@ -162,7 +160,7 @@ export class UserService {
private async sendEmails(
owner: User,
toInviteUsers: { [key: string]: string },
role: 'member' | 'admin',
role: AssignableRole,
) {
const domain = this.urlService.getInstanceBaseUrl();
@@ -224,9 +222,7 @@ export class UserService {
);
}
async inviteUsers(owner: User, attributes: Array<{ email: string; role: 'member' | 'admin' }>) {
const memberRole = await this.roleService.findGlobalMemberRole();
const adminRole = await this.roleService.findGlobalAdminRole();
async inviteUsers(owner: User, attributes: Array<{ email: string; role: AssignableRole }>) {
const emails = attributes.map(({ email }) => email);
const existingUsers = await this.userRepository.findManyByEmail(emails);
@@ -250,10 +246,7 @@ export class UserService {
async (transactionManager) =>
await Promise.all(
toCreateUsers.map(async ({ email, role }) => {
const newUser = Object.assign(new User(), {
email,
globalRole: role === 'member' ? memberRole : adminRole,
});
const newUser = transactionManager.create(User, { email, role });
const savedUser = await transactionManager.save<User>(newUser);
createdUsers.set(email, savedUser.id);
return savedUser;

View File

@@ -4,7 +4,6 @@ import { In } from 'typeorm';
import type { User } from '@db/entities/User';
import { SharedWorkflowRepository } from '@db/repositories/sharedWorkflow.repository';
import { WorkflowRepository } from '@db/repositories/workflow.repository';
import { RoleService } from '@/services/role.service';
import { UserService } from '@/services/user.service';
@Service()
@@ -12,7 +11,6 @@ export class UserOnboardingService {
constructor(
private readonly sharedWorkflowRepository: SharedWorkflowRepository,
private readonly workflowRepository: WorkflowRepository,
private readonly roleService: RoleService,
private readonly userService: UserService,
) {}
@@ -24,12 +22,11 @@ export class UserOnboardingService {
let belowThreshold = true;
const skippedTypes = ['n8n-nodes-base.start', 'n8n-nodes-base.stickyNote'];
const workflowOwnerRole = await this.roleService.findWorkflowOwnerRole();
const ownedWorkflowsIds = await this.sharedWorkflowRepository
.find({
where: {
userId: user.id,
roleId: workflowOwnerRole?.id,
role: 'workflow:owner',
},
select: ['workflowId'],
})