Refactor calendar to use new sync statuses and stages (#6141)

- Refactor calendar modules and some messaging modules to better
organize them by business rules and decouple them
- Work toward a common architecture for the different calendar providers
by introducing interfaces for the drivers
- Modify cron job to use the new sync statuses and stages
This commit is contained in:
bosiraphael
2024-07-08 17:01:06 +02:00
committed by GitHub
parent 1c3ea9b106
commit f458322303
69 changed files with 1300 additions and 884 deletions

View File

@ -0,0 +1,92 @@
import { Injectable } from '@nestjs/common';
import { EntityManager } from 'typeorm';
import { InjectObjectMetadataRepository } from 'src/engine/object-metadata-repository/object-metadata-repository.decorator';
import { WorkspaceDataSourceService } from 'src/engine/workspace-datasource/workspace-datasource.service';
import { PersonRepository } from 'src/modules/person/repositories/person.repository';
import { PersonWorkspaceEntity } from 'src/modules/person/standard-objects/person.workspace-entity';
// TODO: Move inside person module and workspace-member module
@Injectable()
export class AddPersonIdAndWorkspaceMemberIdService {
constructor(
private readonly workspaceDataSourceService: WorkspaceDataSourceService,
@InjectObjectMetadataRepository(PersonWorkspaceEntity)
private readonly personRepository: PersonRepository,
) {}
private async getEmailPersonIdMap(
handles: string[],
workspaceId: string,
transactionManager?: EntityManager,
): Promise<Map<string, string>> {
const personIds = await this.personRepository.getByEmails(
handles,
workspaceId,
transactionManager,
);
return new Map(personIds.map((person) => [person.email, person.id]));
}
private async getEmailWorkspaceMemberIdMap(
handles: string[],
workspaceId: string,
transactionManager?: EntityManager,
): Promise<Map<string, string>> {
const dataSourceSchema =
this.workspaceDataSourceService.getSchemaName(workspaceId);
const workspaceMemberIds: {
id: string;
email: string;
}[] = await this.workspaceDataSourceService.executeRawQuery(
`SELECT "workspaceMember"."id", "connectedAccount"."handle" AS email FROM ${dataSourceSchema}."workspaceMember"
JOIN ${dataSourceSchema}."connectedAccount" ON ${dataSourceSchema}."workspaceMember"."id" = ${dataSourceSchema}."connectedAccount"."accountOwnerId"
WHERE ${dataSourceSchema}."connectedAccount"."handle" = ANY($1)`,
[handles],
workspaceId,
transactionManager,
);
return new Map(
workspaceMemberIds.map((workspaceMember) => [
workspaceMember.email,
workspaceMember.id,
]),
);
}
public async addPersonIdAndWorkspaceMemberId<T extends { handle: string }>(
objects: T[],
workspaceId: string,
transactionManager?: EntityManager,
): Promise<
(T & {
personId: string | null;
workspaceMemberId: string | null;
})[]
> {
const handles = objects.map((object) => object.handle);
const personIdMap = await this.getEmailPersonIdMap(
handles,
workspaceId,
transactionManager,
);
const workspaceMemberIdMap = await this.getEmailWorkspaceMemberIdMap(
handles,
workspaceId,
transactionManager,
);
return objects.map((object) => ({
...object,
personId: personIdMap.get(object.handle) || null,
workspaceMemberId: workspaceMemberIdMap.get(object.handle) || null,
}));
}
}

View File

@ -0,0 +1,69 @@
import { isEmailBlocklisted } from 'src/modules/calendar-messaging-participant-manager/utils/is-email-blocklisted.util';
describe('isEmailBlocklisted', () => {
it('should return true if email is blocklisted', () => {
const channelHandle = 'abc@example.com';
const email = 'hello@twenty.com';
const blocklist = ['hello@twenty.com', 'hey@twenty.com'];
const result = isEmailBlocklisted(channelHandle, email, blocklist);
expect(result).toBe(true);
});
it('should return false if email is not blocklisted', () => {
const channelHandle = 'abc@example.com';
const email = 'hello@twenty.com';
const blocklist = ['hey@example.com'];
const result = isEmailBlocklisted(channelHandle, email, blocklist);
expect(result).toBe(false);
});
it('should return false if email is null', () => {
const channelHandle = 'abc@twenty.com';
const email = null;
const blocklist = ['@example.com'];
const result = isEmailBlocklisted(channelHandle, email, blocklist);
expect(result).toBe(false);
});
it('should return true for subdomains', () => {
const channelHandle = 'abc@example.com';
const email = 'hello@twenty.twenty.com';
const blocklist = ['@twenty.com'];
const result = isEmailBlocklisted(channelHandle, email, blocklist);
expect(result).toBe(true);
});
it('should return false for domains which end with blocklisted domain but are not subdomains', () => {
const channelHandle = 'abc@example.com';
const email = 'hello@twentytwenty.com';
const blocklist = ['@twenty.com'];
const result = isEmailBlocklisted(channelHandle, email, blocklist);
expect(result).toBe(false);
});
it('should return false if email is undefined', () => {
const channelHandle = 'abc@example.com';
const email = undefined;
const blocklist = ['@twenty.com'];
const result = isEmailBlocklisted(channelHandle, email, blocklist);
expect(result).toBe(false);
});
it('should return true if email ends with blocklisted domain', () => {
const channelHandle = 'abc@example.com';
const email = 'hello@twenty.com';
const blocklist = ['@twenty.com'];
const result = isEmailBlocklisted(channelHandle, email, blocklist);
expect(result).toBe(true);
});
it('should return false if email is same as channel handle', () => {
const channelHandle = 'hello@twenty.com';
const email = 'hello@twenty.com';
const blocklist = ['@twenty.com'];
const result = isEmailBlocklisted(channelHandle, email, blocklist);
expect(result).toBe(false);
});
});

View File

@ -0,0 +1,21 @@
// TODO: Move inside blocklist module
export const isEmailBlocklisted = (
channelHandle: string,
email: string | null | undefined,
blocklist: string[],
): boolean => {
if (!email || email === channelHandle) {
return false;
}
return blocklist.some((item) => {
if (item.startsWith('@')) {
const domain = email.split('@')[1];
return domain === item.slice(1) || domain.endsWith(`.${item.slice(1)}`);
}
return email === item;
});
};