[messaging] Add messageParticipant matching once people emails are updated (#3887)
* poc nest event emitter * add match message participant listener * add workspacemember listener * fix after review * fix deep-equal
This commit is contained in:
@ -27,6 +27,7 @@
|
|||||||
"@nestjs/common": "^9.0.0",
|
"@nestjs/common": "^9.0.0",
|
||||||
"@nestjs/config": "^2.3.2",
|
"@nestjs/config": "^2.3.2",
|
||||||
"@nestjs/core": "^9.0.0",
|
"@nestjs/core": "^9.0.0",
|
||||||
|
"@nestjs/event-emitter": "^2.0.3",
|
||||||
"@nestjs/jwt": "^10.0.3",
|
"@nestjs/jwt": "^10.0.3",
|
||||||
"@nestjs/passport": "^9.0.3",
|
"@nestjs/passport": "^9.0.3",
|
||||||
"@nestjs/platform-express": "^9.0.0",
|
"@nestjs/platform-express": "^9.0.0",
|
||||||
|
|||||||
@ -0,0 +1,6 @@
|
|||||||
|
import { BaseObjectMetadata } from 'src/workspace/workspace-sync-metadata/standard-objects/base.object-metadata';
|
||||||
|
|
||||||
|
export class ObjectRecordCreateEvent<T extends BaseObjectMetadata> {
|
||||||
|
workspaceId: string;
|
||||||
|
createdRecord: T;
|
||||||
|
}
|
||||||
@ -0,0 +1,6 @@
|
|||||||
|
import { BaseObjectMetadata } from 'src/workspace/workspace-sync-metadata/standard-objects/base.object-metadata';
|
||||||
|
|
||||||
|
export declare class ObjectRecordDeleteEvent<T extends BaseObjectMetadata> {
|
||||||
|
workspaceId: string;
|
||||||
|
deletedRecord: T;
|
||||||
|
}
|
||||||
@ -0,0 +1,7 @@
|
|||||||
|
import { BaseObjectMetadata } from 'src/workspace/workspace-sync-metadata/standard-objects/base.object-metadata';
|
||||||
|
|
||||||
|
export class ObjectRecordUpdateEvent<T extends BaseObjectMetadata> {
|
||||||
|
workspaceId: string;
|
||||||
|
previousRecord: T;
|
||||||
|
updatedRecord: T;
|
||||||
|
}
|
||||||
@ -0,0 +1,12 @@
|
|||||||
|
import deepEqual from 'deep-equal';
|
||||||
|
|
||||||
|
export const objectRecordChangedProperties = (
|
||||||
|
oldRecord: Record<string, any>,
|
||||||
|
newRecord: Record<string, any>,
|
||||||
|
) => {
|
||||||
|
const changedProperties = Object.keys(newRecord).filter(
|
||||||
|
(key) => !deepEqual(oldRecord[key], newRecord[key]),
|
||||||
|
);
|
||||||
|
|
||||||
|
return changedProperties;
|
||||||
|
};
|
||||||
@ -1,5 +1,6 @@
|
|||||||
import { Module } from '@nestjs/common';
|
import { Module } from '@nestjs/common';
|
||||||
import { HttpAdapterHost } from '@nestjs/core';
|
import { HttpAdapterHost } from '@nestjs/core';
|
||||||
|
import { EventEmitterModule } from '@nestjs/event-emitter';
|
||||||
|
|
||||||
import { ExceptionHandlerModule } from 'src/integrations/exception-handler/exception-handler.module';
|
import { ExceptionHandlerModule } from 'src/integrations/exception-handler/exception-handler.module';
|
||||||
import { exceptionHandlerModuleFactory } from 'src/integrations/exception-handler/exception-handler.module-factory';
|
import { exceptionHandlerModuleFactory } from 'src/integrations/exception-handler/exception-handler.module-factory';
|
||||||
@ -38,6 +39,7 @@ import { MessageQueueModule } from './message-queue/message-queue.module';
|
|||||||
useFactory: emailModuleFactory,
|
useFactory: emailModuleFactory,
|
||||||
inject: [EnvironmentService],
|
inject: [EnvironmentService],
|
||||||
}),
|
}),
|
||||||
|
EventEmitterModule.forRoot(),
|
||||||
],
|
],
|
||||||
exports: [],
|
exports: [],
|
||||||
providers: [],
|
providers: [],
|
||||||
|
|||||||
@ -11,7 +11,7 @@ import { ObjectMetadataModule } from 'src/metadata/object-metadata/object-metada
|
|||||||
import { DataSourceModule } from 'src/metadata/data-source/data-source.module';
|
import { DataSourceModule } from 'src/metadata/data-source/data-source.module';
|
||||||
import { CleanInactiveWorkspaceJob } from 'src/workspace/workspace-cleaner/crons/clean-inactive-workspace.job';
|
import { CleanInactiveWorkspaceJob } from 'src/workspace/workspace-cleaner/crons/clean-inactive-workspace.job';
|
||||||
import { TypeORMModule } from 'src/database/typeorm/typeorm.module';
|
import { TypeORMModule } from 'src/database/typeorm/typeorm.module';
|
||||||
import { FetchWorkspaceMessagesModule } from 'src/workspace/messaging/services/fetch-workspace-messages.module';
|
import { MessagingModule } from 'src/workspace/messaging/messaging.module';
|
||||||
import { GmailPartialSyncJob } from 'src/workspace/messaging/jobs/gmail-partial-sync.job';
|
import { GmailPartialSyncJob } from 'src/workspace/messaging/jobs/gmail-partial-sync.job';
|
||||||
import { EmailSenderJob } from 'src/integrations/email/email-sender.job';
|
import { EmailSenderJob } from 'src/integrations/email/email-sender.job';
|
||||||
import { UserModule } from 'src/core/user/user.module';
|
import { UserModule } from 'src/core/user/user.module';
|
||||||
@ -19,6 +19,8 @@ import { EnvironmentModule } from 'src/integrations/environment/environment.modu
|
|||||||
import { FeatureFlagEntity } from 'src/core/feature-flag/feature-flag.entity';
|
import { FeatureFlagEntity } from 'src/core/feature-flag/feature-flag.entity';
|
||||||
import { FetchAllWorkspacesMessagesJob } from 'src/workspace/messaging/crons/fetch-all-workspaces-messages.job';
|
import { FetchAllWorkspacesMessagesJob } from 'src/workspace/messaging/crons/fetch-all-workspaces-messages.job';
|
||||||
import { ConnectedAccountModule } from 'src/workspace/messaging/connected-account/connected-account.module';
|
import { ConnectedAccountModule } from 'src/workspace/messaging/connected-account/connected-account.module';
|
||||||
|
import { MatchMessageParticipantJob } from 'src/workspace/messaging/jobs/match-message-participant.job';
|
||||||
|
import { MessageParticipantModule } from 'src/workspace/messaging/message-participant/message-participant.module';
|
||||||
|
|
||||||
@Module({
|
@Module({
|
||||||
imports: [
|
imports: [
|
||||||
@ -27,12 +29,13 @@ import { ConnectedAccountModule } from 'src/workspace/messaging/connected-accoun
|
|||||||
DataSourceModule,
|
DataSourceModule,
|
||||||
HttpModule,
|
HttpModule,
|
||||||
TypeORMModule,
|
TypeORMModule,
|
||||||
FetchWorkspaceMessagesModule,
|
MessagingModule,
|
||||||
UserModule,
|
UserModule,
|
||||||
EnvironmentModule,
|
EnvironmentModule,
|
||||||
TypeORMModule,
|
TypeORMModule,
|
||||||
TypeOrmModule.forFeature([FeatureFlagEntity], 'core'),
|
TypeOrmModule.forFeature([FeatureFlagEntity], 'core'),
|
||||||
ConnectedAccountModule,
|
ConnectedAccountModule,
|
||||||
|
MessageParticipantModule,
|
||||||
],
|
],
|
||||||
providers: [
|
providers: [
|
||||||
{
|
{
|
||||||
@ -60,6 +63,10 @@ import { ConnectedAccountModule } from 'src/workspace/messaging/connected-accoun
|
|||||||
provide: FetchAllWorkspacesMessagesJob.name,
|
provide: FetchAllWorkspacesMessagesJob.name,
|
||||||
useClass: FetchAllWorkspacesMessagesJob,
|
useClass: FetchAllWorkspacesMessagesJob,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
provide: MatchMessageParticipantJob.name,
|
||||||
|
useClass: MatchMessageParticipantJob,
|
||||||
|
},
|
||||||
],
|
],
|
||||||
})
|
})
|
||||||
export class JobsModule {
|
export class JobsModule {
|
||||||
|
|||||||
@ -0,0 +1,47 @@
|
|||||||
|
import { Injectable } from '@nestjs/common';
|
||||||
|
|
||||||
|
import { MessageQueueJob } from 'src/integrations/message-queue/interfaces/message-queue-job.interface';
|
||||||
|
|
||||||
|
import { MessageParticipantService } from 'src/workspace/messaging/message-participant/message-participant.service';
|
||||||
|
|
||||||
|
export type MatchMessageParticipantsJobData = {
|
||||||
|
workspaceId: string;
|
||||||
|
email: string;
|
||||||
|
personId?: string;
|
||||||
|
workspaceMemberId?: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
@Injectable()
|
||||||
|
export class MatchMessageParticipantJob
|
||||||
|
implements MessageQueueJob<MatchMessageParticipantsJobData>
|
||||||
|
{
|
||||||
|
constructor(
|
||||||
|
private readonly messageParticipantService: MessageParticipantService,
|
||||||
|
) {}
|
||||||
|
|
||||||
|
async handle(data: MatchMessageParticipantsJobData): Promise<void> {
|
||||||
|
const { workspaceId, personId, workspaceMemberId, email } = data;
|
||||||
|
|
||||||
|
const messageParticipantsToUpdate =
|
||||||
|
await this.messageParticipantService.getByHandles([email], workspaceId);
|
||||||
|
|
||||||
|
const messageParticipantIdsToUpdate = messageParticipantsToUpdate.map(
|
||||||
|
(participant) => participant.id,
|
||||||
|
);
|
||||||
|
|
||||||
|
if (personId) {
|
||||||
|
await this.messageParticipantService.updateParticipantsPersonId(
|
||||||
|
messageParticipantIdsToUpdate,
|
||||||
|
personId,
|
||||||
|
workspaceId,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
if (workspaceMemberId) {
|
||||||
|
await this.messageParticipantService.updateParticipantsWorkspaceMemberId(
|
||||||
|
messageParticipantIdsToUpdate,
|
||||||
|
workspaceMemberId,
|
||||||
|
workspaceId,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,63 @@
|
|||||||
|
import { Inject, Injectable } from '@nestjs/common';
|
||||||
|
import { OnEvent } from '@nestjs/event-emitter';
|
||||||
|
|
||||||
|
import { ObjectRecordCreateEvent } from 'src/integrations/event-emitter/types/object-record-create.event';
|
||||||
|
import { ObjectRecordUpdateEvent } from 'src/integrations/event-emitter/types/object-record-update.event';
|
||||||
|
import { objectRecordChangedProperties as objectRecordUpdateEventChangedProperties } from 'src/integrations/event-emitter/utils/object-record-changed-properties.util';
|
||||||
|
import { MessageQueue } from 'src/integrations/message-queue/message-queue.constants';
|
||||||
|
import { MessageQueueService } from 'src/integrations/message-queue/services/message-queue.service';
|
||||||
|
import {
|
||||||
|
MatchMessageParticipantJob,
|
||||||
|
MatchMessageParticipantsJobData,
|
||||||
|
} from 'src/workspace/messaging/jobs/match-message-participant.job';
|
||||||
|
import { PersonObjectMetadata } from 'src/workspace/workspace-sync-metadata/standard-objects/person.object-metadata';
|
||||||
|
|
||||||
|
@Injectable()
|
||||||
|
export class MessagingPersonListener {
|
||||||
|
constructor(
|
||||||
|
@Inject(MessageQueue.messagingQueue)
|
||||||
|
private readonly messageQueueService: MessageQueueService,
|
||||||
|
) {}
|
||||||
|
|
||||||
|
@OnEvent('person.created')
|
||||||
|
handleCreatedEvent(payload: ObjectRecordCreateEvent<PersonObjectMetadata>) {
|
||||||
|
if (payload.createdRecord.email === null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.messageQueueService.add<MatchMessageParticipantsJobData>(
|
||||||
|
MatchMessageParticipantJob.name,
|
||||||
|
{
|
||||||
|
workspaceId: payload.workspaceId,
|
||||||
|
email: payload.createdRecord.email,
|
||||||
|
personId: payload.createdRecord.id,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
@OnEvent('person.updated')
|
||||||
|
handleUpdatedEvent(payload: ObjectRecordUpdateEvent<PersonObjectMetadata>) {
|
||||||
|
console.log(
|
||||||
|
objectRecordUpdateEventChangedProperties(
|
||||||
|
payload.previousRecord,
|
||||||
|
payload.updatedRecord,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
if (
|
||||||
|
objectRecordUpdateEventChangedProperties(
|
||||||
|
payload.previousRecord,
|
||||||
|
payload.updatedRecord,
|
||||||
|
).includes('email')
|
||||||
|
) {
|
||||||
|
this.messageQueueService.add<MatchMessageParticipantsJobData>(
|
||||||
|
MatchMessageParticipantJob.name,
|
||||||
|
{
|
||||||
|
workspaceId: payload.workspaceId,
|
||||||
|
email: payload.updatedRecord.email,
|
||||||
|
personId: payload.updatedRecord.id,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,60 @@
|
|||||||
|
import { Inject, Injectable } from '@nestjs/common';
|
||||||
|
import { OnEvent } from '@nestjs/event-emitter';
|
||||||
|
|
||||||
|
import { ObjectRecordCreateEvent } from 'src/integrations/event-emitter/types/object-record-create.event';
|
||||||
|
import { ObjectRecordUpdateEvent } from 'src/integrations/event-emitter/types/object-record-update.event';
|
||||||
|
import { objectRecordChangedProperties as objectRecordUpdateEventChangedProperties } from 'src/integrations/event-emitter/utils/object-record-changed-properties.util';
|
||||||
|
import { MessageQueue } from 'src/integrations/message-queue/message-queue.constants';
|
||||||
|
import { MessageQueueService } from 'src/integrations/message-queue/services/message-queue.service';
|
||||||
|
import {
|
||||||
|
MatchMessageParticipantJob,
|
||||||
|
MatchMessageParticipantsJobData,
|
||||||
|
} from 'src/workspace/messaging/jobs/match-message-participant.job';
|
||||||
|
import { WorkspaceMemberObjectMetadata } from 'src/workspace/workspace-sync-metadata/standard-objects/workspace-member.object-metadata';
|
||||||
|
|
||||||
|
@Injectable()
|
||||||
|
export class MessagingWorkspaceMemberListener {
|
||||||
|
constructor(
|
||||||
|
@Inject(MessageQueue.messagingQueue)
|
||||||
|
private readonly messageQueueService: MessageQueueService,
|
||||||
|
) {}
|
||||||
|
|
||||||
|
@OnEvent('workspaceMember.created')
|
||||||
|
handleCreatedEvent(
|
||||||
|
payload: ObjectRecordCreateEvent<WorkspaceMemberObjectMetadata>,
|
||||||
|
) {
|
||||||
|
if (payload.createdRecord.userEmail === null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.messageQueueService.add<MatchMessageParticipantsJobData>(
|
||||||
|
MatchMessageParticipantJob.name,
|
||||||
|
{
|
||||||
|
workspaceId: payload.workspaceId,
|
||||||
|
email: payload.createdRecord.userEmail,
|
||||||
|
workspaceMemberId: payload.createdRecord.id,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
@OnEvent('workspaceMember.updated')
|
||||||
|
handleUpdatedEvent(
|
||||||
|
payload: ObjectRecordUpdateEvent<WorkspaceMemberObjectMetadata>,
|
||||||
|
) {
|
||||||
|
if (
|
||||||
|
objectRecordUpdateEventChangedProperties(
|
||||||
|
payload.previousRecord,
|
||||||
|
payload.updatedRecord,
|
||||||
|
).includes('userEmail')
|
||||||
|
) {
|
||||||
|
this.messageQueueService.add<MatchMessageParticipantsJobData>(
|
||||||
|
MatchMessageParticipantJob.name,
|
||||||
|
{
|
||||||
|
workspaceId: payload.workspaceId,
|
||||||
|
email: payload.updatedRecord.userEmail,
|
||||||
|
workspaceMemberId: payload.updatedRecord.id,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,11 @@
|
|||||||
|
import { Module } from '@nestjs/common';
|
||||||
|
|
||||||
|
import { MessageParticipantService } from 'src/workspace/messaging/message-participant/message-participant.service';
|
||||||
|
import { WorkspaceDataSourceModule } from 'src/workspace/workspace-datasource/workspace-datasource.module';
|
||||||
|
|
||||||
|
@Module({
|
||||||
|
imports: [WorkspaceDataSourceModule],
|
||||||
|
providers: [MessageParticipantService],
|
||||||
|
exports: [MessageParticipantService],
|
||||||
|
})
|
||||||
|
export class MessageParticipantModule {}
|
||||||
@ -0,0 +1,64 @@
|
|||||||
|
import { Injectable } from '@nestjs/common';
|
||||||
|
|
||||||
|
import { EntityManager } from 'typeorm';
|
||||||
|
|
||||||
|
import { WorkspaceDataSourceService } from 'src/workspace/workspace-datasource/workspace-datasource.service';
|
||||||
|
import { MessageParticipantObjectMetadata } from 'src/workspace/workspace-sync-metadata/standard-objects/message-participant.object-metadata';
|
||||||
|
import { ObjectRecord } from 'src/workspace/workspace-sync-metadata/types/object-record';
|
||||||
|
|
||||||
|
@Injectable()
|
||||||
|
export class MessageParticipantService {
|
||||||
|
constructor(
|
||||||
|
private readonly workspaceDataSourceService: WorkspaceDataSourceService,
|
||||||
|
) {}
|
||||||
|
|
||||||
|
public async getByHandles(
|
||||||
|
handles: string[],
|
||||||
|
workspaceId: string,
|
||||||
|
transactionManager?: EntityManager,
|
||||||
|
): Promise<ObjectRecord<MessageParticipantObjectMetadata>[]> {
|
||||||
|
const dataSourceSchema =
|
||||||
|
this.workspaceDataSourceService.getSchemaName(workspaceId);
|
||||||
|
|
||||||
|
return await this.workspaceDataSourceService.executeRawQuery(
|
||||||
|
`SELECT * FROM ${dataSourceSchema}."messageParticipant" WHERE "handle" = ANY($1)`,
|
||||||
|
[handles],
|
||||||
|
workspaceId,
|
||||||
|
transactionManager,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async updateParticipantsPersonId(
|
||||||
|
participantIds: string[],
|
||||||
|
personId: string,
|
||||||
|
workspaceId: string,
|
||||||
|
transactionManager?: EntityManager,
|
||||||
|
) {
|
||||||
|
const dataSourceSchema =
|
||||||
|
this.workspaceDataSourceService.getSchemaName(workspaceId);
|
||||||
|
|
||||||
|
await this.workspaceDataSourceService.executeRawQuery(
|
||||||
|
`UPDATE ${dataSourceSchema}."messageParticipant" SET "personId" = $1 WHERE "id" = ANY($2)`,
|
||||||
|
[personId, participantIds],
|
||||||
|
workspaceId,
|
||||||
|
transactionManager,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async updateParticipantsWorkspaceMemberId(
|
||||||
|
participantIds: string[],
|
||||||
|
workspaceMemberId: string,
|
||||||
|
workspaceId: string,
|
||||||
|
transactionManager?: EntityManager,
|
||||||
|
) {
|
||||||
|
const dataSourceSchema =
|
||||||
|
this.workspaceDataSourceService.getSchemaName(workspaceId);
|
||||||
|
|
||||||
|
await this.workspaceDataSourceService.executeRawQuery(
|
||||||
|
`UPDATE ${dataSourceSchema}."messageParticipant" SET "workspaceMemberId" = $1 WHERE "id" = ANY($2)`,
|
||||||
|
[workspaceMemberId, participantIds],
|
||||||
|
workspaceId,
|
||||||
|
transactionManager,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -1,18 +1,21 @@
|
|||||||
import { Module } from '@nestjs/common';
|
import { Module } from '@nestjs/common';
|
||||||
|
|
||||||
import { EnvironmentModule } from 'src/integrations/environment/environment.module';
|
|
||||||
import { ConnectedAccountModule } from 'src/workspace/messaging/connected-account/connected-account.module';
|
import { ConnectedAccountModule } from 'src/workspace/messaging/connected-account/connected-account.module';
|
||||||
import { MessageChannelMessageAssociationModule } from 'src/workspace/messaging/message-channel-message-association/message-channel-message-assocation.module';
|
import { MessageChannelMessageAssociationModule } from 'src/workspace/messaging/message-channel-message-association/message-channel-message-assocation.module';
|
||||||
import { MessageChannelModule } from 'src/workspace/messaging/message-channel/message-channel.module';
|
import { MessageChannelModule } from 'src/workspace/messaging/message-channel/message-channel.module';
|
||||||
import { MessageThreadModule } from 'src/workspace/messaging/message-thread/message-thread.module';
|
import { MessageThreadModule } from 'src/workspace/messaging/message-thread/message-thread.module';
|
||||||
|
import { MessagingUtilsService } from 'src/workspace/messaging/services/messaging-utils.service';
|
||||||
|
import { EnvironmentModule } from 'src/integrations/environment/environment.module';
|
||||||
|
import { MessagingPersonListener } from 'src/workspace/messaging/listeners/messaging-person.listener';
|
||||||
import { MessageModule } from 'src/workspace/messaging/message/message.module';
|
import { MessageModule } from 'src/workspace/messaging/message/message.module';
|
||||||
import { GmailClientProvider } from 'src/workspace/messaging/providers/gmail/gmail-client.provider';
|
import { GmailClientProvider } from 'src/workspace/messaging/providers/gmail/gmail-client.provider';
|
||||||
import { FetchMessagesByBatchesService } from 'src/workspace/messaging/services/fetch-messages-by-batches.service';
|
import { FetchMessagesByBatchesService } from 'src/workspace/messaging/services/fetch-messages-by-batches.service';
|
||||||
import { GmailFullSyncService } from 'src/workspace/messaging/services/gmail-full-sync.service';
|
import { GmailFullSyncService } from 'src/workspace/messaging/services/gmail-full-sync.service';
|
||||||
import { GmailPartialSyncService } from 'src/workspace/messaging/services/gmail-partial-sync.service';
|
import { GmailPartialSyncService } from 'src/workspace/messaging/services/gmail-partial-sync.service';
|
||||||
import { GmailRefreshAccessTokenService } from 'src/workspace/messaging/services/gmail-refresh-access-token.service';
|
import { GmailRefreshAccessTokenService } from 'src/workspace/messaging/services/gmail-refresh-access-token.service';
|
||||||
import { MessagingUtilsService } from 'src/workspace/messaging/services/messaging-utils.service';
|
|
||||||
import { WorkspaceDataSourceModule } from 'src/workspace/workspace-datasource/workspace-datasource.module';
|
import { WorkspaceDataSourceModule } from 'src/workspace/workspace-datasource/workspace-datasource.module';
|
||||||
|
import { MessageParticipantModule } from 'src/workspace/messaging/message-participant/message-participant.module';
|
||||||
|
import { MessagingWorkspaceMemberListener } from 'src/workspace/messaging/listeners/messaging-workspace-member.listener';
|
||||||
|
|
||||||
@Module({
|
@Module({
|
||||||
imports: [
|
imports: [
|
||||||
@ -23,6 +26,7 @@ import { WorkspaceDataSourceModule } from 'src/workspace/workspace-datasource/wo
|
|||||||
MessageChannelMessageAssociationModule,
|
MessageChannelMessageAssociationModule,
|
||||||
MessageModule,
|
MessageModule,
|
||||||
MessageThreadModule,
|
MessageThreadModule,
|
||||||
|
MessageParticipantModule,
|
||||||
],
|
],
|
||||||
providers: [
|
providers: [
|
||||||
GmailFullSyncService,
|
GmailFullSyncService,
|
||||||
@ -31,6 +35,8 @@ import { WorkspaceDataSourceModule } from 'src/workspace/workspace-datasource/wo
|
|||||||
GmailRefreshAccessTokenService,
|
GmailRefreshAccessTokenService,
|
||||||
MessagingUtilsService,
|
MessagingUtilsService,
|
||||||
GmailClientProvider,
|
GmailClientProvider,
|
||||||
|
MessagingPersonListener,
|
||||||
|
MessagingWorkspaceMemberListener,
|
||||||
],
|
],
|
||||||
exports: [
|
exports: [
|
||||||
GmailPartialSyncService,
|
GmailPartialSyncService,
|
||||||
@ -39,4 +45,4 @@ import { WorkspaceDataSourceModule } from 'src/workspace/workspace-datasource/wo
|
|||||||
MessagingUtilsService,
|
MessagingUtilsService,
|
||||||
],
|
],
|
||||||
})
|
})
|
||||||
export class FetchWorkspaceMessagesModule {}
|
export class MessagingModule {}
|
||||||
@ -4,7 +4,6 @@ import { gmail_v1 } from 'googleapis';
|
|||||||
|
|
||||||
import { FetchMessagesByBatchesService } from 'src/workspace/messaging/services/fetch-messages-by-batches.service';
|
import { FetchMessagesByBatchesService } from 'src/workspace/messaging/services/fetch-messages-by-batches.service';
|
||||||
import { GmailClientProvider } from 'src/workspace/messaging/providers/gmail/gmail-client.provider';
|
import { GmailClientProvider } from 'src/workspace/messaging/providers/gmail/gmail-client.provider';
|
||||||
import { MessagingUtilsService } from 'src/workspace/messaging/services/messaging-utils.service';
|
|
||||||
import { MessageQueueService } from 'src/integrations/message-queue/services/message-queue.service';
|
import { MessageQueueService } from 'src/integrations/message-queue/services/message-queue.service';
|
||||||
import { MessageQueue } from 'src/integrations/message-queue/message-queue.constants';
|
import { MessageQueue } from 'src/integrations/message-queue/message-queue.constants';
|
||||||
import {
|
import {
|
||||||
@ -12,8 +11,9 @@ import {
|
|||||||
GmailFullSyncJobData,
|
GmailFullSyncJobData,
|
||||||
} from 'src/workspace/messaging/jobs/gmail-full-sync.job';
|
} from 'src/workspace/messaging/jobs/gmail-full-sync.job';
|
||||||
import { ConnectedAccountService } from 'src/workspace/messaging/connected-account/connected-account.service';
|
import { ConnectedAccountService } from 'src/workspace/messaging/connected-account/connected-account.service';
|
||||||
import { MessageChannelService } from 'src/workspace/messaging/message-channel/message-channel.service';
|
|
||||||
import { WorkspaceDataSourceService } from 'src/workspace/workspace-datasource/workspace-datasource.service';
|
import { WorkspaceDataSourceService } from 'src/workspace/workspace-datasource/workspace-datasource.service';
|
||||||
|
import { MessageChannelService } from 'src/workspace/messaging/message-channel/message-channel.service';
|
||||||
|
import { MessagingUtilsService } from 'src/workspace/messaging/services/messaging-utils.service';
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class GmailPartialSyncService {
|
export class GmailPartialSyncService {
|
||||||
|
|||||||
@ -3,8 +3,8 @@ import {
|
|||||||
Inject,
|
Inject,
|
||||||
Injectable,
|
Injectable,
|
||||||
InternalServerErrorException,
|
InternalServerErrorException,
|
||||||
Logger,
|
|
||||||
} from '@nestjs/common';
|
} from '@nestjs/common';
|
||||||
|
import { EventEmitter2 } from '@nestjs/event-emitter';
|
||||||
|
|
||||||
import { IConnection } from 'src/utils/pagination/interfaces/connection.interface';
|
import { IConnection } from 'src/utils/pagination/interfaces/connection.interface';
|
||||||
import {
|
import {
|
||||||
@ -34,10 +34,12 @@ import {
|
|||||||
CallWebhookJobsJobOperation,
|
CallWebhookJobsJobOperation,
|
||||||
} from 'src/workspace/workspace-query-runner/jobs/call-webhook-jobs.job';
|
} from 'src/workspace/workspace-query-runner/jobs/call-webhook-jobs.job';
|
||||||
import { parseResult } from 'src/workspace/workspace-query-runner/utils/parse-result.util';
|
import { parseResult } from 'src/workspace/workspace-query-runner/utils/parse-result.util';
|
||||||
import { ExceptionHandlerService } from 'src/integrations/exception-handler/exception-handler.service';
|
|
||||||
import { computeObjectTargetTable } from 'src/workspace/utils/compute-object-target-table.util';
|
import { computeObjectTargetTable } from 'src/workspace/utils/compute-object-target-table.util';
|
||||||
|
import { ObjectRecordDeleteEvent } from 'src/integrations/event-emitter/types/object-record-delete.event';
|
||||||
|
import { ObjectRecordCreateEvent } from 'src/integrations/event-emitter/types/object-record-create.event';
|
||||||
|
import { ObjectRecordUpdateEvent } from 'src/integrations/event-emitter/types/object-record-update.event';
|
||||||
|
|
||||||
import { WorkspaceQueryRunnerOptions } from './interfaces/query-runner-optionts.interface';
|
import { WorkspaceQueryRunnerOptions } from './interfaces/query-runner-option.interface';
|
||||||
import {
|
import {
|
||||||
PGGraphQLMutation,
|
PGGraphQLMutation,
|
||||||
PGGraphQLResult,
|
PGGraphQLResult,
|
||||||
@ -45,14 +47,12 @@ import {
|
|||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class WorkspaceQueryRunnerService {
|
export class WorkspaceQueryRunnerService {
|
||||||
private readonly logger = new Logger(WorkspaceQueryRunnerService.name);
|
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
private readonly workspaceQueryBuilderFactory: WorkspaceQueryBuilderFactory,
|
private readonly workspaceQueryBuilderFactory: WorkspaceQueryBuilderFactory,
|
||||||
private readonly workspaceDataSourceService: WorkspaceDataSourceService,
|
private readonly workspaceDataSourceService: WorkspaceDataSourceService,
|
||||||
@Inject(MessageQueue.webhookQueue)
|
@Inject(MessageQueue.webhookQueue)
|
||||||
private readonly messageQueueService: MessageQueueService,
|
private readonly messageQueueService: MessageQueueService,
|
||||||
private readonly exceptionHandlerService: ExceptionHandlerService,
|
private readonly eventEmitter: EventEmitter2,
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
async findMany<
|
async findMany<
|
||||||
@ -136,6 +136,13 @@ export class WorkspaceQueryRunnerService {
|
|||||||
options,
|
options,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
parsedResults.forEach((record) => {
|
||||||
|
this.eventEmitter.emit(`${objectMetadataItem.nameSingular}.created`, {
|
||||||
|
workspaceId,
|
||||||
|
createdRecord: [this.removeNestedProperties(record)],
|
||||||
|
} satisfies ObjectRecordCreateEvent<any>);
|
||||||
|
});
|
||||||
|
|
||||||
return parsedResults;
|
return parsedResults;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -153,6 +160,12 @@ export class WorkspaceQueryRunnerService {
|
|||||||
options: WorkspaceQueryRunnerOptions,
|
options: WorkspaceQueryRunnerOptions,
|
||||||
): Promise<Record | undefined> {
|
): Promise<Record | undefined> {
|
||||||
const { workspaceId, objectMetadataItem } = options;
|
const { workspaceId, objectMetadataItem } = options;
|
||||||
|
|
||||||
|
const existingRecord = await this.findOne(
|
||||||
|
{ filter: { id: { eq: args.id } } } as FindOneResolverArgs,
|
||||||
|
options,
|
||||||
|
);
|
||||||
|
|
||||||
const query = await this.workspaceQueryBuilderFactory.updateOne(
|
const query = await this.workspaceQueryBuilderFactory.updateOne(
|
||||||
args,
|
args,
|
||||||
options,
|
options,
|
||||||
@ -171,31 +184,11 @@ export class WorkspaceQueryRunnerService {
|
|||||||
options,
|
options,
|
||||||
);
|
);
|
||||||
|
|
||||||
return parsedResults?.[0];
|
this.eventEmitter.emit(`${objectMetadataItem.nameSingular}.updated`, {
|
||||||
}
|
workspaceId,
|
||||||
|
previousRecord: this.removeNestedProperties(existingRecord as Record),
|
||||||
async deleteOne<Record extends IRecord = IRecord>(
|
updatedRecord: this.removeNestedProperties(parsedResults?.[0]),
|
||||||
args: DeleteOneResolverArgs,
|
} satisfies ObjectRecordUpdateEvent<any>);
|
||||||
options: WorkspaceQueryRunnerOptions,
|
|
||||||
): Promise<Record | undefined> {
|
|
||||||
const { workspaceId, objectMetadataItem } = options;
|
|
||||||
const query = await this.workspaceQueryBuilderFactory.deleteOne(
|
|
||||||
args,
|
|
||||||
options,
|
|
||||||
);
|
|
||||||
const result = await this.execute(query, workspaceId);
|
|
||||||
|
|
||||||
const parsedResults = this.parseResult<PGGraphQLMutation<Record>>(
|
|
||||||
result,
|
|
||||||
objectMetadataItem,
|
|
||||||
'deleteFrom',
|
|
||||||
)?.records;
|
|
||||||
|
|
||||||
await this.triggerWebhooks<Record>(
|
|
||||||
parsedResults,
|
|
||||||
CallWebhookJobsJobOperation.delete,
|
|
||||||
options,
|
|
||||||
);
|
|
||||||
|
|
||||||
return parsedResults?.[0];
|
return parsedResults?.[0];
|
||||||
}
|
}
|
||||||
@ -209,6 +202,7 @@ export class WorkspaceQueryRunnerService {
|
|||||||
args,
|
args,
|
||||||
options,
|
options,
|
||||||
);
|
);
|
||||||
|
|
||||||
const result = await this.execute(query, workspaceId);
|
const result = await this.execute(query, workspaceId);
|
||||||
|
|
||||||
const parsedResults = this.parseResult<PGGraphQLMutation<Record>>(
|
const parsedResults = this.parseResult<PGGraphQLMutation<Record>>(
|
||||||
@ -252,9 +246,63 @@ export class WorkspaceQueryRunnerService {
|
|||||||
options,
|
options,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
parsedResults.forEach((record) => {
|
||||||
|
this.eventEmitter.emit(`${objectMetadataItem.nameSingular}.deleted`, {
|
||||||
|
workspaceId,
|
||||||
|
deletedRecord: [this.removeNestedProperties(record)],
|
||||||
|
} satisfies ObjectRecordDeleteEvent<any>);
|
||||||
|
});
|
||||||
|
|
||||||
return parsedResults;
|
return parsedResults;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async deleteOne<Record extends IRecord = IRecord>(
|
||||||
|
args: DeleteOneResolverArgs,
|
||||||
|
options: WorkspaceQueryRunnerOptions,
|
||||||
|
): Promise<Record | undefined> {
|
||||||
|
const { workspaceId, objectMetadataItem } = options;
|
||||||
|
const query = await this.workspaceQueryBuilderFactory.deleteOne(
|
||||||
|
args,
|
||||||
|
options,
|
||||||
|
);
|
||||||
|
const result = await this.execute(query, workspaceId);
|
||||||
|
|
||||||
|
const parsedResults = this.parseResult<PGGraphQLMutation<Record>>(
|
||||||
|
result,
|
||||||
|
objectMetadataItem,
|
||||||
|
'deleteFrom',
|
||||||
|
)?.records;
|
||||||
|
|
||||||
|
await this.triggerWebhooks<Record>(
|
||||||
|
parsedResults,
|
||||||
|
CallWebhookJobsJobOperation.delete,
|
||||||
|
options,
|
||||||
|
);
|
||||||
|
|
||||||
|
this.eventEmitter.emit(`${objectMetadataItem.nameSingular}.deleted`, {
|
||||||
|
workspaceId,
|
||||||
|
deletedRecord: this.removeNestedProperties(parsedResults?.[0]),
|
||||||
|
} satisfies ObjectRecordDeleteEvent<any>);
|
||||||
|
|
||||||
|
return parsedResults?.[0];
|
||||||
|
}
|
||||||
|
|
||||||
|
private removeNestedProperties<Record extends IRecord = IRecord>(
|
||||||
|
record: Record,
|
||||||
|
) {
|
||||||
|
const sanitizedRecord = {};
|
||||||
|
|
||||||
|
for (const [key, value] of Object.entries(record)) {
|
||||||
|
if (value && typeof value === 'object' && value['edges']) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
sanitizedRecord[key] = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
return sanitizedRecord;
|
||||||
|
}
|
||||||
|
|
||||||
async execute(
|
async execute(
|
||||||
query: string,
|
query: string,
|
||||||
workspaceId: string,
|
workspaceId: string,
|
||||||
|
|||||||
@ -9,7 +9,7 @@ import {
|
|||||||
import { Record as IRecord } from 'src/workspace/workspace-query-builder/interfaces/record.interface';
|
import { Record as IRecord } from 'src/workspace/workspace-query-builder/interfaces/record.interface';
|
||||||
import { WorkspaceSchemaBuilderContext } from 'src/workspace/workspace-schema-builder/interfaces/workspace-schema-builder-context.interface';
|
import { WorkspaceSchemaBuilderContext } from 'src/workspace/workspace-schema-builder/interfaces/workspace-schema-builder-context.interface';
|
||||||
import { WorkspaceResolverBuilderFactoryInterface } from 'src/workspace/workspace-resolver-builder/interfaces/workspace-resolver-builder-factory.interface';
|
import { WorkspaceResolverBuilderFactoryInterface } from 'src/workspace/workspace-resolver-builder/interfaces/workspace-resolver-builder-factory.interface';
|
||||||
import { WorkspaceQueryRunnerOptions } from 'src/workspace/workspace-query-runner/interfaces/query-runner-optionts.interface';
|
import { WorkspaceQueryRunnerOptions } from 'src/workspace/workspace-query-runner/interfaces/query-runner-option.interface';
|
||||||
|
|
||||||
import { WorkspaceQueryRunnerService } from 'src/workspace/workspace-query-runner/workspace-query-runner.service';
|
import { WorkspaceQueryRunnerService } from 'src/workspace/workspace-query-runner/workspace-query-runner.service';
|
||||||
import { QuickActionsService } from 'src/core/quick-actions/quick-actions.service';
|
import { QuickActionsService } from 'src/core/quick-actions/quick-actions.service';
|
||||||
|
|||||||
@ -5,6 +5,7 @@ import { DataSourceModule } from 'src/metadata/data-source/data-source.module';
|
|||||||
import { WorkspaceSchemaStorageModule } from 'src/workspace/workspace-schema-storage/workspace-schema-storage.module';
|
import { WorkspaceSchemaStorageModule } from 'src/workspace/workspace-schema-storage/workspace-schema-storage.module';
|
||||||
import { ObjectMetadataModule } from 'src/metadata/object-metadata/object-metadata.module';
|
import { ObjectMetadataModule } from 'src/metadata/object-metadata/object-metadata.module';
|
||||||
import { ScalarsExplorerService } from 'src/workspace/services/scalars-explorer.service';
|
import { ScalarsExplorerService } from 'src/workspace/services/scalars-explorer.service';
|
||||||
|
import { MessagingModule } from 'src/workspace/messaging/messaging.module';
|
||||||
|
|
||||||
import { WorkspaceFactory } from './workspace.factory';
|
import { WorkspaceFactory } from './workspace.factory';
|
||||||
|
|
||||||
@ -19,6 +20,7 @@ import { WorkspaceResolverBuilderModule } from './workspace-resolver-builder/wor
|
|||||||
WorkspaceSchemaBuilderModule,
|
WorkspaceSchemaBuilderModule,
|
||||||
WorkspaceResolverBuilderModule,
|
WorkspaceResolverBuilderModule,
|
||||||
WorkspaceSchemaStorageModule,
|
WorkspaceSchemaStorageModule,
|
||||||
|
MessagingModule,
|
||||||
],
|
],
|
||||||
providers: [WorkspaceFactory, ScalarsExplorerService],
|
providers: [WorkspaceFactory, ScalarsExplorerService],
|
||||||
exports: [WorkspaceFactory],
|
exports: [WorkspaceFactory],
|
||||||
|
|||||||
21
yarn.lock
21
yarn.lock
@ -7338,6 +7338,19 @@ __metadata:
|
|||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
|
"@nestjs/event-emitter@npm:^2.0.3":
|
||||||
|
version: 2.0.3
|
||||||
|
resolution: "@nestjs/event-emitter@npm:2.0.3"
|
||||||
|
dependencies:
|
||||||
|
eventemitter2: "npm:6.4.9"
|
||||||
|
peerDependencies:
|
||||||
|
"@nestjs/common": ^8.0.0 || ^9.0.0 || ^10.0.0
|
||||||
|
"@nestjs/core": ^8.0.0 || ^9.0.0 || ^10.0.0
|
||||||
|
reflect-metadata: ^0.1.12
|
||||||
|
checksum: 22fdbe4b68f9a4f25be8b2cc72afa5f4956b07f3b646befa263a0c112eee200de553a0ed1804090430002dcc6139b4cb20451dabe5c9dde140dc12469489c547
|
||||||
|
languageName: node
|
||||||
|
linkType: hard
|
||||||
|
|
||||||
"@nestjs/graphql@npm:12.0.8":
|
"@nestjs/graphql@npm:12.0.8":
|
||||||
version: 12.0.8
|
version: 12.0.8
|
||||||
resolution: "@nestjs/graphql@npm:12.0.8"
|
resolution: "@nestjs/graphql@npm:12.0.8"
|
||||||
@ -24366,6 +24379,13 @@ __metadata:
|
|||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
|
"eventemitter2@npm:6.4.9":
|
||||||
|
version: 6.4.9
|
||||||
|
resolution: "eventemitter2@npm:6.4.9"
|
||||||
|
checksum: b2adf7d9f1544aa2d95ee271b0621acaf1e309d85ebcef1244fb0ebc7ab0afa6ffd5e371535d0981bc46195ad67fd6ff57a8d1db030584dee69aa5e371a27ea7
|
||||||
|
languageName: node
|
||||||
|
linkType: hard
|
||||||
|
|
||||||
"eventemitter3@npm:^3.1.0":
|
"eventemitter3@npm:^3.1.0":
|
||||||
version: 3.1.2
|
version: 3.1.2
|
||||||
resolution: "eventemitter3@npm:3.1.2"
|
resolution: "eventemitter3@npm:3.1.2"
|
||||||
@ -43324,6 +43344,7 @@ __metadata:
|
|||||||
"@nestjs/common": "npm:^9.0.0"
|
"@nestjs/common": "npm:^9.0.0"
|
||||||
"@nestjs/config": "npm:^2.3.2"
|
"@nestjs/config": "npm:^2.3.2"
|
||||||
"@nestjs/core": "npm:^9.0.0"
|
"@nestjs/core": "npm:^9.0.0"
|
||||||
|
"@nestjs/event-emitter": "npm:^2.0.3"
|
||||||
"@nestjs/jwt": "npm:^10.0.3"
|
"@nestjs/jwt": "npm:^10.0.3"
|
||||||
"@nestjs/passport": "npm:^9.0.3"
|
"@nestjs/passport": "npm:^9.0.3"
|
||||||
"@nestjs/platform-express": "npm:^9.0.0"
|
"@nestjs/platform-express": "npm:^9.0.0"
|
||||||
|
|||||||
Reference in New Issue
Block a user