refactor(core): Continue moving typeorm operators to repositories (no-changelog) (#8186)

Follow-up to: #8163
This commit is contained in:
Iván Ovejero
2024-01-02 17:53:24 +01:00
committed by GitHub
parent 0ca2759d75
commit 40c1eeeddd
35 changed files with 341 additions and 354 deletions

View File

@@ -1,4 +1,5 @@
import { Service } from 'typedi';
import type { FindOptionsWhere } from 'typeorm';
import { DataSource, In, Not, Repository } from 'typeorm';
import { SharedCredentials } from '../entities/SharedCredentials';
import type { User } from '../entities/User';
@@ -50,4 +51,13 @@ export class SharedCredentialsRepository extends Repository<SharedCredentials> {
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 this.find({ where });
}
}

View File

@@ -1,9 +1,11 @@
import { Service } from 'typedi';
import { DataSource, type FindOptionsWhere, Repository, In, Not } from 'typeorm';
import { DataSource, Repository, In, Not } from 'typeorm';
import type { EntityManager, FindOptionsWhere } from 'typeorm';
import { SharedWorkflow } 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()
export class SharedWorkflowRepository extends Repository<SharedWorkflow> {
@@ -72,4 +74,55 @@ export class SharedWorkflowRepository extends Repository<SharedWorkflow> {
async makeOwnerOfAllWorkflows(user: User, role: Role) {
return this.update({ userId: Not(user.id), roleId: role.id }, { user });
}
async getSharing(
user: User,
workflowId: string,
options: { allowGlobalScope: true; globalScope: Scope } | { allowGlobalScope: false },
relations: string[] = ['workflow'],
): Promise<SharedWorkflow | null> {
const where: FindOptionsWhere<SharedWorkflow> = { workflowId };
// Omit user from where if the requesting user has relevant
// global workflow permissions. This allows the user to
// access workflows they don't own.
if (!options.allowGlobalScope || !user.hasGlobalScope(options.globalScope)) {
where.userId = user.id;
}
return this.findOne({ where, relations });
}
async getSharedWorkflows(
user: User,
options: {
relations?: string[];
workflowIds?: string[];
},
): Promise<SharedWorkflow[]> {
return this.find({
where: {
...(!['owner', 'admin'].includes(user.globalRole.name) && { userId: user.id }),
...(options.workflowIds && { workflowId: In(options.workflowIds) }),
},
...(options.relations && { relations: options.relations }),
});
}
async share(transaction: EntityManager, workflow: WorkflowEntity, users: User[], roleId: string) {
const newSharedWorkflows = users.reduce<SharedWorkflow[]>((acc, user) => {
if (user.isPending) {
return acc;
}
const entity: Partial<SharedWorkflow> = {
workflowId: workflow.id,
userId: user.id,
roleId,
};
acc.push(this.create(entity));
return acc;
}, []);
return transaction.save(newSharedWorkflows);
}
}

View File

@@ -1,6 +1,9 @@
import { Service } from 'typedi';
import type { EntityManager } from 'typeorm';
import { DataSource, In, Repository } from 'typeorm';
import { TagEntity } from '../entities/TagEntity';
import type { WorkflowEntity } from '../entities/WorkflowEntity';
import intersection from 'lodash/intersection';
@Service()
export class TagRepository extends Repository<TagEntity> {
@@ -14,4 +17,57 @@ export class TagRepository extends Repository<TagEntity> {
where: { id: In(tagIds) },
});
}
/**
* Set tags on workflow to import while ensuring all tags exist in the database,
* either by matching incoming to existing tags or by creating them first.
*/
async setTags(tx: EntityManager, dbTags: TagEntity[], workflow: WorkflowEntity) {
if (!workflow?.tags?.length) return;
for (let i = 0; i < workflow.tags.length; i++) {
const importTag = workflow.tags[i];
if (!importTag.name) continue;
const identicalMatch = dbTags.find(
(dbTag) =>
dbTag.id === importTag.id &&
dbTag.createdAt &&
importTag.createdAt &&
dbTag.createdAt.getTime() === new Date(importTag.createdAt).getTime(),
);
if (identicalMatch) {
workflow.tags[i] = identicalMatch;
continue;
}
const nameMatch = dbTags.find((dbTag) => dbTag.name === importTag.name);
if (nameMatch) {
workflow.tags[i] = nameMatch;
continue;
}
const tagEntity = this.create(importTag);
workflow.tags[i] = await tx.save<TagEntity>(tagEntity);
}
}
/**
* Returns the workflow IDs that have certain tags.
* Intersection! e.g. workflow needs to have all provided tags.
*/
async getWorkflowIdsViaTags(tags: string[]): Promise<string[]> {
const dbTags = await this.find({
where: { name: In(tags) },
relations: ['workflows'],
});
const workflowIdsPerTag = dbTags.map((tag) => tag.workflows.map((workflow) => workflow.id));
return intersection(...workflowIdsPerTag);
}
}

