feat: drop calendar repository (#5824)

This PR is replacing and removing all the raw queries and repositories
with the new `TwentyORM` and injection system using
`@InjectWorkspaceRepository`.
Some logic that was contained inside repositories has been moved to the
services.
In this PR we're only replacing repositories for calendar feature.

---------

Co-authored-by: Weiko <corentin@twenty.com>
Co-authored-by: bosiraphael <raphael.bosi@gmail.com>
Co-authored-by: Charles Bochet <charles@twenty.com>
This commit is contained in:
Jérémy M
2024-06-22 09:26:58 +02:00
committed by GitHub
parent 91b0c2bb8e
commit 0b4bfce324
90 changed files with 979 additions and 1541 deletions

View File

@ -1,21 +0,0 @@
import { Injectable } from '@nestjs/common';
import { WorkspaceDataSourceService } from 'src/engine/workspace-datasource/workspace-datasource.service';
@Injectable()
export class CommentRepository {
constructor(
private readonly workspaceDataSourceService: WorkspaceDataSourceService,
) {}
async deleteByAuthorId(authorId: string, workspaceId: string): Promise<void> {
const dataSourceSchema =
this.workspaceDataSourceService.getSchemaName(workspaceId);
await this.workspaceDataSourceService.executeRawQuery(
`DELETE FROM ${dataSourceSchema}."comment" WHERE "authorId" = $1`,
[authorId],
workspaceId,
);
}
}

View File

@ -36,7 +36,7 @@ export class ActivityTargetWorkspaceEntity extends BaseWorkspaceEntity {
inverseSideFieldKey: 'activityTargets',
})
@WorkspaceIsNullable()
activity: Relation<ActivityWorkspaceEntity>;
activity: Relation<ActivityWorkspaceEntity> | null;
@WorkspaceRelation({
standardId: ACTIVITY_TARGET_STANDARD_FIELD_IDS.person,
@ -49,7 +49,7 @@ export class ActivityTargetWorkspaceEntity extends BaseWorkspaceEntity {
inverseSideFieldKey: 'activityTargets',
})
@WorkspaceIsNullable()
person: Relation<PersonWorkspaceEntity>;
person: Relation<PersonWorkspaceEntity> | null;
@WorkspaceRelation({
standardId: ACTIVITY_TARGET_STANDARD_FIELD_IDS.company,
@ -62,7 +62,7 @@ export class ActivityTargetWorkspaceEntity extends BaseWorkspaceEntity {
inverseSideFieldKey: 'activityTargets',
})
@WorkspaceIsNullable()
company: Relation<CompanyWorkspaceEntity>;
company: Relation<CompanyWorkspaceEntity> | null;
@WorkspaceRelation({
standardId: ACTIVITY_TARGET_STANDARD_FIELD_IDS.opportunity,
@ -75,7 +75,7 @@ export class ActivityTargetWorkspaceEntity extends BaseWorkspaceEntity {
inverseSideFieldKey: 'activityTargets',
})
@WorkspaceIsNullable()
opportunity: Relation<OpportunityWorkspaceEntity>;
opportunity: Relation<OpportunityWorkspaceEntity> | null;
@WorkspaceDynamicRelation({
type: RelationMetadataType.MANY_TO_ONE,

View File

@ -64,7 +64,7 @@ export class ActivityWorkspaceEntity extends BaseWorkspaceEntity {
icon: 'IconCalendarEvent',
})
@WorkspaceIsNullable()
reminderAt: Date;
reminderAt: Date | null;
@WorkspaceField({
standardId: ACTIVITY_STANDARD_FIELD_IDS.dueAt,
@ -74,7 +74,7 @@ export class ActivityWorkspaceEntity extends BaseWorkspaceEntity {
icon: 'IconCalendarEvent',
})
@WorkspaceIsNullable()
dueAt: Date;
dueAt: Date | null;
@WorkspaceField({
standardId: ACTIVITY_STANDARD_FIELD_IDS.completedAt,
@ -84,7 +84,7 @@ export class ActivityWorkspaceEntity extends BaseWorkspaceEntity {
icon: 'IconCheck',
})
@WorkspaceIsNullable()
completedAt: Date;
completedAt: Date | null;
@WorkspaceRelation({
standardId: ACTIVITY_STANDARD_FIELD_IDS.activityTargets,
@ -134,7 +134,7 @@ export class ActivityWorkspaceEntity extends BaseWorkspaceEntity {
joinColumn: 'authorId',
})
@WorkspaceIsNullable()
author: Relation<WorkspaceMemberWorkspaceEntity>;
author: Relation<WorkspaceMemberWorkspaceEntity> | null;
@WorkspaceRelation({
standardId: ACTIVITY_STANDARD_FIELD_IDS.assignee,
@ -148,5 +148,5 @@ export class ActivityWorkspaceEntity extends BaseWorkspaceEntity {
joinColumn: 'assigneeId',
})
@WorkspaceIsNullable()
assignee: Relation<WorkspaceMemberWorkspaceEntity>;
assignee: Relation<WorkspaceMemberWorkspaceEntity> | null;
}

View File

@ -45,5 +45,5 @@ export class ApiKeyWorkspaceEntity extends BaseWorkspaceEntity {
icon: 'IconCalendar',
})
@WorkspaceIsNullable()
revokedAt?: Date;
revokedAt?: Date | null;
}

View File

@ -1,21 +0,0 @@
import { Injectable } from '@nestjs/common';
import { WorkspaceDataSourceService } from 'src/engine/workspace-datasource/workspace-datasource.service';
@Injectable()
export class AttachmentRepository {
constructor(
private readonly workspaceDataSourceService: WorkspaceDataSourceService,
) {}
async deleteByAuthorId(authorId: string, workspaceId: string): Promise<void> {
const dataSourceSchema =
this.workspaceDataSourceService.getSchemaName(workspaceId);
await this.workspaceDataSourceService.executeRawQuery(
`DELETE FROM ${dataSourceSchema}."attachment" WHERE "authorId" = $1`,
[authorId],
workspaceId,
);
}
}

View File

@ -80,7 +80,7 @@ export class AttachmentWorkspaceEntity extends BaseWorkspaceEntity {
inverseSideFieldKey: 'attachments',
})
@WorkspaceIsNullable()
activity: Relation<ActivityWorkspaceEntity>;
activity: Relation<ActivityWorkspaceEntity> | null;
@WorkspaceRelation({
standardId: ATTACHMENT_STANDARD_FIELD_IDS.person,
@ -93,7 +93,7 @@ export class AttachmentWorkspaceEntity extends BaseWorkspaceEntity {
inverseSideFieldKey: 'attachments',
})
@WorkspaceIsNullable()
person: Relation<PersonWorkspaceEntity>;
person: Relation<PersonWorkspaceEntity> | null;
@WorkspaceRelation({
standardId: ATTACHMENT_STANDARD_FIELD_IDS.company,
@ -106,7 +106,7 @@ export class AttachmentWorkspaceEntity extends BaseWorkspaceEntity {
inverseSideFieldKey: 'attachments',
})
@WorkspaceIsNullable()
company: Relation<CompanyWorkspaceEntity>;
company: Relation<CompanyWorkspaceEntity> | null;
@WorkspaceRelation({
standardId: ATTACHMENT_STANDARD_FIELD_IDS.opportunity,
@ -119,7 +119,7 @@ export class AttachmentWorkspaceEntity extends BaseWorkspaceEntity {
inverseSideFieldKey: 'attachments',
})
@WorkspaceIsNullable()
opportunity: Relation<OpportunityWorkspaceEntity>;
opportunity: Relation<OpportunityWorkspaceEntity> | null;
@WorkspaceDynamicRelation({
type: RelationMetadataType.MANY_TO_ONE,

View File

@ -1,3 +1,5 @@
import { Scope } from '@nestjs/common';
import { Process } from 'src/engine/integrations/message-queue/decorators/process.decorator';
import { Processor } from 'src/engine/integrations/message-queue/decorators/processor.decorator';
import { MessageQueue } from 'src/engine/integrations/message-queue/message-queue.constants';
@ -11,7 +13,10 @@ export type MatchParticipantJobData = {
workspaceMemberId?: string;
};
@Processor(MessageQueue.messagingQueue)
@Processor({
queueName: MessageQueue.messagingQueue,
scope: Scope.REQUEST,
})
export class MatchParticipantJob {
constructor(
private readonly messageParticipantService: MessagingMessageParticipantService,

View File

@ -1,3 +1,5 @@
import { Scope } from '@nestjs/common';
import { Processor } from 'src/engine/integrations/message-queue/decorators/processor.decorator';
import { MessageQueue } from 'src/engine/integrations/message-queue/message-queue.constants';
import { CalendarEventParticipantService } from 'src/modules/calendar/services/calendar-event-participant/calendar-event-participant.service';
@ -11,7 +13,10 @@ export type UnmatchParticipantJobData = {
workspaceMemberId?: string;
};
@Processor(MessageQueue.messagingQueue)
@Processor({
queueName: MessageQueue.messagingQueue,
scope: Scope.REQUEST,
})
export class UnmatchParticipantJob {
constructor(
private readonly messageParticipantService: MessagingMessageParticipantService,

View File

@ -1,19 +1,10 @@
import { Module } from '@nestjs/common';
import { ObjectMetadataRepositoryModule } from 'src/engine/object-metadata-repository/object-metadata-repository.module';
import { GoogleCalendarSyncCommand } from 'src/modules/calendar/commands/google-calendar-sync.command';
import { WorkspaceGoogleCalendarSyncModule } from 'src/modules/calendar/services/workspace-google-calendar-sync/workspace-google-calendar-sync.module';
import { CalendarChannelWorkspaceEntity } from 'src/modules/calendar/standard-objects/calendar-channel.workspace-entity';
import { ConnectedAccountWorkspaceEntity } from 'src/modules/connected-account/standard-objects/connected-account.workspace-entity';
@Module({
imports: [
ObjectMetadataRepositoryModule.forFeature([
ConnectedAccountWorkspaceEntity,
CalendarChannelWorkspaceEntity,
]),
WorkspaceGoogleCalendarSyncModule,
],
imports: [WorkspaceGoogleCalendarSyncModule],
providers: [GoogleCalendarSyncCommand],
})
export class CalendarCommandsModule {}

View File

@ -1,4 +1,5 @@
import { InjectRepository } from '@nestjs/typeorm';
import { Scope } from '@nestjs/common';
import { Repository, In } from 'typeorm';
@ -10,7 +11,10 @@ import { MessageQueue } from 'src/engine/integrations/message-queue/message-queu
import { Processor } from 'src/engine/integrations/message-queue/decorators/processor.decorator';
import { Process } from 'src/engine/integrations/message-queue/decorators/process.decorator';
@Processor(MessageQueue.cronQueue)
@Processor({
queueName: MessageQueue.cronQueue,
scope: Scope.REQUEST,
})
export class GoogleCalendarSyncCronJob {
constructor(
@InjectRepository(Workspace, 'core')

View File

@ -1,35 +1,38 @@
import { Logger } from '@nestjs/common';
import { Logger, Scope } from '@nestjs/common';
import { Any, ILike } from 'typeorm';
import { Processor } from 'src/engine/integrations/message-queue/decorators/processor.decorator';
import { MessageQueue } from 'src/engine/integrations/message-queue/message-queue.constants';
import { InjectObjectMetadataRepository } from 'src/engine/object-metadata-repository/object-metadata-repository.decorator';
import { CalendarChannelEventAssociationRepository } from 'src/modules/calendar/repositories/calendar-channel-event-association.repository';
import { CalendarChannelRepository } from 'src/modules/calendar/repositories/calendar-channel.repository';
import { CalendarEventCleanerService } from 'src/modules/calendar/services/calendar-event-cleaner/calendar-event-cleaner.service';
import { CalendarChannelEventAssociationWorkspaceEntity } from 'src/modules/calendar/standard-objects/calendar-channel-event-association.workspace-entity';
import { CalendarChannelWorkspaceEntity } from 'src/modules/calendar/standard-objects/calendar-channel.workspace-entity';
import { BlocklistRepository } from 'src/modules/connected-account/repositories/blocklist.repository';
import { BlocklistWorkspaceEntity } from 'src/modules/connected-account/standard-objects/blocklist.workspace-entity';
import { Process } from 'src/engine/integrations/message-queue/decorators/process.decorator';
import { InjectWorkspaceRepository } from 'src/engine/twenty-orm/decorators/inject-workspace-repository.decorator';
import { WorkspaceRepository } from 'src/engine/twenty-orm/repository/workspace.repository';
export type BlocklistItemDeleteCalendarEventsJobData = {
workspaceId: string;
blocklistItemId: string;
};
@Processor(MessageQueue.calendarQueue)
@Processor({
queueName: MessageQueue.calendarQueue,
scope: Scope.REQUEST,
})
export class BlocklistItemDeleteCalendarEventsJob {
private readonly logger = new Logger(
BlocklistItemDeleteCalendarEventsJob.name,
);
constructor(
@InjectObjectMetadataRepository(CalendarChannelWorkspaceEntity)
private readonly calendarChannelRepository: CalendarChannelRepository,
@InjectObjectMetadataRepository(
CalendarChannelEventAssociationWorkspaceEntity,
)
private readonly calendarChannelEventAssociationRepository: CalendarChannelEventAssociationRepository,
@InjectWorkspaceRepository(CalendarChannelWorkspaceEntity)
private readonly calendarChannelRepository: WorkspaceRepository<CalendarChannelWorkspaceEntity>,
@InjectWorkspaceRepository(CalendarChannelEventAssociationWorkspaceEntity)
private readonly calendarChannelEventAssociationRepository: WorkspaceRepository<CalendarChannelEventAssociationWorkspaceEntity>,
@InjectObjectMetadataRepository(BlocklistWorkspaceEntity)
private readonly blocklistRepository: BlocklistRepository,
private readonly calendarEventCleanerService: CalendarEventCleanerService,
@ -58,19 +61,39 @@ export class BlocklistItemDeleteCalendarEventsJob {
`Deleting calendar events from ${handle} in workspace ${workspaceId} for workspace member ${workspaceMemberId}`,
);
const calendarChannels =
await this.calendarChannelRepository.getIdsByWorkspaceMemberId(
workspaceMemberId,
workspaceId,
if (!workspaceMemberId) {
throw new Error(
`Workspace member ID is undefined for blocklist item ${blocklistItemId} in workspace ${workspaceId}`,
);
}
const calendarChannels = await this.calendarChannelRepository.find({
where: {
connectedAccount: {
accountOwner: {
id: workspaceMemberId,
},
},
},
relations: ['connectedAccount.accountOwner'],
});
const calendarChannelIds = calendarChannels.map(({ id }) => id);
await this.calendarChannelEventAssociationRepository.deleteByCalendarEventParticipantHandleAndCalendarChannelIds(
handle,
calendarChannelIds,
workspaceId,
);
const isHandleDomain = handle.startsWith('@');
await this.calendarChannelEventAssociationRepository.delete({
calendarEvent: {
calendarEventParticipants: {
handle: isHandleDomain ? ILike(`%${handle}`) : handle,
},
calendarChannelEventAssociations: {
calendarChannel: {
id: Any(calendarChannelIds),
},
},
},
});
await this.calendarEventCleanerService.cleanWorkspaceCalendarEvents(
workspaceId,

View File

@ -1,4 +1,4 @@
import { Logger } from '@nestjs/common';
import { Logger, Scope } from '@nestjs/common';
import { Processor } from 'src/engine/integrations/message-queue/decorators/processor.decorator';
import { MessageQueue } from 'src/engine/integrations/message-queue/message-queue.constants';
@ -14,7 +14,10 @@ export type BlocklistReimportCalendarEventsJobData = {
handle: string;
};
@Processor(MessageQueue.calendarQueue)
@Processor({
queueName: MessageQueue.calendarQueue,
scope: Scope.REQUEST,
})
export class BlocklistReimportCalendarEventsJob {
private readonly logger = new Logger(BlocklistReimportCalendarEventsJob.name);

View File

@ -1,35 +1,35 @@
import { Logger } from '@nestjs/common';
import { Logger, Scope } from '@nestjs/common';
import { IsNull } from 'typeorm';
import { Process } from 'src/engine/integrations/message-queue/decorators/process.decorator';
import { Processor } from 'src/engine/integrations/message-queue/decorators/processor.decorator';
import { MessageQueue } from 'src/engine/integrations/message-queue/message-queue.constants';
import { InjectObjectMetadataRepository } from 'src/engine/object-metadata-repository/object-metadata-repository.decorator';
import { CalendarChannelRepository } from 'src/modules/calendar/repositories/calendar-channel.repository';
import { CalendarEventParticipantRepository } from 'src/modules/calendar/repositories/calendar-event-participant.repository';
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';
import { Process } from 'src/engine/integrations/message-queue/decorators/process.decorator';
import { InjectWorkspaceRepository } from 'src/engine/twenty-orm/decorators/inject-workspace-repository.decorator';
import { WorkspaceRepository } from 'src/engine/twenty-orm/repository/workspace.repository';
export type CalendarCreateCompanyAndContactAfterSyncJobData = {
workspaceId: string;
calendarChannelId: string;
};
@Processor(MessageQueue.calendarQueue)
@Processor({
queueName: MessageQueue.calendarQueue,
scope: Scope.REQUEST,
})
export class CalendarCreateCompanyAndContactAfterSyncJob {
private readonly logger = new Logger(
CalendarCreateCompanyAndContactAfterSyncJob.name,
);
constructor(
private readonly createCompanyAndContactService: CreateCompanyAndContactService,
@InjectObjectMetadataRepository(CalendarChannelWorkspaceEntity)
private readonly calendarChannelService: CalendarChannelRepository,
@InjectObjectMetadataRepository(CalendarEventParticipantWorkspaceEntity)
private readonly calendarEventParticipantRepository: CalendarEventParticipantRepository,
@InjectObjectMetadataRepository(ConnectedAccountWorkspaceEntity)
private readonly connectedAccountRepository: ConnectedAccountRepository,
@InjectWorkspaceRepository(CalendarChannelWorkspaceEntity)
private readonly calendarChannelRepository: WorkspaceRepository<CalendarChannelWorkspaceEntity>,
@InjectWorkspaceRepository(CalendarEventParticipantWorkspaceEntity)
private readonly calendarEventParticipantRepository: WorkspaceRepository<CalendarEventParticipantWorkspaceEntity>,
) {}
@Process(CalendarCreateCompanyAndContactAfterSyncJob.name)
@ -41,40 +41,52 @@ export class CalendarCreateCompanyAndContactAfterSyncJob {
);
const { workspaceId, calendarChannelId } = data;
const calendarChannels = await this.calendarChannelService.getByIds(
[calendarChannelId],
workspaceId,
);
const calendarChannel = await this.calendarChannelRepository.findOne({
where: {
id: calendarChannelId,
},
relations: ['connectedAccount.accountOwner'],
});
if (calendarChannels.length === 0) {
if (!calendarChannel) {
throw new Error(
`Calendar channel with id ${calendarChannelId} not found in workspace ${workspaceId}`,
);
}
const { handle, isContactAutoCreationEnabled, connectedAccountId } =
calendarChannels[0];
const { handle, isContactAutoCreationEnabled, connectedAccount } =
calendarChannel;
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}`,
`Connected account not found in workspace ${workspaceId}`,
);
}
const calendarEventParticipantsWithoutPersonIdAndWorkspaceMemberId =
await this.calendarEventParticipantRepository.getByCalendarChannelIdWithoutPersonIdAndWorkspaceMemberId(
calendarChannelId,
workspaceId,
);
await this.calendarEventParticipantRepository.find({
where: {
calendarEvent: {
calendarChannelEventAssociations: {
calendarChannel: {
id: calendarChannelId,
},
},
calendarEventParticipants: {
person: IsNull(),
workspaceMember: IsNull(),
},
},
},
relations: [
'calendarEvent.calendarChannelEventAssociations',
'calendarEvent.calendarEventParticipants',
],
});
await this.createCompanyAndContactService.createCompaniesAndContactsAndUpdateParticipants(
connectedAccount,

View File

@ -1,6 +1,7 @@
import { Module } from '@nestjs/common';
import { ObjectMetadataRepositoryModule } from 'src/engine/object-metadata-repository/object-metadata-repository.module';
import { TwentyORMModule } from 'src/engine/twenty-orm/twenty-orm.module';
import { BlocklistItemDeleteCalendarEventsJob } from 'src/modules/calendar/jobs/blocklist-item-delete-calendar-events.job';
import { BlocklistReimportCalendarEventsJob } from 'src/modules/calendar/jobs/blocklist-reimport-calendar-events.job';
import { CalendarCreateCompanyAndContactAfterSyncJob } from 'src/modules/calendar/jobs/calendar-create-company-and-contact-after-sync.job';
@ -18,10 +19,12 @@ import { ConnectedAccountWorkspaceEntity } from 'src/modules/connected-account/s
@Module({
imports: [
ObjectMetadataRepositoryModule.forFeature([
TwentyORMModule.forFeature([
CalendarChannelWorkspaceEntity,
CalendarChannelEventAssociationWorkspaceEntity,
CalendarEventParticipantWorkspaceEntity,
]),
ObjectMetadataRepositoryModule.forFeature([
ConnectedAccountWorkspaceEntity,
BlocklistWorkspaceEntity,
]),

View File

@ -1,4 +1,4 @@
import { Logger } from '@nestjs/common';
import { Logger, Scope } from '@nestjs/common';
import { GoogleAPIRefreshAccessTokenService } from 'src/modules/connected-account/services/google-api-refresh-access-token/google-api-refresh-access-token.service';
import { GoogleCalendarSyncService } from 'src/modules/calendar/services/google-calendar-sync/google-calendar-sync.service';
@ -11,7 +11,10 @@ export type GoogleCalendarSyncJobData = {
connectedAccountId: string;
};
@Processor(MessageQueue.calendarQueue)
@Processor({
queueName: MessageQueue.calendarQueue,
scope: Scope.REQUEST,
})
export class GoogleCalendarSyncJob {
private readonly logger = new Logger(GoogleCalendarSyncJob.name);

View File

@ -25,7 +25,7 @@ export class CalendarEventParticipantListener {
@OnEvent('calendarEventParticipant.matched')
public async handleCalendarEventParticipantMatchedEvent(payload: {
workspaceId: string;
userId: string;
workspaceMemberId: string;
calendarEventParticipants: ObjectRecord<CalendarEventParticipantWorkspaceEntity>[];
}): Promise<void> {
const calendarEventParticipants = payload.calendarEventParticipants ?? [];
@ -59,7 +59,7 @@ export class CalendarEventParticipantListener {
properties: null,
objectName: 'calendarEvent',
recordId: participant.personId,
workspaceMemberId: payload.userId,
workspaceMemberId: payload.workspaceMemberId,
workspaceId: payload.workspaceId,
linkedObjectMetadataId: calendarEventObjectMetadata.id,
linkedRecordId: participant.calendarEventId,

View File

@ -1,26 +1,20 @@
import {
BadRequestException,
Injectable,
NotFoundException,
} from '@nestjs/common';
import { BadRequestException, Injectable } from '@nestjs/common';
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 { InjectObjectMetadataRepository } from 'src/engine/object-metadata-repository/object-metadata-repository.decorator';
import { CalendarChannelEventAssociationWorkspaceEntity } from 'src/modules/calendar/standard-objects/calendar-channel-event-association.workspace-entity';
import { CalendarChannelEventAssociationRepository } from 'src/modules/calendar/repositories/calendar-channel-event-association.repository';
import { CanAccessCalendarEventService } from 'src/modules/calendar/query-hooks/calendar-event/services/can-access-calendar-event.service';
import { InjectWorkspaceRepository } from 'src/engine/twenty-orm/decorators/inject-workspace-repository.decorator';
import { WorkspaceRepository } from 'src/engine/twenty-orm/repository/workspace.repository';
@Injectable()
export class CalendarEventFindManyPreQueryHook
implements WorkspacePreQueryHook
{
constructor(
@InjectObjectMetadataRepository(
CalendarChannelEventAssociationWorkspaceEntity,
)
private readonly calendarChannelEventAssociationRepository: CalendarChannelEventAssociationRepository,
@InjectWorkspaceRepository(CalendarChannelEventAssociationWorkspaceEntity)
private readonly calendarChannelEventAssociationRepository: WorkspaceRepository<CalendarChannelEventAssociationWorkspaceEntity>,
private readonly canAccessCalendarEventService: CanAccessCalendarEventService,
) {}
@ -33,20 +27,25 @@ export class CalendarEventFindManyPreQueryHook
throw new BadRequestException('id filter is required');
}
const calendarChannelCalendarEventAssociations =
await this.calendarChannelEventAssociationRepository.getByCalendarEventIds(
[payload?.filter?.id?.eq],
workspaceId,
);
// TODO: Re-implement this using twenty ORM
// const calendarChannelCalendarEventAssociations =
// await this.calendarChannelEventAssociationRepository.find({
// where: {
// calendarEvent: {
// id: payload?.filter?.id?.eq,
// },
// },
// relations: ['calendarChannel.connectedAccount'],
// });
if (calendarChannelCalendarEventAssociations.length === 0) {
throw new NotFoundException();
}
// if (calendarChannelCalendarEventAssociations.length === 0) {
// throw new NotFoundException();
// }
await this.canAccessCalendarEventService.canAccessCalendarEvent(
userId,
workspaceId,
calendarChannelCalendarEventAssociations,
);
// await this.canAccessCalendarEventService.canAccessCalendarEvent(
// userId,
// workspaceId,
// calendarChannelCalendarEventAssociations,
// );
}
}

View File

@ -1,24 +1,18 @@
import {
BadRequestException,
Injectable,
NotFoundException,
} from '@nestjs/common';
import { BadRequestException, Injectable } 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 { InjectWorkspaceRepository } from 'src/engine/twenty-orm/decorators/inject-workspace-repository.decorator';
import { WorkspaceRepository } from 'src/engine/twenty-orm/repository/workspace.repository';
import { CanAccessCalendarEventService } from 'src/modules/calendar/query-hooks/calendar-event/services/can-access-calendar-event.service';
import { CalendarChannelEventAssociationRepository } from 'src/modules/calendar/repositories/calendar-channel-event-association.repository';
import { CalendarChannelEventAssociationWorkspaceEntity } from 'src/modules/calendar/standard-objects/calendar-channel-event-association.workspace-entity';
@Injectable()
export class CalendarEventFindOnePreQueryHook implements WorkspacePreQueryHook {
constructor(
@InjectObjectMetadataRepository(
CalendarChannelEventAssociationWorkspaceEntity,
)
private readonly calendarChannelEventAssociationRepository: CalendarChannelEventAssociationRepository,
@InjectWorkspaceRepository(CalendarChannelEventAssociationWorkspaceEntity)
private readonly calendarChannelEventAssociationRepository: WorkspaceRepository<CalendarChannelEventAssociationWorkspaceEntity>,
private readonly canAccessCalendarEventService: CanAccessCalendarEventService,
) {}
@ -31,20 +25,24 @@ export class CalendarEventFindOnePreQueryHook implements WorkspacePreQueryHook {
throw new BadRequestException('id filter is required');
}
const calendarChannelCalendarEventAssociations =
await this.calendarChannelEventAssociationRepository.getByCalendarEventIds(
[payload?.filter?.id?.eq],
workspaceId,
);
// TODO: Re-implement this using twenty ORM
// const calendarChannelCalendarEventAssociations =
// await this.calendarChannelEventAssociationRepository.find({
// where: {
// calendarEvent: {
// id: payload?.filter?.id?.eq,
// },
// },
// });
if (calendarChannelCalendarEventAssociations.length === 0) {
throw new NotFoundException();
}
// if (calendarChannelCalendarEventAssociations.length === 0) {
// throw new NotFoundException();
// }
await this.canAccessCalendarEventService.canAccessCalendarEvent(
userId,
workspaceId,
calendarChannelCalendarEventAssociations,
);
// await this.canAccessCalendarEventService.canAccessCalendarEvent(
// userId,
// workspaceId,
// calendarChannelCalendarEventAssociations,
// );
}
}

View File

@ -1,10 +1,11 @@
import { ForbiddenException, Injectable } from '@nestjs/common';
import groupBy from 'lodash.groupby';
import { Any } from 'typeorm';
import { InjectObjectMetadataRepository } from 'src/engine/object-metadata-repository/object-metadata-repository.decorator';
import { ObjectRecord } from 'src/engine/workspace-manager/workspace-sync-metadata/types/object-record';
import { CalendarChannelRepository } from 'src/modules/calendar/repositories/calendar-channel.repository';
import { InjectWorkspaceRepository } from 'src/engine/twenty-orm/decorators/inject-workspace-repository.decorator';
import { WorkspaceRepository } from 'src/engine/twenty-orm/repository/workspace.repository';
import { CalendarChannelEventAssociationWorkspaceEntity } from 'src/modules/calendar/standard-objects/calendar-channel-event-association.workspace-entity';
import {
CalendarChannelWorkspaceEntity,
@ -18,8 +19,8 @@ import { WorkspaceMemberWorkspaceEntity } from 'src/modules/workspace-member/sta
@Injectable()
export class CanAccessCalendarEventService {
constructor(
@InjectObjectMetadataRepository(CalendarChannelWorkspaceEntity)
private readonly calendarChannelRepository: CalendarChannelRepository,
@InjectWorkspaceRepository(CalendarChannelWorkspaceEntity)
private readonly calendarChannelRepository: WorkspaceRepository<CalendarChannelWorkspaceEntity>,
@InjectObjectMetadataRepository(ConnectedAccountWorkspaceEntity)
private readonly connectedAccountRepository: ConnectedAccountRepository,
@InjectObjectMetadataRepository(WorkspaceMemberWorkspaceEntity)
@ -29,14 +30,17 @@ export class CanAccessCalendarEventService {
public async canAccessCalendarEvent(
userId: string,
workspaceId: string,
calendarChannelCalendarEventAssociations: ObjectRecord<CalendarChannelEventAssociationWorkspaceEntity>[],
calendarChannelCalendarEventAssociations: CalendarChannelEventAssociationWorkspaceEntity[],
) {
const calendarChannels = await this.calendarChannelRepository.getByIds(
calendarChannelCalendarEventAssociations.map(
(association) => association.calendarChannelId,
),
workspaceId,
);
const calendarChannels = await this.calendarChannelRepository.find({
where: {
id: Any(
calendarChannelCalendarEventAssociations.map(
(association) => association.calendarChannel.id,
),
),
},
});
const calendarChannelsGroupByVisibility = groupBy(
calendarChannels,
@ -56,7 +60,7 @@ export class CanAccessCalendarEventService {
const calendarChannelsConnectedAccounts =
await this.connectedAccountRepository.getByIds(
calendarChannels.map((channel) => channel.connectedAccountId),
calendarChannels.map((channel) => channel.connectedAccount.id),
workspaceId,
);

View File

@ -8,12 +8,15 @@ import { CalendarChannelWorkspaceEntity } from 'src/modules/calendar/standard-ob
import { CalendarEventFindManyPreQueryHook } from 'src/modules/calendar/query-hooks/calendar-event/calendar-event-find-many.pre-query.hook';
import { CalendarEventFindOnePreQueryHook } from 'src/modules/calendar/query-hooks/calendar-event/calendar-event-find-one.pre-query-hook';
import { CanAccessCalendarEventService } from 'src/modules/calendar/query-hooks/calendar-event/services/can-access-calendar-event.service';
import { TwentyORMModule } from 'src/engine/twenty-orm/twenty-orm.module';
@Module({
imports: [
ObjectMetadataRepositoryModule.forFeature([
TwentyORMModule.forFeature([
CalendarChannelEventAssociationWorkspaceEntity,
CalendarChannelWorkspaceEntity,
]),
ObjectMetadataRepositoryModule.forFeature([
ConnectedAccountWorkspaceEntity,
WorkspaceMemberWorkspaceEntity,
]),

View File

@ -1,205 +0,0 @@
import { Injectable } from '@nestjs/common';
import { EntityManager } from 'typeorm';
import { WorkspaceDataSourceService } from 'src/engine/workspace-datasource/workspace-datasource.service';
import { ObjectRecord } from 'src/engine/workspace-manager/workspace-sync-metadata/types/object-record';
import { CalendarChannelEventAssociationWorkspaceEntity } from 'src/modules/calendar/standard-objects/calendar-channel-event-association.workspace-entity';
import { getFlattenedValuesAndValuesStringForBatchRawQuery } from 'src/modules/calendar/utils/get-flattened-values-and-values-string-for-batch-raw-query.util';
@Injectable()
export class CalendarChannelEventAssociationRepository {
constructor(
private readonly workspaceDataSourceService: WorkspaceDataSourceService,
) {}
public async getByEventExternalIdsAndCalendarChannelId(
eventExternalIds: string[],
calendarChannelId: string,
workspaceId: string,
transactionManager?: EntityManager,
): Promise<ObjectRecord<CalendarChannelEventAssociationWorkspaceEntity>[]> {
if (eventExternalIds.length === 0) {
return [];
}
const dataSourceSchema =
this.workspaceDataSourceService.getSchemaName(workspaceId);
return await this.workspaceDataSourceService.executeRawQuery(
`SELECT * FROM ${dataSourceSchema}."calendarChannelEventAssociation"
WHERE "eventExternalId" = ANY($1) AND "calendarChannelId" = $2`,
[eventExternalIds, calendarChannelId],
workspaceId,
transactionManager,
);
}
public async deleteByEventExternalIdsAndCalendarChannelId(
eventExternalIds: string[],
calendarChannelId: string,
workspaceId: string,
transactionManager?: EntityManager,
) {
const dataSourceSchema =
this.workspaceDataSourceService.getSchemaName(workspaceId);
await this.workspaceDataSourceService.executeRawQuery(
`DELETE FROM ${dataSourceSchema}."calendarChannelEventAssociation" WHERE "eventExternalId" = ANY($1) AND "calendarChannelId" = $2`,
[eventExternalIds, calendarChannelId],
workspaceId,
transactionManager,
);
}
public async getByCalendarChannelIds(
calendarChannelIds: string[],
workspaceId: string,
transactionManager?: EntityManager,
): Promise<ObjectRecord<CalendarChannelEventAssociationWorkspaceEntity>[]> {
if (calendarChannelIds.length === 0) {
return [];
}
const dataSourceSchema =
this.workspaceDataSourceService.getSchemaName(workspaceId);
return await this.workspaceDataSourceService.executeRawQuery(
`SELECT * FROM ${dataSourceSchema}."calendarChannelEventAssociation"
WHERE "calendarChannelId" = ANY($1)`,
[calendarChannelIds],
workspaceId,
transactionManager,
);
}
public async deleteByCalendarChannelIds(
calendarChannelIds: string[],
workspaceId: string,
transactionManager?: EntityManager,
) {
if (calendarChannelIds.length === 0) {
return;
}
const dataSourceSchema =
this.workspaceDataSourceService.getSchemaName(workspaceId);
await this.workspaceDataSourceService.executeRawQuery(
`DELETE FROM ${dataSourceSchema}."calendarChannelEventAssociation" WHERE "calendarChannelId" = ANY($1)`,
[calendarChannelIds],
workspaceId,
transactionManager,
);
}
public async deleteByIds(
ids: string[],
workspaceId: string,
transactionManager?: EntityManager,
) {
if (ids.length === 0) {
return;
}
const dataSourceSchema =
this.workspaceDataSourceService.getSchemaName(workspaceId);
await this.workspaceDataSourceService.executeRawQuery(
`DELETE FROM ${dataSourceSchema}."calendarChannelEventAssociation" WHERE "id" = ANY($1)`,
[ids],
workspaceId,
transactionManager,
);
}
public async getByCalendarEventIds(
calendarEventIds: string[],
workspaceId: string,
transactionManager?: EntityManager,
): Promise<ObjectRecord<CalendarChannelEventAssociationWorkspaceEntity>[]> {
if (calendarEventIds.length === 0) {
return [];
}
const dataSourceSchema =
this.workspaceDataSourceService.getSchemaName(workspaceId);
return await this.workspaceDataSourceService.executeRawQuery(
`SELECT * FROM ${dataSourceSchema}."calendarChannelEventAssociation"
WHERE "calendarEventId" = ANY($1)`,
[calendarEventIds],
workspaceId,
transactionManager,
);
}
public async saveCalendarChannelEventAssociations(
calendarChannelEventAssociations: Omit<
ObjectRecord<CalendarChannelEventAssociationWorkspaceEntity>,
'id' | 'createdAt' | 'updatedAt' | 'calendarChannel' | 'calendarEvent'
>[],
workspaceId: string,
transactionManager?: EntityManager,
) {
if (calendarChannelEventAssociations.length === 0) {
return;
}
const dataSourceSchema =
this.workspaceDataSourceService.getSchemaName(workspaceId);
const {
flattenedValues: calendarChannelEventAssociationValues,
valuesString,
} = getFlattenedValuesAndValuesStringForBatchRawQuery(
calendarChannelEventAssociations,
{
calendarChannelId: 'uuid',
calendarEventId: 'uuid',
eventExternalId: 'text',
},
);
await this.workspaceDataSourceService.executeRawQuery(
`INSERT INTO ${dataSourceSchema}."calendarChannelEventAssociation" ("calendarChannelId", "calendarEventId", "eventExternalId")
VALUES ${valuesString}`,
calendarChannelEventAssociationValues,
workspaceId,
transactionManager,
);
}
public async deleteByCalendarEventParticipantHandleAndCalendarChannelIds(
calendarEventParticipantHandle: string,
calendarChannelIds: string[],
workspaceId: string,
transactionManager?: EntityManager,
) {
const dataSourceSchema =
this.workspaceDataSourceService.getSchemaName(workspaceId);
const isHandleDomain = calendarEventParticipantHandle.startsWith('@');
await this.workspaceDataSourceService.executeRawQuery(
`DELETE FROM ${dataSourceSchema}."calendarChannelEventAssociation"
WHERE "id" IN (
SELECT "calendarChannelEventAssociation"."id"
FROM ${dataSourceSchema}."calendarChannelEventAssociation" "calendarChannelEventAssociation"
JOIN ${dataSourceSchema}."calendarEvent" "calendarEvent" ON "calendarChannelEventAssociation"."calendarEventId" = "calendarEvent"."id"
JOIN ${dataSourceSchema}."calendarEventParticipant" "calendarEventParticipant" ON "calendarEvent"."id" = "calendarEventParticipant"."calendarEventId"
WHERE "calendarEventParticipant"."handle" ${
isHandleDomain ? 'ILIKE' : '='
} $1 AND "calendarChannelEventAssociation"."calendarChannelId" = ANY($2)
)`,
[
isHandleDomain
? `%${calendarEventParticipantHandle}`
: calendarEventParticipantHandle,
calendarChannelIds,
],
workspaceId,
transactionManager,
);
}
}

View File

@ -1,135 +0,0 @@
import { Injectable } from '@nestjs/common';
import { EntityManager } from 'typeorm';
import { WorkspaceDataSourceService } from 'src/engine/workspace-datasource/workspace-datasource.service';
import { CalendarChannelWorkspaceEntity } from 'src/modules/calendar/standard-objects/calendar-channel.workspace-entity';
import { ObjectRecord } from 'src/engine/workspace-manager/workspace-sync-metadata/types/object-record';
@Injectable()
export class CalendarChannelRepository {
constructor(
private readonly workspaceDataSourceService: WorkspaceDataSourceService,
) {}
public async getAll(
workspaceId: string,
transactionManager?: EntityManager,
): Promise<ObjectRecord<CalendarChannelWorkspaceEntity>[]> {
const dataSourceSchema =
this.workspaceDataSourceService.getSchemaName(workspaceId);
return await this.workspaceDataSourceService.executeRawQuery(
`SELECT * FROM ${dataSourceSchema}."calendarChannel"`,
[],
workspaceId,
transactionManager,
);
}
public async create(
calendarChannel: Pick<
ObjectRecord<CalendarChannelWorkspaceEntity>,
'id' | 'connectedAccountId' | 'handle' | 'visibility'
>,
workspaceId: string,
transactionManager?: EntityManager,
): Promise<void> {
const dataSourceSchema =
this.workspaceDataSourceService.getSchemaName(workspaceId);
await this.workspaceDataSourceService.executeRawQuery(
`INSERT INTO ${dataSourceSchema}."calendarChannel" (id, "connectedAccountId", "handle", "visibility") VALUES ($1, $2, $3, $4)`,
[
calendarChannel.id,
calendarChannel.connectedAccountId,
calendarChannel.handle,
calendarChannel.visibility,
],
workspaceId,
transactionManager,
);
}
public async getByConnectedAccountId(
connectedAccountId: string,
workspaceId: string,
transactionManager?: EntityManager,
): Promise<ObjectRecord<CalendarChannelWorkspaceEntity>[]> {
const dataSourceSchema =
this.workspaceDataSourceService.getSchemaName(workspaceId);
return await this.workspaceDataSourceService.executeRawQuery(
`SELECT * FROM ${dataSourceSchema}."calendarChannel" WHERE "connectedAccountId" = $1 LIMIT 1`,
[connectedAccountId],
workspaceId,
transactionManager,
);
}
public async getFirstByConnectedAccountId(
connectedAccountId: string,
workspaceId: string,
): Promise<ObjectRecord<CalendarChannelWorkspaceEntity> | undefined> {
const calendarChannels = await this.getByConnectedAccountId(
connectedAccountId,
workspaceId,
);
return calendarChannels[0];
}
public async getByIds(
ids: string[],
workspaceId: string,
transactionManager?: EntityManager,
): Promise<ObjectRecord<CalendarChannelWorkspaceEntity>[]> {
const dataSourceSchema =
this.workspaceDataSourceService.getSchemaName(workspaceId);
return await this.workspaceDataSourceService.executeRawQuery(
`SELECT * FROM ${dataSourceSchema}."calendarChannel" WHERE "id" = ANY($1)`,
[ids],
workspaceId,
transactionManager,
);
}
public async getIdsByWorkspaceMemberId(
workspaceMemberId: string,
workspaceId: string,
transactionManager?: EntityManager,
): Promise<ObjectRecord<CalendarChannelWorkspaceEntity>[]> {
const dataSourceSchema =
this.workspaceDataSourceService.getSchemaName(workspaceId);
const calendarChannelIds =
await this.workspaceDataSourceService.executeRawQuery(
`SELECT "calendarChannel".id FROM ${dataSourceSchema}."calendarChannel" "calendarChannel"
JOIN ${dataSourceSchema}."connectedAccount" ON "calendarChannel"."connectedAccountId" = ${dataSourceSchema}."connectedAccount"."id"
WHERE ${dataSourceSchema}."connectedAccount"."accountOwnerId" = $1`,
[workspaceMemberId],
workspaceId,
transactionManager,
);
return calendarChannelIds;
}
public async updateSyncCursor(
syncCursor: string | null,
calendarChannelId: string,
workspaceId: string,
transactionManager?: EntityManager,
): Promise<void> {
const dataSourceSchema =
this.workspaceDataSourceService.getSchemaName(workspaceId);
await this.workspaceDataSourceService.executeRawQuery(
`UPDATE ${dataSourceSchema}."calendarChannel" SET "syncCursor" = $1 WHERE "id" = $2`,
[syncCursor || '', calendarChannelId],
workspaceId,
transactionManager,
);
}
}

View File

@ -1,305 +0,0 @@
import { Injectable } from '@nestjs/common';
import { EntityManager } from 'typeorm';
import differenceWith from 'lodash.differencewith';
import { WorkspaceDataSourceService } from 'src/engine/workspace-datasource/workspace-datasource.service';
import { ObjectRecord } from 'src/engine/workspace-manager/workspace-sync-metadata/types/object-record';
import { CalendarEventParticipantWorkspaceEntity } from 'src/modules/calendar/standard-objects/calendar-event-participant.workspace-entity';
import { getFlattenedValuesAndValuesStringForBatchRawQuery } from 'src/modules/calendar/utils/get-flattened-values-and-values-string-for-batch-raw-query.util';
import {
CalendarEventParticipant,
CalendarEventParticipantWithId,
} from 'src/modules/calendar/types/calendar-event';
@Injectable()
export class CalendarEventParticipantRepository {
constructor(
private readonly workspaceDataSourceService: WorkspaceDataSourceService,
) {}
public async getByHandles(
handles: string[],
workspaceId: string,
transactionManager?: EntityManager,
): Promise<ObjectRecord<CalendarEventParticipantWorkspaceEntity>[]> {
const dataSourceSchema =
this.workspaceDataSourceService.getSchemaName(workspaceId);
return await this.workspaceDataSourceService.executeRawQuery(
`SELECT * FROM ${dataSourceSchema}."calendarEventParticipant" 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}."calendarEventParticipant" SET "personId" = $1 WHERE "id" = ANY($2)`,
[personId, participantIds],
workspaceId,
transactionManager,
);
}
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,
workspaceId: string,
transactionManager?: EntityManager,
) {
const dataSourceSchema =
this.workspaceDataSourceService.getSchemaName(workspaceId);
await this.workspaceDataSourceService.executeRawQuery(
`UPDATE ${dataSourceSchema}."calendarEventParticipant" SET "workspaceMemberId" = $1 WHERE "id" = ANY($2)`,
[workspaceMemberId, participantIds],
workspaceId,
transactionManager,
);
}
public async removePersonIdByHandle(
handle: string,
workspaceId: string,
transactionManager?: EntityManager,
) {
const dataSourceSchema =
this.workspaceDataSourceService.getSchemaName(workspaceId);
await this.workspaceDataSourceService.executeRawQuery(
`UPDATE ${dataSourceSchema}."calendarEventParticipant" SET "personId" = NULL WHERE "handle" = $1`,
[handle],
workspaceId,
transactionManager,
);
}
public async removeWorkspaceMemberIdByHandle(
handle: string,
workspaceId: string,
transactionManager?: EntityManager,
) {
const dataSourceSchema =
this.workspaceDataSourceService.getSchemaName(workspaceId);
await this.workspaceDataSourceService.executeRawQuery(
`UPDATE ${dataSourceSchema}."calendarEventParticipant" SET "workspaceMemberId" = NULL WHERE "handle" = $1`,
[handle],
workspaceId,
transactionManager,
);
}
public async getByIds(
calendarEventParticipantIds: string[],
workspaceId: string,
transactionManager?: EntityManager,
): Promise<ObjectRecord<CalendarEventParticipantWorkspaceEntity>[]> {
if (calendarEventParticipantIds.length === 0) {
return [];
}
const dataSourceSchema =
this.workspaceDataSourceService.getSchemaName(workspaceId);
return await this.workspaceDataSourceService.executeRawQuery(
`SELECT * FROM ${dataSourceSchema}."calendarEventParticipant" WHERE "id" = ANY($1)`,
[calendarEventParticipantIds],
workspaceId,
transactionManager,
);
}
public async getByCalendarEventIds(
calendarEventIds: string[],
workspaceId: string,
transactionManager?: EntityManager,
): Promise<ObjectRecord<CalendarEventParticipantWorkspaceEntity>[]> {
if (calendarEventIds.length === 0) {
return [];
}
const dataSourceSchema =
this.workspaceDataSourceService.getSchemaName(workspaceId);
return await this.workspaceDataSourceService.executeRawQuery(
`SELECT * FROM ${dataSourceSchema}."calendarEventParticipant" WHERE "calendarEventId" = ANY($1)`,
[calendarEventIds],
workspaceId,
transactionManager,
);
}
public async deleteByIds(
calendarEventParticipantIds: string[],
workspaceId: string,
transactionManager?: EntityManager,
): Promise<void> {
if (calendarEventParticipantIds.length === 0) {
return;
}
const dataSourceSchema =
this.workspaceDataSourceService.getSchemaName(workspaceId);
await this.workspaceDataSourceService.executeRawQuery(
`DELETE FROM ${dataSourceSchema}."calendarEventParticipant" WHERE "id" = ANY($1)`,
[calendarEventParticipantIds],
workspaceId,
transactionManager,
);
}
public async updateCalendarEventParticipantsAndReturnNewOnes(
calendarEventParticipants: CalendarEventParticipant[],
workspaceId: string,
transactionManager?: EntityManager,
): Promise<CalendarEventParticipant[]> {
if (calendarEventParticipants.length === 0) {
return [];
}
const dataSourceSchema =
this.workspaceDataSourceService.getSchemaName(workspaceId);
const existingCalendarEventParticipants = await this.getByCalendarEventIds(
calendarEventParticipants.map(
(calendarEventParticipant) => calendarEventParticipant.calendarEventId,
),
workspaceId,
transactionManager,
);
const calendarEventParticipantsToDelete = differenceWith(
existingCalendarEventParticipants,
calendarEventParticipants,
(existingCalendarEventParticipant, calendarEventParticipant) =>
existingCalendarEventParticipant.handle ===
calendarEventParticipant.handle,
);
const newCalendarEventParticipants = differenceWith(
calendarEventParticipants,
existingCalendarEventParticipants,
(calendarEventParticipant, existingCalendarEventParticipant) =>
calendarEventParticipant.handle ===
existingCalendarEventParticipant.handle,
);
await this.deleteByIds(
calendarEventParticipantsToDelete.map(
(calendarEventParticipant) => calendarEventParticipant.id,
),
workspaceId,
transactionManager,
);
const { flattenedValues, valuesString } =
getFlattenedValuesAndValuesStringForBatchRawQuery(
calendarEventParticipants,
{
calendarEventId: 'uuid',
handle: 'text',
displayName: 'text',
isOrganizer: 'boolean',
responseStatus: `${dataSourceSchema}."calendarEventParticipant_responseStatus_enum"`,
},
);
await this.workspaceDataSourceService.executeRawQuery(
`UPDATE ${dataSourceSchema}."calendarEventParticipant" AS "calendarEventParticipant"
SET "displayName" = "newValues"."displayName",
"isOrganizer" = "newValues"."isOrganizer",
"responseStatus" = "newValues"."responseStatus"
FROM (VALUES ${valuesString}) AS "newValues"("calendarEventId", "handle", "displayName", "isOrganizer", "responseStatus")
WHERE "calendarEventParticipant"."handle" = "newValues"."handle"
AND "calendarEventParticipant"."calendarEventId" = "newValues"."calendarEventId"`,
flattenedValues,
workspaceId,
transactionManager,
);
return newCalendarEventParticipants;
}
public async getWithoutPersonIdAndWorkspaceMemberId(
workspaceId: string,
transactionManager?: EntityManager,
): Promise<CalendarEventParticipantWithId[]> {
if (!workspaceId) {
throw new Error('WorkspaceId is required');
}
const dataSourceSchema =
this.workspaceDataSourceService.getSchemaName(workspaceId);
const calendarEventParticipants: CalendarEventParticipantWithId[] =
await this.workspaceDataSourceService.executeRawQuery(
`SELECT "calendarEventParticipant".*
FROM ${dataSourceSchema}."calendarEventParticipant" AS "calendarEventParticipant"
WHERE "calendarEventParticipant"."personId" IS NULL
AND "calendarEventParticipant"."workspaceMemberId" IS NULL`,
[],
workspaceId,
transactionManager,
);
return calendarEventParticipants;
}
public async getByCalendarChannelIdWithoutPersonIdAndWorkspaceMemberId(
calendarChannelId: string,
workspaceId: string,
transactionManager?: EntityManager,
): Promise<CalendarEventParticipantWithId[]> {
if (!workspaceId) {
throw new Error('WorkspaceId is required');
}
const dataSourceSchema =
this.workspaceDataSourceService.getSchemaName(workspaceId);
const calendarEventParticipants: CalendarEventParticipantWithId[] =
await this.workspaceDataSourceService.executeRawQuery(
`SELECT "calendarEventParticipant".*
FROM ${dataSourceSchema}."calendarEventParticipant" AS "calendarEventParticipant"
LEFT JOIN ${dataSourceSchema}."calendarEvent" AS "calendarEvent" ON "calendarEventParticipant"."calendarEventId" = "calendarEvent"."id"
LEFT JOIN ${dataSourceSchema}."calendarChannelEventAssociation" AS "calendarChannelEventAssociation" ON "calendarEvent"."id" = "calendarChannelEventAssociation"."calendarEventId"
WHERE "calendarChannelEventAssociation"."calendarChannelId" = $1
AND "calendarEventParticipant"."personId" IS NULL
AND "calendarEventParticipant"."workspaceMemberId" IS NULL`,
[calendarChannelId],
workspaceId,
transactionManager,
);
return calendarEventParticipants;
}
}

View File

@ -1,227 +0,0 @@
import { Injectable } from '@nestjs/common';
import { EntityManager } from 'typeorm';
import { WorkspaceDataSourceService } from 'src/engine/workspace-datasource/workspace-datasource.service';
import { ObjectRecord } from 'src/engine/workspace-manager/workspace-sync-metadata/types/object-record';
import { CalendarEventWorkspaceEntity } from 'src/modules/calendar/standard-objects/calendar-event.workspace-entity';
import { getFlattenedValuesAndValuesStringForBatchRawQuery } from 'src/modules/calendar/utils/get-flattened-values-and-values-string-for-batch-raw-query.util';
import { CalendarEvent } from 'src/modules/calendar/types/calendar-event';
import { CalendarEventParticipantWorkspaceEntity } from 'src/modules/calendar/standard-objects/calendar-event-participant.workspace-entity';
@Injectable()
export class CalendarEventRepository {
constructor(
private readonly workspaceDataSourceService: WorkspaceDataSourceService,
) {}
public async getByIds(
calendarEventIds: string[],
workspaceId: string,
transactionManager?: EntityManager,
): Promise<ObjectRecord<CalendarEventWorkspaceEntity>[]> {
if (calendarEventIds.length === 0) {
return [];
}
const dataSourceSchema =
this.workspaceDataSourceService.getSchemaName(workspaceId);
return await this.workspaceDataSourceService.executeRawQuery(
`SELECT * FROM ${dataSourceSchema}."calendarEvent" WHERE "id" = ANY($1)`,
[calendarEventIds],
workspaceId,
transactionManager,
);
}
public async getByICalUIDs(
iCalUIDs: string[],
workspaceId: string,
transactionManager?: EntityManager,
): Promise<ObjectRecord<CalendarEventWorkspaceEntity>[]> {
if (iCalUIDs.length === 0) {
return [];
}
const dataSourceSchema =
this.workspaceDataSourceService.getSchemaName(workspaceId);
return await this.workspaceDataSourceService.executeRawQuery(
`SELECT * FROM ${dataSourceSchema}."calendarEvent" WHERE "iCalUID" = ANY($1)`,
[iCalUIDs],
workspaceId,
transactionManager,
);
}
public async deleteByIds(
calendarEventIds: string[],
workspaceId: string,
transactionManager?: EntityManager,
): Promise<void> {
if (calendarEventIds.length === 0) {
return;
}
const dataSourceSchema =
this.workspaceDataSourceService.getSchemaName(workspaceId);
await this.workspaceDataSourceService.executeRawQuery(
`DELETE FROM ${dataSourceSchema}."calendarEvent" WHERE "id" = ANY($1)`,
[calendarEventIds],
workspaceId,
transactionManager,
);
}
public async getNonAssociatedCalendarEventIdsPaginated(
limit: number,
offset: number,
workspaceId: string,
transactionManager?: EntityManager,
): Promise<ObjectRecord<CalendarEventParticipantWorkspaceEntity>[]> {
const dataSourceSchema =
this.workspaceDataSourceService.getSchemaName(workspaceId);
const nonAssociatedCalendarEvents =
await this.workspaceDataSourceService.executeRawQuery(
`SELECT m.id FROM ${dataSourceSchema}."calendarEvent" m
LEFT JOIN ${dataSourceSchema}."calendarChannelEventAssociation" ccea
ON m.id = ccea."calendarEventId"
WHERE ccea.id IS NULL
LIMIT $1 OFFSET $2`,
[limit, offset],
workspaceId,
transactionManager,
);
return nonAssociatedCalendarEvents.map(({ id }) => id);
}
public async getICalUIDCalendarEventIdMap(
iCalUIDs: string[],
workspaceId: string,
transactionManager?: EntityManager,
): Promise<Map<string, string>> {
if (iCalUIDs.length === 0) {
return new Map();
}
const dataSourceSchema =
this.workspaceDataSourceService.getSchemaName(workspaceId);
const calendarEvents:
| {
id: string;
iCalUID: string;
}[]
| undefined = await this.workspaceDataSourceService.executeRawQuery(
`SELECT id, "iCalUID" FROM ${dataSourceSchema}."calendarEvent" WHERE "iCalUID" = ANY($1)`,
[iCalUIDs],
workspaceId,
transactionManager,
);
const iCalUIDsCalendarEventIdsMap = new Map<string, string>();
calendarEvents?.forEach((calendarEvent) => {
iCalUIDsCalendarEventIdsMap.set(calendarEvent.iCalUID, calendarEvent.id);
});
return iCalUIDsCalendarEventIdsMap;
}
public async saveCalendarEvents(
calendarEvents: CalendarEvent[],
workspaceId: string,
transactionManager?: EntityManager,
): Promise<void> {
if (calendarEvents.length === 0) {
return;
}
const dataSourceSchema =
this.workspaceDataSourceService.getSchemaName(workspaceId);
const { flattenedValues, valuesString } =
getFlattenedValuesAndValuesStringForBatchRawQuery(calendarEvents, {
id: 'uuid',
title: 'text',
isCanceled: 'boolean',
isFullDay: 'boolean',
startsAt: 'timestamptz',
endsAt: 'timestamptz',
externalCreatedAt: 'timestamptz',
externalUpdatedAt: 'timestamptz',
description: 'text',
location: 'text',
iCalUID: 'text',
conferenceSolution: 'text',
conferenceLinkLabel: 'text',
conferenceLinkUrl: 'text',
recurringEventExternalId: 'text',
});
await this.workspaceDataSourceService.executeRawQuery(
`INSERT INTO ${dataSourceSchema}."calendarEvent" ("id", "title", "isCanceled", "isFullDay", "startsAt", "endsAt", "externalCreatedAt", "externalUpdatedAt", "description", "location", "iCalUID", "conferenceSolution", "conferenceLinkLabel", "conferenceLinkUrl", "recurringEventExternalId") VALUES ${valuesString}`,
flattenedValues,
workspaceId,
transactionManager,
);
}
public async updateCalendarEvents(
calendarEvents: CalendarEvent[],
workspaceId: string,
transactionManager?: EntityManager,
): Promise<void> {
if (calendarEvents.length === 0) {
return;
}
const dataSourceSchema =
this.workspaceDataSourceService.getSchemaName(workspaceId);
const { flattenedValues, valuesString } =
getFlattenedValuesAndValuesStringForBatchRawQuery(calendarEvents, {
title: 'text',
isCanceled: 'boolean',
isFullDay: 'boolean',
startsAt: 'timestamptz',
endsAt: 'timestamptz',
externalCreatedAt: 'timestamptz',
externalUpdatedAt: 'timestamptz',
description: 'text',
location: 'text',
iCalUID: 'text',
conferenceSolution: 'text',
conferenceLinkLabel: 'text',
conferenceLinkUrl: 'text',
recurringEventExternalId: 'text',
});
await this.workspaceDataSourceService.executeRawQuery(
`UPDATE ${dataSourceSchema}."calendarEvent" AS "calendarEvent"
SET "title" = "newData"."title",
"isCanceled" = "newData"."isCanceled",
"isFullDay" = "newData"."isFullDay",
"startsAt" = "newData"."startsAt",
"endsAt" = "newData"."endsAt",
"externalCreatedAt" = "newData"."externalCreatedAt",
"externalUpdatedAt" = "newData"."externalUpdatedAt",
"description" = "newData"."description",
"location" = "newData"."location",
"conferenceSolution" = "newData"."conferenceSolution",
"conferenceLinkLabel" = "newData"."conferenceLinkLabel",
"conferenceLinkUrl" = "newData"."conferenceLinkUrl",
"recurringEventExternalId" = "newData"."recurringEventExternalId"
FROM (VALUES ${valuesString})
AS "newData"("title", "isCanceled", "isFullDay", "startsAt", "endsAt", "externalCreatedAt", "externalUpdatedAt", "description", "location", "iCalUID", "conferenceSolution", "conferenceLinkLabel", "conferenceLinkUrl", "recurringEventExternalId")
WHERE "calendarEvent"."iCalUID" = "newData"."iCalUID"`,
flattenedValues,
workspaceId,
transactionManager,
);
}
}

View File

@ -1,13 +1,11 @@
import { Module } from '@nestjs/common';
import { ObjectMetadataRepositoryModule } from 'src/engine/object-metadata-repository/object-metadata-repository.module';
import { TwentyORMModule } from 'src/engine/twenty-orm/twenty-orm.module';
import { CalendarEventCleanerService } from 'src/modules/calendar/services/calendar-event-cleaner/calendar-event-cleaner.service';
import { CalendarEventWorkspaceEntity } from 'src/modules/calendar/standard-objects/calendar-event.workspace-entity';
@Module({
imports: [
ObjectMetadataRepositoryModule.forFeature([CalendarEventWorkspaceEntity]),
],
imports: [TwentyORMModule.forFeature([CalendarEventWorkspaceEntity])],
providers: [CalendarEventCleanerService],
exports: [CalendarEventCleanerService],
})

View File

@ -1,27 +1,40 @@
import { Injectable } from '@nestjs/common';
import { InjectObjectMetadataRepository } from 'src/engine/object-metadata-repository/object-metadata-repository.decorator';
import { CalendarEventRepository } from 'src/modules/calendar/repositories/calendar-event.repository';
import { Any, IsNull } from 'typeorm';
import { InjectWorkspaceRepository } from 'src/engine/twenty-orm/decorators/inject-workspace-repository.decorator';
import { WorkspaceRepository } from 'src/engine/twenty-orm/repository/workspace.repository';
import { CalendarEventWorkspaceEntity } from 'src/modules/calendar/standard-objects/calendar-event.workspace-entity';
import { deleteUsingPagination } from 'src/modules/messaging/message-cleaner/utils/delete-using-pagination.util';
@Injectable()
export class CalendarEventCleanerService {
constructor(
@InjectObjectMetadataRepository(CalendarEventWorkspaceEntity)
private readonly calendarEventRepository: CalendarEventRepository,
@InjectWorkspaceRepository(CalendarEventWorkspaceEntity)
private readonly calendarEventRepository: WorkspaceRepository<CalendarEventWorkspaceEntity>,
) {}
public async cleanWorkspaceCalendarEvents(workspaceId: string) {
await deleteUsingPagination(
workspaceId,
500,
this.calendarEventRepository.getNonAssociatedCalendarEventIdsPaginated.bind(
this.calendarEventRepository,
),
this.calendarEventRepository.deleteByIds.bind(
this.calendarEventRepository,
),
async (limit, offset) => {
const nonAssociatedCalendarEvents =
await this.calendarEventRepository.find({
where: {
calendarChannelEventAssociations: {
id: IsNull(),
},
},
take: limit,
skip: offset,
});
return nonAssociatedCalendarEvents.map(({ id }) => id);
},
async (ids) => {
await this.calendarEventRepository.delete({ id: Any(ids) });
},
);
}
}

View File

@ -1,14 +1,17 @@
import { Module } from '@nestjs/common';
import { ObjectMetadataRepositoryModule } from 'src/engine/object-metadata-repository/object-metadata-repository.module';
import { TwentyORMModule } from 'src/engine/twenty-orm/twenty-orm.module';
import { WorkspaceDataSourceModule } from 'src/engine/workspace-datasource/workspace-datasource.module';
import { AddPersonIdAndWorkspaceMemberIdModule } from 'src/modules/calendar-messaging-participant/services/add-person-id-and-workspace-member-id/add-person-id-and-workspace-member-id.module';
import { CalendarEventParticipantService } from 'src/modules/calendar/services/calendar-event-participant/calendar-event-participant.service';
import { CalendarEventParticipantWorkspaceEntity } from 'src/modules/calendar/standard-objects/calendar-event-participant.workspace-entity';
import { PersonWorkspaceEntity } from 'src/modules/person/standard-objects/person.workspace-entity';
@Module({
imports: [
WorkspaceDataSourceModule,
TwentyORMModule.forFeature([CalendarEventParticipantWorkspaceEntity]),
ObjectMetadataRepositoryModule.forFeature([PersonWorkspaceEntity]),
AddPersonIdAndWorkspaceMemberIdModule,
],

View File

@ -1,7 +1,7 @@
import { Injectable } from '@nestjs/common';
import { EventEmitter2 } from '@nestjs/event-emitter';
import { EntityManager } from 'typeorm';
import { Any, EntityManager } from 'typeorm';
import { InjectObjectMetadataRepository } from 'src/engine/object-metadata-repository/object-metadata-repository.decorator';
import { PersonRepository } from 'src/modules/person/repositories/person.repository';
@ -9,17 +9,18 @@ import { PersonWorkspaceEntity } from 'src/modules/person/standard-objects/perso
import { WorkspaceDataSourceService } from 'src/engine/workspace-datasource/workspace-datasource.service';
import { getFlattenedValuesAndValuesStringForBatchRawQuery } from 'src/modules/calendar/utils/get-flattened-values-and-values-string-for-batch-raw-query.util';
import { CalendarEventParticipant } from 'src/modules/calendar/types/calendar-event';
import { CalendarEventParticipantRepository } from 'src/modules/calendar/repositories/calendar-event-participant.repository';
import { CalendarEventParticipantWorkspaceEntity } from 'src/modules/calendar/standard-objects/calendar-event-participant.workspace-entity';
import { AddPersonIdAndWorkspaceMemberIdService } from 'src/modules/calendar-messaging-participant/services/add-person-id-and-workspace-member-id/add-person-id-and-workspace-member-id.service';
import { ObjectRecord } from 'src/engine/workspace-manager/workspace-sync-metadata/types/object-record';
import { InjectWorkspaceRepository } from 'src/engine/twenty-orm/decorators/inject-workspace-repository.decorator';
import { WorkspaceRepository } from 'src/engine/twenty-orm/repository/workspace.repository';
@Injectable()
export class CalendarEventParticipantService {
constructor(
private readonly workspaceDataSourceService: WorkspaceDataSourceService,
@InjectObjectMetadataRepository(CalendarEventParticipantWorkspaceEntity)
private readonly calendarEventParticipantRepository: CalendarEventParticipantRepository,
@InjectWorkspaceRepository(CalendarEventParticipantWorkspaceEntity)
private readonly calendarEventParticipantRepository: WorkspaceRepository<CalendarEventParticipantWorkspaceEntity>,
@InjectObjectMetadataRepository(PersonWorkspaceEntity)
private readonly personRepository: PersonRepository,
private readonly addPersonIdAndWorkspaceMemberIdService: AddPersonIdAndWorkspaceMemberIdService,
@ -31,11 +32,11 @@ export class CalendarEventParticipantService {
workspaceId: string,
transactionManager?: EntityManager,
): Promise<ObjectRecord<CalendarEventParticipantWorkspaceEntity>[]> {
const participants =
await this.calendarEventParticipantRepository.getByHandles(
createdPeople.map((person) => person.email),
workspaceId,
);
const participants = await this.calendarEventParticipantRepository.find({
where: {
handle: Any(createdPeople.map((person) => person.email)),
},
});
if (!participants) return [];
@ -132,33 +133,50 @@ export class CalendarEventParticipantService {
workspaceMemberId?: string,
) {
const calendarEventParticipantsToUpdate =
await this.calendarEventParticipantRepository.getByHandles(
[email],
workspaceId,
);
await this.calendarEventParticipantRepository.find({
where: {
handle: email,
},
});
const calendarEventParticipantIdsToUpdate =
calendarEventParticipantsToUpdate.map((participant) => participant.id);
if (personId) {
await this.calendarEventParticipantRepository.update(
{
id: Any(calendarEventParticipantIdsToUpdate),
},
{
person: {
id: personId,
},
},
);
const updatedCalendarEventParticipants =
await this.calendarEventParticipantRepository.updateParticipantsPersonIdAndReturn(
calendarEventParticipantIdsToUpdate,
personId,
workspaceId,
);
await this.calendarEventParticipantRepository.find({
where: {
id: Any(calendarEventParticipantIdsToUpdate),
},
});
this.eventEmitter.emit(`calendarEventParticipant.matched`, {
workspaceId,
userId: null,
workspaceMemberId: null,
calendarEventParticipants: updatedCalendarEventParticipants,
});
}
if (workspaceMemberId) {
await this.calendarEventParticipantRepository.updateParticipantsWorkspaceMemberId(
calendarEventParticipantIdsToUpdate,
workspaceMemberId,
workspaceId,
await this.calendarEventParticipantRepository.update(
{
id: Any(calendarEventParticipantIdsToUpdate),
},
{
workspaceMember: {
id: workspaceMemberId,
},
},
);
}
}
@ -170,15 +188,23 @@ export class CalendarEventParticipantService {
workspaceMemberId?: string,
) {
if (personId) {
await this.calendarEventParticipantRepository.removePersonIdByHandle(
handle,
workspaceId,
await this.calendarEventParticipantRepository.update(
{
handle,
},
{
person: null,
},
);
}
if (workspaceMemberId) {
await this.calendarEventParticipantRepository.removeWorkspaceMemberIdByHandle(
handle,
workspaceId,
await this.calendarEventParticipantRepository.update(
{
handle,
},
{
workspaceMember: null,
},
);
}
}

View File

@ -3,6 +3,7 @@ import { TypeOrmModule } from '@nestjs/typeorm';
import { FeatureFlagEntity } from 'src/engine/core-modules/feature-flag/feature-flag.entity';
import { ObjectMetadataRepositoryModule } from 'src/engine/object-metadata-repository/object-metadata-repository.module';
import { TwentyORMModule } from 'src/engine/twenty-orm/twenty-orm.module';
import { WorkspaceDataSourceModule } from 'src/engine/workspace-datasource/workspace-datasource.module';
import { CalendarEventCleanerModule } from 'src/modules/calendar/services/calendar-event-cleaner/calendar-event-cleaner.module';
import { CalendarEventParticipantModule } from 'src/modules/calendar/services/calendar-event-participant/calendar-event-participant.module';
@ -20,12 +21,14 @@ import { WorkspaceMemberWorkspaceEntity } from 'src/modules/workspace-member/sta
@Module({
imports: [
CalendarProvidersModule,
ObjectMetadataRepositoryModule.forFeature([
ConnectedAccountWorkspaceEntity,
TwentyORMModule.forFeature([
CalendarEventWorkspaceEntity,
CalendarChannelWorkspaceEntity,
CalendarChannelEventAssociationWorkspaceEntity,
CalendarEventParticipantWorkspaceEntity,
]),
ObjectMetadataRepositoryModule.forFeature([
ConnectedAccountWorkspaceEntity,
BlocklistWorkspaceEntity,
PersonWorkspaceEntity,
WorkspaceMemberWorkspaceEntity,

View File

@ -2,7 +2,7 @@ import { Injectable, Logger } from '@nestjs/common';
import { InjectRepository } from '@nestjs/typeorm';
import { EventEmitter2 } from '@nestjs/event-emitter';
import { Repository } from 'typeorm';
import { Any, Repository } from 'typeorm';
import { calendar_v3 as calendarV3 } from 'googleapis';
import { GaxiosError } from 'gaxios';
@ -13,12 +13,7 @@ import {
FeatureFlagKeys,
} from 'src/engine/core-modules/feature-flag/feature-flag.entity';
import { GoogleCalendarClientProvider } from 'src/modules/calendar/services/providers/google-calendar/google-calendar.provider';
import { CalendarChannelEventAssociationRepository } from 'src/modules/calendar/repositories/calendar-channel-event-association.repository';
import { CalendarChannelRepository } from 'src/modules/calendar/repositories/calendar-channel.repository';
import { WorkspaceDataSourceService } from 'src/engine/workspace-datasource/workspace-datasource.service';
import { CalendarEventRepository } from 'src/modules/calendar/repositories/calendar-event.repository';
import { formatGoogleCalendarEvent } from 'src/modules/calendar/utils/format-google-calendar-event.util';
import { CalendarEventParticipantRepository } from 'src/modules/calendar/repositories/calendar-event-participant.repository';
import { ConnectedAccountWorkspaceEntity } from 'src/modules/connected-account/standard-objects/connected-account.workspace-entity';
import { InjectObjectMetadataRepository } from 'src/engine/object-metadata-repository/object-metadata-repository.decorator';
import { CalendarEventWorkspaceEntity } from 'src/modules/calendar/standard-objects/calendar-event.workspace-entity';
@ -28,7 +23,10 @@ import { CalendarEventParticipantWorkspaceEntity } from 'src/modules/calendar/st
import { BlocklistWorkspaceEntity } from 'src/modules/connected-account/standard-objects/blocklist.workspace-entity';
import { CalendarEventCleanerService } from 'src/modules/calendar/services/calendar-event-cleaner/calendar-event-cleaner.service';
import { CalendarEventParticipantService } from 'src/modules/calendar/services/calendar-event-participant/calendar-event-participant.service';
import { CalendarEventWithParticipants } from 'src/modules/calendar/types/calendar-event';
import {
CalendarEventParticipant,
CalendarEventWithParticipants,
} from 'src/modules/calendar/types/calendar-event';
import { filterOutBlocklistedEvents } from 'src/modules/calendar/utils/filter-out-blocklisted-events.util';
import { InjectMessageQueue } from 'src/engine/integrations/message-queue/decorators/message-queue.decorator';
import { MessageQueue } from 'src/engine/integrations/message-queue/message-queue.constants';
@ -37,7 +35,12 @@ import {
CreateCompanyAndContactJob,
CreateCompanyAndContactJobData,
} from 'src/modules/connected-account/auto-companies-and-contacts-creation/jobs/create-company-and-contact.job';
import { InjectWorkspaceRepository } from 'src/engine/twenty-orm/decorators/inject-workspace-repository.decorator';
import { WorkspaceRepository } from 'src/engine/twenty-orm/repository/workspace.repository';
import { ObjectRecord } from 'src/engine/workspace-manager/workspace-sync-metadata/types/object-record';
import { isDefined } from 'src/utils/is-defined';
import { WorkspaceDataSource } from 'src/engine/twenty-orm/datasource/workspace.datasource';
import { InjectWorkspaceDatasource } from 'src/engine/twenty-orm/decorators/inject-workspace-datasource.decorator';
@Injectable()
export class GoogleCalendarSyncService {
@ -47,21 +50,20 @@ export class GoogleCalendarSyncService {
private readonly googleCalendarClientProvider: GoogleCalendarClientProvider,
@InjectObjectMetadataRepository(ConnectedAccountWorkspaceEntity)
private readonly connectedAccountRepository: ConnectedAccountRepository,
@InjectObjectMetadataRepository(CalendarEventWorkspaceEntity)
private readonly calendarEventRepository: CalendarEventRepository,
@InjectObjectMetadataRepository(CalendarChannelWorkspaceEntity)
private readonly calendarChannelRepository: CalendarChannelRepository,
@InjectObjectMetadataRepository(
CalendarChannelEventAssociationWorkspaceEntity,
)
private readonly calendarChannelEventAssociationRepository: CalendarChannelEventAssociationRepository,
@InjectObjectMetadataRepository(CalendarEventParticipantWorkspaceEntity)
private readonly calendarEventParticipantsRepository: CalendarEventParticipantRepository,
@InjectWorkspaceRepository(CalendarEventWorkspaceEntity)
private readonly calendarEventRepository: WorkspaceRepository<CalendarEventWorkspaceEntity>,
@InjectWorkspaceRepository(CalendarChannelWorkspaceEntity)
private readonly calendarChannelRepository: WorkspaceRepository<CalendarChannelWorkspaceEntity>,
@InjectWorkspaceRepository(CalendarChannelEventAssociationWorkspaceEntity)
private readonly calendarChannelEventAssociationRepository: WorkspaceRepository<CalendarChannelEventAssociationWorkspaceEntity>,
@InjectWorkspaceRepository(CalendarEventParticipantWorkspaceEntity)
private readonly calendarEventParticipantsRepository: WorkspaceRepository<CalendarEventParticipantWorkspaceEntity>,
@InjectObjectMetadataRepository(BlocklistWorkspaceEntity)
private readonly blocklistRepository: BlocklistRepository,
@InjectRepository(FeatureFlagEntity, 'core')
private readonly featureFlagRepository: Repository<FeatureFlagEntity>,
private readonly workspaceDataSourceService: WorkspaceDataSourceService,
@InjectWorkspaceDatasource()
private readonly workspaceDataSource: WorkspaceDataSource,
private readonly calendarEventCleanerService: CalendarEventCleanerService,
private readonly calendarEventParticipantsService: CalendarEventParticipantService,
@InjectMessageQueue(MessageQueue.contactCreationQueue)
@ -92,11 +94,11 @@ export class GoogleCalendarSyncService {
);
}
const calendarChannel =
await this.calendarChannelRepository.getFirstByConnectedAccountId(
connectedAccountId,
workspaceId,
);
const calendarChannel = await this.calendarChannelRepository.findOneBy({
connectedAccount: {
id: connectedAccountId,
},
});
const syncToken = calendarChannel?.syncCursor || undefined;
@ -122,6 +124,12 @@ export class GoogleCalendarSyncService {
return;
}
if (!workspaceMemberId) {
throw new Error(
`Workspace member ID is undefined for connected account ${connectedAccountId} in workspace ${workspaceId}`,
);
}
const blocklist = await this.getBlocklist(workspaceMemberId, workspaceId);
let filteredEvents = filterOutBlocklistedEvents(
@ -143,11 +151,18 @@ export class GoogleCalendarSyncService {
.filter((event) => event.status === 'cancelled')
.map((event) => event.id as string);
const iCalUIDCalendarEventIdMap =
await this.calendarEventRepository.getICalUIDCalendarEventIdMap(
filteredEvents.map((calendarEvent) => calendarEvent.iCalUID as string),
workspaceId,
);
const existingCalendarEvents = await this.calendarEventRepository.find({
where: {
iCalUID: Any(filteredEvents.map((event) => event.iCalUID as string)),
},
});
const iCalUIDCalendarEventIdMap = new Map(
existingCalendarEvents.map((calendarEvent) => [
calendarEvent.iCalUID,
calendarEvent.id,
]),
);
const formattedEvents = filteredEvents.map((event) =>
formatGoogleCalendarEvent(event, iCalUIDCalendarEventIdMap),
@ -157,31 +172,34 @@ export class GoogleCalendarSyncService {
let startTime = Date.now();
const existingEvents = await this.calendarEventRepository.getByICalUIDs(
formattedEvents.map((event) => event.iCalUID),
workspaceId,
const existingEventsICalUIDs = existingCalendarEvents.map(
(calendarEvent) => calendarEvent.iCalUID,
);
const existingEventsICalUIDs = existingEvents.map((event) => event.iCalUID);
let endTime = Date.now();
const eventsToSave = formattedEvents.filter(
(event) => !existingEventsICalUIDs.includes(event.iCalUID),
(calendarEvent) =>
!existingEventsICalUIDs.includes(calendarEvent.iCalUID),
);
const eventsToUpdate = formattedEvents.filter((event) =>
existingEventsICalUIDs.includes(event.iCalUID),
const eventsToUpdate = formattedEvents.filter((calendarEvent) =>
existingEventsICalUIDs.includes(calendarEvent.iCalUID),
);
startTime = Date.now();
const existingCalendarChannelEventAssociations =
await this.calendarChannelEventAssociationRepository.getByEventExternalIdsAndCalendarChannelId(
formattedEvents.map((event) => event.externalId),
calendarChannelId,
workspaceId,
);
await this.calendarChannelEventAssociationRepository.find({
where: {
eventExternalId: Any(
formattedEvents.map((calendarEvent) => calendarEvent.id),
),
calendarChannel: {
id: calendarChannelId,
},
},
});
endTime = Date.now();
@ -193,14 +211,14 @@ export class GoogleCalendarSyncService {
const calendarChannelEventAssociationsToSave = formattedEvents
.filter(
(event) =>
(calendarEvent) =>
!existingCalendarChannelEventAssociations.some(
(association) => association.eventExternalId === event.id,
(association) => association.eventExternalId === calendarEvent.id,
),
)
.map((event) => ({
calendarEventId: event.id,
eventExternalId: event.externalId,
.map((calendarEvent) => ({
calendarEventId: calendarEvent.id,
eventExternalId: calendarEvent.externalId,
calendarChannelId,
}));
@ -216,11 +234,12 @@ export class GoogleCalendarSyncService {
startTime = Date.now();
await this.calendarChannelEventAssociationRepository.deleteByEventExternalIdsAndCalendarChannelId(
cancelledEventExternalIds,
calendarChannelId,
workspaceId,
);
await this.calendarChannelEventAssociationRepository.delete({
eventExternalId: Any(cancelledEventExternalIds),
calendarChannel: {
id: calendarChannelId,
},
});
endTime = Date.now();
@ -257,10 +276,13 @@ export class GoogleCalendarSyncService {
startTime = Date.now();
await this.calendarChannelRepository.updateSyncCursor(
nextSyncToken,
calendarChannel.id,
workspaceId,
await this.calendarChannelRepository.update(
{
id: calendarChannel.id,
},
{
syncCursor: nextSyncToken,
},
);
endTime = Date.now();
@ -337,10 +359,13 @@ export class GoogleCalendarSyncService {
throw error;
}
await this.calendarChannelRepository.updateSyncCursor(
null,
connectedAccountId,
workspaceId,
await this.calendarChannelRepository.update(
{
id: connectedAccountId,
},
{
syncCursor: '',
},
);
this.logger.log(
@ -395,11 +420,6 @@ export class GoogleCalendarSyncService {
calendarChannel: CalendarChannelWorkspaceEntity,
workspaceId: string,
): Promise<void> {
const dataSourceMetadata =
await this.workspaceDataSourceService.connectToWorkspaceDataSource(
workspaceId,
);
const participantsToSave = eventsToSave.flatMap(
(event) => event.participants,
);
@ -415,103 +435,154 @@ export class GoogleCalendarSyncService {
[];
try {
await dataSourceMetadata?.transaction(async (transactionManager) => {
startTime = Date.now();
await this.workspaceDataSource?.transaction(
async (transactionManager) => {
startTime = Date.now();
await this.calendarEventRepository.saveCalendarEvents(
eventsToSave,
workspaceId,
transactionManager,
);
await this.calendarEventRepository.save(
eventsToSave,
{},
transactionManager,
);
endTime = Date.now();
endTime = Date.now();
this.logger.log(
`google calendar sync for workspace ${workspaceId} and account ${
connectedAccount.id
}: saving ${eventsToSave.length} events in ${endTime - startTime}ms.`,
);
this.logger.log(
`google calendar sync for workspace ${workspaceId} and account ${
connectedAccount.id
}: saving ${eventsToSave.length} events in ${
endTime - startTime
}ms.`,
);
startTime = Date.now();
startTime = Date.now();
await this.calendarEventRepository.updateCalendarEvents(
eventsToUpdate,
workspaceId,
transactionManager,
);
await this.calendarChannelRepository.save(
eventsToUpdate,
{},
transactionManager,
);
endTime = Date.now();
endTime = Date.now();
this.logger.log(
`google calendar sync for workspace ${workspaceId} and account ${
connectedAccount.id
}: updating ${eventsToUpdate.length} events in ${
endTime - startTime
}ms.`,
);
this.logger.log(
`google calendar sync for workspace ${workspaceId} and account ${
connectedAccount.id
}: updating ${eventsToUpdate.length} events in ${
endTime - startTime
}ms.`,
);
startTime = Date.now();
startTime = Date.now();
await this.calendarChannelEventAssociationRepository.saveCalendarChannelEventAssociations(
calendarChannelEventAssociationsToSave,
workspaceId,
transactionManager,
);
await this.calendarChannelEventAssociationRepository.save(
calendarChannelEventAssociationsToSave,
{},
transactionManager,
);
endTime = Date.now();
endTime = Date.now();
this.logger.log(
`google calendar sync for workspace ${workspaceId} and account ${
connectedAccount.id
}: saving calendar channel event associations in ${
endTime - startTime
}ms.`,
);
this.logger.log(
`google calendar sync for workspace ${workspaceId} and account ${
connectedAccount.id
}: saving calendar channel event associations in ${
endTime - startTime
}ms.`,
);
startTime = Date.now();
startTime = Date.now();
const newCalendarEventParticipants =
await this.calendarEventParticipantsRepository.updateCalendarEventParticipantsAndReturnNewOnes(
const existingCalendarEventParticipants =
await this.calendarEventParticipantsRepository.find({
where: {
calendarEvent: {
id: Any(
participantsToUpdate
.map((participant) => participant.calendarEventId)
.filter(isDefined),
),
},
},
});
const {
calendarEventParticipantsToDelete,
newCalendarEventParticipants,
} = participantsToUpdate.reduce(
(acc, calendarEventParticipant) => {
const existingCalendarEventParticipant =
existingCalendarEventParticipants.find(
(existingCalendarEventParticipant) =>
existingCalendarEventParticipant.handle ===
calendarEventParticipant.handle,
);
if (existingCalendarEventParticipant) {
acc.calendarEventParticipantsToDelete.push(
existingCalendarEventParticipant,
);
} else {
acc.newCalendarEventParticipants.push(calendarEventParticipant);
}
return acc;
},
{
calendarEventParticipantsToDelete:
[] as CalendarEventParticipantWorkspaceEntity[],
newCalendarEventParticipants: [] as CalendarEventParticipant[],
},
);
await this.calendarEventParticipantsRepository.delete({
id: Any(
calendarEventParticipantsToDelete.map(
(calendarEventParticipant) => calendarEventParticipant.id,
),
),
});
await this.calendarEventParticipantsRepository.save(
participantsToUpdate,
workspaceId,
transactionManager,
);
endTime = Date.now();
endTime = Date.now();
participantsToSave.push(...newCalendarEventParticipants);
participantsToSave.push(...newCalendarEventParticipants);
this.logger.log(
`google calendar sync for workspace ${workspaceId} and account ${
connectedAccount.id
}: updating participants in ${endTime - startTime}ms.`,
);
startTime = Date.now();
const savedCalendarEventParticipants =
await this.calendarEventParticipantsService.saveCalendarEventParticipants(
participantsToSave,
workspaceId,
transactionManager,
this.logger.log(
`google calendar sync for workspace ${workspaceId} and account ${
connectedAccount.id
}: updating participants in ${endTime - startTime}ms.`,
);
savedCalendarEventParticipantsToEmit.push(
...savedCalendarEventParticipants,
);
startTime = Date.now();
endTime = Date.now();
const savedCalendarEventParticipants =
await this.calendarEventParticipantsService.saveCalendarEventParticipants(
participantsToSave,
workspaceId,
transactionManager,
);
this.logger.log(
`google calendar sync for workspace ${workspaceId} and account ${
connectedAccount.id
}: saving participants in ${endTime - startTime}ms.`,
);
});
savedCalendarEventParticipantsToEmit.push(
...savedCalendarEventParticipants,
);
endTime = Date.now();
this.logger.log(
`google calendar sync for workspace ${workspaceId} and account ${
connectedAccount.id
}: saving participants in ${endTime - startTime}ms.`,
);
},
);
this.eventEmitter.emit(`calendarEventParticipant.matched`, {
workspaceId,
userId: connectedAccount.accountOwnerId,
workspaceMemberId: connectedAccount.accountOwnerId,
calendarEventParticipants: savedCalendarEventParticipantsToEmit,
});

View File

@ -1,13 +1,11 @@
import { Module } from '@nestjs/common';
import { ObjectMetadataRepositoryModule } from 'src/engine/object-metadata-repository/object-metadata-repository.module';
import { TwentyORMModule } from 'src/engine/twenty-orm/twenty-orm.module';
import { WorkspaceGoogleCalendarSyncService } from 'src/modules/calendar/services/workspace-google-calendar-sync/workspace-google-calendar-sync.service';
import { CalendarChannelWorkspaceEntity } from 'src/modules/calendar/standard-objects/calendar-channel.workspace-entity';
@Module({
imports: [
ObjectMetadataRepositoryModule.forFeature([CalendarChannelWorkspaceEntity]),
],
imports: [TwentyORMModule.forFeature([CalendarChannelWorkspaceEntity])],
providers: [WorkspaceGoogleCalendarSyncService],
exports: [WorkspaceGoogleCalendarSyncService],
})

View File

@ -3,19 +3,19 @@ import { Injectable } from '@nestjs/common';
import { InjectMessageQueue } from 'src/engine/integrations/message-queue/decorators/message-queue.decorator';
import { MessageQueue } from 'src/engine/integrations/message-queue/message-queue.constants';
import { MessageQueueService } from 'src/engine/integrations/message-queue/services/message-queue.service';
import { InjectObjectMetadataRepository } from 'src/engine/object-metadata-repository/object-metadata-repository.decorator';
import { InjectWorkspaceRepository } from 'src/engine/twenty-orm/decorators/inject-workspace-repository.decorator';
import { WorkspaceRepository } from 'src/engine/twenty-orm/repository/workspace.repository';
import {
GoogleCalendarSyncJobData,
GoogleCalendarSyncJob,
} from 'src/modules/calendar/jobs/google-calendar-sync.job';
import { CalendarChannelRepository } from 'src/modules/calendar/repositories/calendar-channel.repository';
import { CalendarChannelWorkspaceEntity } from 'src/modules/calendar/standard-objects/calendar-channel.workspace-entity';
@Injectable()
export class WorkspaceGoogleCalendarSyncService {
constructor(
@InjectObjectMetadataRepository(CalendarChannelWorkspaceEntity)
private readonly calendarChannelRepository: CalendarChannelRepository,
@InjectWorkspaceRepository(CalendarChannelWorkspaceEntity)
private readonly calendarChannelRepository: WorkspaceRepository<CalendarChannelWorkspaceEntity>,
@InjectMessageQueue(MessageQueue.calendarQueue)
private readonly messageQueueService: MessageQueueService,
) {}
@ -23,8 +23,7 @@ export class WorkspaceGoogleCalendarSyncService {
public async startWorkspaceGoogleCalendarSync(
workspaceId: string,
): Promise<void> {
const calendarChannels =
await this.calendarChannelRepository.getAll(workspaceId);
const calendarChannels = await this.calendarChannelRepository.find({});
for (const calendarChannel of calendarChannels) {
if (!calendarChannel?.isSyncEnabled) {
@ -35,7 +34,7 @@ export class WorkspaceGoogleCalendarSyncService {
GoogleCalendarSyncJob.name,
{
workspaceId,
connectedAccountId: calendarChannel.connectedAccountId,
connectedAccountId: calendarChannel.connectedAccount.id,
},
{
retryLimit: 2,

View File

@ -120,7 +120,7 @@ export class CalendarEventParticipantWorkspaceEntity extends BaseWorkspaceEntity
inverseSideFieldKey: 'calendarEventParticipants',
})
@WorkspaceIsNullable()
person: Relation<PersonWorkspaceEntity>;
person: Relation<PersonWorkspaceEntity> | null;
@WorkspaceRelation({
standardId: CALENDAR_EVENT_PARTICIPANT_STANDARD_FIELD_IDS.workspaceMember,
@ -133,5 +133,5 @@ export class CalendarEventParticipantWorkspaceEntity extends BaseWorkspaceEntity
inverseSideFieldKey: 'calendarEventParticipants',
})
@WorkspaceIsNullable()
workspaceMember: Relation<WorkspaceMemberWorkspaceEntity>;
workspaceMember: Relation<WorkspaceMemberWorkspaceEntity> | null;
}

View File

@ -68,7 +68,7 @@ export class CompanyWorkspaceEntity extends BaseWorkspaceEntity {
icon: 'IconUsers',
})
@WorkspaceIsNullable()
employees: number;
employees: number | null;
@WorkspaceField({
standardId: COMPANY_STANDARD_FIELD_IDS.linkedinLink,
@ -78,7 +78,7 @@ export class CompanyWorkspaceEntity extends BaseWorkspaceEntity {
icon: 'IconBrandLinkedin',
})
@WorkspaceIsNullable()
linkedinLink: LinkMetadata;
linkedinLink: LinkMetadata | null;
@WorkspaceField({
standardId: COMPANY_STANDARD_FIELD_IDS.xLink,
@ -88,7 +88,7 @@ export class CompanyWorkspaceEntity extends BaseWorkspaceEntity {
icon: 'IconBrandX',
})
@WorkspaceIsNullable()
xLink: LinkMetadata;
xLink: LinkMetadata | null;
@WorkspaceField({
standardId: COMPANY_STANDARD_FIELD_IDS.annualRecurringRevenue,
@ -99,7 +99,7 @@ export class CompanyWorkspaceEntity extends BaseWorkspaceEntity {
icon: 'IconMoneybag',
})
@WorkspaceIsNullable()
annualRecurringRevenue: CurrencyMetadata;
annualRecurringRevenue: CurrencyMetadata | null;
@WorkspaceField({
standardId: COMPANY_STANDARD_FIELD_IDS.idealCustomerProfile,
@ -121,7 +121,7 @@ export class CompanyWorkspaceEntity extends BaseWorkspaceEntity {
})
@WorkspaceIsSystem()
@WorkspaceIsNullable()
position: number;
position: number | null;
// Relations
@WorkspaceRelation({
@ -149,7 +149,7 @@ export class CompanyWorkspaceEntity extends BaseWorkspaceEntity {
onDelete: RelationOnDeleteAction.SET_NULL,
})
@WorkspaceIsNullable()
accountOwner: Relation<WorkspaceMemberWorkspaceEntity>;
accountOwner: Relation<WorkspaceMemberWorkspaceEntity> | null;
@WorkspaceRelation({
standardId: COMPANY_STANDARD_FIELD_IDS.activityTargets,

View File

@ -8,7 +8,6 @@ import { ObjectMetadataRepositoryModule } from 'src/engine/object-metadata-repos
import { PersonWorkspaceEntity } from 'src/modules/person/standard-objects/person.workspace-entity';
import { WorkspaceMemberWorkspaceEntity } from 'src/modules/workspace-member/standard-objects/workspace-member.workspace-entity';
import { WorkspaceDataSourceModule } from 'src/engine/workspace-datasource/workspace-datasource.module';
import { CalendarEventParticipantWorkspaceEntity } from 'src/modules/calendar/standard-objects/calendar-event-participant.workspace-entity';
import { CalendarEventParticipantModule } from 'src/modules/calendar/services/calendar-event-participant/calendar-event-participant.module';
import { FeatureFlagEntity } from 'src/engine/core-modules/feature-flag/feature-flag.entity';
import { MessagingCommonModule } from 'src/modules/messaging/common/messaging-common.module';
@ -20,7 +19,6 @@ import { MessagingCommonModule } from 'src/modules/messaging/common/messaging-co
ObjectMetadataRepositoryModule.forFeature([
PersonWorkspaceEntity,
WorkspaceMemberWorkspaceEntity,
CalendarEventParticipantWorkspaceEntity,
]),
MessagingCommonModule,
WorkspaceDataSourceModule,

View File

@ -1,13 +1,12 @@
import { Process } from 'src/engine/integrations/message-queue/decorators/process.decorator';
import { Processor } from 'src/engine/integrations/message-queue/decorators/processor.decorator';
import { MessageQueue } from 'src/engine/integrations/message-queue/message-queue.constants';
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;
connectedAccount: ObjectRecord<ConnectedAccountWorkspaceEntity>;
connectedAccount: ConnectedAccountWorkspaceEntity;
contactsToCreate: {
displayName: string;
handle: string;

View File

@ -15,14 +15,15 @@ import { PersonWorkspaceEntity } from 'src/modules/person/standard-objects/perso
import { WorkspaceMemberWorkspaceEntity } from 'src/modules/workspace-member/standard-objects/workspace-member.workspace-entity';
import { getUniqueContactsAndHandles } from 'src/modules/connected-account/auto-companies-and-contacts-creation/utils/get-unique-contacts-and-handles.util';
import { Contacts } from 'src/modules/connected-account/auto-companies-and-contacts-creation/types/contact.type';
import { WorkspaceDataSourceService } from 'src/engine/workspace-datasource/workspace-datasource.service';
import { CalendarEventParticipantService } from 'src/modules/calendar/services/calendar-event-participant/calendar-event-participant.service';
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';
import { WorkspaceDataSource } from 'src/engine/twenty-orm/datasource/workspace.datasource';
import { InjectWorkspaceDatasource } from 'src/engine/twenty-orm/decorators/inject-workspace-datasource.decorator';
import { ConnectedAccountWorkspaceEntity } from 'src/modules/connected-account/standard-objects/connected-account.workspace-entity';
@Injectable()
export class CreateCompanyAndContactService {
@ -33,7 +34,8 @@ export class CreateCompanyAndContactService {
private readonly personRepository: PersonRepository,
@InjectObjectMetadataRepository(WorkspaceMemberWorkspaceEntity)
private readonly workspaceMemberRepository: WorkspaceMemberRepository,
private readonly workspaceDataSourceService: WorkspaceDataSourceService,
@InjectWorkspaceDatasource()
private readonly workspaceDataSource: WorkspaceDataSource,
private readonly messageParticipantService: MessagingMessageParticipantService,
private readonly calendarEventParticipantService: CalendarEventParticipantService,
private readonly eventEmitter: EventEmitter2,
@ -130,21 +132,16 @@ export class CreateCompanyAndContactService {
}
async createCompaniesAndContactsAndUpdateParticipants(
connectedAccount: ObjectRecord<ConnectedAccountWorkspaceEntity>,
connectedAccount: ConnectedAccountWorkspaceEntity,
contactsToCreate: Contacts,
workspaceId: string,
) {
const { dataSource: workspaceDataSource } =
await this.workspaceDataSourceService.connectedToWorkspaceDataSourceAndReturnMetadata(
workspaceId,
);
let updatedMessageParticipants: ObjectRecord<MessageParticipantWorkspaceEntity>[] =
[];
let updatedCalendarEventParticipants: ObjectRecord<CalendarEventParticipantWorkspaceEntity>[] =
[];
await workspaceDataSource?.transaction(
await this.workspaceDataSource?.transaction(
async (transactionManager: EntityManager) => {
const createdPeople = await this.createCompaniesAndPeople(
connectedAccount.handle,
@ -171,13 +168,13 @@ export class CreateCompanyAndContactService {
this.eventEmitter.emit(`messageParticipant.matched`, {
workspaceId,
userId: connectedAccount.accountOwnerId,
workspaceMemberId: connectedAccount.accountOwnerId,
messageParticipants: updatedMessageParticipants,
});
this.eventEmitter.emit(`calendarEventParticipant.matched`, {
workspaceId,
userId: connectedAccount.accountOwnerId,
workspaceMemberId: connectedAccount.accountOwnerId,
calendarEventParticipants: updatedCalendarEventParticipants,
});
}

View File

@ -80,6 +80,12 @@ export class GoogleAPIRefreshAccessTokenService {
workspaceId,
);
if (!messageChannel.connectedAccountId) {
throw new Error(
`No connected account ID found for message channel ${messageChannel.id} in workspace ${workspaceId}`,
);
}
await this.connectedAccountRepository.updateAuthFailedAt(
messageChannel.connectedAccountId,
workspaceId,

View File

@ -86,7 +86,7 @@ export class ConnectedAccountWorkspaceEntity extends BaseWorkspaceEntity {
icon: 'IconX',
})
@WorkspaceIsNullable()
authFailedAt: Date;
authFailedAt: Date | null;
@WorkspaceRelation({
standardId: CONNECTED_ACCOUNT_STANDARD_FIELD_IDS.accountOwner,
@ -100,6 +100,8 @@ export class ConnectedAccountWorkspaceEntity extends BaseWorkspaceEntity {
})
accountOwner: Relation<WorkspaceMemberWorkspaceEntity>;
accountOwnerId: string;
@WorkspaceRelation({
standardId: CONNECTED_ACCOUNT_STANDARD_FIELD_IDS.messageChannels,
type: RelationMetadataType.ONE_TO_MANY,

View File

@ -63,7 +63,7 @@ export class FavoriteWorkspaceEntity extends BaseWorkspaceEntity {
inverseSideFieldKey: 'favorites',
})
@WorkspaceIsNullable()
person: Relation<PersonWorkspaceEntity>;
person: Relation<PersonWorkspaceEntity> | null;
@WorkspaceRelation({
standardId: FAVORITE_STANDARD_FIELD_IDS.company,
@ -76,7 +76,7 @@ export class FavoriteWorkspaceEntity extends BaseWorkspaceEntity {
inverseSideFieldKey: 'favorites',
})
@WorkspaceIsNullable()
company: Relation<CompanyWorkspaceEntity>;
company: Relation<CompanyWorkspaceEntity> | null;
@WorkspaceRelation({
standardId: FAVORITE_STANDARD_FIELD_IDS.opportunity,
@ -89,7 +89,7 @@ export class FavoriteWorkspaceEntity extends BaseWorkspaceEntity {
inverseSideFieldKey: 'favorites',
})
@WorkspaceIsNullable()
opportunity: Relation<OpportunityWorkspaceEntity>;
opportunity: Relation<OpportunityWorkspaceEntity> | null;
@WorkspaceDynamicRelation({
type: RelationMetadataType.MANY_TO_ONE,

View File

@ -56,6 +56,12 @@ export class BlocklistItemDeleteMessagesJob {
`Deleting messages from ${handle} in workspace ${workspaceId} for workspace member ${workspaceMemberId}`,
);
if (!workspaceMemberId) {
throw new Error(
`Workspace member ID is not defined for blocklist item ${blocklistItemId} in workspace ${workspaceId}`,
);
}
const messageChannels =
await this.messageChannelRepository.getIdsByWorkspaceMemberId(
workspaceMemberId,

View File

@ -9,6 +9,7 @@ import { MessageChannelRepository } from 'src/modules/messaging/common/repositor
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';
import { isDefined } from 'src/utils/is-defined';
export class CanAccessMessageThreadService {
constructor(
@ -46,7 +47,9 @@ export class CanAccessMessageThreadService {
const messageChannelsConnectedAccounts =
await this.connectedAccountRepository.getByIds(
messageChannels.map((channel) => channel.connectedAccountId),
messageChannels
.map((channel) => channel.connectedAccountId)
.filter(isDefined),
workspaceId,
);

View File

@ -211,6 +211,12 @@ export class MessagingErrorHandlingService {
workspaceId,
);
if (!messageChannel.connectedAccountId) {
throw new Error(
`Connected account ID is not defined for message channel ${messageChannel.id} in workspace ${workspaceId}`,
);
}
await this.connectedAccountRepository.updateAuthFailedAt(
messageChannel.connectedAccountId,
workspaceId,

View File

@ -149,7 +149,7 @@ export class MessagingMessageParticipantService {
this.eventEmitter.emit(`messageParticipant.matched`, {
workspaceId,
userId: null,
workspaceMemberId: null,
messageParticipants: updatedMessageParticipants,
});
}

View File

@ -107,7 +107,7 @@ export class MessagingSaveMessagesAndEnqueueContactCreationService {
this.eventEmitter.emit(`messageParticipant.matched`, {
workspaceId,
userId: connectedAccount.accountOwnerId,
workspaceMemberId: connectedAccount.accountOwnerId,
messageParticipants: savedMessageParticipants,
});

View File

@ -35,7 +35,7 @@ export class MessageChannelMessageAssociationWorkspaceEntity extends BaseWorkspa
icon: 'IconHash',
})
@WorkspaceIsNullable()
messageExternalId: string;
messageExternalId: string | null;
@WorkspaceField({
standardId:
@ -46,7 +46,7 @@ export class MessageChannelMessageAssociationWorkspaceEntity extends BaseWorkspa
icon: 'IconHash',
})
@WorkspaceIsNullable()
messageThreadExternalId: string;
messageThreadExternalId: string | null;
@WorkspaceRelation({
standardId:
@ -60,7 +60,7 @@ export class MessageChannelMessageAssociationWorkspaceEntity extends BaseWorkspa
inverseSideFieldKey: 'messageChannelMessageAssociations',
})
@WorkspaceIsNullable()
messageChannel: Relation<MessageChannelWorkspaceEntity>;
messageChannel: Relation<MessageChannelWorkspaceEntity> | null;
@WorkspaceRelation({
standardId: MESSAGE_CHANNEL_MESSAGE_ASSOCIATION_STANDARD_FIELD_IDS.message,
@ -73,7 +73,7 @@ export class MessageChannelMessageAssociationWorkspaceEntity extends BaseWorkspa
inverseSideFieldKey: 'messageChannelMessageAssociations',
})
@WorkspaceIsNullable()
message: Relation<MessageWorkspaceEntity>;
message: Relation<MessageWorkspaceEntity> | null;
@WorkspaceRelation({
standardId:
@ -87,5 +87,5 @@ export class MessageChannelMessageAssociationWorkspaceEntity extends BaseWorkspa
inverseSideFieldKey: 'messageChannelMessageAssociations',
})
@WorkspaceIsNullable()
messageThread: Relation<MessageThreadWorkspaceEntity>;
messageThread: Relation<MessageThreadWorkspaceEntity> | null;
}

View File

@ -162,7 +162,7 @@ export class MessageChannelWorkspaceEntity extends BaseWorkspaceEntity {
icon: 'IconHistory',
})
@WorkspaceIsNullable()
syncedAt: string;
syncedAt: string | null;
@WorkspaceField({
standardId: MESSAGE_CHANNEL_STANDARD_FIELD_IDS.syncStatus,
@ -224,7 +224,7 @@ export class MessageChannelWorkspaceEntity extends BaseWorkspaceEntity {
],
})
@WorkspaceIsNullable()
syncStatus: MessageChannelSyncStatus;
syncStatus: MessageChannelSyncStatus | null;
@WorkspaceField({
standardId: MESSAGE_CHANNEL_STANDARD_FIELD_IDS.syncStage,
@ -282,7 +282,7 @@ export class MessageChannelWorkspaceEntity extends BaseWorkspaceEntity {
icon: 'IconHistory',
})
@WorkspaceIsNullable()
syncStageStartedAt: string;
syncStageStartedAt: string | null;
@WorkspaceField({
standardId: MESSAGE_CHANNEL_STANDARD_FIELD_IDS.throttleFailureCount,

View File

@ -83,7 +83,7 @@ export class MessageParticipantWorkspaceEntity extends BaseWorkspaceEntity {
inverseSideFieldKey: 'messageParticipants',
})
@WorkspaceIsNullable()
person: Relation<PersonWorkspaceEntity>;
person: Relation<PersonWorkspaceEntity> | null;
@WorkspaceRelation({
standardId: MESSAGE_PARTICIPANT_STANDARD_FIELD_IDS.workspaceMember,
@ -96,5 +96,5 @@ export class MessageParticipantWorkspaceEntity extends BaseWorkspaceEntity {
inverseSideFieldKey: 'messageParticipants',
})
@WorkspaceIsNullable()
workspaceMember: Relation<WorkspaceMemberWorkspaceEntity>;
workspaceMember: Relation<WorkspaceMemberWorkspaceEntity> | null;
}

View File

@ -78,7 +78,7 @@ export class MessageWorkspaceEntity extends BaseWorkspaceEntity {
icon: 'IconCalendar',
})
@WorkspaceIsNullable()
receivedAt: string;
receivedAt: string | null;
@WorkspaceRelation({
standardId: MESSAGE_STANDARD_FIELD_IDS.messageThread,
@ -92,7 +92,7 @@ export class MessageWorkspaceEntity extends BaseWorkspaceEntity {
onDelete: RelationOnDeleteAction.CASCADE,
})
@WorkspaceIsNullable()
messageThread: Relation<MessageThreadWorkspaceEntity>;
messageThread: Relation<MessageThreadWorkspaceEntity> | null;
@WorkspaceRelation({
standardId: MESSAGE_STANDARD_FIELD_IDS.messageParticipants,

View File

@ -1,4 +1,4 @@
import { Logger } from '@nestjs/common';
import { Logger, Scope } from '@nestjs/common';
import { Process } from 'src/engine/integrations/message-queue/decorators/process.decorator';
import { Processor } from 'src/engine/integrations/message-queue/decorators/processor.decorator';
@ -21,7 +21,10 @@ export type MessagingMessageListFetchJobData = {
workspaceId: string;
};
@Processor(MessageQueue.messagingQueue)
@Processor({
queueName: MessageQueue.messagingQueue,
scope: Scope.REQUEST,
})
export class MessagingMessageListFetchJob {
private readonly logger = new Logger(MessagingMessageListFetchJob.name);

View File

@ -1,3 +1,5 @@
import { Scope } from '@nestjs/common';
import { Process } from 'src/engine/integrations/message-queue/decorators/process.decorator';
import { Processor } from 'src/engine/integrations/message-queue/decorators/processor.decorator';
import { MessageQueue } from 'src/engine/integrations/message-queue/message-queue.constants';
@ -18,7 +20,10 @@ export type MessagingMessagesImportJobData = {
workspaceId: string;
};
@Processor(MessageQueue.messagingQueue)
@Processor({
queueName: MessageQueue.messagingQueue,
scope: Scope.REQUEST,
})
export class MessagingMessagesImportJob {
constructor(
@InjectObjectMetadataRepository(ConnectedAccountWorkspaceEntity)

View File

@ -25,7 +25,7 @@ export class MessageParticipantListener {
@OnEvent('messageParticipant.matched')
public async handleMessageParticipantMatched(payload: {
workspaceId: string;
userId: string;
workspaceMemberId: string;
messageParticipants: ObjectRecord<MessageParticipantWorkspaceEntity>[];
}): Promise<void> {
const messageParticipants = payload.messageParticipants ?? [];
@ -60,7 +60,7 @@ export class MessageParticipantListener {
properties: null,
objectName: 'message',
recordId: participant.personId,
workspaceMemberId: payload.userId,
workspaceMemberId: payload.workspaceMemberId,
workspaceId: payload.workspaceId,
linkedObjectMetadataId: messageObjectMetadata.id,
linkedRecordId: participant.messageId,

View File

@ -67,6 +67,9 @@ export class MessagingMessageChannelSyncStatusMonitoringCronJob {
await this.messageChannelRepository.getAll(workspaceId);
for (const messageChannel of messageChannels) {
if (!messageChannel.syncStatus) {
continue;
}
await this.messagingTelemetryService.track({
eventName: `message_channel.monitoring.sync_status.${snakeCase(
messageChannel.syncStatus,

View File

@ -49,7 +49,7 @@ export class OpportunityWorkspaceEntity extends BaseWorkspaceEntity {
icon: 'IconCurrencyDollar',
})
@WorkspaceIsNullable()
amount: CurrencyMetadata;
amount: CurrencyMetadata | null;
@WorkspaceField({
standardId: OPPORTUNITY_STANDARD_FIELD_IDS.closeDate,
@ -59,7 +59,7 @@ export class OpportunityWorkspaceEntity extends BaseWorkspaceEntity {
icon: 'IconCalendarEvent',
})
@WorkspaceIsNullable()
closeDate: Date;
closeDate: Date | null;
@WorkspaceField({
standardId: OPPORTUNITY_STANDARD_FIELD_IDS.probability,
@ -102,7 +102,7 @@ export class OpportunityWorkspaceEntity extends BaseWorkspaceEntity {
})
@WorkspaceIsSystem()
@WorkspaceIsNullable()
position: number;
position: number | null;
@WorkspaceRelation({
standardId: OPPORTUNITY_STANDARD_FIELD_IDS.pointOfContact,
@ -116,7 +116,7 @@ export class OpportunityWorkspaceEntity extends BaseWorkspaceEntity {
onDelete: RelationOnDeleteAction.SET_NULL,
})
@WorkspaceIsNullable()
pointOfContact: Relation<PersonWorkspaceEntity>;
pointOfContact: Relation<PersonWorkspaceEntity> | null;
@WorkspaceRelation({
standardId: OPPORTUNITY_STANDARD_FIELD_IDS.company,
@ -130,7 +130,7 @@ export class OpportunityWorkspaceEntity extends BaseWorkspaceEntity {
onDelete: RelationOnDeleteAction.SET_NULL,
})
@WorkspaceIsNullable()
company: Relation<CompanyWorkspaceEntity>;
company: Relation<CompanyWorkspaceEntity> | null;
@WorkspaceRelation({
standardId: OPPORTUNITY_STANDARD_FIELD_IDS.favorites,

View File

@ -41,7 +41,7 @@ export class PersonWorkspaceEntity extends BaseWorkspaceEntity {
icon: 'IconUser',
})
@WorkspaceIsNullable()
name: FullNameMetadata;
name: FullNameMetadata | null;
@WorkspaceField({
standardId: PERSON_STANDARD_FIELD_IDS.email,
@ -60,7 +60,7 @@ export class PersonWorkspaceEntity extends BaseWorkspaceEntity {
icon: 'IconBrandLinkedin',
})
@WorkspaceIsNullable()
linkedinLink: LinkMetadata;
linkedinLink: LinkMetadata | null;
@WorkspaceField({
standardId: PERSON_STANDARD_FIELD_IDS.xLink,
@ -70,7 +70,7 @@ export class PersonWorkspaceEntity extends BaseWorkspaceEntity {
icon: 'IconBrandX',
})
@WorkspaceIsNullable()
xLink: LinkMetadata;
xLink: LinkMetadata | null;
@WorkspaceField({
standardId: PERSON_STANDARD_FIELD_IDS.jobTitle,
@ -118,7 +118,7 @@ export class PersonWorkspaceEntity extends BaseWorkspaceEntity {
})
@WorkspaceIsSystem()
@WorkspaceIsNullable()
position: number;
position: number | null;
// Relations
@WorkspaceRelation({
@ -132,7 +132,7 @@ export class PersonWorkspaceEntity extends BaseWorkspaceEntity {
inverseSideFieldKey: 'people',
})
@WorkspaceIsNullable()
company: Relation<CompanyWorkspaceEntity>;
company: Relation<CompanyWorkspaceEntity> | null;
@WorkspaceRelation({
standardId: PERSON_STANDARD_FIELD_IDS.pointOfContactForOpportunities,

View File

@ -151,9 +151,9 @@ export class TimelineActivityRepository {
name: string;
properties: Record<string, any> | null;
workspaceMemberId: string | undefined;
recordId: string;
recordId: string | null;
linkedRecordCachedName: string;
linkedRecordId: string | undefined;
linkedRecordId: string | null | undefined;
linkedObjectMetadataId: string | undefined;
}[],
workspaceId: string,

View File

@ -39,7 +39,7 @@ export class AuditLogWorkspaceEntity extends BaseWorkspaceEntity {
icon: 'IconListDetails',
})
@WorkspaceIsNullable()
properties: JSON;
properties: JSON | null;
@WorkspaceField({
standardId: AUDIT_LOGS_STANDARD_FIELD_IDS.context,
@ -50,7 +50,7 @@ export class AuditLogWorkspaceEntity extends BaseWorkspaceEntity {
icon: 'IconListDetails',
})
@WorkspaceIsNullable()
context: JSON;
context: JSON | null;
@WorkspaceField({
standardId: AUDIT_LOGS_STANDARD_FIELD_IDS.objectName,
@ -78,7 +78,7 @@ export class AuditLogWorkspaceEntity extends BaseWorkspaceEntity {
icon: 'IconAbc',
})
@WorkspaceIsNullable()
recordId: string;
recordId: string | null;
@WorkspaceRelation({
standardId: AUDIT_LOGS_STANDARD_FIELD_IDS.workspaceMember,
@ -91,5 +91,5 @@ export class AuditLogWorkspaceEntity extends BaseWorkspaceEntity {
inverseSideFieldKey: 'auditLogs',
})
@WorkspaceIsNullable()
workspaceMember: Relation<WorkspaceMemberWorkspaceEntity>;
workspaceMember: Relation<WorkspaceMemberWorkspaceEntity> | null;
}

View File

@ -56,7 +56,7 @@ export class BehavioralEventWorkspaceEntity extends BaseWorkspaceEntity {
icon: 'IconListDetails',
})
@WorkspaceIsNullable()
properties: JSON;
properties: JSON | null;
@WorkspaceField({
standardId: BEHAVIORAL_EVENT_STANDARD_FIELD_IDS.context,
@ -67,7 +67,7 @@ export class BehavioralEventWorkspaceEntity extends BaseWorkspaceEntity {
icon: 'IconListDetails',
})
@WorkspaceIsNullable()
context: JSON;
context: JSON | null;
@WorkspaceField({
standardId: BEHAVIORAL_EVENT_STANDARD_FIELD_IDS.objectName,
@ -86,5 +86,5 @@ export class BehavioralEventWorkspaceEntity extends BaseWorkspaceEntity {
icon: 'IconAbc',
})
@WorkspaceIsNullable()
recordId: string;
recordId: string | null;
}

View File

@ -56,7 +56,7 @@ export class TimelineActivityWorkspaceEntity extends BaseWorkspaceEntity {
icon: 'IconListDetails',
})
@WorkspaceIsNullable()
properties: JSON;
properties: JSON | null;
// Special objects that don't have their own timeline and are 'link' to the main object
@WorkspaceField({
@ -76,7 +76,7 @@ export class TimelineActivityWorkspaceEntity extends BaseWorkspaceEntity {
icon: 'IconAbc',
})
@WorkspaceIsNullable()
linkedRecordId: string;
linkedRecordId: string | null;
@WorkspaceField({
standardId: TIMELINE_ACTIVITY_STANDARD_FIELD_IDS.linkedObjectMetadataId,
@ -86,7 +86,7 @@ export class TimelineActivityWorkspaceEntity extends BaseWorkspaceEntity {
icon: 'IconAbc',
})
@WorkspaceIsNullable()
linkedObjectMetadataId: string;
linkedObjectMetadataId: string | null;
// Who made the action
@WorkspaceRelation({
@ -100,7 +100,7 @@ export class TimelineActivityWorkspaceEntity extends BaseWorkspaceEntity {
inverseSideFieldKey: 'timelineActivities',
})
@WorkspaceIsNullable()
workspaceMember: Relation<WorkspaceMemberWorkspaceEntity>;
workspaceMember: Relation<WorkspaceMemberWorkspaceEntity> | null;
@WorkspaceRelation({
standardId: TIMELINE_ACTIVITY_STANDARD_FIELD_IDS.person,
@ -113,7 +113,7 @@ export class TimelineActivityWorkspaceEntity extends BaseWorkspaceEntity {
inverseSideFieldKey: 'timelineActivities',
})
@WorkspaceIsNullable()
person: Relation<PersonWorkspaceEntity>;
person: Relation<PersonWorkspaceEntity> | null;
@WorkspaceRelation({
standardId: TIMELINE_ACTIVITY_STANDARD_FIELD_IDS.company,
@ -126,7 +126,7 @@ export class TimelineActivityWorkspaceEntity extends BaseWorkspaceEntity {
inverseSideFieldKey: 'timelineActivities',
})
@WorkspaceIsNullable()
company: Relation<CompanyWorkspaceEntity>;
company: Relation<CompanyWorkspaceEntity> | null;
@WorkspaceRelation({
standardId: TIMELINE_ACTIVITY_STANDARD_FIELD_IDS.opportunity,
@ -139,7 +139,7 @@ export class TimelineActivityWorkspaceEntity extends BaseWorkspaceEntity {
inverseSideFieldKey: 'timelineActivities',
})
@WorkspaceIsNullable()
opportunity: Relation<OpportunityWorkspaceEntity>;
opportunity: Relation<OpportunityWorkspaceEntity> | null;
@WorkspaceDynamicRelation({
type: RelationMetadataType.MANY_TO_ONE,

View File

@ -72,5 +72,5 @@ export class ViewFieldWorkspaceEntity extends BaseWorkspaceEntity {
joinColumn: 'viewId',
})
@WorkspaceIsNullable()
view?: ViewWorkspaceEntity;
view?: ViewWorkspaceEntity | null;
}

View File

@ -68,5 +68,5 @@ export class ViewFilterWorkspaceEntity extends BaseWorkspaceEntity {
inverseSideFieldKey: 'viewFilters',
})
@WorkspaceIsNullable()
view: Relation<ViewWorkspaceEntity>;
view: Relation<ViewWorkspaceEntity> | null;
}

View File

@ -53,5 +53,5 @@ export class ViewSortWorkspaceEntity extends BaseWorkspaceEntity {
inverseSideFieldKey: 'viewSorts',
})
@WorkspaceIsNullable()
view: Relation<ViewWorkspaceEntity>;
view: Relation<ViewWorkspaceEntity> | null;
}

View File

@ -3,10 +3,9 @@ import { Injectable } from '@nestjs/common';
import { WorkspacePreQueryHook } from 'src/engine/api/graphql/workspace-query-runner/workspace-pre-query-hook/interfaces/workspace-pre-query-hook.interface';
import { DeleteOneResolverArgs } 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 { CommentRepository } from 'src/modules/activity/repositories/comment.repository';
import { InjectWorkspaceRepository } from 'src/engine/twenty-orm/decorators/inject-workspace-repository.decorator';
import { WorkspaceRepository } from 'src/engine/twenty-orm/repository/workspace.repository';
import { CommentWorkspaceEntity } from 'src/modules/activity/standard-objects/comment.workspace-entity';
import { AttachmentRepository } from 'src/modules/attachment/repositories/attachment.repository';
import { AttachmentWorkspaceEntity } from 'src/modules/attachment/standard-objects/attachment.workspace-entity';
@Injectable()
@ -14,10 +13,10 @@ export class WorkspaceMemberDeleteOnePreQueryHook
implements WorkspacePreQueryHook
{
constructor(
@InjectObjectMetadataRepository(AttachmentWorkspaceEntity)
private readonly attachmentRepository: AttachmentRepository,
@InjectObjectMetadataRepository(CommentWorkspaceEntity)
private readonly commentRepository: CommentRepository,
@InjectWorkspaceRepository(AttachmentWorkspaceEntity)
private readonly attachmentRepository: WorkspaceRepository<AttachmentWorkspaceEntity>,
@InjectWorkspaceRepository(CommentWorkspaceEntity)
private readonly commentRepository: WorkspaceRepository<CommentWorkspaceEntity>,
) {}
// There is no need to validate the user's access to the workspace member since we don't have permission yet.
@ -26,16 +25,18 @@ export class WorkspaceMemberDeleteOnePreQueryHook
workspaceId: string,
payload: DeleteOneResolverArgs,
): Promise<void> {
const workspaceMemberId = payload.id;
const authorId = payload.id;
await this.attachmentRepository.deleteByAuthorId(
workspaceMemberId,
workspaceId,
);
await this.attachmentRepository.delete({
author: {
id: authorId,
},
});
await this.commentRepository.deleteByAuthorId(
workspaceMemberId,
workspaceId,
);
await this.commentRepository.delete({
author: {
id: authorId,
},
});
}
}

View File

@ -1,6 +1,6 @@
import { Module } from '@nestjs/common';
import { ObjectMetadataRepositoryModule } from 'src/engine/object-metadata-repository/object-metadata-repository.module';
import { TwentyORMModule } from 'src/engine/twenty-orm/twenty-orm.module';
import { CommentWorkspaceEntity } from 'src/modules/activity/standard-objects/comment.workspace-entity';
import { AttachmentWorkspaceEntity } from 'src/modules/attachment/standard-objects/attachment.workspace-entity';
import { WorkspaceMemberDeleteManyPreQueryHook } from 'src/modules/workspace-member/query-hooks/workspace-member-delete-many.pre-query.hook';
@ -8,7 +8,7 @@ import { WorkspaceMemberDeleteOnePreQueryHook } from 'src/modules/workspace-memb
@Module({
imports: [
ObjectMetadataRepositoryModule.forFeature([
TwentyORMModule.forFeature([
AttachmentWorkspaceEntity,
CommentWorkspaceEntity,
]),