Refactor connected account module (#6225)
- Refactor connected account module - Move blocklist into it's own module - Move contact-creation-manager into it's own module --------- Co-authored-by: Charles Bochet <charles@twenty.com>
This commit is contained in:
@ -0,0 +1,18 @@
|
||||
import { Module } from '@nestjs/common';
|
||||
|
||||
import { ObjectMetadataRepositoryModule } from 'src/engine/object-metadata-repository/object-metadata-repository.module';
|
||||
import { BlocklistValidationService } from 'src/modules/blocklist/blocklist-validation-manager/services/blocklist-validation.service';
|
||||
import { BlocklistWorkspaceEntity } from 'src/modules/blocklist/standard-objects/blocklist.workspace-entity';
|
||||
import { WorkspaceMemberWorkspaceEntity } from 'src/modules/workspace-member/standard-objects/workspace-member.workspace-entity';
|
||||
|
||||
@Module({
|
||||
imports: [
|
||||
ObjectMetadataRepositoryModule.forFeature([
|
||||
BlocklistWorkspaceEntity,
|
||||
WorkspaceMemberWorkspaceEntity,
|
||||
]),
|
||||
],
|
||||
providers: [BlocklistValidationService],
|
||||
exports: [BlocklistValidationService],
|
||||
})
|
||||
export class BlocklistValidationManagerModule {}
|
||||
@ -0,0 +1,146 @@
|
||||
import { BadRequestException, Injectable } from '@nestjs/common';
|
||||
|
||||
import { z } from 'zod';
|
||||
|
||||
import {
|
||||
CreateManyResolverArgs,
|
||||
UpdateOneResolverArgs,
|
||||
} from 'src/engine/api/graphql/workspace-resolver-builder/interfaces/workspace-resolvers-builder.interface';
|
||||
|
||||
import { InjectObjectMetadataRepository } from 'src/engine/object-metadata-repository/object-metadata-repository.decorator';
|
||||
import { isDomain } from 'src/engine/utils/is-domain';
|
||||
import { BlocklistRepository } from 'src/modules/blocklist/repositories/blocklist.repository';
|
||||
import { BlocklistWorkspaceEntity } from 'src/modules/blocklist/standard-objects/blocklist.workspace-entity';
|
||||
import { WorkspaceMemberRepository } from 'src/modules/workspace-member/repositories/workspace-member.repository';
|
||||
import { WorkspaceMemberWorkspaceEntity } from 'src/modules/workspace-member/standard-objects/workspace-member.workspace-entity';
|
||||
|
||||
export type BlocklistItem = Omit<
|
||||
BlocklistWorkspaceEntity,
|
||||
'createdAt' | 'updatedAt' | 'workspaceMember'
|
||||
> & {
|
||||
createdAt: string;
|
||||
updatedAt: string;
|
||||
workspaceMemberId: string;
|
||||
};
|
||||
|
||||
@Injectable()
|
||||
export class BlocklistValidationService {
|
||||
constructor(
|
||||
@InjectObjectMetadataRepository(BlocklistWorkspaceEntity)
|
||||
private readonly blocklistRepository: BlocklistRepository,
|
||||
@InjectObjectMetadataRepository(WorkspaceMemberWorkspaceEntity)
|
||||
private readonly workspaceMemberRepository: WorkspaceMemberRepository,
|
||||
) {}
|
||||
|
||||
public async validateBlocklistForCreateMany(
|
||||
payload: CreateManyResolverArgs<BlocklistItem>,
|
||||
userId: string,
|
||||
workspaceId: string,
|
||||
) {
|
||||
await this.validateSchema(payload.data);
|
||||
await this.validateUniquenessForCreateMany(payload, userId, workspaceId);
|
||||
}
|
||||
|
||||
public async validateBlocklistForUpdateOne(
|
||||
payload: UpdateOneResolverArgs<BlocklistItem>,
|
||||
userId: string,
|
||||
workspaceId: string,
|
||||
) {
|
||||
if (payload.data.handle) {
|
||||
await this.validateSchema([payload.data]);
|
||||
}
|
||||
await this.validateUniquenessForUpdateOne(payload, userId, workspaceId);
|
||||
}
|
||||
|
||||
public async validateSchema(blocklist: BlocklistItem[]) {
|
||||
const emailOrDomainSchema = z
|
||||
.string()
|
||||
.trim()
|
||||
.email('Invalid email or domain')
|
||||
.or(
|
||||
z
|
||||
.string()
|
||||
.refine(
|
||||
(value) => value.startsWith('@') && isDomain(value.slice(1)),
|
||||
'Invalid email or domain',
|
||||
),
|
||||
);
|
||||
|
||||
for (const handle of blocklist.map((item) => item.handle)) {
|
||||
if (!handle) {
|
||||
throw new BadRequestException('Blocklist handle is required');
|
||||
}
|
||||
|
||||
const result = emailOrDomainSchema.safeParse(handle);
|
||||
|
||||
if (!result.success) {
|
||||
throw new BadRequestException(result.error.errors[0].message);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public async validateUniquenessForCreateMany(
|
||||
payload: CreateManyResolverArgs<BlocklistItem>,
|
||||
userId: string,
|
||||
workspaceId: string,
|
||||
) {
|
||||
const currentWorkspaceMember =
|
||||
await this.workspaceMemberRepository.getByIdOrFail(userId, workspaceId);
|
||||
|
||||
const currentBlocklist =
|
||||
await this.blocklistRepository.getByWorkspaceMemberId(
|
||||
currentWorkspaceMember.id,
|
||||
workspaceId,
|
||||
);
|
||||
|
||||
const currentBlocklistHandles = currentBlocklist.map(
|
||||
(blocklist) => blocklist.handle,
|
||||
);
|
||||
|
||||
if (
|
||||
payload.data.some((item) => currentBlocklistHandles.includes(item.handle))
|
||||
) {
|
||||
throw new BadRequestException('Blocklist handle already exists');
|
||||
}
|
||||
}
|
||||
|
||||
public async validateUniquenessForUpdateOne(
|
||||
payload: UpdateOneResolverArgs<BlocklistItem>,
|
||||
userId: string,
|
||||
workspaceId: string,
|
||||
) {
|
||||
const existingRecord = await this.blocklistRepository.getById(
|
||||
payload.id,
|
||||
workspaceId,
|
||||
);
|
||||
|
||||
if (!existingRecord) {
|
||||
throw new BadRequestException('Blocklist item not found');
|
||||
}
|
||||
|
||||
if (existingRecord.workspaceMemberId !== payload.data.workspaceMemberId) {
|
||||
throw new BadRequestException('Workspace member cannot be updated');
|
||||
}
|
||||
|
||||
if (existingRecord.handle === payload.data.handle) {
|
||||
return;
|
||||
}
|
||||
|
||||
const currentWorkspaceMember =
|
||||
await this.workspaceMemberRepository.getByIdOrFail(userId, workspaceId);
|
||||
|
||||
const currentBlocklist =
|
||||
await this.blocklistRepository.getByWorkspaceMemberId(
|
||||
currentWorkspaceMember.id,
|
||||
workspaceId,
|
||||
);
|
||||
|
||||
const currentBlocklistHandles = currentBlocklist
|
||||
.filter((blocklist) => blocklist.id !== payload.id)
|
||||
.map((blocklist) => blocklist.handle);
|
||||
|
||||
if (currentBlocklistHandles.includes(payload.data.handle)) {
|
||||
throw new BadRequestException('Blocklist handle already exists');
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,29 @@
|
||||
import { WorkspaceQueryHookInstance } from 'src/engine/api/graphql/workspace-query-runner/workspace-query-hook/interfaces/workspace-query-hook.interface';
|
||||
import { CreateManyResolverArgs } from 'src/engine/api/graphql/workspace-resolver-builder/interfaces/workspace-resolvers-builder.interface';
|
||||
|
||||
import { WorkspaceQueryHook } from 'src/engine/api/graphql/workspace-query-runner/workspace-query-hook/decorators/workspace-query-hook.decorator';
|
||||
import {
|
||||
BlocklistItem,
|
||||
BlocklistValidationService,
|
||||
} from 'src/modules/blocklist/blocklist-validation-manager/services/blocklist-validation.service';
|
||||
|
||||
@WorkspaceQueryHook(`blocklist.createMany`)
|
||||
export class BlocklistCreateManyPreQueryHook
|
||||
implements WorkspaceQueryHookInstance
|
||||
{
|
||||
constructor(
|
||||
private readonly blocklistValidationService: BlocklistValidationService,
|
||||
) {}
|
||||
|
||||
async execute(
|
||||
userId: string,
|
||||
workspaceId: string,
|
||||
payload: CreateManyResolverArgs<BlocklistItem>,
|
||||
): Promise<void> {
|
||||
await this.blocklistValidationService.validateBlocklistForCreateMany(
|
||||
payload,
|
||||
userId,
|
||||
workspaceId,
|
||||
);
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,16 @@
|
||||
import { Module } from '@nestjs/common';
|
||||
|
||||
import { BlocklistValidationManagerModule } from 'src/modules/blocklist/blocklist-validation-manager/blocklist-validation-manager.module';
|
||||
import { BlocklistCreateManyPreQueryHook } from 'src/modules/blocklist/query-hooks/blocklist-create-many.pre-query.hook';
|
||||
import { BlocklistUpdateManyPreQueryHook } from 'src/modules/blocklist/query-hooks/blocklist-update-many.pre-query.hook';
|
||||
import { BlocklistUpdateOnePreQueryHook } from 'src/modules/blocklist/query-hooks/blocklist-update-one.pre-query.hook';
|
||||
|
||||
@Module({
|
||||
imports: [BlocklistValidationManagerModule],
|
||||
providers: [
|
||||
BlocklistCreateManyPreQueryHook,
|
||||
BlocklistUpdateManyPreQueryHook,
|
||||
BlocklistUpdateOnePreQueryHook,
|
||||
],
|
||||
})
|
||||
export class BlocklistQueryHookModule {}
|
||||
@ -0,0 +1,16 @@
|
||||
import { MethodNotAllowedException } from '@nestjs/common';
|
||||
|
||||
import { WorkspaceQueryHookInstance } from 'src/engine/api/graphql/workspace-query-runner/workspace-query-hook/interfaces/workspace-query-hook.interface';
|
||||
|
||||
import { WorkspaceQueryHook } from 'src/engine/api/graphql/workspace-query-runner/workspace-query-hook/decorators/workspace-query-hook.decorator';
|
||||
|
||||
@WorkspaceQueryHook(`blocklist.updateMany`)
|
||||
export class BlocklistUpdateManyPreQueryHook
|
||||
implements WorkspaceQueryHookInstance
|
||||
{
|
||||
constructor() {}
|
||||
|
||||
async execute(): Promise<void> {
|
||||
throw new MethodNotAllowedException('Method not allowed.');
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,29 @@
|
||||
import { WorkspaceQueryHookInstance } from 'src/engine/api/graphql/workspace-query-runner/workspace-query-hook/interfaces/workspace-query-hook.interface';
|
||||
import { UpdateOneResolverArgs } from 'src/engine/api/graphql/workspace-resolver-builder/interfaces/workspace-resolvers-builder.interface';
|
||||
|
||||
import { WorkspaceQueryHook } from 'src/engine/api/graphql/workspace-query-runner/workspace-query-hook/decorators/workspace-query-hook.decorator';
|
||||
import {
|
||||
BlocklistItem,
|
||||
BlocklistValidationService,
|
||||
} from 'src/modules/blocklist/blocklist-validation-manager/services/blocklist-validation.service';
|
||||
|
||||
@WorkspaceQueryHook(`blocklist.updateOne`)
|
||||
export class BlocklistUpdateOnePreQueryHook
|
||||
implements WorkspaceQueryHookInstance
|
||||
{
|
||||
constructor(
|
||||
private readonly blocklistValidationService: BlocklistValidationService,
|
||||
) {}
|
||||
|
||||
async execute(
|
||||
userId: string,
|
||||
workspaceId: string,
|
||||
payload: UpdateOneResolverArgs<BlocklistItem>,
|
||||
): Promise<void> {
|
||||
await this.blocklistValidationService.validateBlocklistForUpdateOne(
|
||||
payload,
|
||||
userId,
|
||||
workspaceId,
|
||||
);
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,69 @@
|
||||
import { Injectable } from '@nestjs/common';
|
||||
|
||||
import { EntityManager } from 'typeorm';
|
||||
|
||||
import { WorkspaceDataSourceService } from 'src/engine/workspace-datasource/workspace-datasource.service';
|
||||
import { BlocklistWorkspaceEntity } from 'src/modules/blocklist/standard-objects/blocklist.workspace-entity';
|
||||
|
||||
@Injectable()
|
||||
export class BlocklistRepository {
|
||||
constructor(
|
||||
private readonly workspaceDataSourceService: WorkspaceDataSourceService,
|
||||
) {}
|
||||
|
||||
public async getById(
|
||||
id: string,
|
||||
workspaceId: string,
|
||||
transactionManager?: EntityManager,
|
||||
): Promise<BlocklistWorkspaceEntity | null> {
|
||||
const dataSourceSchema =
|
||||
this.workspaceDataSourceService.getSchemaName(workspaceId);
|
||||
|
||||
const blocklistItems =
|
||||
await this.workspaceDataSourceService.executeRawQuery(
|
||||
`SELECT * FROM ${dataSourceSchema}."blocklist" WHERE "id" = $1`,
|
||||
[id],
|
||||
workspaceId,
|
||||
transactionManager,
|
||||
);
|
||||
|
||||
if (!blocklistItems || blocklistItems.length === 0) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return blocklistItems[0];
|
||||
}
|
||||
|
||||
public async getByWorkspaceMemberId(
|
||||
workspaceMemberId: string,
|
||||
workspaceId: string,
|
||||
transactionManager?: EntityManager,
|
||||
): Promise<BlocklistWorkspaceEntity[]> {
|
||||
const dataSourceSchema =
|
||||
this.workspaceDataSourceService.getSchemaName(workspaceId);
|
||||
|
||||
return await this.workspaceDataSourceService.executeRawQuery(
|
||||
`SELECT * FROM ${dataSourceSchema}."blocklist" WHERE "workspaceMemberId" = $1`,
|
||||
[workspaceMemberId],
|
||||
workspaceId,
|
||||
transactionManager,
|
||||
);
|
||||
}
|
||||
|
||||
public async getByWorkspaceMemberIdAndHandle(
|
||||
workspaceMemberId: string,
|
||||
handle: string,
|
||||
workspaceId: string,
|
||||
transactionManager?: EntityManager,
|
||||
): Promise<BlocklistWorkspaceEntity[]> {
|
||||
const dataSourceSchema =
|
||||
this.workspaceDataSourceService.getSchemaName(workspaceId);
|
||||
|
||||
return await this.workspaceDataSourceService.executeRawQuery(
|
||||
`SELECT * FROM ${dataSourceSchema}."blocklist" WHERE "workspaceMemberId" = $1 AND "handle" = $2`,
|
||||
[workspaceMemberId, handle],
|
||||
workspaceId,
|
||||
transactionManager,
|
||||
);
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,49 @@
|
||||
import { Relation } from 'src/engine/workspace-manager/workspace-sync-metadata/interfaces/relation.interface';
|
||||
|
||||
import { FieldMetadataType } from 'src/engine/metadata-modules/field-metadata/field-metadata.entity';
|
||||
import { BLOCKLIST_STANDARD_FIELD_IDS } from 'src/engine/workspace-manager/workspace-sync-metadata/constants/standard-field-ids';
|
||||
import { STANDARD_OBJECT_IDS } from 'src/engine/workspace-manager/workspace-sync-metadata/constants/standard-object-ids';
|
||||
import { WorkspaceMemberWorkspaceEntity } from 'src/modules/workspace-member/standard-objects/workspace-member.workspace-entity';
|
||||
import { BaseWorkspaceEntity } from 'src/engine/twenty-orm/base.workspace-entity';
|
||||
import { WorkspaceEntity } from 'src/engine/twenty-orm/decorators/workspace-entity.decorator';
|
||||
import { WorkspaceIsSystem } from 'src/engine/twenty-orm/decorators/workspace-is-system.decorator';
|
||||
import { WorkspaceIsNotAuditLogged } from 'src/engine/twenty-orm/decorators/workspace-is-not-audit-logged.decorator';
|
||||
import { WorkspaceField } from 'src/engine/twenty-orm/decorators/workspace-field.decorator';
|
||||
import { WorkspaceRelation } from 'src/engine/twenty-orm/decorators/workspace-relation.decorator';
|
||||
import { RelationMetadataType } from 'src/engine/metadata-modules/relation-metadata/relation-metadata.entity';
|
||||
import { WorkspaceJoinColumn } from 'src/engine/twenty-orm/decorators/workspace-join-column.decorator';
|
||||
|
||||
@WorkspaceEntity({
|
||||
standardId: STANDARD_OBJECT_IDS.blocklist,
|
||||
namePlural: 'blocklists',
|
||||
labelSingular: 'Blocklist',
|
||||
labelPlural: 'Blocklists',
|
||||
description: 'Blocklist',
|
||||
icon: 'IconForbid2',
|
||||
})
|
||||
@WorkspaceIsSystem()
|
||||
@WorkspaceIsNotAuditLogged()
|
||||
export class BlocklistWorkspaceEntity extends BaseWorkspaceEntity {
|
||||
@WorkspaceField({
|
||||
standardId: BLOCKLIST_STANDARD_FIELD_IDS.handle,
|
||||
type: FieldMetadataType.TEXT,
|
||||
label: 'Handle',
|
||||
description: 'Handle',
|
||||
icon: 'IconAt',
|
||||
})
|
||||
handle: string;
|
||||
|
||||
@WorkspaceRelation({
|
||||
standardId: BLOCKLIST_STANDARD_FIELD_IDS.workspaceMember,
|
||||
type: RelationMetadataType.MANY_TO_ONE,
|
||||
label: 'WorkspaceMember',
|
||||
description: 'WorkspaceMember',
|
||||
icon: 'IconCircleUser',
|
||||
inverseSideTarget: () => WorkspaceMemberWorkspaceEntity,
|
||||
inverseSideFieldKey: 'blocklist',
|
||||
})
|
||||
workspaceMember: Relation<WorkspaceMemberWorkspaceEntity>;
|
||||
|
||||
@WorkspaceJoinColumn('workspaceMember')
|
||||
workspaceMemberId: string;
|
||||
}
|
||||
Reference in New Issue
Block a user