View File

@@ -1,6 +1,8 @@
import { Service } from 'typedi';
import { DataSource, In, Not, Repository } from 'typeorm';
import type { EntityManager, FindManyOptions } from 'typeorm';
import { DataSource, In, IsNull, Not, Repository } from 'typeorm';
import { User } from '../entities/User';
import type { ListQuery } from '@/requests';
@Service()
export class UserRepository extends Repository<User> {
@@ -18,4 +20,68 @@ export class UserRepository extends Repository<User> {
async deleteAllExcept(user: User) {
await this.delete({ id: Not(user.id) });
}
async getByIds(transaction: EntityManager, ids: string[]) {
return transaction.find(User, { where: { id: In(ids) } });
}
async findManyByEmail(emails: string[]) {
return this.find({
where: { email: In(emails) },
relations: ['globalRole'],
select: ['email', 'password', 'id'],
});
}
async deleteMany(userIds: string[]) {
return this.delete({ id: In(userIds) });
}
async findNonShellUser(email: string) {
return this.findOne({
where: {
email,
password: Not(IsNull()),
},
relations: ['authIdentities', 'globalRole'],
});
}
async toFindManyOptions(listQueryOptions?: ListQuery.Options, globalOwnerRoleId?: string) {
const findManyOptions: FindManyOptions<User> = {};
if (!listQueryOptions) {
findManyOptions.relations = ['globalRole', 'authIdentities'];
return findManyOptions;
}
const { filter, select, take, skip } = listQueryOptions;
if (select) findManyOptions.select = select;
if (take) findManyOptions.take = take;
if (skip) findManyOptions.skip = skip;
if (take && !select) {
findManyOptions.relations = ['globalRole', 'authIdentities'];
}
if (take && select && !select?.id) {
findManyOptions.select = { ...findManyOptions.select, id: true }; // pagination requires id
}
if (filter) {
const { isOwner, ...otherFilters } = filter;
findManyOptions.where = otherFilters;
if (isOwner !== undefined && globalOwnerRoleId) {
findManyOptions.relations = ['globalRole'];
findManyOptions.where.globalRole = {
id: isOwner ? globalOwnerRoleId : Not(globalOwnerRoleId),
};
}
}
return findManyOptions;
}
}

View File

@@ -198,4 +198,16 @@ export class WorkflowRepository extends Repository<WorkflowEntity> {
.innerJoin(WebhookEntity, 'webhook_entity', 'workflow.id = webhook_entity.workflowId')
.execute() as Promise<Array<{ id: string; name: string }>>;
}
async updateActiveState(workflowId: string, newState: boolean) {
return this.update({ id: workflowId }, { active: newState });
}
async deactivateAll() {
return this.update({ active: true }, { active: false });
}
async findByActiveState(activeState: boolean) {
return this.findBy({ active: activeState });
}
}