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

@ -19,8 +19,8 @@ export class AnalyticsService {
async create( async create(
createEventInput: CreateEventInput, createEventInput: CreateEventInput,
userId: string | undefined, userId: string | null | undefined,
workspaceId: string | undefined, workspaceId: string | null | undefined,
workspaceDisplayName: string | undefined, workspaceDisplayName: string | undefined,
workspaceDomainName: string | undefined, workspaceDomainName: string | undefined,
hostName: string | undefined, hostName: string | undefined,

View File

@ -28,6 +28,8 @@ import { ConnectedAccountWorkspaceEntity } from 'src/modules/connected-account/s
import { CalendarChannelWorkspaceEntity } from 'src/modules/calendar/standard-objects/calendar-channel.workspace-entity'; import { CalendarChannelWorkspaceEntity } from 'src/modules/calendar/standard-objects/calendar-channel.workspace-entity';
import { MessageChannelWorkspaceEntity } from 'src/modules/messaging/common/standard-objects/message-channel.workspace-entity'; import { MessageChannelWorkspaceEntity } from 'src/modules/messaging/common/standard-objects/message-channel.workspace-entity';
import { OnboardingModule } from 'src/engine/core-modules/onboarding/onboarding.module'; import { OnboardingModule } from 'src/engine/core-modules/onboarding/onboarding.module';
import { TwentyORMModule } from 'src/engine/twenty-orm/twenty-orm.module';
import { WorkspaceDataSourceModule } from 'src/engine/workspace-datasource/workspace-datasource.module';
import { AuthResolver } from './auth.resolver'; import { AuthResolver } from './auth.resolver';
@ -60,11 +62,12 @@ const jwtModule = JwtModule.registerAsync({
ObjectMetadataRepositoryModule.forFeature([ ObjectMetadataRepositoryModule.forFeature([
ConnectedAccountWorkspaceEntity, ConnectedAccountWorkspaceEntity,
MessageChannelWorkspaceEntity, MessageChannelWorkspaceEntity,
CalendarChannelWorkspaceEntity,
]), ]),
HttpModule, HttpModule,
UserWorkspaceModule, UserWorkspaceModule,
OnboardingModule, OnboardingModule,
TwentyORMModule.forFeature([CalendarChannelWorkspaceEntity]),
WorkspaceDataSourceModule,
], ],
controllers: [ controllers: [
GoogleAuthController, GoogleAuthController,

View File

@ -138,6 +138,7 @@ export class AuthResolver {
} }
const transientToken = await this.tokenService.generateTransientToken( const transientToken = await this.tokenService.generateTransientToken(
workspaceMember.id, workspaceMember.id,
user.id,
user.defaultWorkspace.id, user.defaultWorkspace.id,
); );

View File

@ -16,9 +16,7 @@ import { GoogleAPIsService } from 'src/engine/core-modules/auth/services/google-
import { TokenService } from 'src/engine/core-modules/auth/services/token.service'; import { TokenService } from 'src/engine/core-modules/auth/services/token.service';
import { EnvironmentService } from 'src/engine/integrations/environment/environment.service'; import { EnvironmentService } from 'src/engine/integrations/environment/environment.service';
import { OnboardingService } from 'src/engine/core-modules/onboarding/onboarding.service'; import { OnboardingService } from 'src/engine/core-modules/onboarding/onboarding.service';
import { WorkspaceMemberRepository } from 'src/modules/workspace-member/repositories/workspace-member.repository'; import { LoadServiceWithWorkspaceContext } from 'src/engine/twenty-orm/context/load-service-with-workspace.context';
import { WorkspaceMemberWorkspaceEntity } from 'src/modules/workspace-member/standard-objects/workspace-member.workspace-entity';
import { InjectObjectMetadataRepository } from 'src/engine/object-metadata-repository/object-metadata-repository.decorator';
@Controller('auth/google-apis') @Controller('auth/google-apis')
export class GoogleAPIsAuthController { export class GoogleAPIsAuthController {
@ -27,8 +25,7 @@ export class GoogleAPIsAuthController {
private readonly tokenService: TokenService, private readonly tokenService: TokenService,
private readonly environmentService: EnvironmentService, private readonly environmentService: EnvironmentService,
private readonly onboardingService: OnboardingService, private readonly onboardingService: OnboardingService,
@InjectObjectMetadataRepository(WorkspaceMemberWorkspaceEntity) private readonly loadServiceWithWorkspaceContext: LoadServiceWithWorkspaceContext,
private readonly workspaceMemberService: WorkspaceMemberRepository,
) {} ) {}
@Get() @Get()
@ -56,7 +53,7 @@ export class GoogleAPIsAuthController {
messageVisibility, messageVisibility,
} = user; } = user;
const { workspaceMemberId, workspaceId } = const { workspaceMemberId, userId, workspaceId } =
await this.tokenService.verifyTransientToken(transientToken); await this.tokenService.verifyTransientToken(transientToken);
const demoWorkspaceIds = this.environmentService.get('DEMO_WORKSPACE_IDS'); const demoWorkspaceIds = this.environmentService.get('DEMO_WORKSPACE_IDS');
@ -71,7 +68,13 @@ export class GoogleAPIsAuthController {
throw new Error('Workspace not found'); throw new Error('Workspace not found');
} }
await this.googleAPIsService.refreshGoogleRefreshToken({ const googleAPIsServiceInstance =
await this.loadServiceWithWorkspaceContext.load(
this.googleAPIsService,
workspaceId,
);
await googleAPIsServiceInstance.refreshGoogleRefreshToken({
handle: email, handle: email,
workspaceMemberId: workspaceMemberId, workspaceMemberId: workspaceMemberId,
workspaceId: workspaceId, workspaceId: workspaceId,
@ -81,12 +84,14 @@ export class GoogleAPIsAuthController {
messageVisibility, messageVisibility,
}); });
const userId = (
await this.workspaceMemberService.find(workspaceMemberId, workspaceId)
)?.userId;
if (userId) { if (userId) {
await this.onboardingService.skipSyncEmailOnboardingStep( const onboardingServiceInstance =
await this.loadServiceWithWorkspaceContext.load(
this.onboardingService,
workspaceId,
);
await onboardingServiceInstance.skipSyncEmailOnboardingStep(
userId, userId,
workspaceId, workspaceId,
); );

View File

@ -3,17 +3,14 @@ import { Injectable } from '@nestjs/common';
import { EntityManager } from 'typeorm'; import { EntityManager } from 'typeorm';
import { v4 } from 'uuid'; import { v4 } from 'uuid';
import { TypeORMService } from 'src/database/typeorm/typeorm.service';
import { EnvironmentService } from 'src/engine/integrations/environment/environment.service'; import { EnvironmentService } from 'src/engine/integrations/environment/environment.service';
import { MessageQueue } from 'src/engine/integrations/message-queue/message-queue.constants'; import { MessageQueue } from 'src/engine/integrations/message-queue/message-queue.constants';
import { MessageQueueService } from 'src/engine/integrations/message-queue/services/message-queue.service'; import { MessageQueueService } from 'src/engine/integrations/message-queue/services/message-queue.service';
import { DataSourceService } from 'src/engine/metadata-modules/data-source/data-source.service';
import { InjectObjectMetadataRepository } from 'src/engine/object-metadata-repository/object-metadata-repository.decorator'; import { InjectObjectMetadataRepository } from 'src/engine/object-metadata-repository/object-metadata-repository.decorator';
import { import {
GoogleCalendarSyncJobData, GoogleCalendarSyncJobData,
GoogleCalendarSyncJob, GoogleCalendarSyncJob,
} from 'src/modules/calendar/jobs/google-calendar-sync.job'; } from 'src/modules/calendar/jobs/google-calendar-sync.job';
import { CalendarChannelRepository } from 'src/modules/calendar/repositories/calendar-channel.repository';
import { import {
CalendarChannelWorkspaceEntity, CalendarChannelWorkspaceEntity,
CalendarChannelVisibility, CalendarChannelVisibility,
@ -35,12 +32,16 @@ import {
MessagingMessageListFetchJobData, MessagingMessageListFetchJobData,
} from 'src/modules/messaging/message-import-manager/jobs/messaging-message-list-fetch.job'; } from 'src/modules/messaging/message-import-manager/jobs/messaging-message-list-fetch.job';
import { InjectMessageQueue } from 'src/engine/integrations/message-queue/decorators/message-queue.decorator'; import { InjectMessageQueue } from 'src/engine/integrations/message-queue/decorators/message-queue.decorator';
import { InjectWorkspaceRepository } from 'src/engine/twenty-orm/decorators/inject-workspace-repository.decorator';
import { WorkspaceRepository } from 'src/engine/twenty-orm/repository/workspace.repository';
import { WorkspaceDataSource } from 'src/engine/twenty-orm/datasource/workspace.datasource';
import { InjectWorkspaceDatasource } from 'src/engine/twenty-orm/decorators/inject-workspace-datasource.decorator';
@Injectable() @Injectable()
export class GoogleAPIsService { export class GoogleAPIsService {
constructor( constructor(
private readonly dataSourceService: DataSourceService, @InjectWorkspaceDatasource()
private readonly typeORMService: TypeORMService, private readonly workspaceDataSource: WorkspaceDataSource,
@InjectMessageQueue(MessageQueue.messagingQueue) @InjectMessageQueue(MessageQueue.messagingQueue)
private readonly messageQueueService: MessageQueueService, private readonly messageQueueService: MessageQueueService,
@InjectMessageQueue(MessageQueue.calendarQueue) @InjectMessageQueue(MessageQueue.calendarQueue)
@ -50,8 +51,8 @@ export class GoogleAPIsService {
private readonly connectedAccountRepository: ConnectedAccountRepository, private readonly connectedAccountRepository: ConnectedAccountRepository,
@InjectObjectMetadataRepository(MessageChannelWorkspaceEntity) @InjectObjectMetadataRepository(MessageChannelWorkspaceEntity)
private readonly messageChannelRepository: MessageChannelRepository, private readonly messageChannelRepository: MessageChannelRepository,
@InjectObjectMetadataRepository(CalendarChannelWorkspaceEntity) @InjectWorkspaceRepository(CalendarChannelWorkspaceEntity)
private readonly calendarChannelRepository: CalendarChannelRepository, private readonly calendarChannelRepository: WorkspaceRepository<CalendarChannelWorkspaceEntity>,
) {} ) {}
async refreshGoogleRefreshToken(input: { async refreshGoogleRefreshToken(input: {
@ -71,14 +72,6 @@ export class GoogleAPIsService {
messageVisibility, messageVisibility,
} = input; } = input;
const dataSourceMetadata =
await this.dataSourceService.getLastDataSourceMetadataFromWorkspaceIdOrFail(
workspaceId,
);
const workspaceDataSource =
await this.typeORMService.connectToDataSource(dataSourceMetadata);
const isCalendarEnabled = this.environmentService.get( const isCalendarEnabled = this.environmentService.get(
'CALENDAR_PROVIDER_GOOGLE_ENABLED', 'CALENDAR_PROVIDER_GOOGLE_ENABLED',
); );
@ -93,65 +86,67 @@ export class GoogleAPIsService {
const existingAccountId = connectedAccounts?.[0]?.id; const existingAccountId = connectedAccounts?.[0]?.id;
const newOrExistingConnectedAccountId = existingAccountId ?? v4(); const newOrExistingConnectedAccountId = existingAccountId ?? v4();
await workspaceDataSource?.transaction(async (manager: EntityManager) => { await this.workspaceDataSource.transaction(
if (!existingAccountId) { async (manager: EntityManager) => {
await this.connectedAccountRepository.create( if (!existingAccountId) {
{ await this.connectedAccountRepository.create(
id: newOrExistingConnectedAccountId,
handle,
provider: ConnectedAccountProvider.GOOGLE,
accessToken: input.accessToken,
refreshToken: input.refreshToken,
accountOwnerId: workspaceMemberId,
},
workspaceId,
manager,
);
await this.messageChannelRepository.create(
{
id: v4(),
connectedAccountId: newOrExistingConnectedAccountId,
type: MessageChannelType.EMAIL,
handle,
visibility:
messageVisibility || MessageChannelVisibility.SHARE_EVERYTHING,
syncStatus: MessageChannelSyncStatus.ONGOING,
},
workspaceId,
manager,
);
if (isCalendarEnabled) {
await this.calendarChannelRepository.create(
{ {
id: v4(), id: newOrExistingConnectedAccountId,
connectedAccountId: newOrExistingConnectedAccountId,
handle, handle,
visibility: provider: ConnectedAccountProvider.GOOGLE,
calendarVisibility || accessToken: input.accessToken,
CalendarChannelVisibility.SHARE_EVERYTHING, refreshToken: input.refreshToken,
accountOwnerId: workspaceMemberId,
}, },
workspaceId, workspaceId,
manager, manager,
); );
}
} else {
await this.connectedAccountRepository.updateAccessTokenAndRefreshToken(
input.accessToken,
input.refreshToken,
newOrExistingConnectedAccountId,
workspaceId,
manager,
);
await this.messageChannelRepository.resetSync( await this.messageChannelRepository.create(
newOrExistingConnectedAccountId, {
workspaceId, id: v4(),
manager, connectedAccountId: newOrExistingConnectedAccountId,
); type: MessageChannelType.EMAIL,
} handle,
}); visibility:
messageVisibility || MessageChannelVisibility.SHARE_EVERYTHING,
syncStatus: MessageChannelSyncStatus.ONGOING,
},
workspaceId,
manager,
);
if (isCalendarEnabled) {
await this.calendarChannelRepository.save(
{
id: v4(),
connectedAccountId: newOrExistingConnectedAccountId,
handle,
visibility:
calendarVisibility ||
CalendarChannelVisibility.SHARE_EVERYTHING,
},
{},
manager,
);
}
} else {
await this.connectedAccountRepository.updateAccessTokenAndRefreshToken(
input.accessToken,
input.refreshToken,
newOrExistingConnectedAccountId,
workspaceId,
manager,
);
await this.messageChannelRepository.resetSync(
newOrExistingConnectedAccountId,
workspaceId,
manager,
);
}
},
);
if (this.environmentService.get('MESSAGING_PROVIDER_GMAIL_ENABLED')) { if (this.environmentService.get('MESSAGING_PROVIDER_GMAIL_ENABLED')) {
const messageChannels = const messageChannels =

View File

@ -147,6 +147,7 @@ export class TokenService {
async generateTransientToken( async generateTransientToken(
workspaceMemberId: string, workspaceMemberId: string,
userId: string,
workspaceId: string, workspaceId: string,
): Promise<AuthToken> { ): Promise<AuthToken> {
const secret = this.environmentService.get('LOGIN_TOKEN_SECRET'); const secret = this.environmentService.get('LOGIN_TOKEN_SECRET');
@ -158,6 +159,7 @@ export class TokenService {
const expiresAt = addMilliseconds(new Date().getTime(), ms(expiresIn)); const expiresAt = addMilliseconds(new Date().getTime(), ms(expiresIn));
const jwtPayload = { const jwtPayload = {
sub: workspaceMemberId, sub: workspaceMemberId,
userId,
workspaceId, workspaceId,
}; };
@ -234,6 +236,7 @@ export class TokenService {
async verifyTransientToken(transientToken: string): Promise<{ async verifyTransientToken(transientToken: string): Promise<{
workspaceMemberId: string; workspaceMemberId: string;
userId: string;
workspaceId: string; workspaceId: string;
}> { }> {
const transientTokenSecret = const transientTokenSecret =
@ -243,6 +246,7 @@ export class TokenService {
return { return {
workspaceMemberId: payload.sub, workspaceMemberId: payload.sub,
userId: payload.userId,
workspaceId: payload.workspaceId, workspaceId: payload.workspaceId,
}; };
} }

View File

@ -203,9 +203,7 @@ export class BillingService {
: frontBaseUrl; : frontBaseUrl;
const quantity = const quantity =
(await this.userWorkspaceService.getWorkspaceMemberCount( (await this.userWorkspaceService.getWorkspaceMemberCount()) || 1;
user.defaultWorkspaceId,
)) || 1;
const stripeCustomerId = ( const stripeCustomerId = (
await this.billingSubscriptionRepository.findOneBy({ await this.billingSubscriptionRepository.findOneBy({

View File

@ -1,4 +1,4 @@
import { Logger } from '@nestjs/common'; import { Logger, Scope } from '@nestjs/common';
import { BillingService } from 'src/engine/core-modules/billing/billing.service'; import { BillingService } from 'src/engine/core-modules/billing/billing.service';
import { UserWorkspaceService } from 'src/engine/core-modules/user-workspace/user-workspace.service'; import { UserWorkspaceService } from 'src/engine/core-modules/user-workspace/user-workspace.service';
@ -8,7 +8,10 @@ import { MessageQueue } from 'src/engine/integrations/message-queue/message-queu
import { Process } from 'src/engine/integrations/message-queue/decorators/process.decorator'; import { Process } from 'src/engine/integrations/message-queue/decorators/process.decorator';
export type UpdateSubscriptionJobData = { workspaceId: string }; export type UpdateSubscriptionJobData = { workspaceId: string };
@Processor(MessageQueue.billingQueue) @Processor({
queueName: MessageQueue.billingQueue,
scope: Scope.REQUEST,
})
export class UpdateSubscriptionJob { export class UpdateSubscriptionJob {
protected readonly logger = new Logger(UpdateSubscriptionJob.name); protected readonly logger = new Logger(UpdateSubscriptionJob.name);
@ -21,7 +24,7 @@ export class UpdateSubscriptionJob {
@Process(UpdateSubscriptionJob.name) @Process(UpdateSubscriptionJob.name)
async handle(data: UpdateSubscriptionJobData): Promise<void> { async handle(data: UpdateSubscriptionJobData): Promise<void> {
const workspaceMembersCount = const workspaceMembersCount =
await this.userWorkspaceService.getWorkspaceMemberCount(data.workspaceId); await this.userWorkspaceService.getWorkspaceMemberCount();
if (!workspaceMembersCount || workspaceMembersCount <= 0) { if (!workspaceMembersCount || workspaceMembersCount <= 0) {
return; return;

View File

@ -5,10 +5,10 @@ import { UUIDScalarType } from 'src/engine/api/graphql/workspace-schema-builder/
@ObjectType('TimelineCalendarEventParticipant') @ObjectType('TimelineCalendarEventParticipant')
export class TimelineCalendarEventParticipant { export class TimelineCalendarEventParticipant {
@Field(() => UUIDScalarType, { nullable: true }) @Field(() => UUIDScalarType, { nullable: true })
personId: string; personId: string | null;
@Field(() => UUIDScalarType, { nullable: true }) @Field(() => UUIDScalarType, { nullable: true })
workspaceMemberId: string; workspaceMemberId: string | null;
@Field() @Field()
firstName: string; firstName: string;

View File

@ -81,19 +81,19 @@ export class TimelineCalendarEventService {
const participants = event.calendarEventParticipants.map( const participants = event.calendarEventParticipants.map(
(participant) => ({ (participant) => ({
calendarEventId: event.id, calendarEventId: event.id,
personId: participant.person?.id, personId: participant.person?.id ?? null,
workspaceMemberId: participant.workspaceMember?.id, workspaceMemberId: participant.workspaceMember?.id ?? null,
firstName: firstName:
participant.person?.name.firstName || participant.person?.name?.firstName ||
participant.workspaceMember?.name.firstName || participant.workspaceMember?.name.firstName ||
'', '',
lastName: lastName:
participant.person?.name.lastName || participant.person?.name?.lastName ||
participant.workspaceMember?.name.lastName || participant.workspaceMember?.name.lastName ||
'', '',
displayName: displayName:
participant.person?.name.firstName || participant.person?.name?.firstName ||
participant.person?.name.lastName || participant.person?.name?.lastName ||
participant.workspaceMember?.name.firstName || participant.workspaceMember?.name.firstName ||
participant.workspaceMember?.name.lastName || participant.workspaceMember?.name.lastName ||
'', '',

View File

@ -56,7 +56,7 @@ export class OnboardingService {
const isInviteTeamSkipped = const isInviteTeamSkipped =
inviteTeamValue === OnboardingStepValues.SKIPPED; inviteTeamValue === OnboardingStepValues.SKIPPED;
const workspaceMemberCount = const workspaceMemberCount =
await this.userWorkspaceService.getWorkspaceMemberCount(workspace.id); await this.userWorkspaceService.getWorkspaceMemberCount();
return ( return (
!isInviteTeamSkipped && !isInviteTeamSkipped &&

View File

@ -9,6 +9,8 @@ import { UserWorkspaceService } from 'src/engine/core-modules/user-workspace/use
import { DataSourceModule } from 'src/engine/metadata-modules/data-source/data-source.module'; import { DataSourceModule } from 'src/engine/metadata-modules/data-source/data-source.module';
import { WorkspaceDataSourceModule } from 'src/engine/workspace-datasource/workspace-datasource.module'; import { WorkspaceDataSourceModule } from 'src/engine/workspace-datasource/workspace-datasource.module';
import { User } from 'src/engine/core-modules/user/user.entity'; import { User } from 'src/engine/core-modules/user/user.entity';
import { TwentyORMModule } from 'src/engine/twenty-orm/twenty-orm.module';
import { WorkspaceMemberWorkspaceEntity } from 'src/modules/workspace-member/standard-objects/workspace-member.workspace-entity';
@Module({ @Module({
imports: [ imports: [
@ -21,6 +23,7 @@ import { User } from 'src/engine/core-modules/user/user.entity';
], ],
services: [UserWorkspaceService], services: [UserWorkspaceService],
}), }),
TwentyORMModule.forFeature([WorkspaceMemberWorkspaceEntity]),
], ],
exports: [UserWorkspaceService], exports: [UserWorkspaceService],
providers: [UserWorkspaceService], providers: [UserWorkspaceService],

View File

@ -8,11 +8,12 @@ import { UserWorkspace } from 'src/engine/core-modules/user-workspace/user-works
import { TypeORMService } from 'src/database/typeorm/typeorm.service'; import { TypeORMService } from 'src/database/typeorm/typeorm.service';
import { DataSourceService } from 'src/engine/metadata-modules/data-source/data-source.service'; import { DataSourceService } from 'src/engine/metadata-modules/data-source/data-source.service';
import { User } from 'src/engine/core-modules/user/user.entity'; import { User } from 'src/engine/core-modules/user/user.entity';
import { WorkspaceDataSourceService } from 'src/engine/workspace-datasource/workspace-datasource.service';
import { ObjectRecordCreateEvent } from 'src/engine/integrations/event-emitter/types/object-record-create.event'; import { ObjectRecordCreateEvent } from 'src/engine/integrations/event-emitter/types/object-record-create.event';
import { WorkspaceMemberWorkspaceEntity } from 'src/modules/workspace-member/standard-objects/workspace-member.workspace-entity'; import { WorkspaceMemberWorkspaceEntity } from 'src/modules/workspace-member/standard-objects/workspace-member.workspace-entity';
import { assert } from 'src/utils/assert'; import { assert } from 'src/utils/assert';
import { Workspace } from 'src/engine/core-modules/workspace/workspace.entity'; import { Workspace } from 'src/engine/core-modules/workspace/workspace.entity';
import { InjectWorkspaceRepository } from 'src/engine/twenty-orm/decorators/inject-workspace-repository.decorator';
import { WorkspaceRepository } from 'src/engine/twenty-orm/repository/workspace.repository';
export class UserWorkspaceService extends TypeOrmQueryService<UserWorkspace> { export class UserWorkspaceService extends TypeOrmQueryService<UserWorkspace> {
constructor( constructor(
@ -20,9 +21,10 @@ export class UserWorkspaceService extends TypeOrmQueryService<UserWorkspace> {
private readonly userWorkspaceRepository: Repository<UserWorkspace>, private readonly userWorkspaceRepository: Repository<UserWorkspace>,
@InjectRepository(User, 'core') @InjectRepository(User, 'core')
private readonly userRepository: Repository<User>, private readonly userRepository: Repository<User>,
@InjectWorkspaceRepository(WorkspaceMemberWorkspaceEntity)
private readonly workspaceMemberRepository: WorkspaceRepository<WorkspaceMemberWorkspaceEntity>,
private readonly dataSourceService: DataSourceService, private readonly dataSourceService: DataSourceService,
private readonly typeORMService: TypeORMService, private readonly typeORMService: TypeORMService,
private readonly workspaceDataSourceService: WorkspaceDataSourceService,
private eventEmitter: EventEmitter2, private eventEmitter: EventEmitter2,
) { ) {
super(userWorkspaceRepository); super(userWorkspaceRepository);
@ -99,23 +101,10 @@ export class UserWorkspaceService extends TypeOrmQueryService<UserWorkspace> {
}); });
} }
public async getWorkspaceMemberCount( public async getWorkspaceMemberCount(): Promise<number | undefined> {
workspaceId: string, const workspaceMemberCount = await this.workspaceMemberRepository.count();
): Promise<number | undefined> {
try {
const dataSourceSchema =
this.workspaceDataSourceService.getSchemaName(workspaceId);
return ( return workspaceMemberCount;
await this.workspaceDataSourceService.executeRawQuery(
`SELECT * FROM ${dataSourceSchema}."workspaceMember"`,
[],
workspaceId,
)
).length;
} catch {
return undefined;
}
} }
async checkUserWorkspaceExists( async checkUserWorkspaceExists(

View File

@ -29,6 +29,7 @@ import { User } from 'src/engine/core-modules/user/user.entity';
import { WorkspaceMember } from 'src/engine/core-modules/user/dtos/workspace-member.dto'; import { WorkspaceMember } from 'src/engine/core-modules/user/dtos/workspace-member.dto';
import { OnboardingStep } from 'src/engine/core-modules/onboarding/enums/onboarding-step.enum'; import { OnboardingStep } from 'src/engine/core-modules/onboarding/enums/onboarding-step.enum';
import { OnboardingService } from 'src/engine/core-modules/onboarding/onboarding.service'; import { OnboardingService } from 'src/engine/core-modules/onboarding/onboarding.service';
import { LoadServiceWithWorkspaceContext } from 'src/engine/twenty-orm/context/load-service-with-workspace.context';
const getHMACKey = (email?: string, key?: string | null) => { const getHMACKey = (email?: string, key?: string | null) => {
if (!email || !key) return null; if (!email || !key) return null;
@ -48,6 +49,7 @@ export class UserResolver {
private readonly environmentService: EnvironmentService, private readonly environmentService: EnvironmentService,
private readonly fileUploadService: FileUploadService, private readonly fileUploadService: FileUploadService,
private readonly onboardingService: OnboardingService, private readonly onboardingService: OnboardingService,
private readonly loadServiceWithWorkspaceContext: LoadServiceWithWorkspaceContext,
) {} ) {}
@Query(() => User) @Query(() => User)
@ -122,9 +124,11 @@ export class UserResolver {
return null; return null;
} }
return this.onboardingService.getOnboardingStep( const contextInstance = await this.loadServiceWithWorkspaceContext.load(
user, this.onboardingService,
user.defaultWorkspace, user.defaultWorkspaceId,
); );
return contextInstance.getOnboardingStep(user, user.defaultWorkspace);
} }
} }

View File

@ -47,7 +47,11 @@ export class PgBossDriver
} }
: {}, : {},
async (job) => { async (job) => {
await handler({ data: job.data, id: job.id, name: job.name }); await handler({
data: job.data,
id: job.id,
name: job.name.split('.')[1],
});
}, },
); );
} }

View File

@ -156,7 +156,7 @@ export class MessageQueueExplorer implements OnModuleInit {
}), }),
); );
if (isRequestScoped) { if (isRequestScoped && job.data) {
const contextId = createContextId(); const contextId = createContextId();
if (this.moduleRef.registerRequestByContextId) { if (this.moduleRef.registerRequestByContextId) {

View File

@ -1,15 +1,9 @@
import { CalendarChannelEventAssociationRepository } from 'src/modules/calendar/repositories/calendar-channel-event-association.repository';
import { CalendarChannelRepository } from 'src/modules/calendar/repositories/calendar-channel.repository';
import { CalendarEventParticipantRepository } from 'src/modules/calendar/repositories/calendar-event-participant.repository';
import { CalendarEventRepository } from 'src/modules/calendar/repositories/calendar-event.repository';
import { CompanyRepository } from 'src/modules/company/repositories/company.repository'; import { CompanyRepository } from 'src/modules/company/repositories/company.repository';
import { BlocklistRepository } from 'src/modules/connected-account/repositories/blocklist.repository'; import { BlocklistRepository } from 'src/modules/connected-account/repositories/blocklist.repository';
import { ConnectedAccountRepository } from 'src/modules/connected-account/repositories/connected-account.repository'; import { ConnectedAccountRepository } from 'src/modules/connected-account/repositories/connected-account.repository';
import { AuditLogRepository } from 'src/modules/timeline/repositiories/audit-log.repository'; import { AuditLogRepository } from 'src/modules/timeline/repositiories/audit-log.repository';
import { TimelineActivityRepository } from 'src/modules/timeline/repositiories/timeline-activity.repository'; import { TimelineActivityRepository } from 'src/modules/timeline/repositiories/timeline-activity.repository';
import { WorkspaceMemberRepository } from 'src/modules/workspace-member/repositories/workspace-member.repository'; import { WorkspaceMemberRepository } from 'src/modules/workspace-member/repositories/workspace-member.repository';
import { AttachmentRepository } from 'src/modules/attachment/repositories/attachment.repository';
import { CommentRepository } from 'src/modules/activity/repositories/comment.repository';
import { MessageChannelMessageAssociationRepository } from 'src/modules/messaging/common/repositories/message-channel-message-association.repository'; import { MessageChannelMessageAssociationRepository } from 'src/modules/messaging/common/repositories/message-channel-message-association.repository';
import { MessageChannelRepository } from 'src/modules/messaging/common/repositories/message-channel.repository'; import { MessageChannelRepository } from 'src/modules/messaging/common/repositories/message-channel.repository';
import { MessageParticipantRepository } from 'src/modules/messaging/common/repositories/message-participant.repository'; import { MessageParticipantRepository } from 'src/modules/messaging/common/repositories/message-participant.repository';
@ -20,11 +14,6 @@ import { PersonRepository } from 'src/modules/person/repositories/person.reposit
export const metadataToRepositoryMapping = { export const metadataToRepositoryMapping = {
AuditLogWorkspaceEntity: AuditLogRepository, AuditLogWorkspaceEntity: AuditLogRepository,
BlocklistWorkspaceEntity: BlocklistRepository, BlocklistWorkspaceEntity: BlocklistRepository,
CalendarChannelEventAssociationWorkspaceEntity:
CalendarChannelEventAssociationRepository,
CalendarChannelWorkspaceEntity: CalendarChannelRepository,
CalendarEventParticipantWorkspaceEntity: CalendarEventParticipantRepository,
CalendarEventWorkspaceEntity: CalendarEventRepository,
CompanyWorkspaceEntity: CompanyRepository, CompanyWorkspaceEntity: CompanyRepository,
ConnectedAccountWorkspaceEntity: ConnectedAccountRepository, ConnectedAccountWorkspaceEntity: ConnectedAccountRepository,
MessageChannelMessageAssociationWorkspaceEntity: MessageChannelMessageAssociationWorkspaceEntity:
@ -36,6 +25,4 @@ export const metadataToRepositoryMapping = {
PersonWorkspaceEntity: PersonRepository, PersonWorkspaceEntity: PersonRepository,
TimelineActivityWorkspaceEntity: TimelineActivityRepository, TimelineActivityWorkspaceEntity: TimelineActivityRepository,
WorkspaceMemberWorkspaceEntity: WorkspaceMemberRepository, WorkspaceMemberWorkspaceEntity: WorkspaceMemberRepository,
AttachmentWorkspaceEntity: AttachmentRepository,
CommentWorkspaceEntity: CommentRepository,
}; };

View File

@ -0,0 +1,39 @@
import { Inject, Type } from '@nestjs/common';
import { ModuleRef, createContextId } from '@nestjs/core';
import { Injector } from '@nestjs/core/injector/injector';
export class LoadServiceWithWorkspaceContext {
private readonly injector = new Injector();
constructor(
@Inject(ModuleRef)
private readonly moduleRef: ModuleRef,
) {}
async load<T>(service: T, workspaceId: string): Promise<T> {
const modules = this.moduleRef['container'].getModules();
const host = [...modules.values()].find((module) =>
module.providers.has((service as Type<T>).constructor),
);
if (!host) {
throw new Error('Host module not found for the service');
}
const contextId = createContextId();
if (this.moduleRef.registerRequestByContextId) {
this.moduleRef.registerRequestByContextId(
{ req: { workspaceId } },
contextId,
);
}
return this.injector.loadPerContext(
service,
host,
new Map(host.providers),
contextId,
);
}
}

View File

@ -36,7 +36,7 @@ export class CustomWorkspaceEntity extends BaseWorkspaceEntity {
}) })
@WorkspaceIsNullable() @WorkspaceIsNullable()
@WorkspaceIsSystem() @WorkspaceIsSystem()
position: number; position: number | null;
@WorkspaceRelation({ @WorkspaceRelation({
standardId: CUSTOM_OBJECT_STANDARD_FIELD_IDS.activityTargets, standardId: CUSTOM_OBJECT_STANDARD_FIELD_IDS.activityTargets,

View File

@ -6,8 +6,8 @@ import {
QueryRunner, QueryRunner,
} from 'typeorm'; } from 'typeorm';
import { WorkspaceRepository } from 'src/engine/twenty-orm/repository/workspace.repository';
import { WorkspaceEntityManager } from 'src/engine/twenty-orm/entity-manager/entity.manager'; import { WorkspaceEntityManager } from 'src/engine/twenty-orm/entity-manager/entity.manager';
import { WorkspaceRepository } from 'src/engine/twenty-orm/repository/workspace.repository';
export class WorkspaceDataSource extends DataSource { export class WorkspaceDataSource extends DataSource {
readonly manager: WorkspaceEntityManager; readonly manager: WorkspaceEntityManager;

View File

@ -32,10 +32,9 @@ export class WorkspaceDatasourceFactory {
dataSourceMetadata.url ?? dataSourceMetadata.url ??
this.environmentService.get('PG_DATABASE_URL'), this.environmentService.get('PG_DATABASE_URL'),
type: 'postgres', type: 'postgres',
// logging: this.environmentService.get('DEBUG_MODE') logging: this.environmentService.get('DEBUG_MODE')
// ? ['query', 'error'] ? ['query', 'error']
// : ['error'], : ['error'],
logging: 'all',
schema: dataSourceMetadata.schema, schema: dataSourceMetadata.schema,
entities, entities,
ssl: this.environmentService.get('PG_SSL_ALLOW_SELF_SIGNED') ssl: this.environmentService.get('PG_SSL_ALLOW_SELF_SIGNED')

View File

@ -1,6 +1,7 @@
import { import {
DeepPartial, DeepPartial,
DeleteResult, DeleteResult,
EntityManager,
FindManyOptions, FindManyOptions,
FindOneOptions, FindOneOptions,
FindOptionsWhere, FindOptionsWhere,
@ -29,9 +30,13 @@ export class WorkspaceRepository<
/** /**
* FIND METHODS * FIND METHODS
*/ */
override async find(options?: FindManyOptions<Entity>): Promise<Entity[]> { override async find(
options?: FindManyOptions<Entity>,
entityManager?: EntityManager,
): Promise<Entity[]> {
const manager = entityManager || this.manager;
const computedOptions = this.transformOptions(options); const computedOptions = this.transformOptions(options);
const result = await super.find(computedOptions); const result = await manager.find(this.target, computedOptions);
const formattedResult = this.formatResult(result); const formattedResult = this.formatResult(result);
return formattedResult; return formattedResult;
@ -39,9 +44,11 @@ export class WorkspaceRepository<
override async findBy( override async findBy(
where: FindOptionsWhere<Entity> | FindOptionsWhere<Entity>[], where: FindOptionsWhere<Entity> | FindOptionsWhere<Entity>[],
entityManager?: EntityManager,
): Promise<Entity[]> { ): Promise<Entity[]> {
const manager = entityManager || this.manager;
const computedOptions = this.transformOptions({ where }); const computedOptions = this.transformOptions({ where });
const result = await super.findBy(computedOptions.where); const result = await manager.findBy(this.target, computedOptions.where);
const formattedResult = this.formatResult(result); const formattedResult = this.formatResult(result);
return formattedResult; return formattedResult;
@ -49,9 +56,11 @@ export class WorkspaceRepository<
override async findAndCount( override async findAndCount(
options?: FindManyOptions<Entity>, options?: FindManyOptions<Entity>,
entityManager?: EntityManager,
): Promise<[Entity[], number]> { ): Promise<[Entity[], number]> {
const manager = entityManager || this.manager;
const computedOptions = this.transformOptions(options); const computedOptions = this.transformOptions(options);
const result = await super.findAndCount(computedOptions); const result = await manager.findAndCount(this.target, computedOptions);
const formattedResult = this.formatResult(result); const formattedResult = this.formatResult(result);
return formattedResult; return formattedResult;
@ -59,9 +68,14 @@ export class WorkspaceRepository<
override async findAndCountBy( override async findAndCountBy(
where: FindOptionsWhere<Entity> | FindOptionsWhere<Entity>[], where: FindOptionsWhere<Entity> | FindOptionsWhere<Entity>[],
entityManager?: EntityManager,
): Promise<[Entity[], number]> { ): Promise<[Entity[], number]> {
const manager = entityManager || this.manager;
const computedOptions = this.transformOptions({ where }); const computedOptions = this.transformOptions({ where });
const result = await super.findAndCountBy(computedOptions.where); const result = await manager.findAndCountBy(
this.target,
computedOptions.where,
);
const formattedResult = this.formatResult(result); const formattedResult = this.formatResult(result);
return formattedResult; return formattedResult;
@ -69,9 +83,11 @@ export class WorkspaceRepository<
override async findOne( override async findOne(
options: FindOneOptions<Entity>, options: FindOneOptions<Entity>,
entityManager?: EntityManager,
): Promise<Entity | null> { ): Promise<Entity | null> {
const manager = entityManager || this.manager;
const computedOptions = this.transformOptions(options); const computedOptions = this.transformOptions(options);
const result = await super.findOne(computedOptions); const result = await manager.findOne(this.target, computedOptions);
const formattedResult = this.formatResult(result); const formattedResult = this.formatResult(result);
return formattedResult; return formattedResult;
@ -79,9 +95,11 @@ export class WorkspaceRepository<
override async findOneBy( override async findOneBy(
where: FindOptionsWhere<Entity> | FindOptionsWhere<Entity>[], where: FindOptionsWhere<Entity> | FindOptionsWhere<Entity>[],
entityManager?: EntityManager,
): Promise<Entity | null> { ): Promise<Entity | null> {
const manager = entityManager || this.manager;
const computedOptions = this.transformOptions({ where }); const computedOptions = this.transformOptions({ where });
const result = await super.findOneBy(computedOptions.where); const result = await manager.findOneBy(this.target, computedOptions.where);
const formattedResult = this.formatResult(result); const formattedResult = this.formatResult(result);
return formattedResult; return formattedResult;
@ -89,9 +107,11 @@ export class WorkspaceRepository<
override async findOneOrFail( override async findOneOrFail(
options: FindOneOptions<Entity>, options: FindOneOptions<Entity>,
entityManager?: EntityManager,
): Promise<Entity> { ): Promise<Entity> {
const manager = entityManager || this.manager;
const computedOptions = this.transformOptions(options); const computedOptions = this.transformOptions(options);
const result = await super.findOneOrFail(computedOptions); const result = await manager.findOneOrFail(this.target, computedOptions);
const formattedResult = this.formatResult(result); const formattedResult = this.formatResult(result);
return formattedResult; return formattedResult;
@ -99,9 +119,14 @@ export class WorkspaceRepository<
override async findOneByOrFail( override async findOneByOrFail(
where: FindOptionsWhere<Entity> | FindOptionsWhere<Entity>[], where: FindOptionsWhere<Entity> | FindOptionsWhere<Entity>[],
entityManager?: EntityManager,
): Promise<Entity> { ): Promise<Entity> {
const manager = entityManager || this.manager;
const computedOptions = this.transformOptions({ where }); const computedOptions = this.transformOptions({ where });
const result = await super.findOneByOrFail(computedOptions.where); const result = await manager.findOneByOrFail(
this.target,
computedOptions.where,
);
const formattedResult = this.formatResult(result); const formattedResult = this.formatResult(result);
return formattedResult; return formattedResult;
@ -113,29 +138,40 @@ export class WorkspaceRepository<
override save<T extends DeepPartial<Entity>>( override save<T extends DeepPartial<Entity>>(
entities: T[], entities: T[],
options: SaveOptions & { reload: false }, options: SaveOptions & { reload: false },
entityManager?: EntityManager,
): Promise<T[]>; ): Promise<T[]>;
override save<T extends DeepPartial<Entity>>( override save<T extends DeepPartial<Entity>>(
entities: T[], entities: T[],
options?: SaveOptions, options?: SaveOptions,
entityManager?: EntityManager,
): Promise<(T & Entity)[]>; ): Promise<(T & Entity)[]>;
override save<T extends DeepPartial<Entity>>( override save<T extends DeepPartial<Entity>>(
entity: T, entity: T,
options: SaveOptions & { reload: false }, options: SaveOptions & { reload: false },
entityManager?: EntityManager,
): Promise<T>; ): Promise<T>;
override save<T extends DeepPartial<Entity>>( override save<T extends DeepPartial<Entity>>(
entity: T, entity: T,
options?: SaveOptions, options?: SaveOptions,
entityManager?: EntityManager,
): Promise<T & Entity>; ): Promise<T & Entity>;
override async save<T extends DeepPartial<Entity>>( override async save<T extends DeepPartial<Entity>>(
entityOrEntities: T | T[], entityOrEntities: T | T[],
options?: SaveOptions, options?: SaveOptions,
entityManager?: EntityManager,
): Promise<T | T[]> { ): Promise<T | T[]> {
const manager = entityManager || this.manager;
const formattedEntityOrEntities = this.formatData(entityOrEntities); const formattedEntityOrEntities = this.formatData(entityOrEntities);
const result = await super.save(formattedEntityOrEntities as any, options); const result = await manager.save(
this.target,
formattedEntityOrEntities as any,
options,
);
const formattedResult = this.formatResult(result); const formattedResult = this.formatResult(result);
return formattedResult; return formattedResult;
@ -147,15 +183,27 @@ export class WorkspaceRepository<
override remove( override remove(
entities: Entity[], entities: Entity[],
options?: RemoveOptions, options?: RemoveOptions,
entityManager?: EntityManager,
): Promise<Entity[]>; ): Promise<Entity[]>;
override remove(entity: Entity, options?: RemoveOptions): Promise<Entity>; override remove(
entity: Entity,
options?: RemoveOptions,
entityManager?: EntityManager,
): Promise<Entity>;
override async remove( override async remove(
entityOrEntities: Entity | Entity[], entityOrEntities: Entity | Entity[],
options?: RemoveOptions,
entityManager?: EntityManager,
): Promise<Entity | Entity[]> { ): Promise<Entity | Entity[]> {
const manager = entityManager || this.manager;
const formattedEntityOrEntities = this.formatData(entityOrEntities); const formattedEntityOrEntities = this.formatData(entityOrEntities);
const result = await super.remove(formattedEntityOrEntities as any); const result = await manager.remove(
this.target,
formattedEntityOrEntities,
options,
);
const formattedResult = this.formatResult(result); const formattedResult = this.formatResult(result);
return formattedResult; return formattedResult;
@ -172,40 +220,50 @@ export class WorkspaceRepository<
| ObjectId | ObjectId
| ObjectId[] | ObjectId[]
| FindOptionsWhere<Entity>, | FindOptionsWhere<Entity>,
entityManager?: EntityManager,
): Promise<DeleteResult> { ): Promise<DeleteResult> {
const manager = entityManager || this.manager;
if (typeof criteria === 'object' && 'where' in criteria) { if (typeof criteria === 'object' && 'where' in criteria) {
criteria = this.transformOptions(criteria); criteria = this.transformOptions(criteria);
} }
return this.delete(criteria); return manager.delete(this.target, criteria);
} }
override softRemove<T extends DeepPartial<Entity>>( override softRemove<T extends DeepPartial<Entity>>(
entities: T[], entities: T[],
options: SaveOptions & { reload: false }, options: SaveOptions & { reload: false },
entityManager?: EntityManager,
): Promise<T[]>; ): Promise<T[]>;
override softRemove<T extends DeepPartial<Entity>>( override softRemove<T extends DeepPartial<Entity>>(
entities: T[], entities: T[],
options?: SaveOptions, options?: SaveOptions,
entityManager?: EntityManager,
): Promise<(T & Entity)[]>; ): Promise<(T & Entity)[]>;
override softRemove<T extends DeepPartial<Entity>>( override softRemove<T extends DeepPartial<Entity>>(
entity: T, entity: T,
options: SaveOptions & { reload: false }, options: SaveOptions & { reload: false },
entityManager?: EntityManager,
): Promise<T>; ): Promise<T>;
override softRemove<T extends DeepPartial<Entity>>( override softRemove<T extends DeepPartial<Entity>>(
entity: T, entity: T,
options?: SaveOptions, options?: SaveOptions,
entityManager?: EntityManager,
): Promise<T & Entity>; ): Promise<T & Entity>;
override async softRemove<T extends DeepPartial<Entity>>( override async softRemove<T extends DeepPartial<Entity>>(
entityOrEntities: T | T[], entityOrEntities: T | T[],
options?: SaveOptions, options?: SaveOptions,
entityManager?: EntityManager,
): Promise<T | T[]> { ): Promise<T | T[]> {
const manager = entityManager || this.manager;
const formattedEntityOrEntities = this.formatData(entityOrEntities); const formattedEntityOrEntities = this.formatData(entityOrEntities);
const result = await super.softRemove( const result = await manager.softRemove(
this.target,
formattedEntityOrEntities as any, formattedEntityOrEntities as any,
options, options,
); );
@ -225,12 +283,15 @@ export class WorkspaceRepository<
| ObjectId | ObjectId
| ObjectId[] | ObjectId[]
| FindOptionsWhere<Entity>, | FindOptionsWhere<Entity>,
entityManager?: EntityManager,
): Promise<UpdateResult> { ): Promise<UpdateResult> {
const manager = entityManager || this.manager;
if (typeof criteria === 'object' && 'where' in criteria) { if (typeof criteria === 'object' && 'where' in criteria) {
criteria = this.transformOptions(criteria); criteria = this.transformOptions(criteria);
} }
return this.softDelete(criteria); return manager.softDelete(this.target, criteria);
} }
/** /**
@ -239,29 +300,36 @@ export class WorkspaceRepository<
override recover<T extends DeepPartial<Entity>>( override recover<T extends DeepPartial<Entity>>(
entities: T[], entities: T[],
options: SaveOptions & { reload: false }, options: SaveOptions & { reload: false },
entityManager?: EntityManager,
): Promise<T[]>; ): Promise<T[]>;
override recover<T extends DeepPartial<Entity>>( override recover<T extends DeepPartial<Entity>>(
entities: T[], entities: T[],
options?: SaveOptions, options?: SaveOptions,
entityManager?: EntityManager,
): Promise<(T & Entity)[]>; ): Promise<(T & Entity)[]>;
override recover<T extends DeepPartial<Entity>>( override recover<T extends DeepPartial<Entity>>(
entity: T, entity: T,
options: SaveOptions & { reload: false }, options: SaveOptions & { reload: false },
entityManager?: EntityManager,
): Promise<T>; ): Promise<T>;
override recover<T extends DeepPartial<Entity>>( override recover<T extends DeepPartial<Entity>>(
entity: T, entity: T,
options?: SaveOptions, options?: SaveOptions,
entityManager?: EntityManager,
): Promise<T & Entity>; ): Promise<T & Entity>;
override async recover<T extends DeepPartial<Entity>>( override async recover<T extends DeepPartial<Entity>>(
entityOrEntities: T | T[], entityOrEntities: T | T[],
options?: SaveOptions, options?: SaveOptions,
entityManager?: EntityManager,
): Promise<T | T[]> { ): Promise<T | T[]> {
const manager = entityManager || this.manager;
const formattedEntityOrEntities = this.formatData(entityOrEntities); const formattedEntityOrEntities = this.formatData(entityOrEntities);
const result = await super.recover( const result = await manager.recover(
this.target,
formattedEntityOrEntities as any, formattedEntityOrEntities as any,
options, options,
); );
@ -281,12 +349,15 @@ export class WorkspaceRepository<
| ObjectId | ObjectId
| ObjectId[] | ObjectId[]
| FindOptionsWhere<Entity>, | FindOptionsWhere<Entity>,
entityManager?: EntityManager,
): Promise<UpdateResult> { ): Promise<UpdateResult> {
const manager = entityManager || this.manager;
if (typeof criteria === 'object' && 'where' in criteria) { if (typeof criteria === 'object' && 'where' in criteria) {
criteria = this.transformOptions(criteria); criteria = this.transformOptions(criteria);
} }
return this.restore(criteria); return manager.restore(this.target, criteria);
} }
/** /**
@ -294,9 +365,11 @@ export class WorkspaceRepository<
*/ */
override async insert( override async insert(
entity: QueryDeepPartialEntity<Entity> | QueryDeepPartialEntity<Entity>[], entity: QueryDeepPartialEntity<Entity> | QueryDeepPartialEntity<Entity>[],
entityManager?: EntityManager,
): Promise<InsertResult> { ): Promise<InsertResult> {
const manager = entityManager || this.manager;
const formatedEntity = this.formatData(entity); const formatedEntity = this.formatData(entity);
const result = await super.insert(formatedEntity); const result = await manager.insert(this.target, formatedEntity);
const formattedResult = this.formatResult(result); const formattedResult = this.formatResult(result);
return formattedResult; return formattedResult;
@ -317,12 +390,15 @@ export class WorkspaceRepository<
| ObjectId[] | ObjectId[]
| FindOptionsWhere<Entity>, | FindOptionsWhere<Entity>,
partialEntity: QueryDeepPartialEntity<Entity>, partialEntity: QueryDeepPartialEntity<Entity>,
entityManager?: EntityManager,
): Promise<UpdateResult> { ): Promise<UpdateResult> {
const manager = entityManager || this.manager;
if (typeof criteria === 'object' && 'where' in criteria) { if (typeof criteria === 'object' && 'where' in criteria) {
criteria = this.transformOptions(criteria); criteria = this.transformOptions(criteria);
} }
return this.update(criteria, partialEntity); return manager.update(this.target, criteria, partialEntity);
} }
override upsert( override upsert(
@ -330,50 +406,63 @@ export class WorkspaceRepository<
| QueryDeepPartialEntity<Entity> | QueryDeepPartialEntity<Entity>
| QueryDeepPartialEntity<Entity>[], | QueryDeepPartialEntity<Entity>[],
conflictPathsOrOptions: string[] | UpsertOptions<Entity>, conflictPathsOrOptions: string[] | UpsertOptions<Entity>,
entityManager?: EntityManager,
): Promise<InsertResult> { ): Promise<InsertResult> {
const manager = entityManager || this.manager;
const formattedEntityOrEntities = this.formatData(entityOrEntities); const formattedEntityOrEntities = this.formatData(entityOrEntities);
return this.upsert(formattedEntityOrEntities, conflictPathsOrOptions); return manager.upsert(
this.target,
formattedEntityOrEntities,
conflictPathsOrOptions,
);
} }
/** /**
* EXIST METHODS * EXIST METHODS
*/ */
override exist(options?: FindManyOptions<Entity>): Promise<boolean> { override exists(
options?: FindManyOptions<Entity>,
entityManager?: EntityManager,
): Promise<boolean> {
const manager = entityManager || this.manager;
const computedOptions = this.transformOptions(options); const computedOptions = this.transformOptions(options);
return super.exist(computedOptions); return manager.exists(this.target, computedOptions);
}
override exists(options?: FindManyOptions<Entity>): Promise<boolean> {
const computedOptions = this.transformOptions(options);
return super.exists(computedOptions);
} }
override existsBy( override existsBy(
where: FindOptionsWhere<Entity> | FindOptionsWhere<Entity>[], where: FindOptionsWhere<Entity> | FindOptionsWhere<Entity>[],
entityManager?: EntityManager,
): Promise<boolean> { ): Promise<boolean> {
const manager = entityManager || this.manager;
const computedOptions = this.transformOptions({ where }); const computedOptions = this.transformOptions({ where });
return super.existsBy(computedOptions.where); return manager.existsBy(this.target, computedOptions.where);
} }
/** /**
* COUNT METHODS * COUNT METHODS
*/ */
override count(options?: FindManyOptions<Entity>): Promise<number> { override count(
options?: FindManyOptions<Entity>,
entityManager?: EntityManager,
): Promise<number> {
const manager = entityManager || this.manager;
const computedOptions = this.transformOptions(options); const computedOptions = this.transformOptions(options);
return super.count(computedOptions); return manager.count(this.target, computedOptions);
} }
override countBy( override countBy(
where: FindOptionsWhere<Entity> | FindOptionsWhere<Entity>[], where: FindOptionsWhere<Entity> | FindOptionsWhere<Entity>[],
entityManager?: EntityManager,
): Promise<number> { ): Promise<number> {
const manager = entityManager || this.manager;
const computedOptions = this.transformOptions({ where }); const computedOptions = this.transformOptions({ where });
return super.countBy(computedOptions.where); return manager.countBy(this.target, computedOptions.where);
} }
/** /**
@ -382,57 +471,79 @@ export class WorkspaceRepository<
override sum( override sum(
columnName: PickKeysByType<Entity, number>, columnName: PickKeysByType<Entity, number>,
where?: FindOptionsWhere<Entity> | FindOptionsWhere<Entity>[], where?: FindOptionsWhere<Entity> | FindOptionsWhere<Entity>[],
entityManager?: EntityManager,
): Promise<number | null> { ): Promise<number | null> {
const manager = entityManager || this.manager;
const computedOptions = this.transformOptions({ where }); const computedOptions = this.transformOptions({ where });
return super.sum(columnName, computedOptions.where); return manager.sum(this.target, columnName, computedOptions.where);
} }
override average( override average(
columnName: PickKeysByType<Entity, number>, columnName: PickKeysByType<Entity, number>,
where?: FindOptionsWhere<Entity> | FindOptionsWhere<Entity>[], where?: FindOptionsWhere<Entity> | FindOptionsWhere<Entity>[],
entityManager?: EntityManager,
): Promise<number | null> { ): Promise<number | null> {
const manager = entityManager || this.manager;
const computedOptions = this.transformOptions({ where }); const computedOptions = this.transformOptions({ where });
return super.average(columnName, computedOptions.where); return manager.average(this.target, columnName, computedOptions.where);
} }
override minimum( override minimum(
columnName: PickKeysByType<Entity, number>, columnName: PickKeysByType<Entity, number>,
where?: FindOptionsWhere<Entity> | FindOptionsWhere<Entity>[], where?: FindOptionsWhere<Entity> | FindOptionsWhere<Entity>[],
entityManager?: EntityManager,
): Promise<number | null> { ): Promise<number | null> {
const manager = entityManager || this.manager;
const computedOptions = this.transformOptions({ where }); const computedOptions = this.transformOptions({ where });
return super.minimum(columnName, computedOptions.where); return manager.minimum(this.target, columnName, computedOptions.where);
} }
override maximum( override maximum(
columnName: PickKeysByType<Entity, number>, columnName: PickKeysByType<Entity, number>,
where?: FindOptionsWhere<Entity> | FindOptionsWhere<Entity>[], where?: FindOptionsWhere<Entity> | FindOptionsWhere<Entity>[],
entityManager?: EntityManager,
): Promise<number | null> { ): Promise<number | null> {
const manager = entityManager || this.manager;
const computedOptions = this.transformOptions({ where }); const computedOptions = this.transformOptions({ where });
return super.maximum(columnName, computedOptions.where); return manager.maximum(this.target, columnName, computedOptions.where);
} }
override increment( override increment(
conditions: FindOptionsWhere<Entity>, conditions: FindOptionsWhere<Entity>,
propertyPath: string, propertyPath: string,
value: number | string, value: number | string,
entityManager?: EntityManager,
): Promise<UpdateResult> { ): Promise<UpdateResult> {
const manager = entityManager || this.manager;
const computedConditions = this.transformOptions({ where: conditions }); const computedConditions = this.transformOptions({ where: conditions });
return this.increment(computedConditions.where, propertyPath, value); return manager.increment(
this.target,
computedConditions.where,
propertyPath,
value,
);
} }
override decrement( override decrement(
conditions: FindOptionsWhere<Entity>, conditions: FindOptionsWhere<Entity>,
propertyPath: string, propertyPath: string,
value: number | string, value: number | string,
entityManager?: EntityManager,
): Promise<UpdateResult> { ): Promise<UpdateResult> {
const manager = entityManager || this.manager;
const computedConditions = this.transformOptions({ where: conditions }); const computedConditions = this.transformOptions({ where: conditions });
return this.decrement(computedConditions.where, propertyPath, value); return manager.decrement(
this.target,
computedConditions.where,
propertyPath,
value,
);
} }
/** /**

View File

@ -30,12 +30,21 @@ import {
ConfigurableModuleClass, ConfigurableModuleClass,
MODULE_OPTIONS_TOKEN, MODULE_OPTIONS_TOKEN,
} from 'src/engine/twenty-orm/twenty-orm.module-definition'; } from 'src/engine/twenty-orm/twenty-orm.module-definition';
import { LoadServiceWithWorkspaceContext } from 'src/engine/twenty-orm/context/load-service-with-workspace.context';
@Global() @Global()
@Module({ @Module({
imports: [DataSourceModule], imports: [DataSourceModule],
providers: [...entitySchemaFactories, TwentyORMManager], providers: [
exports: [EntitySchemaFactory, TwentyORMManager], ...entitySchemaFactories,
TwentyORMManager,
LoadServiceWithWorkspaceContext,
],
exports: [
EntitySchemaFactory,
TwentyORMManager,
LoadServiceWithWorkspaceContext,
],
}) })
export class TwentyORMCoreModule export class TwentyORMCoreModule
extends ConfigurableModuleClass extends ConfigurableModuleClass

View File

@ -1,19 +1,20 @@
import { ObjectLiteral } from 'typeorm';
import { BaseWorkspaceEntity } from 'src/engine/twenty-orm/base.workspace-entity'; import { BaseWorkspaceEntity } from 'src/engine/twenty-orm/base.workspace-entity';
export type ObjectRecord<T extends ObjectLiteral> = { type RelationKeys<T> = {
[K in keyof T as T[K] extends BaseWorkspaceEntity [K in keyof T]: NonNullable<T[K]> extends BaseWorkspaceEntity ? K : never;
? `${Extract<K, string>}Id` }[keyof T];
: K]: T[K] extends BaseWorkspaceEntity
? string type ForeignKeyMap<T> = {
: T[K] extends BaseWorkspaceEntity[] [K in RelationKeys<T> as `${K & string}Id`]: string;
? string[]
: T[K];
} & {
[K in keyof T]: T[K] extends BaseWorkspaceEntity
? ObjectRecord<T[K]>
: T[K] extends BaseWorkspaceEntity[]
? ObjectRecord<T[K][number]>[]
: T[K];
}; };
type RecursiveObjectRecord<T> = {
[P in keyof T]: NonNullable<T[P]> extends BaseWorkspaceEntity
? ObjectRecord<NonNullable<T[P]>> & ForeignKeyMap<NonNullable<T[P]>>
: T[P];
};
// TODO: We should get rid of that it's causing too much issues
// Some relations can be null or undefined because they're not mendatory and other cannot
// This utility type put as defined all the joinColumn, so it's not well typed
export type ObjectRecord<T> = RecursiveObjectRecord<T> & ForeignKeyMap<T>;

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

View File

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

View File

@ -45,5 +45,5 @@ export class ApiKeyWorkspaceEntity extends BaseWorkspaceEntity {
icon: 'IconCalendar', icon: 'IconCalendar',
}) })
@WorkspaceIsNullable() @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', inverseSideFieldKey: 'attachments',
}) })
@WorkspaceIsNullable() @WorkspaceIsNullable()
activity: Relation<ActivityWorkspaceEntity>; activity: Relation<ActivityWorkspaceEntity> | null;
@WorkspaceRelation({ @WorkspaceRelation({
standardId: ATTACHMENT_STANDARD_FIELD_IDS.person, standardId: ATTACHMENT_STANDARD_FIELD_IDS.person,
@ -93,7 +93,7 @@ export class AttachmentWorkspaceEntity extends BaseWorkspaceEntity {
inverseSideFieldKey: 'attachments', inverseSideFieldKey: 'attachments',
}) })
@WorkspaceIsNullable() @WorkspaceIsNullable()
person: Relation<PersonWorkspaceEntity>; person: Relation<PersonWorkspaceEntity> | null;
@WorkspaceRelation({ @WorkspaceRelation({
standardId: ATTACHMENT_STANDARD_FIELD_IDS.company, standardId: ATTACHMENT_STANDARD_FIELD_IDS.company,
@ -106,7 +106,7 @@ export class AttachmentWorkspaceEntity extends BaseWorkspaceEntity {
inverseSideFieldKey: 'attachments', inverseSideFieldKey: 'attachments',
}) })
@WorkspaceIsNullable() @WorkspaceIsNullable()
company: Relation<CompanyWorkspaceEntity>; company: Relation<CompanyWorkspaceEntity> | null;
@WorkspaceRelation({ @WorkspaceRelation({
standardId: ATTACHMENT_STANDARD_FIELD_IDS.opportunity, standardId: ATTACHMENT_STANDARD_FIELD_IDS.opportunity,
@ -119,7 +119,7 @@ export class AttachmentWorkspaceEntity extends BaseWorkspaceEntity {
inverseSideFieldKey: 'attachments', inverseSideFieldKey: 'attachments',
}) })
@WorkspaceIsNullable() @WorkspaceIsNullable()
opportunity: Relation<OpportunityWorkspaceEntity>; opportunity: Relation<OpportunityWorkspaceEntity> | null;
@WorkspaceDynamicRelation({ @WorkspaceDynamicRelation({
type: RelationMetadataType.MANY_TO_ONE, 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 { Process } from 'src/engine/integrations/message-queue/decorators/process.decorator';
import { Processor } from 'src/engine/integrations/message-queue/decorators/processor.decorator'; import { Processor } from 'src/engine/integrations/message-queue/decorators/processor.decorator';
import { MessageQueue } from 'src/engine/integrations/message-queue/message-queue.constants'; import { MessageQueue } from 'src/engine/integrations/message-queue/message-queue.constants';
@ -11,7 +13,10 @@ export type MatchParticipantJobData = {
workspaceMemberId?: string; workspaceMemberId?: string;
}; };
@Processor(MessageQueue.messagingQueue) @Processor({
queueName: MessageQueue.messagingQueue,
scope: Scope.REQUEST,
})
export class MatchParticipantJob { export class MatchParticipantJob {
constructor( constructor(
private readonly messageParticipantService: MessagingMessageParticipantService, 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 { Processor } from 'src/engine/integrations/message-queue/decorators/processor.decorator';
import { MessageQueue } from 'src/engine/integrations/message-queue/message-queue.constants'; 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'; import { CalendarEventParticipantService } from 'src/modules/calendar/services/calendar-event-participant/calendar-event-participant.service';
@ -11,7 +13,10 @@ export type UnmatchParticipantJobData = {
workspaceMemberId?: string; workspaceMemberId?: string;
}; };
@Processor(MessageQueue.messagingQueue) @Processor({
queueName: MessageQueue.messagingQueue,
scope: Scope.REQUEST,
})
export class UnmatchParticipantJob { export class UnmatchParticipantJob {
constructor( constructor(
private readonly messageParticipantService: MessagingMessageParticipantService, private readonly messageParticipantService: MessagingMessageParticipantService,

View File

@ -1,19 +1,10 @@
import { Module } from '@nestjs/common'; 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 { 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 { 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({ @Module({
imports: [ imports: [WorkspaceGoogleCalendarSyncModule],
ObjectMetadataRepositoryModule.forFeature([
ConnectedAccountWorkspaceEntity,
CalendarChannelWorkspaceEntity,
]),
WorkspaceGoogleCalendarSyncModule,
],
providers: [GoogleCalendarSyncCommand], providers: [GoogleCalendarSyncCommand],
}) })
export class CalendarCommandsModule {} export class CalendarCommandsModule {}

View File

@ -1,4 +1,5 @@
import { InjectRepository } from '@nestjs/typeorm'; import { InjectRepository } from '@nestjs/typeorm';
import { Scope } from '@nestjs/common';
import { Repository, In } from 'typeorm'; 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 { Processor } from 'src/engine/integrations/message-queue/decorators/processor.decorator';
import { Process } from 'src/engine/integrations/message-queue/decorators/process.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 { export class GoogleCalendarSyncCronJob {
constructor( constructor(
@InjectRepository(Workspace, 'core') @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 { Processor } from 'src/engine/integrations/message-queue/decorators/processor.decorator';
import { MessageQueue } from 'src/engine/integrations/message-queue/message-queue.constants'; import { MessageQueue } from 'src/engine/integrations/message-queue/message-queue.constants';
import { InjectObjectMetadataRepository } from 'src/engine/object-metadata-repository/object-metadata-repository.decorator'; 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 { 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 { 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 { CalendarChannelWorkspaceEntity } from 'src/modules/calendar/standard-objects/calendar-channel.workspace-entity';
import { BlocklistRepository } from 'src/modules/connected-account/repositories/blocklist.repository'; import { BlocklistRepository } from 'src/modules/connected-account/repositories/blocklist.repository';
import { BlocklistWorkspaceEntity } from 'src/modules/connected-account/standard-objects/blocklist.workspace-entity'; import { BlocklistWorkspaceEntity } from 'src/modules/connected-account/standard-objects/blocklist.workspace-entity';
import { Process } from 'src/engine/integrations/message-queue/decorators/process.decorator'; 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 = { export type BlocklistItemDeleteCalendarEventsJobData = {
workspaceId: string; workspaceId: string;
blocklistItemId: string; blocklistItemId: string;
}; };
@Processor(MessageQueue.calendarQueue) @Processor({
queueName: MessageQueue.calendarQueue,
scope: Scope.REQUEST,
})
export class BlocklistItemDeleteCalendarEventsJob { export class BlocklistItemDeleteCalendarEventsJob {
private readonly logger = new Logger( private readonly logger = new Logger(
BlocklistItemDeleteCalendarEventsJob.name, BlocklistItemDeleteCalendarEventsJob.name,
); );
constructor( constructor(
@InjectObjectMetadataRepository(CalendarChannelWorkspaceEntity) @InjectWorkspaceRepository(CalendarChannelWorkspaceEntity)
private readonly calendarChannelRepository: CalendarChannelRepository, private readonly calendarChannelRepository: WorkspaceRepository<CalendarChannelWorkspaceEntity>,
@InjectObjectMetadataRepository( @InjectWorkspaceRepository(CalendarChannelEventAssociationWorkspaceEntity)
CalendarChannelEventAssociationWorkspaceEntity, private readonly calendarChannelEventAssociationRepository: WorkspaceRepository<CalendarChannelEventAssociationWorkspaceEntity>,
)
private readonly calendarChannelEventAssociationRepository: CalendarChannelEventAssociationRepository,
@InjectObjectMetadataRepository(BlocklistWorkspaceEntity) @InjectObjectMetadataRepository(BlocklistWorkspaceEntity)
private readonly blocklistRepository: BlocklistRepository, private readonly blocklistRepository: BlocklistRepository,
private readonly calendarEventCleanerService: CalendarEventCleanerService, private readonly calendarEventCleanerService: CalendarEventCleanerService,
@ -58,19 +61,39 @@ export class BlocklistItemDeleteCalendarEventsJob {
`Deleting calendar events from ${handle} in workspace ${workspaceId} for workspace member ${workspaceMemberId}`, `Deleting calendar events from ${handle} in workspace ${workspaceId} for workspace member ${workspaceMemberId}`,
); );
const calendarChannels = if (!workspaceMemberId) {
await this.calendarChannelRepository.getIdsByWorkspaceMemberId( throw new Error(
workspaceMemberId, `Workspace member ID is undefined for blocklist item ${blocklistItemId} in workspace ${workspaceId}`,
workspaceId,
); );
}
const calendarChannels = await this.calendarChannelRepository.find({
where: {
connectedAccount: {
accountOwner: {
id: workspaceMemberId,
},
},
},
relations: ['connectedAccount.accountOwner'],
});
const calendarChannelIds = calendarChannels.map(({ id }) => id); const calendarChannelIds = calendarChannels.map(({ id }) => id);
await this.calendarChannelEventAssociationRepository.deleteByCalendarEventParticipantHandleAndCalendarChannelIds( const isHandleDomain = handle.startsWith('@');
handle,
calendarChannelIds, await this.calendarChannelEventAssociationRepository.delete({
workspaceId, calendarEvent: {
); calendarEventParticipants: {
handle: isHandleDomain ? ILike(`%${handle}`) : handle,
},
calendarChannelEventAssociations: {
calendarChannel: {
id: Any(calendarChannelIds),
},
},
},
});
await this.calendarEventCleanerService.cleanWorkspaceCalendarEvents( await this.calendarEventCleanerService.cleanWorkspaceCalendarEvents(
workspaceId, 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 { Processor } from 'src/engine/integrations/message-queue/decorators/processor.decorator';
import { MessageQueue } from 'src/engine/integrations/message-queue/message-queue.constants'; import { MessageQueue } from 'src/engine/integrations/message-queue/message-queue.constants';
@ -14,7 +14,10 @@ export type BlocklistReimportCalendarEventsJobData = {
handle: string; handle: string;
}; };
@Processor(MessageQueue.calendarQueue) @Processor({
queueName: MessageQueue.calendarQueue,
scope: Scope.REQUEST,
})
export class BlocklistReimportCalendarEventsJob { export class BlocklistReimportCalendarEventsJob {
private readonly logger = new Logger(BlocklistReimportCalendarEventsJob.name); 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 { Processor } from 'src/engine/integrations/message-queue/decorators/processor.decorator';
import { MessageQueue } from 'src/engine/integrations/message-queue/message-queue.constants'; 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 { 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 { 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 { 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 { Process } from 'src/engine/integrations/message-queue/decorators/process.decorator';
import { ConnectedAccountWorkspaceEntity } from 'src/modules/connected-account/standard-objects/connected-account.workspace-entity'; 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 = { export type CalendarCreateCompanyAndContactAfterSyncJobData = {
workspaceId: string; workspaceId: string;
calendarChannelId: string; calendarChannelId: string;
}; };
@Processor(MessageQueue.calendarQueue) @Processor({
queueName: MessageQueue.calendarQueue,
scope: Scope.REQUEST,
})
export class CalendarCreateCompanyAndContactAfterSyncJob { export class CalendarCreateCompanyAndContactAfterSyncJob {
private readonly logger = new Logger( private readonly logger = new Logger(
CalendarCreateCompanyAndContactAfterSyncJob.name, CalendarCreateCompanyAndContactAfterSyncJob.name,
); );
constructor( constructor(
private readonly createCompanyAndContactService: CreateCompanyAndContactService, private readonly createCompanyAndContactService: CreateCompanyAndContactService,
@InjectObjectMetadataRepository(CalendarChannelWorkspaceEntity) @InjectWorkspaceRepository(CalendarChannelWorkspaceEntity)
private readonly calendarChannelService: CalendarChannelRepository, private readonly calendarChannelRepository: WorkspaceRepository<CalendarChannelWorkspaceEntity>,
@InjectObjectMetadataRepository(CalendarEventParticipantWorkspaceEntity) @InjectWorkspaceRepository(CalendarEventParticipantWorkspaceEntity)
private readonly calendarEventParticipantRepository: CalendarEventParticipantRepository, private readonly calendarEventParticipantRepository: WorkspaceRepository<CalendarEventParticipantWorkspaceEntity>,
@InjectObjectMetadataRepository(ConnectedAccountWorkspaceEntity)
private readonly connectedAccountRepository: ConnectedAccountRepository,
) {} ) {}
@Process(CalendarCreateCompanyAndContactAfterSyncJob.name) @Process(CalendarCreateCompanyAndContactAfterSyncJob.name)
@ -41,40 +41,52 @@ export class CalendarCreateCompanyAndContactAfterSyncJob {
); );
const { workspaceId, calendarChannelId } = data; const { workspaceId, calendarChannelId } = data;
const calendarChannels = await this.calendarChannelService.getByIds( const calendarChannel = await this.calendarChannelRepository.findOne({
[calendarChannelId], where: {
workspaceId, id: calendarChannelId,
); },
relations: ['connectedAccount.accountOwner'],
});
if (calendarChannels.length === 0) { if (!calendarChannel) {
throw new Error( throw new Error(
`Calendar channel with id ${calendarChannelId} not found in workspace ${workspaceId}`, `Calendar channel with id ${calendarChannelId} not found in workspace ${workspaceId}`,
); );
} }
const { handle, isContactAutoCreationEnabled, connectedAccountId } = const { handle, isContactAutoCreationEnabled, connectedAccount } =
calendarChannels[0]; calendarChannel;
if (!isContactAutoCreationEnabled || !handle) { if (!isContactAutoCreationEnabled || !handle) {
return; return;
} }
const connectedAccount = await this.connectedAccountRepository.getById(
connectedAccountId,
workspaceId,
);
if (!connectedAccount) { if (!connectedAccount) {
throw new Error( throw new Error(
`Connected account with id ${connectedAccountId} not found in workspace ${workspaceId}`, `Connected account not found in workspace ${workspaceId}`,
); );
} }
const calendarEventParticipantsWithoutPersonIdAndWorkspaceMemberId = const calendarEventParticipantsWithoutPersonIdAndWorkspaceMemberId =
await this.calendarEventParticipantRepository.getByCalendarChannelIdWithoutPersonIdAndWorkspaceMemberId( await this.calendarEventParticipantRepository.find({
calendarChannelId, where: {
workspaceId, calendarEvent: {
); calendarChannelEventAssociations: {
calendarChannel: {
id: calendarChannelId,
},
},
calendarEventParticipants: {
person: IsNull(),
workspaceMember: IsNull(),
},
},
},
relations: [
'calendarEvent.calendarChannelEventAssociations',
'calendarEvent.calendarEventParticipants',
],
});
await this.createCompanyAndContactService.createCompaniesAndContactsAndUpdateParticipants( await this.createCompanyAndContactService.createCompaniesAndContactsAndUpdateParticipants(
connectedAccount, connectedAccount,

View File

@ -1,6 +1,7 @@
import { Module } from '@nestjs/common'; import { Module } from '@nestjs/common';
import { ObjectMetadataRepositoryModule } from 'src/engine/object-metadata-repository/object-metadata-repository.module'; 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 { 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 { 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'; 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({ @Module({
imports: [ imports: [
ObjectMetadataRepositoryModule.forFeature([ TwentyORMModule.forFeature([
CalendarChannelWorkspaceEntity, CalendarChannelWorkspaceEntity,
CalendarChannelEventAssociationWorkspaceEntity, CalendarChannelEventAssociationWorkspaceEntity,
CalendarEventParticipantWorkspaceEntity, CalendarEventParticipantWorkspaceEntity,
]),
ObjectMetadataRepositoryModule.forFeature([
ConnectedAccountWorkspaceEntity, ConnectedAccountWorkspaceEntity,
BlocklistWorkspaceEntity, 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 { 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'; import { GoogleCalendarSyncService } from 'src/modules/calendar/services/google-calendar-sync/google-calendar-sync.service';
@ -11,7 +11,10 @@ export type GoogleCalendarSyncJobData = {
connectedAccountId: string; connectedAccountId: string;
}; };
@Processor(MessageQueue.calendarQueue) @Processor({
queueName: MessageQueue.calendarQueue,
scope: Scope.REQUEST,
})
export class GoogleCalendarSyncJob { export class GoogleCalendarSyncJob {
private readonly logger = new Logger(GoogleCalendarSyncJob.name); private readonly logger = new Logger(GoogleCalendarSyncJob.name);

View File

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

View File

@ -1,26 +1,20 @@
import { import { BadRequestException, Injectable } from '@nestjs/common';
BadRequestException,
Injectable,
NotFoundException,
} from '@nestjs/common';
import { WorkspacePreQueryHook } from 'src/engine/api/graphql/workspace-query-runner/workspace-pre-query-hook/interfaces/workspace-pre-query-hook.interface'; import { 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 { 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 { 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 { 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() @Injectable()
export class CalendarEventFindManyPreQueryHook export class CalendarEventFindManyPreQueryHook
implements WorkspacePreQueryHook implements WorkspacePreQueryHook
{ {
constructor( constructor(
@InjectObjectMetadataRepository( @InjectWorkspaceRepository(CalendarChannelEventAssociationWorkspaceEntity)
CalendarChannelEventAssociationWorkspaceEntity, private readonly calendarChannelEventAssociationRepository: WorkspaceRepository<CalendarChannelEventAssociationWorkspaceEntity>,
)
private readonly calendarChannelEventAssociationRepository: CalendarChannelEventAssociationRepository,
private readonly canAccessCalendarEventService: CanAccessCalendarEventService, private readonly canAccessCalendarEventService: CanAccessCalendarEventService,
) {} ) {}
@ -33,20 +27,25 @@ export class CalendarEventFindManyPreQueryHook
throw new BadRequestException('id filter is required'); throw new BadRequestException('id filter is required');
} }
const calendarChannelCalendarEventAssociations = // TODO: Re-implement this using twenty ORM
await this.calendarChannelEventAssociationRepository.getByCalendarEventIds( // const calendarChannelCalendarEventAssociations =
[payload?.filter?.id?.eq], // await this.calendarChannelEventAssociationRepository.find({
workspaceId, // where: {
); // calendarEvent: {
// id: payload?.filter?.id?.eq,
// },
// },
// relations: ['calendarChannel.connectedAccount'],
// });
if (calendarChannelCalendarEventAssociations.length === 0) { // if (calendarChannelCalendarEventAssociations.length === 0) {
throw new NotFoundException(); // throw new NotFoundException();
} // }
await this.canAccessCalendarEventService.canAccessCalendarEvent( // await this.canAccessCalendarEventService.canAccessCalendarEvent(
userId, // userId,
workspaceId, // workspaceId,
calendarChannelCalendarEventAssociations, // calendarChannelCalendarEventAssociations,
); // );
} }
} }

View File

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

View File

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

View File

@ -1,27 +1,40 @@
import { Injectable } from '@nestjs/common'; import { Injectable } from '@nestjs/common';
import { InjectObjectMetadataRepository } from 'src/engine/object-metadata-repository/object-metadata-repository.decorator'; import { Any, IsNull } from 'typeorm';
import { CalendarEventRepository } from 'src/modules/calendar/repositories/calendar-event.repository';
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 { CalendarEventWorkspaceEntity } from 'src/modules/calendar/standard-objects/calendar-event.workspace-entity';
import { deleteUsingPagination } from 'src/modules/messaging/message-cleaner/utils/delete-using-pagination.util'; import { deleteUsingPagination } from 'src/modules/messaging/message-cleaner/utils/delete-using-pagination.util';
@Injectable() @Injectable()
export class CalendarEventCleanerService { export class CalendarEventCleanerService {
constructor( constructor(
@InjectObjectMetadataRepository(CalendarEventWorkspaceEntity) @InjectWorkspaceRepository(CalendarEventWorkspaceEntity)
private readonly calendarEventRepository: CalendarEventRepository, private readonly calendarEventRepository: WorkspaceRepository<CalendarEventWorkspaceEntity>,
) {} ) {}
public async cleanWorkspaceCalendarEvents(workspaceId: string) { public async cleanWorkspaceCalendarEvents(workspaceId: string) {
await deleteUsingPagination( await deleteUsingPagination(
workspaceId, workspaceId,
500, 500,
this.calendarEventRepository.getNonAssociatedCalendarEventIdsPaginated.bind( async (limit, offset) => {
this.calendarEventRepository, const nonAssociatedCalendarEvents =
), await this.calendarEventRepository.find({
this.calendarEventRepository.deleteByIds.bind( where: {
this.calendarEventRepository, 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 { Module } from '@nestjs/common';
import { ObjectMetadataRepositoryModule } from 'src/engine/object-metadata-repository/object-metadata-repository.module'; 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 { 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 { 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 { 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'; import { PersonWorkspaceEntity } from 'src/modules/person/standard-objects/person.workspace-entity';
@Module({ @Module({
imports: [ imports: [
WorkspaceDataSourceModule, WorkspaceDataSourceModule,
TwentyORMModule.forFeature([CalendarEventParticipantWorkspaceEntity]),
ObjectMetadataRepositoryModule.forFeature([PersonWorkspaceEntity]), ObjectMetadataRepositoryModule.forFeature([PersonWorkspaceEntity]),
AddPersonIdAndWorkspaceMemberIdModule, AddPersonIdAndWorkspaceMemberIdModule,
], ],

View File

@ -1,7 +1,7 @@
import { Injectable } from '@nestjs/common'; import { Injectable } from '@nestjs/common';
import { EventEmitter2 } from '@nestjs/event-emitter'; 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 { InjectObjectMetadataRepository } from 'src/engine/object-metadata-repository/object-metadata-repository.decorator';
import { PersonRepository } from 'src/modules/person/repositories/person.repository'; 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 { 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 { 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 { 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 { 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 { 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 { 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() @Injectable()
export class CalendarEventParticipantService { export class CalendarEventParticipantService {
constructor( constructor(
private readonly workspaceDataSourceService: WorkspaceDataSourceService, private readonly workspaceDataSourceService: WorkspaceDataSourceService,
@InjectObjectMetadataRepository(CalendarEventParticipantWorkspaceEntity) @InjectWorkspaceRepository(CalendarEventParticipantWorkspaceEntity)
private readonly calendarEventParticipantRepository: CalendarEventParticipantRepository, private readonly calendarEventParticipantRepository: WorkspaceRepository<CalendarEventParticipantWorkspaceEntity>,
@InjectObjectMetadataRepository(PersonWorkspaceEntity) @InjectObjectMetadataRepository(PersonWorkspaceEntity)
private readonly personRepository: PersonRepository, private readonly personRepository: PersonRepository,
private readonly addPersonIdAndWorkspaceMemberIdService: AddPersonIdAndWorkspaceMemberIdService, private readonly addPersonIdAndWorkspaceMemberIdService: AddPersonIdAndWorkspaceMemberIdService,
@ -31,11 +32,11 @@ export class CalendarEventParticipantService {
workspaceId: string, workspaceId: string,
transactionManager?: EntityManager, transactionManager?: EntityManager,
): Promise<ObjectRecord<CalendarEventParticipantWorkspaceEntity>[]> { ): Promise<ObjectRecord<CalendarEventParticipantWorkspaceEntity>[]> {
const participants = const participants = await this.calendarEventParticipantRepository.find({
await this.calendarEventParticipantRepository.getByHandles( where: {
createdPeople.map((person) => person.email), handle: Any(createdPeople.map((person) => person.email)),
workspaceId, },
); });
if (!participants) return []; if (!participants) return [];
@ -132,33 +133,50 @@ export class CalendarEventParticipantService {
workspaceMemberId?: string, workspaceMemberId?: string,
) { ) {
const calendarEventParticipantsToUpdate = const calendarEventParticipantsToUpdate =
await this.calendarEventParticipantRepository.getByHandles( await this.calendarEventParticipantRepository.find({
[email], where: {
workspaceId, handle: email,
); },
});
const calendarEventParticipantIdsToUpdate = const calendarEventParticipantIdsToUpdate =
calendarEventParticipantsToUpdate.map((participant) => participant.id); calendarEventParticipantsToUpdate.map((participant) => participant.id);
if (personId) { if (personId) {
await this.calendarEventParticipantRepository.update(
{
id: Any(calendarEventParticipantIdsToUpdate),
},
{
person: {
id: personId,
},
},
);
const updatedCalendarEventParticipants = const updatedCalendarEventParticipants =
await this.calendarEventParticipantRepository.updateParticipantsPersonIdAndReturn( await this.calendarEventParticipantRepository.find({
calendarEventParticipantIdsToUpdate, where: {
personId, id: Any(calendarEventParticipantIdsToUpdate),
workspaceId, },
); });
this.eventEmitter.emit(`calendarEventParticipant.matched`, { this.eventEmitter.emit(`calendarEventParticipant.matched`, {
workspaceId, workspaceId,
userId: null, workspaceMemberId: null,
calendarEventParticipants: updatedCalendarEventParticipants, calendarEventParticipants: updatedCalendarEventParticipants,
}); });
} }
if (workspaceMemberId) { if (workspaceMemberId) {
await this.calendarEventParticipantRepository.updateParticipantsWorkspaceMemberId( await this.calendarEventParticipantRepository.update(
calendarEventParticipantIdsToUpdate, {
workspaceMemberId, id: Any(calendarEventParticipantIdsToUpdate),
workspaceId, },
{
workspaceMember: {
id: workspaceMemberId,
},
},
); );
} }
} }
@ -170,15 +188,23 @@ export class CalendarEventParticipantService {
workspaceMemberId?: string, workspaceMemberId?: string,
) { ) {
if (personId) { if (personId) {
await this.calendarEventParticipantRepository.removePersonIdByHandle( await this.calendarEventParticipantRepository.update(
handle, {
workspaceId, handle,
},
{
person: null,
},
); );
} }
if (workspaceMemberId) { if (workspaceMemberId) {
await this.calendarEventParticipantRepository.removeWorkspaceMemberIdByHandle( await this.calendarEventParticipantRepository.update(
handle, {
workspaceId, 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 { FeatureFlagEntity } from 'src/engine/core-modules/feature-flag/feature-flag.entity';
import { ObjectMetadataRepositoryModule } from 'src/engine/object-metadata-repository/object-metadata-repository.module'; 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 { WorkspaceDataSourceModule } from 'src/engine/workspace-datasource/workspace-datasource.module';
import { CalendarEventCleanerModule } from 'src/modules/calendar/services/calendar-event-cleaner/calendar-event-cleaner.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'; 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({ @Module({
imports: [ imports: [
CalendarProvidersModule, CalendarProvidersModule,
ObjectMetadataRepositoryModule.forFeature([ TwentyORMModule.forFeature([
ConnectedAccountWorkspaceEntity,
CalendarEventWorkspaceEntity, CalendarEventWorkspaceEntity,
CalendarChannelWorkspaceEntity, CalendarChannelWorkspaceEntity,
CalendarChannelEventAssociationWorkspaceEntity, CalendarChannelEventAssociationWorkspaceEntity,
CalendarEventParticipantWorkspaceEntity, CalendarEventParticipantWorkspaceEntity,
]),
ObjectMetadataRepositoryModule.forFeature([
ConnectedAccountWorkspaceEntity,
BlocklistWorkspaceEntity, BlocklistWorkspaceEntity,
PersonWorkspaceEntity, PersonWorkspaceEntity,
WorkspaceMemberWorkspaceEntity, WorkspaceMemberWorkspaceEntity,

View File

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

View File

@ -1,13 +1,11 @@
import { Module } from '@nestjs/common'; 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 { 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'; import { CalendarChannelWorkspaceEntity } from 'src/modules/calendar/standard-objects/calendar-channel.workspace-entity';
@Module({ @Module({
imports: [ imports: [TwentyORMModule.forFeature([CalendarChannelWorkspaceEntity])],
ObjectMetadataRepositoryModule.forFeature([CalendarChannelWorkspaceEntity]),
],
providers: [WorkspaceGoogleCalendarSyncService], providers: [WorkspaceGoogleCalendarSyncService],
exports: [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 { InjectMessageQueue } from 'src/engine/integrations/message-queue/decorators/message-queue.decorator';
import { MessageQueue } from 'src/engine/integrations/message-queue/message-queue.constants'; import { MessageQueue } from 'src/engine/integrations/message-queue/message-queue.constants';
import { MessageQueueService } from 'src/engine/integrations/message-queue/services/message-queue.service'; 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 { import {
GoogleCalendarSyncJobData, GoogleCalendarSyncJobData,
GoogleCalendarSyncJob, GoogleCalendarSyncJob,
} from 'src/modules/calendar/jobs/google-calendar-sync.job'; } 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'; import { CalendarChannelWorkspaceEntity } from 'src/modules/calendar/standard-objects/calendar-channel.workspace-entity';
@Injectable() @Injectable()
export class WorkspaceGoogleCalendarSyncService { export class WorkspaceGoogleCalendarSyncService {
constructor( constructor(
@InjectObjectMetadataRepository(CalendarChannelWorkspaceEntity) @InjectWorkspaceRepository(CalendarChannelWorkspaceEntity)
private readonly calendarChannelRepository: CalendarChannelRepository, private readonly calendarChannelRepository: WorkspaceRepository<CalendarChannelWorkspaceEntity>,
@InjectMessageQueue(MessageQueue.calendarQueue) @InjectMessageQueue(MessageQueue.calendarQueue)
private readonly messageQueueService: MessageQueueService, private readonly messageQueueService: MessageQueueService,
) {} ) {}
@ -23,8 +23,7 @@ export class WorkspaceGoogleCalendarSyncService {
public async startWorkspaceGoogleCalendarSync( public async startWorkspaceGoogleCalendarSync(
workspaceId: string, workspaceId: string,
): Promise<void> { ): Promise<void> {
const calendarChannels = const calendarChannels = await this.calendarChannelRepository.find({});
await this.calendarChannelRepository.getAll(workspaceId);
for (const calendarChannel of calendarChannels) { for (const calendarChannel of calendarChannels) {
if (!calendarChannel?.isSyncEnabled) { if (!calendarChannel?.isSyncEnabled) {
@ -35,7 +34,7 @@ export class WorkspaceGoogleCalendarSyncService {
GoogleCalendarSyncJob.name, GoogleCalendarSyncJob.name,
{ {
workspaceId, workspaceId,
connectedAccountId: calendarChannel.connectedAccountId, connectedAccountId: calendarChannel.connectedAccount.id,
}, },
{ {
retryLimit: 2, retryLimit: 2,

View File

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

View File

@ -68,7 +68,7 @@ export class CompanyWorkspaceEntity extends BaseWorkspaceEntity {
icon: 'IconUsers', icon: 'IconUsers',
}) })
@WorkspaceIsNullable() @WorkspaceIsNullable()
employees: number; employees: number | null;
@WorkspaceField({ @WorkspaceField({
standardId: COMPANY_STANDARD_FIELD_IDS.linkedinLink, standardId: COMPANY_STANDARD_FIELD_IDS.linkedinLink,
@ -78,7 +78,7 @@ export class CompanyWorkspaceEntity extends BaseWorkspaceEntity {
icon: 'IconBrandLinkedin', icon: 'IconBrandLinkedin',
}) })
@WorkspaceIsNullable() @WorkspaceIsNullable()
linkedinLink: LinkMetadata; linkedinLink: LinkMetadata | null;
@WorkspaceField({ @WorkspaceField({
standardId: COMPANY_STANDARD_FIELD_IDS.xLink, standardId: COMPANY_STANDARD_FIELD_IDS.xLink,
@ -88,7 +88,7 @@ export class CompanyWorkspaceEntity extends BaseWorkspaceEntity {
icon: 'IconBrandX', icon: 'IconBrandX',
}) })
@WorkspaceIsNullable() @WorkspaceIsNullable()
xLink: LinkMetadata; xLink: LinkMetadata | null;
@WorkspaceField({ @WorkspaceField({
standardId: COMPANY_STANDARD_FIELD_IDS.annualRecurringRevenue, standardId: COMPANY_STANDARD_FIELD_IDS.annualRecurringRevenue,
@ -99,7 +99,7 @@ export class CompanyWorkspaceEntity extends BaseWorkspaceEntity {
icon: 'IconMoneybag', icon: 'IconMoneybag',
}) })
@WorkspaceIsNullable() @WorkspaceIsNullable()
annualRecurringRevenue: CurrencyMetadata; annualRecurringRevenue: CurrencyMetadata | null;
@WorkspaceField({ @WorkspaceField({
standardId: COMPANY_STANDARD_FIELD_IDS.idealCustomerProfile, standardId: COMPANY_STANDARD_FIELD_IDS.idealCustomerProfile,
@ -121,7 +121,7 @@ export class CompanyWorkspaceEntity extends BaseWorkspaceEntity {
}) })
@WorkspaceIsSystem() @WorkspaceIsSystem()
@WorkspaceIsNullable() @WorkspaceIsNullable()
position: number; position: number | null;
// Relations // Relations
@WorkspaceRelation({ @WorkspaceRelation({
@ -149,7 +149,7 @@ export class CompanyWorkspaceEntity extends BaseWorkspaceEntity {
onDelete: RelationOnDeleteAction.SET_NULL, onDelete: RelationOnDeleteAction.SET_NULL,
}) })
@WorkspaceIsNullable() @WorkspaceIsNullable()
accountOwner: Relation<WorkspaceMemberWorkspaceEntity>; accountOwner: Relation<WorkspaceMemberWorkspaceEntity> | null;
@WorkspaceRelation({ @WorkspaceRelation({
standardId: COMPANY_STANDARD_FIELD_IDS.activityTargets, 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 { PersonWorkspaceEntity } from 'src/modules/person/standard-objects/person.workspace-entity';
import { WorkspaceMemberWorkspaceEntity } from 'src/modules/workspace-member/standard-objects/workspace-member.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 { 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 { 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 { FeatureFlagEntity } from 'src/engine/core-modules/feature-flag/feature-flag.entity';
import { MessagingCommonModule } from 'src/modules/messaging/common/messaging-common.module'; 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([ ObjectMetadataRepositoryModule.forFeature([
PersonWorkspaceEntity, PersonWorkspaceEntity,
WorkspaceMemberWorkspaceEntity, WorkspaceMemberWorkspaceEntity,
CalendarEventParticipantWorkspaceEntity,
]), ]),
MessagingCommonModule, MessagingCommonModule,
WorkspaceDataSourceModule, WorkspaceDataSourceModule,

View File

@ -1,13 +1,12 @@
import { Process } from 'src/engine/integrations/message-queue/decorators/process.decorator'; import { Process } from 'src/engine/integrations/message-queue/decorators/process.decorator';
import { Processor } from 'src/engine/integrations/message-queue/decorators/processor.decorator'; import { Processor } from 'src/engine/integrations/message-queue/decorators/processor.decorator';
import { MessageQueue } from 'src/engine/integrations/message-queue/message-queue.constants'; 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 { 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'; import { ConnectedAccountWorkspaceEntity } from 'src/modules/connected-account/standard-objects/connected-account.workspace-entity';
export type CreateCompanyAndContactJobData = { export type CreateCompanyAndContactJobData = {
workspaceId: string; workspaceId: string;
connectedAccount: ObjectRecord<ConnectedAccountWorkspaceEntity>; connectedAccount: ConnectedAccountWorkspaceEntity;
contactsToCreate: { contactsToCreate: {
displayName: string; displayName: string;
handle: 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 { 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 { 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 { 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 { 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 { 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 { 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 { 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 { 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 { 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() @Injectable()
export class CreateCompanyAndContactService { export class CreateCompanyAndContactService {
@ -33,7 +34,8 @@ export class CreateCompanyAndContactService {
private readonly personRepository: PersonRepository, private readonly personRepository: PersonRepository,
@InjectObjectMetadataRepository(WorkspaceMemberWorkspaceEntity) @InjectObjectMetadataRepository(WorkspaceMemberWorkspaceEntity)
private readonly workspaceMemberRepository: WorkspaceMemberRepository, private readonly workspaceMemberRepository: WorkspaceMemberRepository,
private readonly workspaceDataSourceService: WorkspaceDataSourceService, @InjectWorkspaceDatasource()
private readonly workspaceDataSource: WorkspaceDataSource,
private readonly messageParticipantService: MessagingMessageParticipantService, private readonly messageParticipantService: MessagingMessageParticipantService,
private readonly calendarEventParticipantService: CalendarEventParticipantService, private readonly calendarEventParticipantService: CalendarEventParticipantService,
private readonly eventEmitter: EventEmitter2, private readonly eventEmitter: EventEmitter2,
@ -130,21 +132,16 @@ export class CreateCompanyAndContactService {
} }
async createCompaniesAndContactsAndUpdateParticipants( async createCompaniesAndContactsAndUpdateParticipants(
connectedAccount: ObjectRecord<ConnectedAccountWorkspaceEntity>, connectedAccount: ConnectedAccountWorkspaceEntity,
contactsToCreate: Contacts, contactsToCreate: Contacts,
workspaceId: string, workspaceId: string,
) { ) {
const { dataSource: workspaceDataSource } =
await this.workspaceDataSourceService.connectedToWorkspaceDataSourceAndReturnMetadata(
workspaceId,
);
let updatedMessageParticipants: ObjectRecord<MessageParticipantWorkspaceEntity>[] = let updatedMessageParticipants: ObjectRecord<MessageParticipantWorkspaceEntity>[] =
[]; [];
let updatedCalendarEventParticipants: ObjectRecord<CalendarEventParticipantWorkspaceEntity>[] = let updatedCalendarEventParticipants: ObjectRecord<CalendarEventParticipantWorkspaceEntity>[] =
[]; [];
await workspaceDataSource?.transaction( await this.workspaceDataSource?.transaction(
async (transactionManager: EntityManager) => { async (transactionManager: EntityManager) => {
const createdPeople = await this.createCompaniesAndPeople( const createdPeople = await this.createCompaniesAndPeople(
connectedAccount.handle, connectedAccount.handle,
@ -171,13 +168,13 @@ export class CreateCompanyAndContactService {
this.eventEmitter.emit(`messageParticipant.matched`, { this.eventEmitter.emit(`messageParticipant.matched`, {
workspaceId, workspaceId,
userId: connectedAccount.accountOwnerId, workspaceMemberId: connectedAccount.accountOwnerId,
messageParticipants: updatedMessageParticipants, messageParticipants: updatedMessageParticipants,
}); });
this.eventEmitter.emit(`calendarEventParticipant.matched`, { this.eventEmitter.emit(`calendarEventParticipant.matched`, {
workspaceId, workspaceId,
userId: connectedAccount.accountOwnerId, workspaceMemberId: connectedAccount.accountOwnerId,
calendarEventParticipants: updatedCalendarEventParticipants, calendarEventParticipants: updatedCalendarEventParticipants,
}); });
} }

View File

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

View File

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

View File

@ -63,7 +63,7 @@ export class FavoriteWorkspaceEntity extends BaseWorkspaceEntity {
inverseSideFieldKey: 'favorites', inverseSideFieldKey: 'favorites',
}) })
@WorkspaceIsNullable() @WorkspaceIsNullable()
person: Relation<PersonWorkspaceEntity>; person: Relation<PersonWorkspaceEntity> | null;
@WorkspaceRelation({ @WorkspaceRelation({
standardId: FAVORITE_STANDARD_FIELD_IDS.company, standardId: FAVORITE_STANDARD_FIELD_IDS.company,
@ -76,7 +76,7 @@ export class FavoriteWorkspaceEntity extends BaseWorkspaceEntity {
inverseSideFieldKey: 'favorites', inverseSideFieldKey: 'favorites',
}) })
@WorkspaceIsNullable() @WorkspaceIsNullable()
company: Relation<CompanyWorkspaceEntity>; company: Relation<CompanyWorkspaceEntity> | null;
@WorkspaceRelation({ @WorkspaceRelation({
standardId: FAVORITE_STANDARD_FIELD_IDS.opportunity, standardId: FAVORITE_STANDARD_FIELD_IDS.opportunity,
@ -89,7 +89,7 @@ export class FavoriteWorkspaceEntity extends BaseWorkspaceEntity {
inverseSideFieldKey: 'favorites', inverseSideFieldKey: 'favorites',
}) })
@WorkspaceIsNullable() @WorkspaceIsNullable()
opportunity: Relation<OpportunityWorkspaceEntity>; opportunity: Relation<OpportunityWorkspaceEntity> | null;
@WorkspaceDynamicRelation({ @WorkspaceDynamicRelation({
type: RelationMetadataType.MANY_TO_ONE, 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}`, `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 = const messageChannels =
await this.messageChannelRepository.getIdsByWorkspaceMemberId( await this.messageChannelRepository.getIdsByWorkspaceMemberId(
workspaceMemberId, 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 { MessageChannelWorkspaceEntity } from 'src/modules/messaging/common/standard-objects/message-channel.workspace-entity';
import { WorkspaceMemberRepository } from 'src/modules/workspace-member/repositories/workspace-member.repository'; 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 { WorkspaceMemberWorkspaceEntity } from 'src/modules/workspace-member/standard-objects/workspace-member.workspace-entity';
import { isDefined } from 'src/utils/is-defined';
export class CanAccessMessageThreadService { export class CanAccessMessageThreadService {
constructor( constructor(
@ -46,7 +47,9 @@ export class CanAccessMessageThreadService {
const messageChannelsConnectedAccounts = const messageChannelsConnectedAccounts =
await this.connectedAccountRepository.getByIds( await this.connectedAccountRepository.getByIds(
messageChannels.map((channel) => channel.connectedAccountId), messageChannels
.map((channel) => channel.connectedAccountId)
.filter(isDefined),
workspaceId, workspaceId,
); );

View File

@ -211,6 +211,12 @@ export class MessagingErrorHandlingService {
workspaceId, 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( await this.connectedAccountRepository.updateAuthFailedAt(
messageChannel.connectedAccountId, messageChannel.connectedAccountId,
workspaceId, workspaceId,

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -78,7 +78,7 @@ export class MessageWorkspaceEntity extends BaseWorkspaceEntity {
icon: 'IconCalendar', icon: 'IconCalendar',
}) })
@WorkspaceIsNullable() @WorkspaceIsNullable()
receivedAt: string; receivedAt: string | null;
@WorkspaceRelation({ @WorkspaceRelation({
standardId: MESSAGE_STANDARD_FIELD_IDS.messageThread, standardId: MESSAGE_STANDARD_FIELD_IDS.messageThread,
@ -92,7 +92,7 @@ export class MessageWorkspaceEntity extends BaseWorkspaceEntity {
onDelete: RelationOnDeleteAction.CASCADE, onDelete: RelationOnDeleteAction.CASCADE,
}) })
@WorkspaceIsNullable() @WorkspaceIsNullable()
messageThread: Relation<MessageThreadWorkspaceEntity>; messageThread: Relation<MessageThreadWorkspaceEntity> | null;
@WorkspaceRelation({ @WorkspaceRelation({
standardId: MESSAGE_STANDARD_FIELD_IDS.messageParticipants, 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 { Process } from 'src/engine/integrations/message-queue/decorators/process.decorator';
import { Processor } from 'src/engine/integrations/message-queue/decorators/processor.decorator'; import { Processor } from 'src/engine/integrations/message-queue/decorators/processor.decorator';
@ -21,7 +21,10 @@ export type MessagingMessageListFetchJobData = {
workspaceId: string; workspaceId: string;
}; };
@Processor(MessageQueue.messagingQueue) @Processor({
queueName: MessageQueue.messagingQueue,
scope: Scope.REQUEST,
})
export class MessagingMessageListFetchJob { export class MessagingMessageListFetchJob {
private readonly logger = new Logger(MessagingMessageListFetchJob.name); 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 { Process } from 'src/engine/integrations/message-queue/decorators/process.decorator';
import { Processor } from 'src/engine/integrations/message-queue/decorators/processor.decorator'; import { Processor } from 'src/engine/integrations/message-queue/decorators/processor.decorator';
import { MessageQueue } from 'src/engine/integrations/message-queue/message-queue.constants'; import { MessageQueue } from 'src/engine/integrations/message-queue/message-queue.constants';
@ -18,7 +20,10 @@ export type MessagingMessagesImportJobData = {
workspaceId: string; workspaceId: string;
}; };
@Processor(MessageQueue.messagingQueue) @Processor({
queueName: MessageQueue.messagingQueue,
scope: Scope.REQUEST,
})
export class MessagingMessagesImportJob { export class MessagingMessagesImportJob {
constructor( constructor(
@InjectObjectMetadataRepository(ConnectedAccountWorkspaceEntity) @InjectObjectMetadataRepository(ConnectedAccountWorkspaceEntity)

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -53,5 +53,5 @@ export class ViewSortWorkspaceEntity extends BaseWorkspaceEntity {
inverseSideFieldKey: 'viewSorts', inverseSideFieldKey: 'viewSorts',
}) })
@WorkspaceIsNullable() @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 { 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 { 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 { InjectWorkspaceRepository } from 'src/engine/twenty-orm/decorators/inject-workspace-repository.decorator';
import { CommentRepository } from 'src/modules/activity/repositories/comment.repository'; import { WorkspaceRepository } from 'src/engine/twenty-orm/repository/workspace.repository';
import { CommentWorkspaceEntity } from 'src/modules/activity/standard-objects/comment.workspace-entity'; 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'; import { AttachmentWorkspaceEntity } from 'src/modules/attachment/standard-objects/attachment.workspace-entity';
@Injectable() @Injectable()
@ -14,10 +13,10 @@ export class WorkspaceMemberDeleteOnePreQueryHook
implements WorkspacePreQueryHook implements WorkspacePreQueryHook
{ {
constructor( constructor(
@InjectObjectMetadataRepository(AttachmentWorkspaceEntity) @InjectWorkspaceRepository(AttachmentWorkspaceEntity)
private readonly attachmentRepository: AttachmentRepository, private readonly attachmentRepository: WorkspaceRepository<AttachmentWorkspaceEntity>,
@InjectObjectMetadataRepository(CommentWorkspaceEntity) @InjectWorkspaceRepository(CommentWorkspaceEntity)
private readonly commentRepository: CommentRepository, 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. // 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, workspaceId: string,
payload: DeleteOneResolverArgs, payload: DeleteOneResolverArgs,
): Promise<void> { ): Promise<void> {
const workspaceMemberId = payload.id; const authorId = payload.id;
await this.attachmentRepository.deleteByAuthorId( await this.attachmentRepository.delete({
workspaceMemberId, author: {
workspaceId, id: authorId,
); },
});
await this.commentRepository.deleteByAuthorId( await this.commentRepository.delete({
workspaceMemberId, author: {
workspaceId, id: authorId,
); },
});
} }
} }

View File

@ -1,6 +1,6 @@
import { Module } from '@nestjs/common'; 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 { CommentWorkspaceEntity } from 'src/modules/activity/standard-objects/comment.workspace-entity';
import { AttachmentWorkspaceEntity } from 'src/modules/attachment/standard-objects/attachment.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'; 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({ @Module({
imports: [ imports: [
ObjectMetadataRepositoryModule.forFeature([ TwentyORMModule.forFeature([
AttachmentWorkspaceEntity, AttachmentWorkspaceEntity,
CommentWorkspaceEntity, CommentWorkspaceEntity,
]), ]),

View File

@ -0,0 +1,3 @@
export const isDefined = <T>(value: T | null | undefined): value is T => {
return value !== null && value !== undefined;
};

View File

@ -8,6 +8,7 @@ export interface ReflectMetadataTypeMap {
['workspace:is-system-metadata-args']: true; ['workspace:is-system-metadata-args']: true;
['workspace:is-audit-logged-metadata-args']: false; ['workspace:is-audit-logged-metadata-args']: false;
['workspace:is-primary-field-metadata-args']: true; ['workspace:is-primary-field-metadata-args']: true;
['workspace:join-column']: true;
} }
export class TypedReflect { export class TypedReflect {