4745 move common logic between messaging and calendar in packagestwenty serversrcmodulesconnected account (#4962)
Closes #4745
This commit is contained in:
@ -0,0 +1,60 @@
|
||||
import { Injectable } from '@nestjs/common';
|
||||
import { InjectRepository } from '@nestjs/typeorm';
|
||||
|
||||
import { Repository } from 'typeorm';
|
||||
|
||||
import { MessageQueueJob } from 'src/engine/integrations/message-queue/interfaces/message-queue-job.interface';
|
||||
|
||||
import {
|
||||
FeatureFlagEntity,
|
||||
FeatureFlagKeys,
|
||||
} from 'src/engine/core-modules/feature-flag/feature-flag.entity';
|
||||
import { CalendarEventParticipantService } from 'src/modules/calendar/services/calendar-event-participant/calendar-event-participant.service';
|
||||
import { MessageParticipantService } from 'src/modules/messaging/services/message-participant/message-participant.service';
|
||||
|
||||
export type MatchParticipantJobData = {
|
||||
workspaceId: string;
|
||||
email: string;
|
||||
personId?: string;
|
||||
workspaceMemberId?: string;
|
||||
};
|
||||
|
||||
@Injectable()
|
||||
export class MatchParticipantJob
|
||||
implements MessageQueueJob<MatchParticipantJobData>
|
||||
{
|
||||
constructor(
|
||||
private readonly messageParticipantService: MessageParticipantService,
|
||||
private readonly calendarEventParticipantService: CalendarEventParticipantService,
|
||||
@InjectRepository(FeatureFlagEntity, 'core')
|
||||
private readonly featureFlagRepository: Repository<FeatureFlagEntity>,
|
||||
) {}
|
||||
|
||||
async handle(data: MatchParticipantJobData): Promise<void> {
|
||||
const { workspaceId, email, personId, workspaceMemberId } = data;
|
||||
|
||||
await this.messageParticipantService.matchMessageParticipants(
|
||||
workspaceId,
|
||||
email,
|
||||
personId,
|
||||
workspaceMemberId,
|
||||
);
|
||||
|
||||
const isCalendarEnabled = await this.featureFlagRepository.findOneBy({
|
||||
workspaceId,
|
||||
key: FeatureFlagKeys.IsCalendarEnabled,
|
||||
value: true,
|
||||
});
|
||||
|
||||
if (!isCalendarEnabled || !isCalendarEnabled.value) {
|
||||
return;
|
||||
}
|
||||
|
||||
await this.calendarEventParticipantService.matchCalendarEventParticipants(
|
||||
workspaceId,
|
||||
email,
|
||||
personId,
|
||||
workspaceMemberId,
|
||||
);
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,60 @@
|
||||
import { Injectable } from '@nestjs/common';
|
||||
import { InjectRepository } from '@nestjs/typeorm';
|
||||
|
||||
import { Repository } from 'typeorm';
|
||||
|
||||
import { MessageQueueJob } from 'src/engine/integrations/message-queue/interfaces/message-queue-job.interface';
|
||||
|
||||
import {
|
||||
FeatureFlagEntity,
|
||||
FeatureFlagKeys,
|
||||
} from 'src/engine/core-modules/feature-flag/feature-flag.entity';
|
||||
import { CalendarEventParticipantService } from 'src/modules/calendar/services/calendar-event-participant/calendar-event-participant.service';
|
||||
import { MessageParticipantService } from 'src/modules/messaging/services/message-participant/message-participant.service';
|
||||
|
||||
export type UnmatchParticipantJobData = {
|
||||
workspaceId: string;
|
||||
email: string;
|
||||
personId?: string;
|
||||
workspaceMemberId?: string;
|
||||
};
|
||||
|
||||
@Injectable()
|
||||
export class UnmatchParticipantJob
|
||||
implements MessageQueueJob<UnmatchParticipantJobData>
|
||||
{
|
||||
constructor(
|
||||
private readonly messageParticipantService: MessageParticipantService,
|
||||
private readonly calendarEventParticipantService: CalendarEventParticipantService,
|
||||
@InjectRepository(FeatureFlagEntity, 'core')
|
||||
private readonly featureFlagRepository: Repository<FeatureFlagEntity>,
|
||||
) {}
|
||||
|
||||
async handle(data: UnmatchParticipantJobData): Promise<void> {
|
||||
const { workspaceId, email, personId, workspaceMemberId } = data;
|
||||
|
||||
await this.messageParticipantService.unmatchMessageParticipants(
|
||||
workspaceId,
|
||||
email,
|
||||
personId,
|
||||
workspaceMemberId,
|
||||
);
|
||||
|
||||
const isCalendarEnabled = await this.featureFlagRepository.findOneBy({
|
||||
workspaceId,
|
||||
key: FeatureFlagKeys.IsCalendarEnabled,
|
||||
value: true,
|
||||
});
|
||||
|
||||
if (!isCalendarEnabled || !isCalendarEnabled.value) {
|
||||
return;
|
||||
}
|
||||
|
||||
await this.calendarEventParticipantService.unmatchCalendarEventParticipants(
|
||||
workspaceId,
|
||||
email,
|
||||
personId,
|
||||
workspaceMemberId,
|
||||
);
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,73 @@
|
||||
import { Inject, Injectable } from '@nestjs/common';
|
||||
import { OnEvent } from '@nestjs/event-emitter';
|
||||
|
||||
import { ObjectRecordCreateEvent } from 'src/engine/integrations/event-emitter/types/object-record-create.event';
|
||||
import { ObjectRecordUpdateEvent } from 'src/engine/integrations/event-emitter/types/object-record-update.event';
|
||||
import { objectRecordChangedProperties as objectRecordUpdateEventChangedProperties } from 'src/engine/integrations/event-emitter/utils/object-record-changed-properties.util';
|
||||
import { MessageQueue } from 'src/engine/integrations/message-queue/message-queue.constants';
|
||||
import { MessageQueueService } from 'src/engine/integrations/message-queue/services/message-queue.service';
|
||||
import {
|
||||
MatchParticipantJobData,
|
||||
MatchParticipantJob,
|
||||
} from 'src/modules/calendar-messaging-participant/jobs/match-participant.job';
|
||||
import {
|
||||
UnmatchParticipantJobData,
|
||||
UnmatchParticipantJob,
|
||||
} from 'src/modules/calendar-messaging-participant/jobs/unmatch-participant.job';
|
||||
import { PersonObjectMetadata } from 'src/modules/person/standard-objects/person.object-metadata';
|
||||
|
||||
@Injectable()
|
||||
export class ParticipantPersonListener {
|
||||
constructor(
|
||||
@Inject(MessageQueue.messagingQueue)
|
||||
private readonly messageQueueService: MessageQueueService,
|
||||
) {}
|
||||
|
||||
@OnEvent('person.created')
|
||||
async handleCreatedEvent(
|
||||
payload: ObjectRecordCreateEvent<PersonObjectMetadata>,
|
||||
) {
|
||||
if (payload.details.after.email === null) {
|
||||
return;
|
||||
}
|
||||
|
||||
await this.messageQueueService.add<MatchParticipantJobData>(
|
||||
MatchParticipantJob.name,
|
||||
{
|
||||
workspaceId: payload.workspaceId,
|
||||
email: payload.details.after.email,
|
||||
personId: payload.recordId,
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
@OnEvent('person.updated')
|
||||
async handleUpdatedEvent(
|
||||
payload: ObjectRecordUpdateEvent<PersonObjectMetadata>,
|
||||
) {
|
||||
if (
|
||||
objectRecordUpdateEventChangedProperties(
|
||||
payload.details.before,
|
||||
payload.details.after,
|
||||
).includes('email')
|
||||
) {
|
||||
await this.messageQueueService.add<UnmatchParticipantJobData>(
|
||||
UnmatchParticipantJob.name,
|
||||
{
|
||||
workspaceId: payload.workspaceId,
|
||||
email: payload.details.before.email,
|
||||
personId: payload.recordId,
|
||||
},
|
||||
);
|
||||
|
||||
await this.messageQueueService.add<MatchParticipantJobData>(
|
||||
MatchParticipantJob.name,
|
||||
{
|
||||
workspaceId: payload.workspaceId,
|
||||
email: payload.details.after.email,
|
||||
personId: payload.recordId,
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,73 @@
|
||||
import { Inject, Injectable } from '@nestjs/common';
|
||||
import { OnEvent } from '@nestjs/event-emitter';
|
||||
|
||||
import { ObjectRecordCreateEvent } from 'src/engine/integrations/event-emitter/types/object-record-create.event';
|
||||
import { ObjectRecordUpdateEvent } from 'src/engine/integrations/event-emitter/types/object-record-update.event';
|
||||
import { objectRecordChangedProperties as objectRecordUpdateEventChangedProperties } from 'src/engine/integrations/event-emitter/utils/object-record-changed-properties.util';
|
||||
import { MessageQueue } from 'src/engine/integrations/message-queue/message-queue.constants';
|
||||
import { MessageQueueService } from 'src/engine/integrations/message-queue/services/message-queue.service';
|
||||
import {
|
||||
MatchParticipantJobData,
|
||||
MatchParticipantJob,
|
||||
} from 'src/modules/calendar-messaging-participant/jobs/match-participant.job';
|
||||
import {
|
||||
UnmatchParticipantJobData,
|
||||
UnmatchParticipantJob,
|
||||
} from 'src/modules/calendar-messaging-participant/jobs/unmatch-participant.job';
|
||||
import { WorkspaceMemberObjectMetadata } from 'src/modules/workspace-member/standard-objects/workspace-member.object-metadata';
|
||||
|
||||
@Injectable()
|
||||
export class ParticipantWorkspaceMemberListener {
|
||||
constructor(
|
||||
@Inject(MessageQueue.messagingQueue)
|
||||
private readonly messageQueueService: MessageQueueService,
|
||||
) {}
|
||||
|
||||
@OnEvent('workspaceMember.created')
|
||||
async handleCreatedEvent(
|
||||
payload: ObjectRecordCreateEvent<WorkspaceMemberObjectMetadata>,
|
||||
) {
|
||||
if (payload.details.after.userEmail === null) {
|
||||
return;
|
||||
}
|
||||
|
||||
await this.messageQueueService.add<MatchParticipantJobData>(
|
||||
MatchParticipantJob.name,
|
||||
{
|
||||
workspaceId: payload.workspaceId,
|
||||
email: payload.details.after.userEmail,
|
||||
workspaceMemberId: payload.details.after.id,
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
@OnEvent('workspaceMember.updated')
|
||||
async handleUpdatedEvent(
|
||||
payload: ObjectRecordUpdateEvent<WorkspaceMemberObjectMetadata>,
|
||||
) {
|
||||
if (
|
||||
objectRecordUpdateEventChangedProperties(
|
||||
payload.details.before,
|
||||
payload.details.after,
|
||||
).includes('userEmail')
|
||||
) {
|
||||
await this.messageQueueService.add<UnmatchParticipantJobData>(
|
||||
UnmatchParticipantJob.name,
|
||||
{
|
||||
workspaceId: payload.workspaceId,
|
||||
email: payload.details.before.userEmail,
|
||||
personId: payload.recordId,
|
||||
},
|
||||
);
|
||||
|
||||
await this.messageQueueService.add<MatchParticipantJobData>(
|
||||
MatchParticipantJob.name,
|
||||
{
|
||||
workspaceId: payload.workspaceId,
|
||||
email: payload.details.after.userEmail,
|
||||
workspaceMemberId: payload.recordId,
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,16 @@
|
||||
import { Module } from '@nestjs/common';
|
||||
|
||||
import { ObjectMetadataRepositoryModule } from 'src/engine/object-metadata-repository/object-metadata-repository.module';
|
||||
import { WorkspaceDataSourceModule } from 'src/engine/workspace-datasource/workspace-datasource.module';
|
||||
import { AddPersonIdAndWorkspaceMemberIdService } from 'src/modules/calendar-messaging-participant/services/add-person-id-and-workspace-member-id/add-person-id-and-workspace-member-id.service';
|
||||
import { PersonObjectMetadata } from 'src/modules/person/standard-objects/person.object-metadata';
|
||||
|
||||
@Module({
|
||||
imports: [
|
||||
WorkspaceDataSourceModule,
|
||||
ObjectMetadataRepositoryModule.forFeature([PersonObjectMetadata]),
|
||||
],
|
||||
providers: [AddPersonIdAndWorkspaceMemberIdService],
|
||||
exports: [AddPersonIdAndWorkspaceMemberIdService],
|
||||
})
|
||||
export class AddPersonIdAndWorkspaceMemberIdModule {}
|
||||
@ -0,0 +1,90 @@
|
||||
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 { PersonObjectMetadata } from 'src/modules/person/standard-objects/person.object-metadata';
|
||||
|
||||
@Injectable()
|
||||
export class AddPersonIdAndWorkspaceMemberIdService {
|
||||
constructor(
|
||||
private readonly workspaceDataSourceService: WorkspaceDataSourceService,
|
||||
@InjectObjectMetadataRepository(PersonObjectMetadata)
|
||||
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,
|
||||
}));
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,9 @@
|
||||
import psl from 'psl';
|
||||
|
||||
import { capitalize } from 'src/utils/capitalize';
|
||||
|
||||
export const getCompanyNameFromDomainName = (domainName: string) => {
|
||||
const { sld } = psl.parse(domainName);
|
||||
|
||||
return sld ? capitalize(sld) : '';
|
||||
};
|
||||
@ -0,0 +1,9 @@
|
||||
import psl from 'psl';
|
||||
|
||||
export const getDomainNameFromHandle = (handle: string): string => {
|
||||
const wholeDomain = handle?.split('@')?.[1] || '';
|
||||
|
||||
const { domain } = psl.parse(wholeDomain);
|
||||
|
||||
return domain || '';
|
||||
};
|
||||
@ -0,0 +1,18 @@
|
||||
import { capitalize } from 'src/utils/capitalize';
|
||||
|
||||
export function getFirstNameAndLastNameFromHandleAndDisplayName(
|
||||
handle: string,
|
||||
displayName: string,
|
||||
): { firstName: string; lastName: string } {
|
||||
const firstName = displayName.split(' ')[0];
|
||||
const lastName = displayName.split(' ')[1];
|
||||
|
||||
const contactFullNameFromHandle = handle.split('@')[0];
|
||||
const firstNameFromHandle = contactFullNameFromHandle.split('.')[0];
|
||||
const lastNameFromHandle = contactFullNameFromHandle.split('.')[1];
|
||||
|
||||
return {
|
||||
firstName: capitalize(firstName || firstNameFromHandle || ''),
|
||||
lastName: capitalize(lastName || lastNameFromHandle || ''),
|
||||
};
|
||||
}
|
||||
@ -0,0 +1,8 @@
|
||||
export const isPersonEmail = (email: string | undefined): boolean => {
|
||||
if (!email) return false;
|
||||
|
||||
const nonPersonalPattern =
|
||||
/noreply|no-reply|do_not_reply|no\.reply|^(accounts@|info@|admin@|contact@|hello@|support@|sales@|feedback@|service@|help@|mailer-daemon|notifications?|digest|auto|apps|assign|comments|customer-success|enterprise|esign|express|forum|gc@|learn|mailer|marketing|messages|news|notification|payments|receipts|recrutement|security|service|support|team)/;
|
||||
|
||||
return !nonPersonalPattern.test(email);
|
||||
};
|
||||
Reference in New Issue
Block a user