POC timeline activity (#5697)
TODO: - remove WorkspaceIsNotAuditLogged decorators on activity/activityTarget to log task/note creations - handle attachments - fix css and remove unnecessary styled components or duplicates
This commit is contained in:
@ -9,7 +9,6 @@ import { OpportunityWorkspaceEntity } from 'src/modules/opportunity/standard-obj
|
||||
import { PersonWorkspaceEntity } from 'src/modules/person/standard-objects/person.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 { WorkspaceIsNullable } from 'src/engine/twenty-orm/decorators/workspace-is-nullable.decorator';
|
||||
import { WorkspaceDynamicRelation } from 'src/engine/twenty-orm/decorators/workspace-dynamic-relation.decorator';
|
||||
import { RelationMetadataType } from 'src/engine/metadata-modules/relation-metadata/relation-metadata.entity';
|
||||
@ -25,7 +24,6 @@ import { BaseWorkspaceEntity } from 'src/engine/twenty-orm/base.workspace-entity
|
||||
icon: 'IconCheckbox',
|
||||
})
|
||||
@WorkspaceIsSystem()
|
||||
@WorkspaceIsNotAuditLogged()
|
||||
export class ActivityTargetWorkspaceEntity extends BaseWorkspaceEntity {
|
||||
@WorkspaceRelation({
|
||||
standardId: ACTIVITY_TARGET_STANDARD_FIELD_IDS.activity,
|
||||
|
||||
@ -12,7 +12,6 @@ import { AttachmentWorkspaceEntity } from 'src/modules/attachment/standard-objec
|
||||
import { CommentWorkspaceEntity } from 'src/modules/activity/standard-objects/comment.workspace-entity';
|
||||
import { WorkspaceMemberWorkspaceEntity } from 'src/modules/workspace-member/standard-objects/workspace-member.workspace-entity';
|
||||
import { WorkspaceEntity } from 'src/engine/twenty-orm/decorators/workspace-entity.decorator';
|
||||
import { WorkspaceIsNotAuditLogged } from 'src/engine/twenty-orm/decorators/workspace-is-not-audit-logged.decorator';
|
||||
import { WorkspaceIsSystem } from 'src/engine/twenty-orm/decorators/workspace-is-system.decorator';
|
||||
import { WorkspaceField } from 'src/engine/twenty-orm/decorators/workspace-field.decorator';
|
||||
import { WorkspaceRelation } from 'src/engine/twenty-orm/decorators/workspace-relation.decorator';
|
||||
@ -27,7 +26,6 @@ import { BaseWorkspaceEntity } from 'src/engine/twenty-orm/base.workspace-entity
|
||||
description: 'An activity',
|
||||
icon: 'IconCheckbox',
|
||||
})
|
||||
@WorkspaceIsNotAuditLogged()
|
||||
@WorkspaceIsSystem()
|
||||
export class ActivityWorkspaceEntity extends BaseWorkspaceEntity {
|
||||
@WorkspaceField({
|
||||
|
||||
@ -7,7 +7,6 @@ import { ActivityWorkspaceEntity } from 'src/modules/activity/standard-objects/a
|
||||
import { WorkspaceMemberWorkspaceEntity } from 'src/modules/workspace-member/standard-objects/workspace-member.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';
|
||||
@ -22,7 +21,6 @@ import { BaseWorkspaceEntity } from 'src/engine/twenty-orm/base.workspace-entity
|
||||
icon: 'IconMessageCircle',
|
||||
})
|
||||
@WorkspaceIsSystem()
|
||||
@WorkspaceIsNotAuditLogged()
|
||||
export class CommentWorkspaceEntity extends BaseWorkspaceEntity {
|
||||
@WorkspaceField({
|
||||
standardId: COMMENT_STANDARD_FIELD_IDS.body,
|
||||
|
||||
@ -1,11 +1,27 @@
|
||||
import { Module } from '@nestjs/common';
|
||||
import { TypeOrmModule } from '@nestjs/typeorm';
|
||||
|
||||
import { ObjectMetadataEntity } from 'src/engine/metadata-modules/object-metadata/object-metadata.entity';
|
||||
import { ObjectMetadataRepositoryModule } from 'src/engine/object-metadata-repository/object-metadata-repository.module';
|
||||
import { WorkspaceDataSourceModule } from 'src/engine/workspace-datasource/workspace-datasource.module';
|
||||
import { CalendarBlocklistListener } from 'src/modules/calendar/listeners/calendar-blocklist.listener';
|
||||
import { CalendarChannelListener } from 'src/modules/calendar/listeners/calendar-channel.listener';
|
||||
import { CalendarEventParticipantListener } from 'src/modules/calendar/listeners/calendar-event-participant.listener';
|
||||
import { TimelineActivityWorkspaceEntity } from 'src/modules/timeline/standard-objects/timeline-activity.workspace-entity';
|
||||
|
||||
@Module({
|
||||
imports: [],
|
||||
providers: [CalendarChannelListener, CalendarBlocklistListener],
|
||||
imports: [
|
||||
WorkspaceDataSourceModule,
|
||||
ObjectMetadataRepositoryModule.forFeature([
|
||||
TimelineActivityWorkspaceEntity,
|
||||
]),
|
||||
TypeOrmModule.forFeature([ObjectMetadataEntity], 'metadata'),
|
||||
],
|
||||
providers: [
|
||||
CalendarChannelListener,
|
||||
CalendarBlocklistListener,
|
||||
CalendarEventParticipantListener,
|
||||
],
|
||||
exports: [],
|
||||
})
|
||||
export class CalendarModule {}
|
||||
|
||||
@ -8,6 +8,8 @@ import { CalendarEventParticipantRepository } from 'src/modules/calendar/reposit
|
||||
import { CalendarChannelWorkspaceEntity } from 'src/modules/calendar/standard-objects/calendar-channel.workspace-entity';
|
||||
import { CalendarEventParticipantWorkspaceEntity } from 'src/modules/calendar/standard-objects/calendar-event-participant.workspace-entity';
|
||||
import { CreateCompanyAndContactService } from 'src/modules/connected-account/auto-companies-and-contacts-creation/services/create-company-and-contact.service';
|
||||
import { ConnectedAccountRepository } from 'src/modules/connected-account/repositories/connected-account.repository';
|
||||
import { ConnectedAccountWorkspaceEntity } from 'src/modules/connected-account/standard-objects/connected-account.workspace-entity';
|
||||
|
||||
export type CalendarCreateCompanyAndContactAfterSyncJobData = {
|
||||
workspaceId: string;
|
||||
@ -27,6 +29,8 @@ export class CalendarCreateCompanyAndContactAfterSyncJob
|
||||
private readonly calendarChannelService: CalendarChannelRepository,
|
||||
@InjectObjectMetadataRepository(CalendarEventParticipantWorkspaceEntity)
|
||||
private readonly calendarEventParticipantRepository: CalendarEventParticipantRepository,
|
||||
@InjectObjectMetadataRepository(ConnectedAccountWorkspaceEntity)
|
||||
private readonly connectedAccountRepository: ConnectedAccountRepository,
|
||||
) {}
|
||||
|
||||
async handle(
|
||||
@ -48,12 +52,24 @@ export class CalendarCreateCompanyAndContactAfterSyncJob
|
||||
);
|
||||
}
|
||||
|
||||
const { handle, isContactAutoCreationEnabled } = calendarChannels[0];
|
||||
const { handle, isContactAutoCreationEnabled, connectedAccountId } =
|
||||
calendarChannels[0];
|
||||
|
||||
if (!isContactAutoCreationEnabled || !handle) {
|
||||
return;
|
||||
}
|
||||
|
||||
const connectedAccount = await this.connectedAccountRepository.getById(
|
||||
connectedAccountId,
|
||||
workspaceId,
|
||||
);
|
||||
|
||||
if (!connectedAccount) {
|
||||
throw new Error(
|
||||
`Connected account with id ${connectedAccountId} not found in workspace ${workspaceId}`,
|
||||
);
|
||||
}
|
||||
|
||||
const calendarEventParticipantsWithoutPersonIdAndWorkspaceMemberId =
|
||||
await this.calendarEventParticipantRepository.getByCalendarChannelIdWithoutPersonIdAndWorkspaceMemberId(
|
||||
calendarChannelId,
|
||||
@ -61,7 +77,7 @@ export class CalendarCreateCompanyAndContactAfterSyncJob
|
||||
);
|
||||
|
||||
await this.createCompanyAndContactService.createCompaniesAndContactsAndUpdateParticipants(
|
||||
handle,
|
||||
connectedAccount,
|
||||
calendarEventParticipantsWithoutPersonIdAndWorkspaceMemberId,
|
||||
workspaceId,
|
||||
);
|
||||
|
||||
@ -0,0 +1,71 @@
|
||||
import { Injectable } from '@nestjs/common';
|
||||
import { OnEvent } from '@nestjs/event-emitter';
|
||||
import { InjectRepository } from '@nestjs/typeorm';
|
||||
|
||||
import { Repository } from 'typeorm';
|
||||
|
||||
import { ObjectMetadataEntity } from 'src/engine/metadata-modules/object-metadata/object-metadata.entity';
|
||||
import { InjectObjectMetadataRepository } from 'src/engine/object-metadata-repository/object-metadata-repository.decorator';
|
||||
import { WorkspaceDataSourceService } from 'src/engine/workspace-datasource/workspace-datasource.service';
|
||||
import { CalendarEventParticipantWorkspaceEntity } from 'src/modules/calendar/standard-objects/calendar-event-participant.workspace-entity';
|
||||
import { TimelineActivityRepository } from 'src/modules/timeline/repositiories/timeline-activity.repository';
|
||||
import { TimelineActivityWorkspaceEntity } from 'src/modules/timeline/standard-objects/timeline-activity.workspace-entity';
|
||||
import { ObjectRecord } from 'src/engine/workspace-manager/workspace-sync-metadata/types/object-record';
|
||||
|
||||
@Injectable()
|
||||
export class CalendarEventParticipantListener {
|
||||
constructor(
|
||||
@InjectObjectMetadataRepository(TimelineActivityWorkspaceEntity)
|
||||
private readonly timelineActivityRepository: TimelineActivityRepository,
|
||||
private readonly workspaceDataSourceService: WorkspaceDataSourceService,
|
||||
@InjectRepository(ObjectMetadataEntity, 'metadata')
|
||||
private readonly objectMetadataRepository: Repository<ObjectMetadataEntity>,
|
||||
) {}
|
||||
|
||||
@OnEvent('calendarEventParticipant.matched')
|
||||
public async handleCalendarEventParticipantMatchedEvent(payload: {
|
||||
workspaceId: string;
|
||||
userId: string;
|
||||
calendarEventParticipants: ObjectRecord<CalendarEventParticipantWorkspaceEntity>[];
|
||||
}): Promise<void> {
|
||||
const calendarEventParticipants = payload.calendarEventParticipants ?? [];
|
||||
|
||||
// TODO: move to a job?
|
||||
|
||||
const dataSourceSchema = this.workspaceDataSourceService.getSchemaName(
|
||||
payload.workspaceId,
|
||||
);
|
||||
|
||||
const calendarEventObjectMetadata =
|
||||
await this.objectMetadataRepository.findOneOrFail({
|
||||
where: {
|
||||
nameSingular: 'calendarEvent',
|
||||
workspaceId: payload.workspaceId,
|
||||
},
|
||||
});
|
||||
|
||||
const calendarEventParticipantsWithPersonId =
|
||||
calendarEventParticipants.filter((participant) => participant.personId);
|
||||
|
||||
if (calendarEventParticipantsWithPersonId.length === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
await this.timelineActivityRepository.insertTimelineActivitiesForObject(
|
||||
'person',
|
||||
calendarEventParticipantsWithPersonId.map((participant) => ({
|
||||
dataSourceSchema,
|
||||
name: 'calendarEvent.linked',
|
||||
properties: null,
|
||||
objectName: 'calendarEvent',
|
||||
recordId: participant.personId,
|
||||
workspaceMemberId: payload.userId,
|
||||
workspaceId: payload.workspaceId,
|
||||
linkedObjectMetadataId: calendarEventObjectMetadata.id,
|
||||
linkedRecordId: participant.calendarEventId,
|
||||
linkedRecordCachedName: '',
|
||||
})),
|
||||
payload.workspaceId,
|
||||
);
|
||||
}
|
||||
}
|
||||
@ -51,6 +51,23 @@ export class CalendarEventParticipantRepository {
|
||||
);
|
||||
}
|
||||
|
||||
public async updateParticipantsPersonIdAndReturn(
|
||||
participantIds: string[],
|
||||
personId: string,
|
||||
workspaceId: string,
|
||||
transactionManager?: EntityManager,
|
||||
): Promise<ObjectRecord<CalendarEventParticipantWorkspaceEntity>[]> {
|
||||
const dataSourceSchema =
|
||||
this.workspaceDataSourceService.getSchemaName(workspaceId);
|
||||
|
||||
return await this.workspaceDataSourceService.executeRawQuery(
|
||||
`UPDATE ${dataSourceSchema}."calendarEventParticipant" SET "personId" = $1 WHERE "id" = ANY($2) RETURNING *`,
|
||||
[personId, participantIds],
|
||||
workspaceId,
|
||||
transactionManager,
|
||||
);
|
||||
}
|
||||
|
||||
public async updateParticipantsWorkspaceMemberId(
|
||||
participantIds: string[],
|
||||
workspaceMemberId: string,
|
||||
|
||||
@ -1,4 +1,5 @@
|
||||
import { Injectable } from '@nestjs/common';
|
||||
import { EventEmitter2 } from '@nestjs/event-emitter';
|
||||
|
||||
import { EntityManager } from 'typeorm';
|
||||
|
||||
@ -22,20 +23,21 @@ export class CalendarEventParticipantService {
|
||||
@InjectObjectMetadataRepository(PersonWorkspaceEntity)
|
||||
private readonly personRepository: PersonRepository,
|
||||
private readonly addPersonIdAndWorkspaceMemberIdService: AddPersonIdAndWorkspaceMemberIdService,
|
||||
private readonly eventEmitter: EventEmitter2,
|
||||
) {}
|
||||
|
||||
public async updateCalendarEventParticipantsAfterPeopleCreation(
|
||||
createdPeople: ObjectRecord<PersonWorkspaceEntity>[],
|
||||
workspaceId: string,
|
||||
transactionManager?: EntityManager,
|
||||
): Promise<void> {
|
||||
): Promise<ObjectRecord<CalendarEventParticipantWorkspaceEntity>[]> {
|
||||
const participants =
|
||||
await this.calendarEventParticipantRepository.getByHandles(
|
||||
createdPeople.map((person) => person.email),
|
||||
workspaceId,
|
||||
);
|
||||
|
||||
if (!participants) return;
|
||||
if (!participants) return [];
|
||||
|
||||
const dataSourceSchema =
|
||||
this.workspaceDataSourceService.getSchemaName(workspaceId);
|
||||
@ -57,7 +59,7 @@ export class CalendarEventParticipantService {
|
||||
}),
|
||||
);
|
||||
|
||||
if (calendarEventParticipantsToUpdate.length === 0) return;
|
||||
if (calendarEventParticipantsToUpdate.length === 0) return [];
|
||||
|
||||
const { flattenedValues, valuesString } =
|
||||
getFlattenedValuesAndValuesStringForBatchRawQuery(
|
||||
@ -68,23 +70,26 @@ export class CalendarEventParticipantService {
|
||||
},
|
||||
);
|
||||
|
||||
await this.workspaceDataSourceService.executeRawQuery(
|
||||
`UPDATE ${dataSourceSchema}."calendarEventParticipant" AS "calendarEventParticipant" SET "personId" = "data"."personId"
|
||||
return (
|
||||
await this.workspaceDataSourceService.executeRawQuery(
|
||||
`UPDATE ${dataSourceSchema}."calendarEventParticipant" AS "calendarEventParticipant" SET "personId" = "data"."personId"
|
||||
FROM (VALUES ${valuesString}) AS "data"("id", "personId")
|
||||
WHERE "calendarEventParticipant"."id" = "data"."id"`,
|
||||
flattenedValues,
|
||||
workspaceId,
|
||||
transactionManager,
|
||||
);
|
||||
WHERE "calendarEventParticipant"."id" = "data"."id"
|
||||
RETURNING *`,
|
||||
flattenedValues,
|
||||
workspaceId,
|
||||
transactionManager,
|
||||
)
|
||||
).flat();
|
||||
}
|
||||
|
||||
public async saveCalendarEventParticipants(
|
||||
calendarEventParticipants: CalendarEventParticipant[],
|
||||
workspaceId: string,
|
||||
transactionManager?: EntityManager,
|
||||
): Promise<void> {
|
||||
): Promise<ObjectRecord<CalendarEventParticipantWorkspaceEntity>[]> {
|
||||
if (calendarEventParticipants.length === 0) {
|
||||
return;
|
||||
return [];
|
||||
}
|
||||
|
||||
const dataSourceSchema =
|
||||
@ -111,8 +116,9 @@ export class CalendarEventParticipantService {
|
||||
},
|
||||
);
|
||||
|
||||
await this.workspaceDataSourceService.executeRawQuery(
|
||||
`INSERT INTO ${dataSourceSchema}."calendarEventParticipant" ("calendarEventId", "handle", "displayName", "isOrganizer", "responseStatus", "personId", "workspaceMemberId") VALUES ${valuesString}`,
|
||||
return await this.workspaceDataSourceService.executeRawQuery(
|
||||
`INSERT INTO ${dataSourceSchema}."calendarEventParticipant" ("calendarEventId", "handle", "displayName", "isOrganizer", "responseStatus", "personId", "workspaceMemberId") VALUES ${valuesString}
|
||||
RETURNING *`,
|
||||
flattenedValues,
|
||||
workspaceId,
|
||||
transactionManager,
|
||||
@ -135,11 +141,18 @@ export class CalendarEventParticipantService {
|
||||
calendarEventParticipantsToUpdate.map((participant) => participant.id);
|
||||
|
||||
if (personId) {
|
||||
await this.calendarEventParticipantRepository.updateParticipantsPersonId(
|
||||
calendarEventParticipantIdsToUpdate,
|
||||
personId,
|
||||
const updatedCalendarEventParticipants =
|
||||
await this.calendarEventParticipantRepository.updateParticipantsPersonIdAndReturn(
|
||||
calendarEventParticipantIdsToUpdate,
|
||||
personId,
|
||||
workspaceId,
|
||||
);
|
||||
|
||||
this.eventEmitter.emit(`calendarEventParticipant.matched`, {
|
||||
workspaceId,
|
||||
);
|
||||
userId: null,
|
||||
calendarEventParticipants: updatedCalendarEventParticipants,
|
||||
});
|
||||
}
|
||||
if (workspaceMemberId) {
|
||||
await this.calendarEventParticipantRepository.updateParticipantsWorkspaceMemberId(
|
||||
|
||||
@ -1,5 +1,6 @@
|
||||
import { Injectable, Logger } from '@nestjs/common';
|
||||
import { InjectRepository } from '@nestjs/typeorm';
|
||||
import { EventEmitter2 } from '@nestjs/event-emitter';
|
||||
|
||||
import { Repository } from 'typeorm';
|
||||
import { calendar_v3 as calendarV3 } from 'googleapis';
|
||||
@ -33,9 +34,10 @@ import { InjectMessageQueue } from 'src/engine/integrations/message-queue/decora
|
||||
import { MessageQueue } from 'src/engine/integrations/message-queue/message-queue.constants';
|
||||
import { MessageQueueService } from 'src/engine/integrations/message-queue/services/message-queue.service';
|
||||
import {
|
||||
CreateCompanyAndContactJobData,
|
||||
CreateCompanyAndContactJob,
|
||||
CreateCompanyAndContactJobData,
|
||||
} from 'src/modules/connected-account/auto-companies-and-contacts-creation/jobs/create-company-and-contact.job';
|
||||
import { ObjectRecord } from 'src/engine/workspace-manager/workspace-sync-metadata/types/object-record';
|
||||
|
||||
@Injectable()
|
||||
export class GoogleCalendarSyncService {
|
||||
@ -64,6 +66,7 @@ export class GoogleCalendarSyncService {
|
||||
private readonly calendarEventParticipantsService: CalendarEventParticipantService,
|
||||
@InjectMessageQueue(MessageQueue.emailQueue)
|
||||
private readonly messageQueueService: MessageQueueService,
|
||||
private readonly eventEmitter: EventEmitter2,
|
||||
) {}
|
||||
|
||||
public async startGoogleCalendarSync(
|
||||
@ -389,7 +392,7 @@ export class GoogleCalendarSyncService {
|
||||
eventExternalId: string;
|
||||
calendarChannelId: string;
|
||||
}[],
|
||||
connectedAccount: ConnectedAccountWorkspaceEntity,
|
||||
connectedAccount: ObjectRecord<ConnectedAccountWorkspaceEntity>,
|
||||
calendarChannel: CalendarChannelWorkspaceEntity,
|
||||
workspaceId: string,
|
||||
): Promise<void> {
|
||||
@ -409,8 +412,11 @@ export class GoogleCalendarSyncService {
|
||||
let startTime: number;
|
||||
let endTime: number;
|
||||
|
||||
const savedCalendarEventParticipantsToEmit: ObjectRecord<CalendarEventParticipantWorkspaceEntity>[] =
|
||||
[];
|
||||
|
||||
try {
|
||||
dataSourceMetadata?.transaction(async (transactionManager) => {
|
||||
await dataSourceMetadata?.transaction(async (transactionManager) => {
|
||||
startTime = Date.now();
|
||||
|
||||
await this.calendarEventRepository.saveCalendarEvents(
|
||||
@ -484,10 +490,15 @@ export class GoogleCalendarSyncService {
|
||||
|
||||
startTime = Date.now();
|
||||
|
||||
await this.calendarEventParticipantsService.saveCalendarEventParticipants(
|
||||
participantsToSave,
|
||||
workspaceId,
|
||||
transactionManager,
|
||||
const savedCalendarEventParticipants =
|
||||
await this.calendarEventParticipantsService.saveCalendarEventParticipants(
|
||||
participantsToSave,
|
||||
workspaceId,
|
||||
transactionManager,
|
||||
);
|
||||
|
||||
savedCalendarEventParticipantsToEmit.push(
|
||||
...savedCalendarEventParticipants,
|
||||
);
|
||||
|
||||
endTime = Date.now();
|
||||
@ -499,12 +510,18 @@ export class GoogleCalendarSyncService {
|
||||
);
|
||||
});
|
||||
|
||||
this.eventEmitter.emit(`calendarEventParticipant.matched`, {
|
||||
workspaceId,
|
||||
userId: connectedAccount.accountOwnerId,
|
||||
calendarEventParticipants: savedCalendarEventParticipantsToEmit,
|
||||
});
|
||||
|
||||
if (calendarChannel.isContactAutoCreationEnabled) {
|
||||
await this.messageQueueService.add<CreateCompanyAndContactJobData>(
|
||||
CreateCompanyAndContactJob.name,
|
||||
{
|
||||
workspaceId,
|
||||
connectedAccountHandle: connectedAccount.handle,
|
||||
connectedAccount,
|
||||
contactsToCreate: participantsToSave,
|
||||
},
|
||||
);
|
||||
|
||||
@ -2,11 +2,13 @@ import { Injectable } from '@nestjs/common';
|
||||
|
||||
import { MessageQueueJob } from 'src/engine/integrations/message-queue/interfaces/message-queue-job.interface';
|
||||
|
||||
import { ObjectRecord } from 'src/engine/workspace-manager/workspace-sync-metadata/types/object-record';
|
||||
import { CreateCompanyAndContactService } from 'src/modules/connected-account/auto-companies-and-contacts-creation/services/create-company-and-contact.service';
|
||||
import { ConnectedAccountWorkspaceEntity } from 'src/modules/connected-account/standard-objects/connected-account.workspace-entity';
|
||||
|
||||
export type CreateCompanyAndContactJobData = {
|
||||
workspaceId: string;
|
||||
connectedAccountHandle: string;
|
||||
connectedAccount: ObjectRecord<ConnectedAccountWorkspaceEntity>;
|
||||
contactsToCreate: {
|
||||
displayName: string;
|
||||
handle: string;
|
||||
@ -22,10 +24,10 @@ export class CreateCompanyAndContactJob
|
||||
) {}
|
||||
|
||||
async handle(data: CreateCompanyAndContactJobData): Promise<void> {
|
||||
const { workspaceId, connectedAccountHandle, contactsToCreate } = data;
|
||||
const { workspaceId, connectedAccount, contactsToCreate } = data;
|
||||
|
||||
await this.createCompanyAndContactService.createCompaniesAndContactsAndUpdateParticipants(
|
||||
connectedAccountHandle,
|
||||
connectedAccount,
|
||||
contactsToCreate.map((contact) => ({
|
||||
handle: contact.handle,
|
||||
displayName: contact.displayName,
|
||||
|
||||
@ -1,4 +1,5 @@
|
||||
import { Injectable } from '@nestjs/common';
|
||||
import { EventEmitter2 } from '@nestjs/event-emitter';
|
||||
|
||||
import { EntityManager } from 'typeorm';
|
||||
import compact from 'lodash.compact';
|
||||
@ -19,6 +20,9 @@ import { CalendarEventParticipantService } from 'src/modules/calendar/services/c
|
||||
import { filterOutContactsFromCompanyOrWorkspace } from 'src/modules/connected-account/auto-companies-and-contacts-creation/utils/filter-out-contacts-from-company-or-workspace.util';
|
||||
import { ObjectRecord } from 'src/engine/workspace-manager/workspace-sync-metadata/types/object-record';
|
||||
import { MessagingMessageParticipantService } from 'src/modules/messaging/common/services/messaging-message-participant.service';
|
||||
import { ConnectedAccountWorkspaceEntity } from 'src/modules/connected-account/standard-objects/connected-account.workspace-entity';
|
||||
import { MessageParticipantWorkspaceEntity } from 'src/modules/messaging/common/standard-objects/message-participant.workspace-entity';
|
||||
import { CalendarEventParticipantWorkspaceEntity } from 'src/modules/calendar/standard-objects/calendar-event-participant.workspace-entity';
|
||||
|
||||
@Injectable()
|
||||
export class CreateCompanyAndContactService {
|
||||
@ -32,6 +36,7 @@ export class CreateCompanyAndContactService {
|
||||
private readonly workspaceDataSourceService: WorkspaceDataSourceService,
|
||||
private readonly messageParticipantService: MessagingMessageParticipantService,
|
||||
private readonly calendarEventParticipantService: CalendarEventParticipantService,
|
||||
private readonly eventEmitter: EventEmitter2,
|
||||
) {}
|
||||
|
||||
async createCompaniesAndPeople(
|
||||
@ -125,7 +130,7 @@ export class CreateCompanyAndContactService {
|
||||
}
|
||||
|
||||
async createCompaniesAndContactsAndUpdateParticipants(
|
||||
connectedAccountHandle: string,
|
||||
connectedAccount: ObjectRecord<ConnectedAccountWorkspaceEntity>,
|
||||
contactsToCreate: Contacts,
|
||||
workspaceId: string,
|
||||
) {
|
||||
@ -134,27 +139,46 @@ export class CreateCompanyAndContactService {
|
||||
workspaceId,
|
||||
);
|
||||
|
||||
let updatedMessageParticipants: ObjectRecord<MessageParticipantWorkspaceEntity>[] =
|
||||
[];
|
||||
let updatedCalendarEventParticipants: ObjectRecord<CalendarEventParticipantWorkspaceEntity>[] =
|
||||
[];
|
||||
|
||||
await workspaceDataSource?.transaction(
|
||||
async (transactionManager: EntityManager) => {
|
||||
const createdPeople = await this.createCompaniesAndPeople(
|
||||
connectedAccountHandle,
|
||||
connectedAccount.handle,
|
||||
contactsToCreate,
|
||||
workspaceId,
|
||||
transactionManager,
|
||||
);
|
||||
|
||||
await this.messageParticipantService.updateMessageParticipantsAfterPeopleCreation(
|
||||
createdPeople,
|
||||
workspaceId,
|
||||
transactionManager,
|
||||
);
|
||||
updatedMessageParticipants =
|
||||
await this.messageParticipantService.updateMessageParticipantsAfterPeopleCreation(
|
||||
createdPeople,
|
||||
workspaceId,
|
||||
transactionManager,
|
||||
);
|
||||
|
||||
await this.calendarEventParticipantService.updateCalendarEventParticipantsAfterPeopleCreation(
|
||||
createdPeople,
|
||||
workspaceId,
|
||||
transactionManager,
|
||||
);
|
||||
updatedCalendarEventParticipants =
|
||||
await this.calendarEventParticipantService.updateCalendarEventParticipantsAfterPeopleCreation(
|
||||
createdPeople,
|
||||
workspaceId,
|
||||
transactionManager,
|
||||
);
|
||||
},
|
||||
);
|
||||
|
||||
this.eventEmitter.emit(`messageParticipant.matched`, {
|
||||
workspaceId,
|
||||
userId: connectedAccount.accountOwnerId,
|
||||
messageParticipants: updatedMessageParticipants,
|
||||
});
|
||||
|
||||
this.eventEmitter.emit(`calendarEventParticipant.matched`, {
|
||||
workspaceId,
|
||||
userId: connectedAccount.accountOwnerId,
|
||||
calendarEventParticipants: updatedCalendarEventParticipants,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@ -0,0 +1,64 @@
|
||||
import { ForbiddenException } from '@nestjs/common';
|
||||
|
||||
import groupBy from 'lodash.groupby';
|
||||
|
||||
import { InjectObjectMetadataRepository } from 'src/engine/object-metadata-repository/object-metadata-repository.decorator';
|
||||
import { ConnectedAccountRepository } from 'src/modules/connected-account/repositories/connected-account.repository';
|
||||
import { ConnectedAccountWorkspaceEntity } from 'src/modules/connected-account/standard-objects/connected-account.workspace-entity';
|
||||
import { MessageChannelRepository } from 'src/modules/messaging/common/repositories/message-channel.repository';
|
||||
import { MessageChannelWorkspaceEntity } from 'src/modules/messaging/common/standard-objects/message-channel.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 class CanAccessMessageThreadService {
|
||||
constructor(
|
||||
@InjectObjectMetadataRepository(MessageChannelWorkspaceEntity)
|
||||
private readonly messageChannelService: MessageChannelRepository,
|
||||
@InjectObjectMetadataRepository(ConnectedAccountWorkspaceEntity)
|
||||
private readonly connectedAccountRepository: ConnectedAccountRepository,
|
||||
@InjectObjectMetadataRepository(WorkspaceMemberWorkspaceEntity)
|
||||
private readonly workspaceMemberRepository: WorkspaceMemberRepository,
|
||||
) {}
|
||||
|
||||
public async canAccessMessageThread(
|
||||
userId: string,
|
||||
workspaceId: string,
|
||||
messageChannelMessageAssociations: any[],
|
||||
) {
|
||||
const messageChannels = await this.messageChannelService.getByIds(
|
||||
messageChannelMessageAssociations.map(
|
||||
(association) => association.messageChannelId,
|
||||
),
|
||||
workspaceId,
|
||||
);
|
||||
|
||||
const messageChannelsGroupByVisibility = groupBy(
|
||||
messageChannels,
|
||||
(channel) => channel.visibility,
|
||||
);
|
||||
|
||||
if (messageChannelsGroupByVisibility.share_everything) {
|
||||
return;
|
||||
}
|
||||
|
||||
const currentWorkspaceMember =
|
||||
await this.workspaceMemberRepository.getByIdOrFail(userId, workspaceId);
|
||||
|
||||
const messageChannelsConnectedAccounts =
|
||||
await this.connectedAccountRepository.getByIds(
|
||||
messageChannels.map((channel) => channel.connectedAccountId),
|
||||
workspaceId,
|
||||
);
|
||||
|
||||
const messageChannelsWorkspaceMemberIds =
|
||||
messageChannelsConnectedAccounts.map(
|
||||
(connectedAccount) => connectedAccount.accountOwnerId,
|
||||
);
|
||||
|
||||
if (messageChannelsWorkspaceMemberIds.includes(currentWorkspaceMember.id)) {
|
||||
return;
|
||||
}
|
||||
|
||||
throw new ForbiddenException();
|
||||
}
|
||||
}
|
||||
@ -1,24 +1,16 @@
|
||||
import {
|
||||
BadRequestException,
|
||||
ForbiddenException,
|
||||
Injectable,
|
||||
NotFoundException,
|
||||
} from '@nestjs/common';
|
||||
|
||||
import groupBy from 'lodash.groupby';
|
||||
|
||||
import { WorkspacePreQueryHook } from 'src/engine/api/graphql/workspace-query-runner/workspace-pre-query-hook/interfaces/workspace-pre-query-hook.interface';
|
||||
import { FindManyResolverArgs } from 'src/engine/api/graphql/workspace-resolver-builder/interfaces/workspace-resolvers-builder.interface';
|
||||
|
||||
import { ConnectedAccountRepository } from 'src/modules/connected-account/repositories/connected-account.repository';
|
||||
import { WorkspaceMemberRepository } from 'src/modules/workspace-member/repositories/workspace-member.repository';
|
||||
import { InjectObjectMetadataRepository } from 'src/engine/object-metadata-repository/object-metadata-repository.decorator';
|
||||
import { ConnectedAccountWorkspaceEntity } from 'src/modules/connected-account/standard-objects/connected-account.workspace-entity';
|
||||
import { WorkspaceMemberWorkspaceEntity } from 'src/modules/workspace-member/standard-objects/workspace-member.workspace-entity';
|
||||
import { CanAccessMessageThreadService } from 'src/modules/messaging/common/query-hooks/message/can-access-message-thread.service';
|
||||
import { MessageChannelMessageAssociationRepository } from 'src/modules/messaging/common/repositories/message-channel-message-association.repository';
|
||||
import { MessageChannelRepository } from 'src/modules/messaging/common/repositories/message-channel.repository';
|
||||
import { MessageChannelMessageAssociationWorkspaceEntity } from 'src/modules/messaging/common/standard-objects/message-channel-message-association.workspace-entity';
|
||||
import { MessageChannelWorkspaceEntity } from 'src/modules/messaging/common/standard-objects/message-channel.workspace-entity';
|
||||
|
||||
@Injectable()
|
||||
export class MessageFindManyPreQueryHook implements WorkspacePreQueryHook {
|
||||
@ -27,12 +19,7 @@ export class MessageFindManyPreQueryHook implements WorkspacePreQueryHook {
|
||||
MessageChannelMessageAssociationWorkspaceEntity,
|
||||
)
|
||||
private readonly messageChannelMessageAssociationService: MessageChannelMessageAssociationRepository,
|
||||
@InjectObjectMetadataRepository(MessageChannelWorkspaceEntity)
|
||||
private readonly messageChannelService: MessageChannelRepository,
|
||||
@InjectObjectMetadataRepository(ConnectedAccountWorkspaceEntity)
|
||||
private readonly connectedAccountRepository: ConnectedAccountRepository,
|
||||
@InjectObjectMetadataRepository(WorkspaceMemberWorkspaceEntity)
|
||||
private readonly workspaceMemberRepository: WorkspaceMemberRepository,
|
||||
private readonly canAccessMessageThreadService: CanAccessMessageThreadService,
|
||||
) {}
|
||||
|
||||
async execute(
|
||||
@ -54,52 +41,10 @@ export class MessageFindManyPreQueryHook implements WorkspacePreQueryHook {
|
||||
throw new NotFoundException();
|
||||
}
|
||||
|
||||
await this.canAccessMessageThread(
|
||||
await this.canAccessMessageThreadService.canAccessMessageThread(
|
||||
userId,
|
||||
workspaceId,
|
||||
messageChannelMessageAssociations,
|
||||
);
|
||||
}
|
||||
|
||||
private async canAccessMessageThread(
|
||||
userId: string,
|
||||
workspaceId: string,
|
||||
messageChannelMessageAssociations: any[],
|
||||
) {
|
||||
const messageChannels = await this.messageChannelService.getByIds(
|
||||
messageChannelMessageAssociations.map(
|
||||
(association) => association.messageChannelId,
|
||||
),
|
||||
workspaceId,
|
||||
);
|
||||
|
||||
const messageChannelsGroupByVisibility = groupBy(
|
||||
messageChannels,
|
||||
(channel) => channel.visibility,
|
||||
);
|
||||
|
||||
if (messageChannelsGroupByVisibility.SHARE_EVERYTHING) {
|
||||
return;
|
||||
}
|
||||
|
||||
const currentWorkspaceMember =
|
||||
await this.workspaceMemberRepository.getByIdOrFail(userId, workspaceId);
|
||||
|
||||
const messageChannelsConnectedAccounts =
|
||||
await this.connectedAccountRepository.getByIds(
|
||||
messageChannels.map((channel) => channel.connectedAccountId),
|
||||
workspaceId,
|
||||
);
|
||||
|
||||
const messageChannelsWorkspaceMemberIds =
|
||||
messageChannelsConnectedAccounts.map(
|
||||
(connectedAccount) => connectedAccount.accountOwnerId,
|
||||
);
|
||||
|
||||
if (messageChannelsWorkspaceMemberIds.includes(currentWorkspaceMember.id)) {
|
||||
return;
|
||||
}
|
||||
|
||||
throw new ForbiddenException();
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,16 +1,43 @@
|
||||
/* eslint-disable @typescript-eslint/no-unused-vars */
|
||||
import { Injectable, MethodNotAllowedException } from '@nestjs/common';
|
||||
import { Injectable, NotFoundException } from '@nestjs/common';
|
||||
|
||||
import { WorkspacePreQueryHook } from 'src/engine/api/graphql/workspace-query-runner/workspace-pre-query-hook/interfaces/workspace-pre-query-hook.interface';
|
||||
import { FindOneResolverArgs } 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 { CanAccessMessageThreadService } from 'src/modules/messaging/common/query-hooks/message/can-access-message-thread.service';
|
||||
import { MessageChannelMessageAssociationRepository } from 'src/modules/messaging/common/repositories/message-channel-message-association.repository';
|
||||
import { MessageChannelMessageAssociationWorkspaceEntity } from 'src/modules/messaging/common/standard-objects/message-channel-message-association.workspace-entity';
|
||||
|
||||
@Injectable()
|
||||
export class MessageFindOnePreQueryHook implements WorkspacePreQueryHook {
|
||||
constructor(
|
||||
@InjectObjectMetadataRepository(
|
||||
MessageChannelMessageAssociationWorkspaceEntity,
|
||||
)
|
||||
private readonly messageChannelMessageAssociationService: MessageChannelMessageAssociationRepository,
|
||||
private readonly canAccessMessageThreadService: CanAccessMessageThreadService,
|
||||
) {}
|
||||
|
||||
async execute(
|
||||
_userId: string,
|
||||
_workspaceId: string,
|
||||
_payload: FindOneResolverArgs,
|
||||
userId: string,
|
||||
workspaceId: string,
|
||||
payload: FindOneResolverArgs,
|
||||
): Promise<void> {
|
||||
throw new MethodNotAllowedException('Method not allowed.');
|
||||
const messageChannelMessageAssociations =
|
||||
await this.messageChannelMessageAssociationService.getByMessageIds(
|
||||
[payload?.filter?.id?.eq],
|
||||
workspaceId,
|
||||
);
|
||||
|
||||
if (messageChannelMessageAssociations.length === 0) {
|
||||
throw new NotFoundException();
|
||||
}
|
||||
|
||||
await this.canAccessMessageThreadService.canAccessMessageThread(
|
||||
userId,
|
||||
workspaceId,
|
||||
messageChannelMessageAssociations,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@ -3,6 +3,7 @@ import { Module } from '@nestjs/common';
|
||||
import { ObjectMetadataRepositoryModule } from 'src/engine/object-metadata-repository/object-metadata-repository.module';
|
||||
import { WorkspaceMemberWorkspaceEntity } from 'src/modules/workspace-member/standard-objects/workspace-member.workspace-entity';
|
||||
import { ConnectedAccountWorkspaceEntity } from 'src/modules/connected-account/standard-objects/connected-account.workspace-entity';
|
||||
import { CanAccessMessageThreadService } from 'src/modules/messaging/common/query-hooks/message/can-access-message-thread.service';
|
||||
import { MessageFindManyPreQueryHook } from 'src/modules/messaging/common/query-hooks/message/message-find-many.pre-query.hook';
|
||||
import { MessageFindOnePreQueryHook } from 'src/modules/messaging/common/query-hooks/message/message-find-one.pre-query-hook';
|
||||
import { MessageChannelMessageAssociationWorkspaceEntity } from 'src/modules/messaging/common/standard-objects/message-channel-message-association.workspace-entity';
|
||||
@ -18,6 +19,7 @@ import { MessageChannelWorkspaceEntity } from 'src/modules/messaging/common/stan
|
||||
]),
|
||||
],
|
||||
providers: [
|
||||
CanAccessMessageThreadService,
|
||||
{
|
||||
provide: MessageFindOnePreQueryHook.name,
|
||||
useClass: MessageFindOnePreQueryHook,
|
||||
|
||||
@ -46,6 +46,23 @@ export class MessageParticipantRepository {
|
||||
);
|
||||
}
|
||||
|
||||
public async updateParticipantsPersonIdAndReturn(
|
||||
participantIds: string[],
|
||||
personId: string,
|
||||
workspaceId: string,
|
||||
transactionManager?: EntityManager,
|
||||
) {
|
||||
const dataSourceSchema =
|
||||
this.workspaceDataSourceService.getSchemaName(workspaceId);
|
||||
|
||||
return await this.workspaceDataSourceService.executeRawQuery(
|
||||
`UPDATE ${dataSourceSchema}."messageParticipant" SET "personId" = $1 WHERE "id" = ANY($2) RETURNING *`,
|
||||
[personId, participantIds],
|
||||
workspaceId,
|
||||
transactionManager,
|
||||
);
|
||||
}
|
||||
|
||||
public async updateParticipantsWorkspaceMemberId(
|
||||
participantIds: string[],
|
||||
workspaceMemberId: string,
|
||||
|
||||
@ -1,4 +1,5 @@
|
||||
import { Injectable } from '@nestjs/common';
|
||||
import { EventEmitter2 } from '@nestjs/event-emitter';
|
||||
|
||||
import { EntityManager } from 'typeorm';
|
||||
|
||||
@ -24,20 +25,21 @@ export class MessagingMessageParticipantService {
|
||||
@InjectObjectMetadataRepository(PersonWorkspaceEntity)
|
||||
private readonly personRepository: PersonRepository,
|
||||
private readonly addPersonIdAndWorkspaceMemberIdService: AddPersonIdAndWorkspaceMemberIdService,
|
||||
private readonly eventEmitter: EventEmitter2,
|
||||
) {}
|
||||
|
||||
public async updateMessageParticipantsAfterPeopleCreation(
|
||||
createdPeople: ObjectRecord<PersonWorkspaceEntity>[],
|
||||
workspaceId: string,
|
||||
transactionManager?: EntityManager,
|
||||
): Promise<void> {
|
||||
): Promise<ObjectRecord<MessageParticipantWorkspaceEntity>[]> {
|
||||
const participants = await this.messageParticipantRepository.getByHandles(
|
||||
createdPeople.map((person) => person.email),
|
||||
workspaceId,
|
||||
transactionManager,
|
||||
);
|
||||
|
||||
if (!participants) return;
|
||||
if (!participants) return [];
|
||||
|
||||
const dataSourceSchema =
|
||||
this.workspaceDataSourceService.getSchemaName(workspaceId);
|
||||
@ -57,7 +59,7 @@ export class MessagingMessageParticipantService {
|
||||
)?.id,
|
||||
}));
|
||||
|
||||
if (messageParticipantsToUpdate.length === 0) return;
|
||||
if (messageParticipantsToUpdate.length === 0) return [];
|
||||
|
||||
const { flattenedValues, valuesString } =
|
||||
getFlattenedValuesAndValuesStringForBatchRawQuery(
|
||||
@ -68,22 +70,25 @@ export class MessagingMessageParticipantService {
|
||||
},
|
||||
);
|
||||
|
||||
await this.workspaceDataSourceService.executeRawQuery(
|
||||
`UPDATE ${dataSourceSchema}."messageParticipant" AS "messageParticipant" SET "personId" = "data"."personId"
|
||||
return (
|
||||
await this.workspaceDataSourceService.executeRawQuery(
|
||||
`UPDATE ${dataSourceSchema}."messageParticipant" AS "messageParticipant" SET "personId" = "data"."personId"
|
||||
FROM (VALUES ${valuesString}) AS "data"("id", "personId")
|
||||
WHERE "messageParticipant"."id" = "data"."id"`,
|
||||
flattenedValues,
|
||||
workspaceId,
|
||||
transactionManager,
|
||||
);
|
||||
WHERE "messageParticipant"."id" = "data"."id"
|
||||
RETURNING *`,
|
||||
flattenedValues,
|
||||
workspaceId,
|
||||
transactionManager,
|
||||
)
|
||||
).flat();
|
||||
}
|
||||
|
||||
public async saveMessageParticipants(
|
||||
participants: ParticipantWithMessageId[],
|
||||
workspaceId: string,
|
||||
transactionManager?: EntityManager,
|
||||
): Promise<void> {
|
||||
if (!participants) return;
|
||||
): Promise<ObjectRecord<MessageParticipantWorkspaceEntity>[]> {
|
||||
if (!participants) return [];
|
||||
|
||||
const dataSourceSchema =
|
||||
this.workspaceDataSourceService.getSchemaName(workspaceId);
|
||||
@ -108,10 +113,10 @@ export class MessagingMessageParticipantService {
|
||||
},
|
||||
);
|
||||
|
||||
if (messageParticipantsToSave.length === 0) return;
|
||||
if (messageParticipantsToSave.length === 0) return [];
|
||||
|
||||
await this.workspaceDataSourceService.executeRawQuery(
|
||||
`INSERT INTO ${dataSourceSchema}."messageParticipant" ("messageId", "role", "handle", "displayName", "personId", "workspaceMemberId") VALUES ${valuesString}`,
|
||||
return await this.workspaceDataSourceService.executeRawQuery(
|
||||
`INSERT INTO ${dataSourceSchema}."messageParticipant" ("messageId", "role", "handle", "displayName", "personId", "workspaceMemberId") VALUES ${valuesString} RETURNING *`,
|
||||
flattenedValues,
|
||||
workspaceId,
|
||||
transactionManager,
|
||||
@ -135,11 +140,18 @@ export class MessagingMessageParticipantService {
|
||||
);
|
||||
|
||||
if (personId) {
|
||||
await this.messageParticipantRepository.updateParticipantsPersonId(
|
||||
messageParticipantIdsToUpdate,
|
||||
personId,
|
||||
const updatedMessageParticipants =
|
||||
await this.messageParticipantRepository.updateParticipantsPersonIdAndReturn(
|
||||
messageParticipantIdsToUpdate,
|
||||
personId,
|
||||
workspaceId,
|
||||
);
|
||||
|
||||
this.eventEmitter.emit(`messageParticipant.matched`, {
|
||||
workspaceId,
|
||||
);
|
||||
userId: null,
|
||||
messageParticipants: updatedMessageParticipants,
|
||||
});
|
||||
}
|
||||
if (workspaceMemberId) {
|
||||
await this.messageParticipantRepository.updateParticipantsWorkspaceMemberId(
|
||||
|
||||
@ -1,5 +1,6 @@
|
||||
import { Injectable, Inject } from '@nestjs/common';
|
||||
import { InjectRepository } from '@nestjs/typeorm';
|
||||
import { EventEmitter2 } from '@nestjs/event-emitter';
|
||||
|
||||
import { EntityManager, Repository } from 'typeorm';
|
||||
|
||||
@ -19,10 +20,12 @@ import {
|
||||
import { MessageChannelWorkspaceEntity } from 'src/modules/messaging/common/standard-objects/message-channel.workspace-entity';
|
||||
import {
|
||||
GmailMessage,
|
||||
Participant,
|
||||
ParticipantWithMessageId,
|
||||
} from 'src/modules/messaging/message-import-manager/drivers/gmail/types/gmail-message';
|
||||
import { MessagingMessageService } from 'src/modules/messaging/common/services/messaging-message.service';
|
||||
import { MessagingMessageParticipantService } from 'src/modules/messaging/common/services/messaging-message-participant.service';
|
||||
import { MessageParticipantWorkspaceEntity } from 'src/modules/messaging/common/standard-objects/message-participant.workspace-entity';
|
||||
|
||||
@Injectable()
|
||||
export class MessagingSaveMessagesAndEnqueueContactCreationService {
|
||||
@ -34,6 +37,7 @@ export class MessagingSaveMessagesAndEnqueueContactCreationService {
|
||||
private readonly messageParticipantService: MessagingMessageParticipantService,
|
||||
@InjectRepository(FeatureFlagEntity, 'core')
|
||||
private readonly featureFlagRepository: Repository<FeatureFlagEntity>,
|
||||
private readonly eventEmitter: EventEmitter2,
|
||||
) {}
|
||||
|
||||
async saveMessagesAndEnqueueContactCreationJob(
|
||||
@ -57,6 +61,9 @@ export class MessagingSaveMessagesAndEnqueueContactCreationService {
|
||||
const isContactCreationForSentAndReceivedEmailsEnabled =
|
||||
isContactCreationForSentAndReceivedEmailsEnabledFeatureFlag?.value;
|
||||
|
||||
let savedMessageParticipants: ObjectRecord<MessageParticipantWorkspaceEntity>[] =
|
||||
[];
|
||||
|
||||
const participantsWithMessageId = await workspaceDataSource?.transaction(
|
||||
async (transactionManager: EntityManager) => {
|
||||
const messageExternalIdsAndIdsMap =
|
||||
@ -74,7 +81,7 @@ export class MessagingSaveMessagesAndEnqueueContactCreationService {
|
||||
const messageId = messageExternalIdsAndIdsMap.get(message.externalId);
|
||||
|
||||
return messageId
|
||||
? message.participants.map((participant) => ({
|
||||
? message.participants.map((participant: Participant) => ({
|
||||
...participant,
|
||||
messageId,
|
||||
shouldCreateContact:
|
||||
@ -86,16 +93,23 @@ export class MessagingSaveMessagesAndEnqueueContactCreationService {
|
||||
: [];
|
||||
});
|
||||
|
||||
await this.messageParticipantService.saveMessageParticipants(
|
||||
participantsWithMessageId,
|
||||
workspaceId,
|
||||
transactionManager,
|
||||
);
|
||||
savedMessageParticipants =
|
||||
await this.messageParticipantService.saveMessageParticipants(
|
||||
participantsWithMessageId,
|
||||
workspaceId,
|
||||
transactionManager,
|
||||
);
|
||||
|
||||
return participantsWithMessageId;
|
||||
},
|
||||
);
|
||||
|
||||
this.eventEmitter.emit(`messageParticipant.matched`, {
|
||||
workspaceId,
|
||||
userId: connectedAccount.accountOwnerId,
|
||||
messageParticipants: savedMessageParticipants,
|
||||
});
|
||||
|
||||
if (messageChannel.isContactAutoCreationEnabled) {
|
||||
const contactsToCreate = participantsWithMessageId.filter(
|
||||
(participant) => participant.shouldCreateContact,
|
||||
@ -105,7 +119,7 @@ export class MessagingSaveMessagesAndEnqueueContactCreationService {
|
||||
CreateCompanyAndContactJob.name,
|
||||
{
|
||||
workspaceId,
|
||||
connectedAccountHandle: connectedAccount.handle,
|
||||
connectedAccount,
|
||||
contactsToCreate,
|
||||
},
|
||||
);
|
||||
|
||||
@ -15,6 +15,8 @@ import { MessageChannelRepository } from 'src/modules/messaging/common/repositor
|
||||
import { MessageParticipantRepository } from 'src/modules/messaging/common/repositories/message-participant.repository';
|
||||
import { MessageChannelWorkspaceEntity } from 'src/modules/messaging/common/standard-objects/message-channel.workspace-entity';
|
||||
import { MessageParticipantWorkspaceEntity } from 'src/modules/messaging/common/standard-objects/message-participant.workspace-entity';
|
||||
import { ConnectedAccountWorkspaceEntity } from 'src/modules/connected-account/standard-objects/connected-account.workspace-entity';
|
||||
import { ConnectedAccountRepository } from 'src/modules/connected-account/repositories/connected-account.repository';
|
||||
|
||||
export type MessagingCreateCompanyAndContactAfterSyncJobData = {
|
||||
workspaceId: string;
|
||||
@ -36,6 +38,8 @@ export class MessagingCreateCompanyAndContactAfterSyncJob
|
||||
private readonly messageParticipantRepository: MessageParticipantRepository,
|
||||
@InjectRepository(FeatureFlagEntity, 'core')
|
||||
private readonly featureFlagRepository: Repository<FeatureFlagEntity>,
|
||||
@InjectObjectMetadataRepository(ConnectedAccountWorkspaceEntity)
|
||||
private readonly connectedAccountRepository: ConnectedAccountRepository,
|
||||
) {}
|
||||
|
||||
async handle(
|
||||
@ -51,12 +55,24 @@ export class MessagingCreateCompanyAndContactAfterSyncJob
|
||||
workspaceId,
|
||||
);
|
||||
|
||||
const { handle, isContactAutoCreationEnabled } = messageChannel[0];
|
||||
const { isContactAutoCreationEnabled, connectedAccountId } =
|
||||
messageChannel[0];
|
||||
|
||||
if (!isContactAutoCreationEnabled) {
|
||||
return;
|
||||
}
|
||||
|
||||
const connectedAccount = await this.connectedAccountRepository.getById(
|
||||
connectedAccountId,
|
||||
workspaceId,
|
||||
);
|
||||
|
||||
if (!connectedAccount) {
|
||||
throw new Error(
|
||||
`Connected account with id ${connectedAccountId} not found in workspace ${workspaceId}`,
|
||||
);
|
||||
}
|
||||
|
||||
const isContactCreationForSentAndReceivedEmailsEnabledFeatureFlag =
|
||||
await this.featureFlagRepository.findOneBy({
|
||||
workspaceId: workspaceId,
|
||||
@ -78,7 +94,7 @@ export class MessagingCreateCompanyAndContactAfterSyncJob
|
||||
);
|
||||
|
||||
await this.createCompanyAndContactService.createCompaniesAndContactsAndUpdateParticipants(
|
||||
handle,
|
||||
connectedAccount,
|
||||
contactsToCreate,
|
||||
workspaceId,
|
||||
);
|
||||
|
||||
@ -0,0 +1,72 @@
|
||||
import { Injectable } from '@nestjs/common';
|
||||
import { OnEvent } from '@nestjs/event-emitter';
|
||||
import { InjectRepository } from '@nestjs/typeorm';
|
||||
|
||||
import { Repository } from 'typeorm';
|
||||
|
||||
import { ObjectMetadataEntity } from 'src/engine/metadata-modules/object-metadata/object-metadata.entity';
|
||||
import { InjectObjectMetadataRepository } from 'src/engine/object-metadata-repository/object-metadata-repository.decorator';
|
||||
import { WorkspaceDataSourceService } from 'src/engine/workspace-datasource/workspace-datasource.service';
|
||||
import { TimelineActivityRepository } from 'src/modules/timeline/repositiories/timeline-activity.repository';
|
||||
import { TimelineActivityWorkspaceEntity } from 'src/modules/timeline/standard-objects/timeline-activity.workspace-entity';
|
||||
import { ObjectRecord } from 'src/engine/workspace-manager/workspace-sync-metadata/types/object-record';
|
||||
import { MessageParticipantWorkspaceEntity } from 'src/modules/messaging/common/standard-objects/message-participant.workspace-entity';
|
||||
|
||||
@Injectable()
|
||||
export class MessageParticipantListener {
|
||||
constructor(
|
||||
@InjectObjectMetadataRepository(TimelineActivityWorkspaceEntity)
|
||||
private readonly timelineActivityRepository: TimelineActivityRepository,
|
||||
private readonly workspaceDataSourceService: WorkspaceDataSourceService,
|
||||
@InjectRepository(ObjectMetadataEntity, 'metadata')
|
||||
private readonly objectMetadataRepository: Repository<ObjectMetadataEntity>,
|
||||
) {}
|
||||
|
||||
@OnEvent('messageParticipant.matched')
|
||||
public async handleMessageParticipantMatched(payload: {
|
||||
workspaceId: string;
|
||||
userId: string;
|
||||
messageParticipants: ObjectRecord<MessageParticipantWorkspaceEntity>[];
|
||||
}): Promise<void> {
|
||||
const messageParticipants = payload.messageParticipants ?? [];
|
||||
|
||||
// TODO: move to a job?
|
||||
|
||||
const dataSourceSchema = this.workspaceDataSourceService.getSchemaName(
|
||||
payload.workspaceId,
|
||||
);
|
||||
|
||||
const messageObjectMetadata =
|
||||
await this.objectMetadataRepository.findOneOrFail({
|
||||
where: {
|
||||
nameSingular: 'message',
|
||||
workspaceId: payload.workspaceId,
|
||||
},
|
||||
});
|
||||
|
||||
const messageParticipantsWithPersonId = messageParticipants.filter(
|
||||
(participant) => participant.personId,
|
||||
);
|
||||
|
||||
if (messageParticipantsWithPersonId.length === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
await this.timelineActivityRepository.insertTimelineActivitiesForObject(
|
||||
'person',
|
||||
messageParticipantsWithPersonId.map((participant) => ({
|
||||
dataSourceSchema,
|
||||
name: 'message.linked',
|
||||
properties: null,
|
||||
objectName: 'message',
|
||||
recordId: participant.personId,
|
||||
workspaceMemberId: payload.userId,
|
||||
workspaceId: payload.workspaceId,
|
||||
linkedObjectMetadataId: messageObjectMetadata.id,
|
||||
linkedRecordId: participant.messageId,
|
||||
linkedRecordCachedName: '',
|
||||
})),
|
||||
payload.workspaceId,
|
||||
);
|
||||
}
|
||||
}
|
||||
@ -3,9 +3,14 @@ import { TypeOrmModule } from '@nestjs/typeorm';
|
||||
|
||||
import { AnalyticsModule } from 'src/engine/core-modules/analytics/analytics.module';
|
||||
import { FeatureFlagEntity } from 'src/engine/core-modules/feature-flag/feature-flag.entity';
|
||||
import { ObjectMetadataEntity } from 'src/engine/metadata-modules/object-metadata/object-metadata.entity';
|
||||
import { ObjectMetadataRepositoryModule } from 'src/engine/object-metadata-repository/object-metadata-repository.module';
|
||||
import { WorkspaceDataSourceModule } from 'src/engine/workspace-datasource/workspace-datasource.module';
|
||||
import { AutoCompaniesAndContactsCreationModule } from 'src/modules/connected-account/auto-companies-and-contacts-creation/auto-companies-and-contacts-creation.module';
|
||||
import { MessagingGmailDriverModule } from 'src/modules/messaging/message-import-manager/drivers/gmail/messaging-gmail-driver.module';
|
||||
import { MessagingCreateCompanyAndContactAfterSyncJob } from 'src/modules/messaging/message-participants-manager/jobs/messaging-create-company-and-contact-after-sync.job';
|
||||
import { MessageParticipantListener } from 'src/modules/messaging/message-participants-manager/listeners/message-participant.listener';
|
||||
import { TimelineActivityWorkspaceEntity } from 'src/modules/timeline/standard-objects/timeline-activity.workspace-entity';
|
||||
|
||||
@Module({
|
||||
imports: [
|
||||
@ -13,12 +18,18 @@ import { MessagingCreateCompanyAndContactAfterSyncJob } from 'src/modules/messag
|
||||
AnalyticsModule,
|
||||
MessagingGmailDriverModule,
|
||||
AutoCompaniesAndContactsCreationModule,
|
||||
WorkspaceDataSourceModule,
|
||||
ObjectMetadataRepositoryModule.forFeature([
|
||||
TimelineActivityWorkspaceEntity,
|
||||
]),
|
||||
TypeOrmModule.forFeature([ObjectMetadataEntity], 'metadata'),
|
||||
],
|
||||
providers: [
|
||||
{
|
||||
provide: MessagingCreateCompanyAndContactAfterSyncJob.name,
|
||||
useClass: MessagingCreateCompanyAndContactAfterSyncJob,
|
||||
},
|
||||
MessageParticipantListener,
|
||||
],
|
||||
})
|
||||
export class MessaginParticipantsManagerModule {}
|
||||
|
||||
@ -1,5 +1,7 @@
|
||||
import { Injectable } from '@nestjs/common';
|
||||
|
||||
import { EntityManager } from 'typeorm';
|
||||
|
||||
import { WorkspaceDataSourceService } from 'src/engine/workspace-datasource/workspace-datasource.service';
|
||||
import { objectRecordDiffMerge } from 'src/engine/integrations/event-emitter/utils/object-record-diff-merge';
|
||||
|
||||
@ -74,17 +76,15 @@ export class TimelineActivityRepository {
|
||||
return this.workspaceDataSourceService.executeRawQuery(
|
||||
`SELECT * FROM ${dataSourceSchema}."timelineActivity"
|
||||
WHERE "${objectName}Id" = $1
|
||||
AND ("name" = $2 OR "name" = $3)
|
||||
AND "workspaceMemberId" = $4
|
||||
AND "linkedRecordId" = $5
|
||||
AND "name" = $2
|
||||
AND "workspaceMemberId" = $3
|
||||
AND ${
|
||||
linkedRecordId ? `"linkedRecordId" = $4` : `"linkedRecordId" IS NULL`
|
||||
}
|
||||
AND "createdAt" >= NOW() - interval '10 minutes'`,
|
||||
[
|
||||
recordId,
|
||||
name,
|
||||
name.replace(/\.updated$/, '.created'),
|
||||
workspaceMemberId,
|
||||
linkedRecordId,
|
||||
],
|
||||
linkedRecordId
|
||||
? [recordId, name, workspaceMemberId, linkedRecordId]
|
||||
: [recordId, name, workspaceMemberId],
|
||||
workspaceId,
|
||||
);
|
||||
}
|
||||
@ -133,4 +133,52 @@ export class TimelineActivityRepository {
|
||||
workspaceId,
|
||||
);
|
||||
}
|
||||
|
||||
public async insertTimelineActivitiesForObject(
|
||||
objectName: string,
|
||||
activities: {
|
||||
name: string;
|
||||
properties: Record<string, any> | null;
|
||||
workspaceMemberId: string | undefined;
|
||||
recordId: string;
|
||||
linkedRecordCachedName: string;
|
||||
linkedRecordId: string | undefined;
|
||||
linkedObjectMetadataId: string | undefined;
|
||||
}[],
|
||||
workspaceId: string,
|
||||
transactionManager?: EntityManager,
|
||||
) {
|
||||
if (activities.length === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
const dataSourceSchema =
|
||||
this.workspaceDataSourceService.getSchemaName(workspaceId);
|
||||
|
||||
return this.workspaceDataSourceService.executeRawQuery(
|
||||
`INSERT INTO ${dataSourceSchema}."timelineActivity"
|
||||
("name", "properties", "workspaceMemberId", "${objectName}Id", "linkedRecordCachedName", "linkedRecordId", "linkedObjectMetadataId")
|
||||
VALUES ${activities
|
||||
.map(
|
||||
(_, index) =>
|
||||
`($${index * 7 + 1}, $${index * 7 + 2}, $${index * 7 + 3}, $${
|
||||
index * 7 + 4
|
||||
}, $${index * 7 + 5}, $${index * 7 + 6}, $${index * 7 + 7})`,
|
||||
)
|
||||
.join(',')}`,
|
||||
activities
|
||||
.map((activity) => [
|
||||
activity.name,
|
||||
activity.properties,
|
||||
activity.workspaceMemberId,
|
||||
activity.recordId,
|
||||
activity.linkedRecordCachedName ?? '',
|
||||
activity.linkedRecordId,
|
||||
activity.linkedObjectMetadataId,
|
||||
])
|
||||
.flat(),
|
||||
workspaceId,
|
||||
transactionManager,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user