mirror of
https://github.com/twentyhq/twenty.git
synced 2024-12-25 13:02:15 +03:00
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:
parent
91b0c2bb8e
commit
0b4bfce324
@ -19,8 +19,8 @@ export class AnalyticsService {
|
||||
|
||||
async create(
|
||||
createEventInput: CreateEventInput,
|
||||
userId: string | undefined,
|
||||
workspaceId: string | undefined,
|
||||
userId: string | null | undefined,
|
||||
workspaceId: string | null | undefined,
|
||||
workspaceDisplayName: string | undefined,
|
||||
workspaceDomainName: string | undefined,
|
||||
hostName: string | undefined,
|
||||
|
@ -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 { MessageChannelWorkspaceEntity } from 'src/modules/messaging/common/standard-objects/message-channel.workspace-entity';
|
||||
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';
|
||||
|
||||
@ -60,11 +62,12 @@ const jwtModule = JwtModule.registerAsync({
|
||||
ObjectMetadataRepositoryModule.forFeature([
|
||||
ConnectedAccountWorkspaceEntity,
|
||||
MessageChannelWorkspaceEntity,
|
||||
CalendarChannelWorkspaceEntity,
|
||||
]),
|
||||
HttpModule,
|
||||
UserWorkspaceModule,
|
||||
OnboardingModule,
|
||||
TwentyORMModule.forFeature([CalendarChannelWorkspaceEntity]),
|
||||
WorkspaceDataSourceModule,
|
||||
],
|
||||
controllers: [
|
||||
GoogleAuthController,
|
||||
|
@ -138,6 +138,7 @@ export class AuthResolver {
|
||||
}
|
||||
const transientToken = await this.tokenService.generateTransientToken(
|
||||
workspaceMember.id,
|
||||
user.id,
|
||||
user.defaultWorkspace.id,
|
||||
);
|
||||
|
||||
|
@ -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 { EnvironmentService } from 'src/engine/integrations/environment/environment.service';
|
||||
import { OnboardingService } from 'src/engine/core-modules/onboarding/onboarding.service';
|
||||
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 { InjectObjectMetadataRepository } from 'src/engine/object-metadata-repository/object-metadata-repository.decorator';
|
||||
import { LoadServiceWithWorkspaceContext } from 'src/engine/twenty-orm/context/load-service-with-workspace.context';
|
||||
|
||||
@Controller('auth/google-apis')
|
||||
export class GoogleAPIsAuthController {
|
||||
@ -27,8 +25,7 @@ export class GoogleAPIsAuthController {
|
||||
private readonly tokenService: TokenService,
|
||||
private readonly environmentService: EnvironmentService,
|
||||
private readonly onboardingService: OnboardingService,
|
||||
@InjectObjectMetadataRepository(WorkspaceMemberWorkspaceEntity)
|
||||
private readonly workspaceMemberService: WorkspaceMemberRepository,
|
||||
private readonly loadServiceWithWorkspaceContext: LoadServiceWithWorkspaceContext,
|
||||
) {}
|
||||
|
||||
@Get()
|
||||
@ -56,7 +53,7 @@ export class GoogleAPIsAuthController {
|
||||
messageVisibility,
|
||||
} = user;
|
||||
|
||||
const { workspaceMemberId, workspaceId } =
|
||||
const { workspaceMemberId, userId, workspaceId } =
|
||||
await this.tokenService.verifyTransientToken(transientToken);
|
||||
|
||||
const demoWorkspaceIds = this.environmentService.get('DEMO_WORKSPACE_IDS');
|
||||
@ -71,7 +68,13 @@ export class GoogleAPIsAuthController {
|
||||
throw new Error('Workspace not found');
|
||||
}
|
||||
|
||||
await this.googleAPIsService.refreshGoogleRefreshToken({
|
||||
const googleAPIsServiceInstance =
|
||||
await this.loadServiceWithWorkspaceContext.load(
|
||||
this.googleAPIsService,
|
||||
workspaceId,
|
||||
);
|
||||
|
||||
await googleAPIsServiceInstance.refreshGoogleRefreshToken({
|
||||
handle: email,
|
||||
workspaceMemberId: workspaceMemberId,
|
||||
workspaceId: workspaceId,
|
||||
@ -81,12 +84,14 @@ export class GoogleAPIsAuthController {
|
||||
messageVisibility,
|
||||
});
|
||||
|
||||
const userId = (
|
||||
await this.workspaceMemberService.find(workspaceMemberId, workspaceId)
|
||||
)?.userId;
|
||||
|
||||
if (userId) {
|
||||
await this.onboardingService.skipSyncEmailOnboardingStep(
|
||||
const onboardingServiceInstance =
|
||||
await this.loadServiceWithWorkspaceContext.load(
|
||||
this.onboardingService,
|
||||
workspaceId,
|
||||
);
|
||||
|
||||
await onboardingServiceInstance.skipSyncEmailOnboardingStep(
|
||||
userId,
|
||||
workspaceId,
|
||||
);
|
||||
|
@ -3,17 +3,14 @@ import { Injectable } from '@nestjs/common';
|
||||
import { EntityManager } from 'typeorm';
|
||||
import { v4 } from 'uuid';
|
||||
|
||||
import { TypeORMService } from 'src/database/typeorm/typeorm.service';
|
||||
import { EnvironmentService } from 'src/engine/integrations/environment/environment.service';
|
||||
import { MessageQueue } from 'src/engine/integrations/message-queue/message-queue.constants';
|
||||
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 {
|
||||
GoogleCalendarSyncJobData,
|
||||
GoogleCalendarSyncJob,
|
||||
} from 'src/modules/calendar/jobs/google-calendar-sync.job';
|
||||
import { CalendarChannelRepository } from 'src/modules/calendar/repositories/calendar-channel.repository';
|
||||
import {
|
||||
CalendarChannelWorkspaceEntity,
|
||||
CalendarChannelVisibility,
|
||||
@ -35,12 +32,16 @@ import {
|
||||
MessagingMessageListFetchJobData,
|
||||
} 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 { 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()
|
||||
export class GoogleAPIsService {
|
||||
constructor(
|
||||
private readonly dataSourceService: DataSourceService,
|
||||
private readonly typeORMService: TypeORMService,
|
||||
@InjectWorkspaceDatasource()
|
||||
private readonly workspaceDataSource: WorkspaceDataSource,
|
||||
@InjectMessageQueue(MessageQueue.messagingQueue)
|
||||
private readonly messageQueueService: MessageQueueService,
|
||||
@InjectMessageQueue(MessageQueue.calendarQueue)
|
||||
@ -50,8 +51,8 @@ export class GoogleAPIsService {
|
||||
private readonly connectedAccountRepository: ConnectedAccountRepository,
|
||||
@InjectObjectMetadataRepository(MessageChannelWorkspaceEntity)
|
||||
private readonly messageChannelRepository: MessageChannelRepository,
|
||||
@InjectObjectMetadataRepository(CalendarChannelWorkspaceEntity)
|
||||
private readonly calendarChannelRepository: CalendarChannelRepository,
|
||||
@InjectWorkspaceRepository(CalendarChannelWorkspaceEntity)
|
||||
private readonly calendarChannelRepository: WorkspaceRepository<CalendarChannelWorkspaceEntity>,
|
||||
) {}
|
||||
|
||||
async refreshGoogleRefreshToken(input: {
|
||||
@ -71,14 +72,6 @@ export class GoogleAPIsService {
|
||||
messageVisibility,
|
||||
} = input;
|
||||
|
||||
const dataSourceMetadata =
|
||||
await this.dataSourceService.getLastDataSourceMetadataFromWorkspaceIdOrFail(
|
||||
workspaceId,
|
||||
);
|
||||
|
||||
const workspaceDataSource =
|
||||
await this.typeORMService.connectToDataSource(dataSourceMetadata);
|
||||
|
||||
const isCalendarEnabled = this.environmentService.get(
|
||||
'CALENDAR_PROVIDER_GOOGLE_ENABLED',
|
||||
);
|
||||
@ -93,65 +86,67 @@ export class GoogleAPIsService {
|
||||
const existingAccountId = connectedAccounts?.[0]?.id;
|
||||
const newOrExistingConnectedAccountId = existingAccountId ?? v4();
|
||||
|
||||
await workspaceDataSource?.transaction(async (manager: EntityManager) => {
|
||||
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(
|
||||
await this.workspaceDataSource.transaction(
|
||||
async (manager: EntityManager) => {
|
||||
if (!existingAccountId) {
|
||||
await this.connectedAccountRepository.create(
|
||||
{
|
||||
id: v4(),
|
||||
connectedAccountId: newOrExistingConnectedAccountId,
|
||||
id: newOrExistingConnectedAccountId,
|
||||
handle,
|
||||
visibility:
|
||||
calendarVisibility ||
|
||||
CalendarChannelVisibility.SHARE_EVERYTHING,
|
||||
provider: ConnectedAccountProvider.GOOGLE,
|
||||
accessToken: input.accessToken,
|
||||
refreshToken: input.refreshToken,
|
||||
accountOwnerId: workspaceMemberId,
|
||||
},
|
||||
workspaceId,
|
||||
manager,
|
||||
);
|
||||
}
|
||||
} else {
|
||||
await this.connectedAccountRepository.updateAccessTokenAndRefreshToken(
|
||||
input.accessToken,
|
||||
input.refreshToken,
|
||||
newOrExistingConnectedAccountId,
|
||||
workspaceId,
|
||||
manager,
|
||||
);
|
||||
|
||||
await this.messageChannelRepository.resetSync(
|
||||
newOrExistingConnectedAccountId,
|
||||
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.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')) {
|
||||
const messageChannels =
|
||||
|
@ -147,6 +147,7 @@ export class TokenService {
|
||||
|
||||
async generateTransientToken(
|
||||
workspaceMemberId: string,
|
||||
userId: string,
|
||||
workspaceId: string,
|
||||
): Promise<AuthToken> {
|
||||
const secret = this.environmentService.get('LOGIN_TOKEN_SECRET');
|
||||
@ -158,6 +159,7 @@ export class TokenService {
|
||||
const expiresAt = addMilliseconds(new Date().getTime(), ms(expiresIn));
|
||||
const jwtPayload = {
|
||||
sub: workspaceMemberId,
|
||||
userId,
|
||||
workspaceId,
|
||||
};
|
||||
|
||||
@ -234,6 +236,7 @@ export class TokenService {
|
||||
|
||||
async verifyTransientToken(transientToken: string): Promise<{
|
||||
workspaceMemberId: string;
|
||||
userId: string;
|
||||
workspaceId: string;
|
||||
}> {
|
||||
const transientTokenSecret =
|
||||
@ -243,6 +246,7 @@ export class TokenService {
|
||||
|
||||
return {
|
||||
workspaceMemberId: payload.sub,
|
||||
userId: payload.userId,
|
||||
workspaceId: payload.workspaceId,
|
||||
};
|
||||
}
|
||||
|
@ -203,9 +203,7 @@ export class BillingService {
|
||||
: frontBaseUrl;
|
||||
|
||||
const quantity =
|
||||
(await this.userWorkspaceService.getWorkspaceMemberCount(
|
||||
user.defaultWorkspaceId,
|
||||
)) || 1;
|
||||
(await this.userWorkspaceService.getWorkspaceMemberCount()) || 1;
|
||||
|
||||
const stripeCustomerId = (
|
||||
await this.billingSubscriptionRepository.findOneBy({
|
||||
|
@ -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 { 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';
|
||||
export type UpdateSubscriptionJobData = { workspaceId: string };
|
||||
|
||||
@Processor(MessageQueue.billingQueue)
|
||||
@Processor({
|
||||
queueName: MessageQueue.billingQueue,
|
||||
scope: Scope.REQUEST,
|
||||
})
|
||||
export class UpdateSubscriptionJob {
|
||||
protected readonly logger = new Logger(UpdateSubscriptionJob.name);
|
||||
|
||||
@ -21,7 +24,7 @@ export class UpdateSubscriptionJob {
|
||||
@Process(UpdateSubscriptionJob.name)
|
||||
async handle(data: UpdateSubscriptionJobData): Promise<void> {
|
||||
const workspaceMembersCount =
|
||||
await this.userWorkspaceService.getWorkspaceMemberCount(data.workspaceId);
|
||||
await this.userWorkspaceService.getWorkspaceMemberCount();
|
||||
|
||||
if (!workspaceMembersCount || workspaceMembersCount <= 0) {
|
||||
return;
|
||||
|
@ -5,10 +5,10 @@ import { UUIDScalarType } from 'src/engine/api/graphql/workspace-schema-builder/
|
||||
@ObjectType('TimelineCalendarEventParticipant')
|
||||
export class TimelineCalendarEventParticipant {
|
||||
@Field(() => UUIDScalarType, { nullable: true })
|
||||
personId: string;
|
||||
personId: string | null;
|
||||
|
||||
@Field(() => UUIDScalarType, { nullable: true })
|
||||
workspaceMemberId: string;
|
||||
workspaceMemberId: string | null;
|
||||
|
||||
@Field()
|
||||
firstName: string;
|
||||
|
@ -81,19 +81,19 @@ export class TimelineCalendarEventService {
|
||||
const participants = event.calendarEventParticipants.map(
|
||||
(participant) => ({
|
||||
calendarEventId: event.id,
|
||||
personId: participant.person?.id,
|
||||
workspaceMemberId: participant.workspaceMember?.id,
|
||||
personId: participant.person?.id ?? null,
|
||||
workspaceMemberId: participant.workspaceMember?.id ?? null,
|
||||
firstName:
|
||||
participant.person?.name.firstName ||
|
||||
participant.person?.name?.firstName ||
|
||||
participant.workspaceMember?.name.firstName ||
|
||||
'',
|
||||
lastName:
|
||||
participant.person?.name.lastName ||
|
||||
participant.person?.name?.lastName ||
|
||||
participant.workspaceMember?.name.lastName ||
|
||||
'',
|
||||
displayName:
|
||||
participant.person?.name.firstName ||
|
||||
participant.person?.name.lastName ||
|
||||
participant.person?.name?.firstName ||
|
||||
participant.person?.name?.lastName ||
|
||||
participant.workspaceMember?.name.firstName ||
|
||||
participant.workspaceMember?.name.lastName ||
|
||||
'',
|
||||
|
@ -56,7 +56,7 @@ export class OnboardingService {
|
||||
const isInviteTeamSkipped =
|
||||
inviteTeamValue === OnboardingStepValues.SKIPPED;
|
||||
const workspaceMemberCount =
|
||||
await this.userWorkspaceService.getWorkspaceMemberCount(workspace.id);
|
||||
await this.userWorkspaceService.getWorkspaceMemberCount();
|
||||
|
||||
return (
|
||||
!isInviteTeamSkipped &&
|
||||
|
@ -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 { WorkspaceDataSourceModule } from 'src/engine/workspace-datasource/workspace-datasource.module';
|
||||
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({
|
||||
imports: [
|
||||
@ -21,6 +23,7 @@ import { User } from 'src/engine/core-modules/user/user.entity';
|
||||
],
|
||||
services: [UserWorkspaceService],
|
||||
}),
|
||||
TwentyORMModule.forFeature([WorkspaceMemberWorkspaceEntity]),
|
||||
],
|
||||
exports: [UserWorkspaceService],
|
||||
providers: [UserWorkspaceService],
|
||||
|
@ -8,11 +8,12 @@ import { UserWorkspace } from 'src/engine/core-modules/user-workspace/user-works
|
||||
import { TypeORMService } from 'src/database/typeorm/typeorm.service';
|
||||
import { DataSourceService } from 'src/engine/metadata-modules/data-source/data-source.service';
|
||||
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 { WorkspaceMemberWorkspaceEntity } from 'src/modules/workspace-member/standard-objects/workspace-member.workspace-entity';
|
||||
import { assert } from 'src/utils/assert';
|
||||
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> {
|
||||
constructor(
|
||||
@ -20,9 +21,10 @@ export class UserWorkspaceService extends TypeOrmQueryService<UserWorkspace> {
|
||||
private readonly userWorkspaceRepository: Repository<UserWorkspace>,
|
||||
@InjectRepository(User, 'core')
|
||||
private readonly userRepository: Repository<User>,
|
||||
@InjectWorkspaceRepository(WorkspaceMemberWorkspaceEntity)
|
||||
private readonly workspaceMemberRepository: WorkspaceRepository<WorkspaceMemberWorkspaceEntity>,
|
||||
private readonly dataSourceService: DataSourceService,
|
||||
private readonly typeORMService: TypeORMService,
|
||||
private readonly workspaceDataSourceService: WorkspaceDataSourceService,
|
||||
private eventEmitter: EventEmitter2,
|
||||
) {
|
||||
super(userWorkspaceRepository);
|
||||
@ -99,23 +101,10 @@ export class UserWorkspaceService extends TypeOrmQueryService<UserWorkspace> {
|
||||
});
|
||||
}
|
||||
|
||||
public async getWorkspaceMemberCount(
|
||||
workspaceId: string,
|
||||
): Promise<number | undefined> {
|
||||
try {
|
||||
const dataSourceSchema =
|
||||
this.workspaceDataSourceService.getSchemaName(workspaceId);
|
||||
public async getWorkspaceMemberCount(): Promise<number | undefined> {
|
||||
const workspaceMemberCount = await this.workspaceMemberRepository.count();
|
||||
|
||||
return (
|
||||
await this.workspaceDataSourceService.executeRawQuery(
|
||||
`SELECT * FROM ${dataSourceSchema}."workspaceMember"`,
|
||||
[],
|
||||
workspaceId,
|
||||
)
|
||||
).length;
|
||||
} catch {
|
||||
return undefined;
|
||||
}
|
||||
return workspaceMemberCount;
|
||||
}
|
||||
|
||||
async checkUserWorkspaceExists(
|
||||
|
@ -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 { OnboardingStep } from 'src/engine/core-modules/onboarding/enums/onboarding-step.enum';
|
||||
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) => {
|
||||
if (!email || !key) return null;
|
||||
@ -48,6 +49,7 @@ export class UserResolver {
|
||||
private readonly environmentService: EnvironmentService,
|
||||
private readonly fileUploadService: FileUploadService,
|
||||
private readonly onboardingService: OnboardingService,
|
||||
private readonly loadServiceWithWorkspaceContext: LoadServiceWithWorkspaceContext,
|
||||
) {}
|
||||
|
||||
@Query(() => User)
|
||||
@ -122,9 +124,11 @@ export class UserResolver {
|
||||
return null;
|
||||
}
|
||||
|
||||
return this.onboardingService.getOnboardingStep(
|
||||
user,
|
||||
user.defaultWorkspace,
|
||||
const contextInstance = await this.loadServiceWithWorkspaceContext.load(
|
||||
this.onboardingService,
|
||||
user.defaultWorkspaceId,
|
||||
);
|
||||
|
||||
return contextInstance.getOnboardingStep(user, user.defaultWorkspace);
|
||||
}
|
||||
}
|
||||
|
@ -47,7 +47,11 @@ export class PgBossDriver
|
||||
}
|
||||
: {},
|
||||
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],
|
||||
});
|
||||
},
|
||||
);
|
||||
}
|
||||
|
@ -156,7 +156,7 @@ export class MessageQueueExplorer implements OnModuleInit {
|
||||
}),
|
||||
);
|
||||
|
||||
if (isRequestScoped) {
|
||||
if (isRequestScoped && job.data) {
|
||||
const contextId = createContextId();
|
||||
|
||||
if (this.moduleRef.registerRequestByContextId) {
|
||||
|
@ -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 { BlocklistRepository } from 'src/modules/connected-account/repositories/blocklist.repository';
|
||||
import { ConnectedAccountRepository } from 'src/modules/connected-account/repositories/connected-account.repository';
|
||||
import { AuditLogRepository } from 'src/modules/timeline/repositiories/audit-log.repository';
|
||||
import { TimelineActivityRepository } from 'src/modules/timeline/repositiories/timeline-activity.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 { MessageChannelRepository } from 'src/modules/messaging/common/repositories/message-channel.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 = {
|
||||
AuditLogWorkspaceEntity: AuditLogRepository,
|
||||
BlocklistWorkspaceEntity: BlocklistRepository,
|
||||
CalendarChannelEventAssociationWorkspaceEntity:
|
||||
CalendarChannelEventAssociationRepository,
|
||||
CalendarChannelWorkspaceEntity: CalendarChannelRepository,
|
||||
CalendarEventParticipantWorkspaceEntity: CalendarEventParticipantRepository,
|
||||
CalendarEventWorkspaceEntity: CalendarEventRepository,
|
||||
CompanyWorkspaceEntity: CompanyRepository,
|
||||
ConnectedAccountWorkspaceEntity: ConnectedAccountRepository,
|
||||
MessageChannelMessageAssociationWorkspaceEntity:
|
||||
@ -36,6 +25,4 @@ export const metadataToRepositoryMapping = {
|
||||
PersonWorkspaceEntity: PersonRepository,
|
||||
TimelineActivityWorkspaceEntity: TimelineActivityRepository,
|
||||
WorkspaceMemberWorkspaceEntity: WorkspaceMemberRepository,
|
||||
AttachmentWorkspaceEntity: AttachmentRepository,
|
||||
CommentWorkspaceEntity: CommentRepository,
|
||||
};
|
||||
|
@ -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,
|
||||
);
|
||||
}
|
||||
}
|
@ -36,7 +36,7 @@ export class CustomWorkspaceEntity extends BaseWorkspaceEntity {
|
||||
})
|
||||
@WorkspaceIsNullable()
|
||||
@WorkspaceIsSystem()
|
||||
position: number;
|
||||
position: number | null;
|
||||
|
||||
@WorkspaceRelation({
|
||||
standardId: CUSTOM_OBJECT_STANDARD_FIELD_IDS.activityTargets,
|
||||
|
@ -6,8 +6,8 @@ import {
|
||||
QueryRunner,
|
||||
} from 'typeorm';
|
||||
|
||||
import { WorkspaceRepository } from 'src/engine/twenty-orm/repository/workspace.repository';
|
||||
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 {
|
||||
readonly manager: WorkspaceEntityManager;
|
||||
|
@ -32,10 +32,9 @@ export class WorkspaceDatasourceFactory {
|
||||
dataSourceMetadata.url ??
|
||||
this.environmentService.get('PG_DATABASE_URL'),
|
||||
type: 'postgres',
|
||||
// logging: this.environmentService.get('DEBUG_MODE')
|
||||
// ? ['query', 'error']
|
||||
// : ['error'],
|
||||
logging: 'all',
|
||||
logging: this.environmentService.get('DEBUG_MODE')
|
||||
? ['query', 'error']
|
||||
: ['error'],
|
||||
schema: dataSourceMetadata.schema,
|
||||
entities,
|
||||
ssl: this.environmentService.get('PG_SSL_ALLOW_SELF_SIGNED')
|
||||
|
@ -1,6 +1,7 @@
|
||||
import {
|
||||
DeepPartial,
|
||||
DeleteResult,
|
||||
EntityManager,
|
||||
FindManyOptions,
|
||||
FindOneOptions,
|
||||
FindOptionsWhere,
|
||||
@ -29,9 +30,13 @@ export class WorkspaceRepository<
|
||||
/**
|
||||
* 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 result = await super.find(computedOptions);
|
||||
const result = await manager.find(this.target, computedOptions);
|
||||
const formattedResult = this.formatResult(result);
|
||||
|
||||
return formattedResult;
|
||||
@ -39,9 +44,11 @@ export class WorkspaceRepository<
|
||||
|
||||
override async findBy(
|
||||
where: FindOptionsWhere<Entity> | FindOptionsWhere<Entity>[],
|
||||
entityManager?: EntityManager,
|
||||
): Promise<Entity[]> {
|
||||
const manager = entityManager || this.manager;
|
||||
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);
|
||||
|
||||
return formattedResult;
|
||||
@ -49,9 +56,11 @@ export class WorkspaceRepository<
|
||||
|
||||
override async findAndCount(
|
||||
options?: FindManyOptions<Entity>,
|
||||
entityManager?: EntityManager,
|
||||
): Promise<[Entity[], number]> {
|
||||
const manager = entityManager || this.manager;
|
||||
const computedOptions = this.transformOptions(options);
|
||||
const result = await super.findAndCount(computedOptions);
|
||||
const result = await manager.findAndCount(this.target, computedOptions);
|
||||
const formattedResult = this.formatResult(result);
|
||||
|
||||
return formattedResult;
|
||||
@ -59,9 +68,14 @@ export class WorkspaceRepository<
|
||||
|
||||
override async findAndCountBy(
|
||||
where: FindOptionsWhere<Entity> | FindOptionsWhere<Entity>[],
|
||||
entityManager?: EntityManager,
|
||||
): Promise<[Entity[], number]> {
|
||||
const manager = entityManager || this.manager;
|
||||
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);
|
||||
|
||||
return formattedResult;
|
||||
@ -69,9 +83,11 @@ export class WorkspaceRepository<
|
||||
|
||||
override async findOne(
|
||||
options: FindOneOptions<Entity>,
|
||||
entityManager?: EntityManager,
|
||||
): Promise<Entity | null> {
|
||||
const manager = entityManager || this.manager;
|
||||
const computedOptions = this.transformOptions(options);
|
||||
const result = await super.findOne(computedOptions);
|
||||
const result = await manager.findOne(this.target, computedOptions);
|
||||
const formattedResult = this.formatResult(result);
|
||||
|
||||
return formattedResult;
|
||||
@ -79,9 +95,11 @@ export class WorkspaceRepository<
|
||||
|
||||
override async findOneBy(
|
||||
where: FindOptionsWhere<Entity> | FindOptionsWhere<Entity>[],
|
||||
entityManager?: EntityManager,
|
||||
): Promise<Entity | null> {
|
||||
const manager = entityManager || this.manager;
|
||||
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);
|
||||
|
||||
return formattedResult;
|
||||
@ -89,9 +107,11 @@ export class WorkspaceRepository<
|
||||
|
||||
override async findOneOrFail(
|
||||
options: FindOneOptions<Entity>,
|
||||
entityManager?: EntityManager,
|
||||
): Promise<Entity> {
|
||||
const manager = entityManager || this.manager;
|
||||
const computedOptions = this.transformOptions(options);
|
||||
const result = await super.findOneOrFail(computedOptions);
|
||||
const result = await manager.findOneOrFail(this.target, computedOptions);
|
||||
const formattedResult = this.formatResult(result);
|
||||
|
||||
return formattedResult;
|
||||
@ -99,9 +119,14 @@ export class WorkspaceRepository<
|
||||
|
||||
override async findOneByOrFail(
|
||||
where: FindOptionsWhere<Entity> | FindOptionsWhere<Entity>[],
|
||||
entityManager?: EntityManager,
|
||||
): Promise<Entity> {
|
||||
const manager = entityManager || this.manager;
|
||||
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);
|
||||
|
||||
return formattedResult;
|
||||
@ -113,29 +138,40 @@ export class WorkspaceRepository<
|
||||
override save<T extends DeepPartial<Entity>>(
|
||||
entities: T[],
|
||||
options: SaveOptions & { reload: false },
|
||||
entityManager?: EntityManager,
|
||||
): Promise<T[]>;
|
||||
|
||||
override save<T extends DeepPartial<Entity>>(
|
||||
entities: T[],
|
||||
options?: SaveOptions,
|
||||
entityManager?: EntityManager,
|
||||
): Promise<(T & Entity)[]>;
|
||||
|
||||
override save<T extends DeepPartial<Entity>>(
|
||||
entity: T,
|
||||
options: SaveOptions & { reload: false },
|
||||
entityManager?: EntityManager,
|
||||
): Promise<T>;
|
||||
|
||||
override save<T extends DeepPartial<Entity>>(
|
||||
entity: T,
|
||||
options?: SaveOptions,
|
||||
entityManager?: EntityManager,
|
||||
): Promise<T & Entity>;
|
||||
|
||||
override async save<T extends DeepPartial<Entity>>(
|
||||
entityOrEntities: T | T[],
|
||||
options?: SaveOptions,
|
||||
entityManager?: EntityManager,
|
||||
): Promise<T | T[]> {
|
||||
const manager = entityManager || this.manager;
|
||||
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);
|
||||
|
||||
return formattedResult;
|
||||
@ -147,15 +183,27 @@ export class WorkspaceRepository<
|
||||
override remove(
|
||||
entities: Entity[],
|
||||
options?: RemoveOptions,
|
||||
entityManager?: EntityManager,
|
||||
): Promise<Entity[]>;
|
||||
|
||||
override remove(entity: Entity, options?: RemoveOptions): Promise<Entity>;
|
||||
override remove(
|
||||
entity: Entity,
|
||||
options?: RemoveOptions,
|
||||
entityManager?: EntityManager,
|
||||
): Promise<Entity>;
|
||||
|
||||
override async remove(
|
||||
entityOrEntities: Entity | Entity[],
|
||||
options?: RemoveOptions,
|
||||
entityManager?: EntityManager,
|
||||
): Promise<Entity | Entity[]> {
|
||||
const manager = entityManager || this.manager;
|
||||
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);
|
||||
|
||||
return formattedResult;
|
||||
@ -172,40 +220,50 @@ export class WorkspaceRepository<
|
||||
| ObjectId
|
||||
| ObjectId[]
|
||||
| FindOptionsWhere<Entity>,
|
||||
entityManager?: EntityManager,
|
||||
): Promise<DeleteResult> {
|
||||
const manager = entityManager || this.manager;
|
||||
|
||||
if (typeof criteria === 'object' && 'where' in criteria) {
|
||||
criteria = this.transformOptions(criteria);
|
||||
}
|
||||
|
||||
return this.delete(criteria);
|
||||
return manager.delete(this.target, criteria);
|
||||
}
|
||||
|
||||
override softRemove<T extends DeepPartial<Entity>>(
|
||||
entities: T[],
|
||||
options: SaveOptions & { reload: false },
|
||||
entityManager?: EntityManager,
|
||||
): Promise<T[]>;
|
||||
|
||||
override softRemove<T extends DeepPartial<Entity>>(
|
||||
entities: T[],
|
||||
options?: SaveOptions,
|
||||
entityManager?: EntityManager,
|
||||
): Promise<(T & Entity)[]>;
|
||||
|
||||
override softRemove<T extends DeepPartial<Entity>>(
|
||||
entity: T,
|
||||
options: SaveOptions & { reload: false },
|
||||
entityManager?: EntityManager,
|
||||
): Promise<T>;
|
||||
|
||||
override softRemove<T extends DeepPartial<Entity>>(
|
||||
entity: T,
|
||||
options?: SaveOptions,
|
||||
entityManager?: EntityManager,
|
||||
): Promise<T & Entity>;
|
||||
|
||||
override async softRemove<T extends DeepPartial<Entity>>(
|
||||
entityOrEntities: T | T[],
|
||||
options?: SaveOptions,
|
||||
entityManager?: EntityManager,
|
||||
): Promise<T | T[]> {
|
||||
const manager = entityManager || this.manager;
|
||||
const formattedEntityOrEntities = this.formatData(entityOrEntities);
|
||||
const result = await super.softRemove(
|
||||
const result = await manager.softRemove(
|
||||
this.target,
|
||||
formattedEntityOrEntities as any,
|
||||
options,
|
||||
);
|
||||
@ -225,12 +283,15 @@ export class WorkspaceRepository<
|
||||
| ObjectId
|
||||
| ObjectId[]
|
||||
| FindOptionsWhere<Entity>,
|
||||
entityManager?: EntityManager,
|
||||
): Promise<UpdateResult> {
|
||||
const manager = entityManager || this.manager;
|
||||
|
||||
if (typeof criteria === 'object' && 'where' in 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>>(
|
||||
entities: T[],
|
||||
options: SaveOptions & { reload: false },
|
||||
entityManager?: EntityManager,
|
||||
): Promise<T[]>;
|
||||
|
||||
override recover<T extends DeepPartial<Entity>>(
|
||||
entities: T[],
|
||||
options?: SaveOptions,
|
||||
entityManager?: EntityManager,
|
||||
): Promise<(T & Entity)[]>;
|
||||
|
||||
override recover<T extends DeepPartial<Entity>>(
|
||||
entity: T,
|
||||
options: SaveOptions & { reload: false },
|
||||
entityManager?: EntityManager,
|
||||
): Promise<T>;
|
||||
|
||||
override recover<T extends DeepPartial<Entity>>(
|
||||
entity: T,
|
||||
options?: SaveOptions,
|
||||
entityManager?: EntityManager,
|
||||
): Promise<T & Entity>;
|
||||
|
||||
override async recover<T extends DeepPartial<Entity>>(
|
||||
entityOrEntities: T | T[],
|
||||
options?: SaveOptions,
|
||||
entityManager?: EntityManager,
|
||||
): Promise<T | T[]> {
|
||||
const manager = entityManager || this.manager;
|
||||
const formattedEntityOrEntities = this.formatData(entityOrEntities);
|
||||
const result = await super.recover(
|
||||
const result = await manager.recover(
|
||||
this.target,
|
||||
formattedEntityOrEntities as any,
|
||||
options,
|
||||
);
|
||||
@ -281,12 +349,15 @@ export class WorkspaceRepository<
|
||||
| ObjectId
|
||||
| ObjectId[]
|
||||
| FindOptionsWhere<Entity>,
|
||||
entityManager?: EntityManager,
|
||||
): Promise<UpdateResult> {
|
||||
const manager = entityManager || this.manager;
|
||||
|
||||
if (typeof criteria === 'object' && 'where' in 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(
|
||||
entity: QueryDeepPartialEntity<Entity> | QueryDeepPartialEntity<Entity>[],
|
||||
entityManager?: EntityManager,
|
||||
): Promise<InsertResult> {
|
||||
const manager = entityManager || this.manager;
|
||||
const formatedEntity = this.formatData(entity);
|
||||
const result = await super.insert(formatedEntity);
|
||||
const result = await manager.insert(this.target, formatedEntity);
|
||||
const formattedResult = this.formatResult(result);
|
||||
|
||||
return formattedResult;
|
||||
@ -317,12 +390,15 @@ export class WorkspaceRepository<
|
||||
| ObjectId[]
|
||||
| FindOptionsWhere<Entity>,
|
||||
partialEntity: QueryDeepPartialEntity<Entity>,
|
||||
entityManager?: EntityManager,
|
||||
): Promise<UpdateResult> {
|
||||
const manager = entityManager || this.manager;
|
||||
|
||||
if (typeof criteria === 'object' && 'where' in criteria) {
|
||||
criteria = this.transformOptions(criteria);
|
||||
}
|
||||
|
||||
return this.update(criteria, partialEntity);
|
||||
return manager.update(this.target, criteria, partialEntity);
|
||||
}
|
||||
|
||||
override upsert(
|
||||
@ -330,50 +406,63 @@ export class WorkspaceRepository<
|
||||
| QueryDeepPartialEntity<Entity>
|
||||
| QueryDeepPartialEntity<Entity>[],
|
||||
conflictPathsOrOptions: string[] | UpsertOptions<Entity>,
|
||||
entityManager?: EntityManager,
|
||||
): Promise<InsertResult> {
|
||||
const manager = entityManager || this.manager;
|
||||
|
||||
const formattedEntityOrEntities = this.formatData(entityOrEntities);
|
||||
|
||||
return this.upsert(formattedEntityOrEntities, conflictPathsOrOptions);
|
||||
return manager.upsert(
|
||||
this.target,
|
||||
formattedEntityOrEntities,
|
||||
conflictPathsOrOptions,
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* 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);
|
||||
|
||||
return super.exist(computedOptions);
|
||||
}
|
||||
|
||||
override exists(options?: FindManyOptions<Entity>): Promise<boolean> {
|
||||
const computedOptions = this.transformOptions(options);
|
||||
|
||||
return super.exists(computedOptions);
|
||||
return manager.exists(this.target, computedOptions);
|
||||
}
|
||||
|
||||
override existsBy(
|
||||
where: FindOptionsWhere<Entity> | FindOptionsWhere<Entity>[],
|
||||
entityManager?: EntityManager,
|
||||
): Promise<boolean> {
|
||||
const manager = entityManager || this.manager;
|
||||
const computedOptions = this.transformOptions({ where });
|
||||
|
||||
return super.existsBy(computedOptions.where);
|
||||
return manager.existsBy(this.target, computedOptions.where);
|
||||
}
|
||||
|
||||
/**
|
||||
* 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);
|
||||
|
||||
return super.count(computedOptions);
|
||||
return manager.count(this.target, computedOptions);
|
||||
}
|
||||
|
||||
override countBy(
|
||||
where: FindOptionsWhere<Entity> | FindOptionsWhere<Entity>[],
|
||||
entityManager?: EntityManager,
|
||||
): Promise<number> {
|
||||
const manager = entityManager || this.manager;
|
||||
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(
|
||||
columnName: PickKeysByType<Entity, number>,
|
||||
where?: FindOptionsWhere<Entity> | FindOptionsWhere<Entity>[],
|
||||
entityManager?: EntityManager,
|
||||
): Promise<number | null> {
|
||||
const manager = entityManager || this.manager;
|
||||
const computedOptions = this.transformOptions({ where });
|
||||
|
||||
return super.sum(columnName, computedOptions.where);
|
||||
return manager.sum(this.target, columnName, computedOptions.where);
|
||||
}
|
||||
|
||||
override average(
|
||||
columnName: PickKeysByType<Entity, number>,
|
||||
where?: FindOptionsWhere<Entity> | FindOptionsWhere<Entity>[],
|
||||
entityManager?: EntityManager,
|
||||
): Promise<number | null> {
|
||||
const manager = entityManager || this.manager;
|
||||
const computedOptions = this.transformOptions({ where });
|
||||
|
||||
return super.average(columnName, computedOptions.where);
|
||||
return manager.average(this.target, columnName, computedOptions.where);
|
||||
}
|
||||
|
||||
override minimum(
|
||||
columnName: PickKeysByType<Entity, number>,
|
||||
where?: FindOptionsWhere<Entity> | FindOptionsWhere<Entity>[],
|
||||
entityManager?: EntityManager,
|
||||
): Promise<number | null> {
|
||||
const manager = entityManager || this.manager;
|
||||
const computedOptions = this.transformOptions({ where });
|
||||
|
||||
return super.minimum(columnName, computedOptions.where);
|
||||
return manager.minimum(this.target, columnName, computedOptions.where);
|
||||
}
|
||||
|
||||
override maximum(
|
||||
columnName: PickKeysByType<Entity, number>,
|
||||
where?: FindOptionsWhere<Entity> | FindOptionsWhere<Entity>[],
|
||||
entityManager?: EntityManager,
|
||||
): Promise<number | null> {
|
||||
const manager = entityManager || this.manager;
|
||||
const computedOptions = this.transformOptions({ where });
|
||||
|
||||
return super.maximum(columnName, computedOptions.where);
|
||||
return manager.maximum(this.target, columnName, computedOptions.where);
|
||||
}
|
||||
|
||||
override increment(
|
||||
conditions: FindOptionsWhere<Entity>,
|
||||
propertyPath: string,
|
||||
value: number | string,
|
||||
entityManager?: EntityManager,
|
||||
): Promise<UpdateResult> {
|
||||
const manager = entityManager || this.manager;
|
||||
const computedConditions = this.transformOptions({ where: conditions });
|
||||
|
||||
return this.increment(computedConditions.where, propertyPath, value);
|
||||
return manager.increment(
|
||||
this.target,
|
||||
computedConditions.where,
|
||||
propertyPath,
|
||||
value,
|
||||
);
|
||||
}
|
||||
|
||||
override decrement(
|
||||
conditions: FindOptionsWhere<Entity>,
|
||||
propertyPath: string,
|
||||
value: number | string,
|
||||
entityManager?: EntityManager,
|
||||
): Promise<UpdateResult> {
|
||||
const manager = entityManager || this.manager;
|
||||
const computedConditions = this.transformOptions({ where: conditions });
|
||||
|
||||
return this.decrement(computedConditions.where, propertyPath, value);
|
||||
return manager.decrement(
|
||||
this.target,
|
||||
computedConditions.where,
|
||||
propertyPath,
|
||||
value,
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -30,12 +30,21 @@ import {
|
||||
ConfigurableModuleClass,
|
||||
MODULE_OPTIONS_TOKEN,
|
||||
} from 'src/engine/twenty-orm/twenty-orm.module-definition';
|
||||
import { LoadServiceWithWorkspaceContext } from 'src/engine/twenty-orm/context/load-service-with-workspace.context';
|
||||
|
||||
@Global()
|
||||
@Module({
|
||||
imports: [DataSourceModule],
|
||||
providers: [...entitySchemaFactories, TwentyORMManager],
|
||||
exports: [EntitySchemaFactory, TwentyORMManager],
|
||||
providers: [
|
||||
...entitySchemaFactories,
|
||||
TwentyORMManager,
|
||||
LoadServiceWithWorkspaceContext,
|
||||
],
|
||||
exports: [
|
||||
EntitySchemaFactory,
|
||||
TwentyORMManager,
|
||||
LoadServiceWithWorkspaceContext,
|
||||
],
|
||||
})
|
||||
export class TwentyORMCoreModule
|
||||
extends ConfigurableModuleClass
|
||||
|
@ -1,19 +1,20 @@
|
||||
import { ObjectLiteral } from 'typeorm';
|
||||
|
||||
import { BaseWorkspaceEntity } from 'src/engine/twenty-orm/base.workspace-entity';
|
||||
|
||||
export type ObjectRecord<T extends ObjectLiteral> = {
|
||||
[K in keyof T as T[K] extends BaseWorkspaceEntity
|
||||
? `${Extract<K, string>}Id`
|
||||
: K]: T[K] extends BaseWorkspaceEntity
|
||||
? string
|
||||
: T[K] extends BaseWorkspaceEntity[]
|
||||
? 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 RelationKeys<T> = {
|
||||
[K in keyof T]: NonNullable<T[K]> extends BaseWorkspaceEntity ? K : never;
|
||||
}[keyof T];
|
||||
|
||||
type ForeignKeyMap<T> = {
|
||||
[K in RelationKeys<T> as `${K & string}Id`]: string;
|
||||
};
|
||||
|
||||
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>;
|
||||
|
@ -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,
|
||||
);
|
||||
}
|
||||
}
|
@ -36,7 +36,7 @@ export class ActivityTargetWorkspaceEntity extends BaseWorkspaceEntity {
|
||||
inverseSideFieldKey: 'activityTargets',
|
||||
})
|
||||
@WorkspaceIsNullable()
|
||||
activity: Relation<ActivityWorkspaceEntity>;
|
||||
activity: Relation<ActivityWorkspaceEntity> | null;
|
||||
|
||||
@WorkspaceRelation({
|
||||
standardId: ACTIVITY_TARGET_STANDARD_FIELD_IDS.person,
|
||||
@ -49,7 +49,7 @@ export class ActivityTargetWorkspaceEntity extends BaseWorkspaceEntity {
|
||||
inverseSideFieldKey: 'activityTargets',
|
||||
})
|
||||
@WorkspaceIsNullable()
|
||||
person: Relation<PersonWorkspaceEntity>;
|
||||
person: Relation<PersonWorkspaceEntity> | null;
|
||||
|
||||
@WorkspaceRelation({
|
||||
standardId: ACTIVITY_TARGET_STANDARD_FIELD_IDS.company,
|
||||
@ -62,7 +62,7 @@ export class ActivityTargetWorkspaceEntity extends BaseWorkspaceEntity {
|
||||
inverseSideFieldKey: 'activityTargets',
|
||||
})
|
||||
@WorkspaceIsNullable()
|
||||
company: Relation<CompanyWorkspaceEntity>;
|
||||
company: Relation<CompanyWorkspaceEntity> | null;
|
||||
|
||||
@WorkspaceRelation({
|
||||
standardId: ACTIVITY_TARGET_STANDARD_FIELD_IDS.opportunity,
|
||||
@ -75,7 +75,7 @@ export class ActivityTargetWorkspaceEntity extends BaseWorkspaceEntity {
|
||||
inverseSideFieldKey: 'activityTargets',
|
||||
})
|
||||
@WorkspaceIsNullable()
|
||||
opportunity: Relation<OpportunityWorkspaceEntity>;
|
||||
opportunity: Relation<OpportunityWorkspaceEntity> | null;
|
||||
|
||||
@WorkspaceDynamicRelation({
|
||||
type: RelationMetadataType.MANY_TO_ONE,
|
||||
|
@ -64,7 +64,7 @@ export class ActivityWorkspaceEntity extends BaseWorkspaceEntity {
|
||||
icon: 'IconCalendarEvent',
|
||||
})
|
||||
@WorkspaceIsNullable()
|
||||
reminderAt: Date;
|
||||
reminderAt: Date | null;
|
||||
|
||||
@WorkspaceField({
|
||||
standardId: ACTIVITY_STANDARD_FIELD_IDS.dueAt,
|
||||
@ -74,7 +74,7 @@ export class ActivityWorkspaceEntity extends BaseWorkspaceEntity {
|
||||
icon: 'IconCalendarEvent',
|
||||
})
|
||||
@WorkspaceIsNullable()
|
||||
dueAt: Date;
|
||||
dueAt: Date | null;
|
||||
|
||||
@WorkspaceField({
|
||||
standardId: ACTIVITY_STANDARD_FIELD_IDS.completedAt,
|
||||
@ -84,7 +84,7 @@ export class ActivityWorkspaceEntity extends BaseWorkspaceEntity {
|
||||
icon: 'IconCheck',
|
||||
})
|
||||
@WorkspaceIsNullable()
|
||||
completedAt: Date;
|
||||
completedAt: Date | null;
|
||||
|
||||
@WorkspaceRelation({
|
||||
standardId: ACTIVITY_STANDARD_FIELD_IDS.activityTargets,
|
||||
@ -134,7 +134,7 @@ export class ActivityWorkspaceEntity extends BaseWorkspaceEntity {
|
||||
joinColumn: 'authorId',
|
||||
})
|
||||
@WorkspaceIsNullable()
|
||||
author: Relation<WorkspaceMemberWorkspaceEntity>;
|
||||
author: Relation<WorkspaceMemberWorkspaceEntity> | null;
|
||||
|
||||
@WorkspaceRelation({
|
||||
standardId: ACTIVITY_STANDARD_FIELD_IDS.assignee,
|
||||
@ -148,5 +148,5 @@ export class ActivityWorkspaceEntity extends BaseWorkspaceEntity {
|
||||
joinColumn: 'assigneeId',
|
||||
})
|
||||
@WorkspaceIsNullable()
|
||||
assignee: Relation<WorkspaceMemberWorkspaceEntity>;
|
||||
assignee: Relation<WorkspaceMemberWorkspaceEntity> | null;
|
||||
}
|
||||
|
@ -45,5 +45,5 @@ export class ApiKeyWorkspaceEntity extends BaseWorkspaceEntity {
|
||||
icon: 'IconCalendar',
|
||||
})
|
||||
@WorkspaceIsNullable()
|
||||
revokedAt?: Date;
|
||||
revokedAt?: Date | null;
|
||||
}
|
||||
|
@ -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,
|
||||
);
|
||||
}
|
||||
}
|
@ -80,7 +80,7 @@ export class AttachmentWorkspaceEntity extends BaseWorkspaceEntity {
|
||||
inverseSideFieldKey: 'attachments',
|
||||
})
|
||||
@WorkspaceIsNullable()
|
||||
activity: Relation<ActivityWorkspaceEntity>;
|
||||
activity: Relation<ActivityWorkspaceEntity> | null;
|
||||
|
||||
@WorkspaceRelation({
|
||||
standardId: ATTACHMENT_STANDARD_FIELD_IDS.person,
|
||||
@ -93,7 +93,7 @@ export class AttachmentWorkspaceEntity extends BaseWorkspaceEntity {
|
||||
inverseSideFieldKey: 'attachments',
|
||||
})
|
||||
@WorkspaceIsNullable()
|
||||
person: Relation<PersonWorkspaceEntity>;
|
||||
person: Relation<PersonWorkspaceEntity> | null;
|
||||
|
||||
@WorkspaceRelation({
|
||||
standardId: ATTACHMENT_STANDARD_FIELD_IDS.company,
|
||||
@ -106,7 +106,7 @@ export class AttachmentWorkspaceEntity extends BaseWorkspaceEntity {
|
||||
inverseSideFieldKey: 'attachments',
|
||||
})
|
||||
@WorkspaceIsNullable()
|
||||
company: Relation<CompanyWorkspaceEntity>;
|
||||
company: Relation<CompanyWorkspaceEntity> | null;
|
||||
|
||||
@WorkspaceRelation({
|
||||
standardId: ATTACHMENT_STANDARD_FIELD_IDS.opportunity,
|
||||
@ -119,7 +119,7 @@ export class AttachmentWorkspaceEntity extends BaseWorkspaceEntity {
|
||||
inverseSideFieldKey: 'attachments',
|
||||
})
|
||||
@WorkspaceIsNullable()
|
||||
opportunity: Relation<OpportunityWorkspaceEntity>;
|
||||
opportunity: Relation<OpportunityWorkspaceEntity> | null;
|
||||
|
||||
@WorkspaceDynamicRelation({
|
||||
type: RelationMetadataType.MANY_TO_ONE,
|
||||
|
@ -1,3 +1,5 @@
|
||||
import { Scope } from '@nestjs/common';
|
||||
|
||||
import { Process } from 'src/engine/integrations/message-queue/decorators/process.decorator';
|
||||
import { Processor } from 'src/engine/integrations/message-queue/decorators/processor.decorator';
|
||||
import { MessageQueue } from 'src/engine/integrations/message-queue/message-queue.constants';
|
||||
@ -11,7 +13,10 @@ export type MatchParticipantJobData = {
|
||||
workspaceMemberId?: string;
|
||||
};
|
||||
|
||||
@Processor(MessageQueue.messagingQueue)
|
||||
@Processor({
|
||||
queueName: MessageQueue.messagingQueue,
|
||||
scope: Scope.REQUEST,
|
||||
})
|
||||
export class MatchParticipantJob {
|
||||
constructor(
|
||||
private readonly messageParticipantService: MessagingMessageParticipantService,
|
||||
|
@ -1,3 +1,5 @@
|
||||
import { Scope } from '@nestjs/common';
|
||||
|
||||
import { Processor } from 'src/engine/integrations/message-queue/decorators/processor.decorator';
|
||||
import { MessageQueue } from 'src/engine/integrations/message-queue/message-queue.constants';
|
||||
import { CalendarEventParticipantService } from 'src/modules/calendar/services/calendar-event-participant/calendar-event-participant.service';
|
||||
@ -11,7 +13,10 @@ export type UnmatchParticipantJobData = {
|
||||
workspaceMemberId?: string;
|
||||
};
|
||||
|
||||
@Processor(MessageQueue.messagingQueue)
|
||||
@Processor({
|
||||
queueName: MessageQueue.messagingQueue,
|
||||
scope: Scope.REQUEST,
|
||||
})
|
||||
export class UnmatchParticipantJob {
|
||||
constructor(
|
||||
private readonly messageParticipantService: MessagingMessageParticipantService,
|
||||
|
@ -1,19 +1,10 @@
|
||||
import { Module } from '@nestjs/common';
|
||||
|
||||
import { ObjectMetadataRepositoryModule } from 'src/engine/object-metadata-repository/object-metadata-repository.module';
|
||||
import { GoogleCalendarSyncCommand } from 'src/modules/calendar/commands/google-calendar-sync.command';
|
||||
import { WorkspaceGoogleCalendarSyncModule } from 'src/modules/calendar/services/workspace-google-calendar-sync/workspace-google-calendar-sync.module';
|
||||
import { CalendarChannelWorkspaceEntity } from 'src/modules/calendar/standard-objects/calendar-channel.workspace-entity';
|
||||
import { ConnectedAccountWorkspaceEntity } from 'src/modules/connected-account/standard-objects/connected-account.workspace-entity';
|
||||
|
||||
@Module({
|
||||
imports: [
|
||||
ObjectMetadataRepositoryModule.forFeature([
|
||||
ConnectedAccountWorkspaceEntity,
|
||||
CalendarChannelWorkspaceEntity,
|
||||
]),
|
||||
WorkspaceGoogleCalendarSyncModule,
|
||||
],
|
||||
imports: [WorkspaceGoogleCalendarSyncModule],
|
||||
providers: [GoogleCalendarSyncCommand],
|
||||
})
|
||||
export class CalendarCommandsModule {}
|
||||
|
@ -1,4 +1,5 @@
|
||||
import { InjectRepository } from '@nestjs/typeorm';
|
||||
import { Scope } from '@nestjs/common';
|
||||
|
||||
import { Repository, In } from 'typeorm';
|
||||
|
||||
@ -10,7 +11,10 @@ import { MessageQueue } from 'src/engine/integrations/message-queue/message-queu
|
||||
import { Processor } from 'src/engine/integrations/message-queue/decorators/processor.decorator';
|
||||
import { Process } from 'src/engine/integrations/message-queue/decorators/process.decorator';
|
||||
|
||||
@Processor(MessageQueue.cronQueue)
|
||||
@Processor({
|
||||
queueName: MessageQueue.cronQueue,
|
||||
scope: Scope.REQUEST,
|
||||
})
|
||||
export class GoogleCalendarSyncCronJob {
|
||||
constructor(
|
||||
@InjectRepository(Workspace, 'core')
|
||||
|
@ -1,35 +1,38 @@
|
||||
import { Logger } from '@nestjs/common';
|
||||
import { Logger, Scope } from '@nestjs/common';
|
||||
|
||||
import { Any, ILike } from 'typeorm';
|
||||
|
||||
import { Processor } from 'src/engine/integrations/message-queue/decorators/processor.decorator';
|
||||
import { MessageQueue } from 'src/engine/integrations/message-queue/message-queue.constants';
|
||||
import { InjectObjectMetadataRepository } from 'src/engine/object-metadata-repository/object-metadata-repository.decorator';
|
||||
import { CalendarChannelEventAssociationRepository } from 'src/modules/calendar/repositories/calendar-channel-event-association.repository';
|
||||
import { CalendarChannelRepository } from 'src/modules/calendar/repositories/calendar-channel.repository';
|
||||
import { CalendarEventCleanerService } from 'src/modules/calendar/services/calendar-event-cleaner/calendar-event-cleaner.service';
|
||||
import { CalendarChannelEventAssociationWorkspaceEntity } from 'src/modules/calendar/standard-objects/calendar-channel-event-association.workspace-entity';
|
||||
import { CalendarChannelWorkspaceEntity } from 'src/modules/calendar/standard-objects/calendar-channel.workspace-entity';
|
||||
import { BlocklistRepository } from 'src/modules/connected-account/repositories/blocklist.repository';
|
||||
import { BlocklistWorkspaceEntity } from 'src/modules/connected-account/standard-objects/blocklist.workspace-entity';
|
||||
import { Process } from 'src/engine/integrations/message-queue/decorators/process.decorator';
|
||||
import { InjectWorkspaceRepository } from 'src/engine/twenty-orm/decorators/inject-workspace-repository.decorator';
|
||||
import { WorkspaceRepository } from 'src/engine/twenty-orm/repository/workspace.repository';
|
||||
|
||||
export type BlocklistItemDeleteCalendarEventsJobData = {
|
||||
workspaceId: string;
|
||||
blocklistItemId: string;
|
||||
};
|
||||
|
||||
@Processor(MessageQueue.calendarQueue)
|
||||
@Processor({
|
||||
queueName: MessageQueue.calendarQueue,
|
||||
scope: Scope.REQUEST,
|
||||
})
|
||||
export class BlocklistItemDeleteCalendarEventsJob {
|
||||
private readonly logger = new Logger(
|
||||
BlocklistItemDeleteCalendarEventsJob.name,
|
||||
);
|
||||
|
||||
constructor(
|
||||
@InjectObjectMetadataRepository(CalendarChannelWorkspaceEntity)
|
||||
private readonly calendarChannelRepository: CalendarChannelRepository,
|
||||
@InjectObjectMetadataRepository(
|
||||
CalendarChannelEventAssociationWorkspaceEntity,
|
||||
)
|
||||
private readonly calendarChannelEventAssociationRepository: CalendarChannelEventAssociationRepository,
|
||||
@InjectWorkspaceRepository(CalendarChannelWorkspaceEntity)
|
||||
private readonly calendarChannelRepository: WorkspaceRepository<CalendarChannelWorkspaceEntity>,
|
||||
@InjectWorkspaceRepository(CalendarChannelEventAssociationWorkspaceEntity)
|
||||
private readonly calendarChannelEventAssociationRepository: WorkspaceRepository<CalendarChannelEventAssociationWorkspaceEntity>,
|
||||
@InjectObjectMetadataRepository(BlocklistWorkspaceEntity)
|
||||
private readonly blocklistRepository: BlocklistRepository,
|
||||
private readonly calendarEventCleanerService: CalendarEventCleanerService,
|
||||
@ -58,19 +61,39 @@ export class BlocklistItemDeleteCalendarEventsJob {
|
||||
`Deleting calendar events from ${handle} in workspace ${workspaceId} for workspace member ${workspaceMemberId}`,
|
||||
);
|
||||
|
||||
const calendarChannels =
|
||||
await this.calendarChannelRepository.getIdsByWorkspaceMemberId(
|
||||
workspaceMemberId,
|
||||
workspaceId,
|
||||
if (!workspaceMemberId) {
|
||||
throw new Error(
|
||||
`Workspace member ID is undefined for blocklist item ${blocklistItemId} in workspace ${workspaceId}`,
|
||||
);
|
||||
}
|
||||
|
||||
const calendarChannels = await this.calendarChannelRepository.find({
|
||||
where: {
|
||||
connectedAccount: {
|
||||
accountOwner: {
|
||||
id: workspaceMemberId,
|
||||
},
|
||||
},
|
||||
},
|
||||
relations: ['connectedAccount.accountOwner'],
|
||||
});
|
||||
|
||||
const calendarChannelIds = calendarChannels.map(({ id }) => id);
|
||||
|
||||
await this.calendarChannelEventAssociationRepository.deleteByCalendarEventParticipantHandleAndCalendarChannelIds(
|
||||
handle,
|
||||
calendarChannelIds,
|
||||
workspaceId,
|
||||
);
|
||||
const isHandleDomain = handle.startsWith('@');
|
||||
|
||||
await this.calendarChannelEventAssociationRepository.delete({
|
||||
calendarEvent: {
|
||||
calendarEventParticipants: {
|
||||
handle: isHandleDomain ? ILike(`%${handle}`) : handle,
|
||||
},
|
||||
calendarChannelEventAssociations: {
|
||||
calendarChannel: {
|
||||
id: Any(calendarChannelIds),
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
await this.calendarEventCleanerService.cleanWorkspaceCalendarEvents(
|
||||
workspaceId,
|
||||
|
@ -1,4 +1,4 @@
|
||||
import { Logger } from '@nestjs/common';
|
||||
import { Logger, Scope } from '@nestjs/common';
|
||||
|
||||
import { Processor } from 'src/engine/integrations/message-queue/decorators/processor.decorator';
|
||||
import { MessageQueue } from 'src/engine/integrations/message-queue/message-queue.constants';
|
||||
@ -14,7 +14,10 @@ export type BlocklistReimportCalendarEventsJobData = {
|
||||
handle: string;
|
||||
};
|
||||
|
||||
@Processor(MessageQueue.calendarQueue)
|
||||
@Processor({
|
||||
queueName: MessageQueue.calendarQueue,
|
||||
scope: Scope.REQUEST,
|
||||
})
|
||||
export class BlocklistReimportCalendarEventsJob {
|
||||
private readonly logger = new Logger(BlocklistReimportCalendarEventsJob.name);
|
||||
|
||||
|
@ -1,35 +1,35 @@
|
||||
import { Logger } from '@nestjs/common';
|
||||
import { Logger, Scope } from '@nestjs/common';
|
||||
|
||||
import { IsNull } from 'typeorm';
|
||||
|
||||
import { Process } from 'src/engine/integrations/message-queue/decorators/process.decorator';
|
||||
import { Processor } from 'src/engine/integrations/message-queue/decorators/processor.decorator';
|
||||
import { MessageQueue } from 'src/engine/integrations/message-queue/message-queue.constants';
|
||||
import { InjectObjectMetadataRepository } from 'src/engine/object-metadata-repository/object-metadata-repository.decorator';
|
||||
import { CalendarChannelRepository } from 'src/modules/calendar/repositories/calendar-channel.repository';
|
||||
import { CalendarEventParticipantRepository } from 'src/modules/calendar/repositories/calendar-event-participant.repository';
|
||||
import { CalendarChannelWorkspaceEntity } from 'src/modules/calendar/standard-objects/calendar-channel.workspace-entity';
|
||||
import { CalendarEventParticipantWorkspaceEntity } from 'src/modules/calendar/standard-objects/calendar-event-participant.workspace-entity';
|
||||
import { CreateCompanyAndContactService } from 'src/modules/connected-account/auto-companies-and-contacts-creation/services/create-company-and-contact.service';
|
||||
import { ConnectedAccountRepository } from 'src/modules/connected-account/repositories/connected-account.repository';
|
||||
import { ConnectedAccountWorkspaceEntity } from 'src/modules/connected-account/standard-objects/connected-account.workspace-entity';
|
||||
import { Process } from 'src/engine/integrations/message-queue/decorators/process.decorator';
|
||||
import { InjectWorkspaceRepository } from 'src/engine/twenty-orm/decorators/inject-workspace-repository.decorator';
|
||||
import { WorkspaceRepository } from 'src/engine/twenty-orm/repository/workspace.repository';
|
||||
|
||||
export type CalendarCreateCompanyAndContactAfterSyncJobData = {
|
||||
workspaceId: string;
|
||||
calendarChannelId: string;
|
||||
};
|
||||
|
||||
@Processor(MessageQueue.calendarQueue)
|
||||
@Processor({
|
||||
queueName: MessageQueue.calendarQueue,
|
||||
scope: Scope.REQUEST,
|
||||
})
|
||||
export class CalendarCreateCompanyAndContactAfterSyncJob {
|
||||
private readonly logger = new Logger(
|
||||
CalendarCreateCompanyAndContactAfterSyncJob.name,
|
||||
);
|
||||
constructor(
|
||||
private readonly createCompanyAndContactService: CreateCompanyAndContactService,
|
||||
@InjectObjectMetadataRepository(CalendarChannelWorkspaceEntity)
|
||||
private readonly calendarChannelService: CalendarChannelRepository,
|
||||
@InjectObjectMetadataRepository(CalendarEventParticipantWorkspaceEntity)
|
||||
private readonly calendarEventParticipantRepository: CalendarEventParticipantRepository,
|
||||
@InjectObjectMetadataRepository(ConnectedAccountWorkspaceEntity)
|
||||
private readonly connectedAccountRepository: ConnectedAccountRepository,
|
||||
@InjectWorkspaceRepository(CalendarChannelWorkspaceEntity)
|
||||
private readonly calendarChannelRepository: WorkspaceRepository<CalendarChannelWorkspaceEntity>,
|
||||
@InjectWorkspaceRepository(CalendarEventParticipantWorkspaceEntity)
|
||||
private readonly calendarEventParticipantRepository: WorkspaceRepository<CalendarEventParticipantWorkspaceEntity>,
|
||||
) {}
|
||||
|
||||
@Process(CalendarCreateCompanyAndContactAfterSyncJob.name)
|
||||
@ -41,40 +41,52 @@ export class CalendarCreateCompanyAndContactAfterSyncJob {
|
||||
);
|
||||
const { workspaceId, calendarChannelId } = data;
|
||||
|
||||
const calendarChannels = await this.calendarChannelService.getByIds(
|
||||
[calendarChannelId],
|
||||
workspaceId,
|
||||
);
|
||||
const calendarChannel = await this.calendarChannelRepository.findOne({
|
||||
where: {
|
||||
id: calendarChannelId,
|
||||
},
|
||||
relations: ['connectedAccount.accountOwner'],
|
||||
});
|
||||
|
||||
if (calendarChannels.length === 0) {
|
||||
if (!calendarChannel) {
|
||||
throw new Error(
|
||||
`Calendar channel with id ${calendarChannelId} not found in workspace ${workspaceId}`,
|
||||
);
|
||||
}
|
||||
|
||||
const { handle, isContactAutoCreationEnabled, connectedAccountId } =
|
||||
calendarChannels[0];
|
||||
const { handle, isContactAutoCreationEnabled, connectedAccount } =
|
||||
calendarChannel;
|
||||
|
||||
if (!isContactAutoCreationEnabled || !handle) {
|
||||
return;
|
||||
}
|
||||
|
||||
const connectedAccount = await this.connectedAccountRepository.getById(
|
||||
connectedAccountId,
|
||||
workspaceId,
|
||||
);
|
||||
|
||||
if (!connectedAccount) {
|
||||
throw new Error(
|
||||
`Connected account with id ${connectedAccountId} not found in workspace ${workspaceId}`,
|
||||
`Connected account not found in workspace ${workspaceId}`,
|
||||
);
|
||||
}
|
||||
|
||||
const calendarEventParticipantsWithoutPersonIdAndWorkspaceMemberId =
|
||||
await this.calendarEventParticipantRepository.getByCalendarChannelIdWithoutPersonIdAndWorkspaceMemberId(
|
||||
calendarChannelId,
|
||||
workspaceId,
|
||||
);
|
||||
await this.calendarEventParticipantRepository.find({
|
||||
where: {
|
||||
calendarEvent: {
|
||||
calendarChannelEventAssociations: {
|
||||
calendarChannel: {
|
||||
id: calendarChannelId,
|
||||
},
|
||||
},
|
||||
calendarEventParticipants: {
|
||||
person: IsNull(),
|
||||
workspaceMember: IsNull(),
|
||||
},
|
||||
},
|
||||
},
|
||||
relations: [
|
||||
'calendarEvent.calendarChannelEventAssociations',
|
||||
'calendarEvent.calendarEventParticipants',
|
||||
],
|
||||
});
|
||||
|
||||
await this.createCompanyAndContactService.createCompaniesAndContactsAndUpdateParticipants(
|
||||
connectedAccount,
|
||||
|
@ -1,6 +1,7 @@
|
||||
import { Module } from '@nestjs/common';
|
||||
|
||||
import { ObjectMetadataRepositoryModule } from 'src/engine/object-metadata-repository/object-metadata-repository.module';
|
||||
import { TwentyORMModule } from 'src/engine/twenty-orm/twenty-orm.module';
|
||||
import { BlocklistItemDeleteCalendarEventsJob } from 'src/modules/calendar/jobs/blocklist-item-delete-calendar-events.job';
|
||||
import { BlocklistReimportCalendarEventsJob } from 'src/modules/calendar/jobs/blocklist-reimport-calendar-events.job';
|
||||
import { CalendarCreateCompanyAndContactAfterSyncJob } from 'src/modules/calendar/jobs/calendar-create-company-and-contact-after-sync.job';
|
||||
@ -18,10 +19,12 @@ import { ConnectedAccountWorkspaceEntity } from 'src/modules/connected-account/s
|
||||
|
||||
@Module({
|
||||
imports: [
|
||||
ObjectMetadataRepositoryModule.forFeature([
|
||||
TwentyORMModule.forFeature([
|
||||
CalendarChannelWorkspaceEntity,
|
||||
CalendarChannelEventAssociationWorkspaceEntity,
|
||||
CalendarEventParticipantWorkspaceEntity,
|
||||
]),
|
||||
ObjectMetadataRepositoryModule.forFeature([
|
||||
ConnectedAccountWorkspaceEntity,
|
||||
BlocklistWorkspaceEntity,
|
||||
]),
|
||||
|
@ -1,4 +1,4 @@
|
||||
import { Logger } from '@nestjs/common';
|
||||
import { Logger, Scope } from '@nestjs/common';
|
||||
|
||||
import { GoogleAPIRefreshAccessTokenService } from 'src/modules/connected-account/services/google-api-refresh-access-token/google-api-refresh-access-token.service';
|
||||
import { GoogleCalendarSyncService } from 'src/modules/calendar/services/google-calendar-sync/google-calendar-sync.service';
|
||||
@ -11,7 +11,10 @@ export type GoogleCalendarSyncJobData = {
|
||||
connectedAccountId: string;
|
||||
};
|
||||
|
||||
@Processor(MessageQueue.calendarQueue)
|
||||
@Processor({
|
||||
queueName: MessageQueue.calendarQueue,
|
||||
scope: Scope.REQUEST,
|
||||
})
|
||||
export class GoogleCalendarSyncJob {
|
||||
private readonly logger = new Logger(GoogleCalendarSyncJob.name);
|
||||
|
||||
|
@ -25,7 +25,7 @@ export class CalendarEventParticipantListener {
|
||||
@OnEvent('calendarEventParticipant.matched')
|
||||
public async handleCalendarEventParticipantMatchedEvent(payload: {
|
||||
workspaceId: string;
|
||||
userId: string;
|
||||
workspaceMemberId: string;
|
||||
calendarEventParticipants: ObjectRecord<CalendarEventParticipantWorkspaceEntity>[];
|
||||
}): Promise<void> {
|
||||
const calendarEventParticipants = payload.calendarEventParticipants ?? [];
|
||||
@ -59,7 +59,7 @@ export class CalendarEventParticipantListener {
|
||||
properties: null,
|
||||
objectName: 'calendarEvent',
|
||||
recordId: participant.personId,
|
||||
workspaceMemberId: payload.userId,
|
||||
workspaceMemberId: payload.workspaceMemberId,
|
||||
workspaceId: payload.workspaceId,
|
||||
linkedObjectMetadataId: calendarEventObjectMetadata.id,
|
||||
linkedRecordId: participant.calendarEventId,
|
||||
|
@ -1,26 +1,20 @@
|
||||
import {
|
||||
BadRequestException,
|
||||
Injectable,
|
||||
NotFoundException,
|
||||
} from '@nestjs/common';
|
||||
import { BadRequestException, Injectable } from '@nestjs/common';
|
||||
|
||||
import { WorkspacePreQueryHook } from 'src/engine/api/graphql/workspace-query-runner/workspace-pre-query-hook/interfaces/workspace-pre-query-hook.interface';
|
||||
import { FindManyResolverArgs } from 'src/engine/api/graphql/workspace-resolver-builder/interfaces/workspace-resolvers-builder.interface';
|
||||
|
||||
import { InjectObjectMetadataRepository } from 'src/engine/object-metadata-repository/object-metadata-repository.decorator';
|
||||
import { CalendarChannelEventAssociationWorkspaceEntity } from 'src/modules/calendar/standard-objects/calendar-channel-event-association.workspace-entity';
|
||||
import { CalendarChannelEventAssociationRepository } from 'src/modules/calendar/repositories/calendar-channel-event-association.repository';
|
||||
import { CanAccessCalendarEventService } from 'src/modules/calendar/query-hooks/calendar-event/services/can-access-calendar-event.service';
|
||||
import { InjectWorkspaceRepository } from 'src/engine/twenty-orm/decorators/inject-workspace-repository.decorator';
|
||||
import { WorkspaceRepository } from 'src/engine/twenty-orm/repository/workspace.repository';
|
||||
|
||||
@Injectable()
|
||||
export class CalendarEventFindManyPreQueryHook
|
||||
implements WorkspacePreQueryHook
|
||||
{
|
||||
constructor(
|
||||
@InjectObjectMetadataRepository(
|
||||
CalendarChannelEventAssociationWorkspaceEntity,
|
||||
)
|
||||
private readonly calendarChannelEventAssociationRepository: CalendarChannelEventAssociationRepository,
|
||||
@InjectWorkspaceRepository(CalendarChannelEventAssociationWorkspaceEntity)
|
||||
private readonly calendarChannelEventAssociationRepository: WorkspaceRepository<CalendarChannelEventAssociationWorkspaceEntity>,
|
||||
private readonly canAccessCalendarEventService: CanAccessCalendarEventService,
|
||||
) {}
|
||||
|
||||
@ -33,20 +27,25 @@ export class CalendarEventFindManyPreQueryHook
|
||||
throw new BadRequestException('id filter is required');
|
||||
}
|
||||
|
||||
const calendarChannelCalendarEventAssociations =
|
||||
await this.calendarChannelEventAssociationRepository.getByCalendarEventIds(
|
||||
[payload?.filter?.id?.eq],
|
||||
workspaceId,
|
||||
);
|
||||
// TODO: Re-implement this using twenty ORM
|
||||
// const calendarChannelCalendarEventAssociations =
|
||||
// await this.calendarChannelEventAssociationRepository.find({
|
||||
// where: {
|
||||
// calendarEvent: {
|
||||
// id: payload?.filter?.id?.eq,
|
||||
// },
|
||||
// },
|
||||
// relations: ['calendarChannel.connectedAccount'],
|
||||
// });
|
||||
|
||||
if (calendarChannelCalendarEventAssociations.length === 0) {
|
||||
throw new NotFoundException();
|
||||
}
|
||||
// if (calendarChannelCalendarEventAssociations.length === 0) {
|
||||
// throw new NotFoundException();
|
||||
// }
|
||||
|
||||
await this.canAccessCalendarEventService.canAccessCalendarEvent(
|
||||
userId,
|
||||
workspaceId,
|
||||
calendarChannelCalendarEventAssociations,
|
||||
);
|
||||
// await this.canAccessCalendarEventService.canAccessCalendarEvent(
|
||||
// userId,
|
||||
// workspaceId,
|
||||
// calendarChannelCalendarEventAssociations,
|
||||
// );
|
||||
}
|
||||
}
|
||||
|
@ -1,24 +1,18 @@
|
||||
import {
|
||||
BadRequestException,
|
||||
Injectable,
|
||||
NotFoundException,
|
||||
} from '@nestjs/common';
|
||||
import { BadRequestException, Injectable } from '@nestjs/common';
|
||||
|
||||
import { WorkspacePreQueryHook } from 'src/engine/api/graphql/workspace-query-runner/workspace-pre-query-hook/interfaces/workspace-pre-query-hook.interface';
|
||||
import { FindOneResolverArgs } from 'src/engine/api/graphql/workspace-resolver-builder/interfaces/workspace-resolvers-builder.interface';
|
||||
|
||||
import { InjectObjectMetadataRepository } from 'src/engine/object-metadata-repository/object-metadata-repository.decorator';
|
||||
import { InjectWorkspaceRepository } from 'src/engine/twenty-orm/decorators/inject-workspace-repository.decorator';
|
||||
import { WorkspaceRepository } from 'src/engine/twenty-orm/repository/workspace.repository';
|
||||
import { CanAccessCalendarEventService } from 'src/modules/calendar/query-hooks/calendar-event/services/can-access-calendar-event.service';
|
||||
import { CalendarChannelEventAssociationRepository } from 'src/modules/calendar/repositories/calendar-channel-event-association.repository';
|
||||
import { CalendarChannelEventAssociationWorkspaceEntity } from 'src/modules/calendar/standard-objects/calendar-channel-event-association.workspace-entity';
|
||||
|
||||
@Injectable()
|
||||
export class CalendarEventFindOnePreQueryHook implements WorkspacePreQueryHook {
|
||||
constructor(
|
||||
@InjectObjectMetadataRepository(
|
||||
CalendarChannelEventAssociationWorkspaceEntity,
|
||||
)
|
||||
private readonly calendarChannelEventAssociationRepository: CalendarChannelEventAssociationRepository,
|
||||
@InjectWorkspaceRepository(CalendarChannelEventAssociationWorkspaceEntity)
|
||||
private readonly calendarChannelEventAssociationRepository: WorkspaceRepository<CalendarChannelEventAssociationWorkspaceEntity>,
|
||||
private readonly canAccessCalendarEventService: CanAccessCalendarEventService,
|
||||
) {}
|
||||
|
||||
@ -31,20 +25,24 @@ export class CalendarEventFindOnePreQueryHook implements WorkspacePreQueryHook {
|
||||
throw new BadRequestException('id filter is required');
|
||||
}
|
||||
|
||||
const calendarChannelCalendarEventAssociations =
|
||||
await this.calendarChannelEventAssociationRepository.getByCalendarEventIds(
|
||||
[payload?.filter?.id?.eq],
|
||||
workspaceId,
|
||||
);
|
||||
// TODO: Re-implement this using twenty ORM
|
||||
// const calendarChannelCalendarEventAssociations =
|
||||
// await this.calendarChannelEventAssociationRepository.find({
|
||||
// where: {
|
||||
// calendarEvent: {
|
||||
// id: payload?.filter?.id?.eq,
|
||||
// },
|
||||
// },
|
||||
// });
|
||||
|
||||
if (calendarChannelCalendarEventAssociations.length === 0) {
|
||||
throw new NotFoundException();
|
||||
}
|
||||
// if (calendarChannelCalendarEventAssociations.length === 0) {
|
||||
// throw new NotFoundException();
|
||||
// }
|
||||
|
||||
await this.canAccessCalendarEventService.canAccessCalendarEvent(
|
||||
userId,
|
||||
workspaceId,
|
||||
calendarChannelCalendarEventAssociations,
|
||||
);
|
||||
// await this.canAccessCalendarEventService.canAccessCalendarEvent(
|
||||
// userId,
|
||||
// workspaceId,
|
||||
// calendarChannelCalendarEventAssociations,
|
||||
// );
|
||||
}
|
||||
}
|
||||
|
@ -1,10 +1,11 @@
|
||||
import { ForbiddenException, Injectable } from '@nestjs/common';
|
||||
|
||||
import groupBy from 'lodash.groupby';
|
||||
import { Any } from 'typeorm';
|
||||
|
||||
import { InjectObjectMetadataRepository } from 'src/engine/object-metadata-repository/object-metadata-repository.decorator';
|
||||
import { ObjectRecord } from 'src/engine/workspace-manager/workspace-sync-metadata/types/object-record';
|
||||
import { CalendarChannelRepository } from 'src/modules/calendar/repositories/calendar-channel.repository';
|
||||
import { InjectWorkspaceRepository } from 'src/engine/twenty-orm/decorators/inject-workspace-repository.decorator';
|
||||
import { WorkspaceRepository } from 'src/engine/twenty-orm/repository/workspace.repository';
|
||||
import { CalendarChannelEventAssociationWorkspaceEntity } from 'src/modules/calendar/standard-objects/calendar-channel-event-association.workspace-entity';
|
||||
import {
|
||||
CalendarChannelWorkspaceEntity,
|
||||
@ -18,8 +19,8 @@ import { WorkspaceMemberWorkspaceEntity } from 'src/modules/workspace-member/sta
|
||||
@Injectable()
|
||||
export class CanAccessCalendarEventService {
|
||||
constructor(
|
||||
@InjectObjectMetadataRepository(CalendarChannelWorkspaceEntity)
|
||||
private readonly calendarChannelRepository: CalendarChannelRepository,
|
||||
@InjectWorkspaceRepository(CalendarChannelWorkspaceEntity)
|
||||
private readonly calendarChannelRepository: WorkspaceRepository<CalendarChannelWorkspaceEntity>,
|
||||
@InjectObjectMetadataRepository(ConnectedAccountWorkspaceEntity)
|
||||
private readonly connectedAccountRepository: ConnectedAccountRepository,
|
||||
@InjectObjectMetadataRepository(WorkspaceMemberWorkspaceEntity)
|
||||
@ -29,14 +30,17 @@ export class CanAccessCalendarEventService {
|
||||
public async canAccessCalendarEvent(
|
||||
userId: string,
|
||||
workspaceId: string,
|
||||
calendarChannelCalendarEventAssociations: ObjectRecord<CalendarChannelEventAssociationWorkspaceEntity>[],
|
||||
calendarChannelCalendarEventAssociations: CalendarChannelEventAssociationWorkspaceEntity[],
|
||||
) {
|
||||
const calendarChannels = await this.calendarChannelRepository.getByIds(
|
||||
calendarChannelCalendarEventAssociations.map(
|
||||
(association) => association.calendarChannelId,
|
||||
),
|
||||
workspaceId,
|
||||
);
|
||||
const calendarChannels = await this.calendarChannelRepository.find({
|
||||
where: {
|
||||
id: Any(
|
||||
calendarChannelCalendarEventAssociations.map(
|
||||
(association) => association.calendarChannel.id,
|
||||
),
|
||||
),
|
||||
},
|
||||
});
|
||||
|
||||
const calendarChannelsGroupByVisibility = groupBy(
|
||||
calendarChannels,
|
||||
@ -56,7 +60,7 @@ export class CanAccessCalendarEventService {
|
||||
|
||||
const calendarChannelsConnectedAccounts =
|
||||
await this.connectedAccountRepository.getByIds(
|
||||
calendarChannels.map((channel) => channel.connectedAccountId),
|
||||
calendarChannels.map((channel) => channel.connectedAccount.id),
|
||||
workspaceId,
|
||||
);
|
||||
|
||||
|
@ -8,12 +8,15 @@ import { CalendarChannelWorkspaceEntity } from 'src/modules/calendar/standard-ob
|
||||
import { CalendarEventFindManyPreQueryHook } from 'src/modules/calendar/query-hooks/calendar-event/calendar-event-find-many.pre-query.hook';
|
||||
import { CalendarEventFindOnePreQueryHook } from 'src/modules/calendar/query-hooks/calendar-event/calendar-event-find-one.pre-query-hook';
|
||||
import { CanAccessCalendarEventService } from 'src/modules/calendar/query-hooks/calendar-event/services/can-access-calendar-event.service';
|
||||
import { TwentyORMModule } from 'src/engine/twenty-orm/twenty-orm.module';
|
||||
|
||||
@Module({
|
||||
imports: [
|
||||
ObjectMetadataRepositoryModule.forFeature([
|
||||
TwentyORMModule.forFeature([
|
||||
CalendarChannelEventAssociationWorkspaceEntity,
|
||||
CalendarChannelWorkspaceEntity,
|
||||
]),
|
||||
ObjectMetadataRepositoryModule.forFeature([
|
||||
ConnectedAccountWorkspaceEntity,
|
||||
WorkspaceMemberWorkspaceEntity,
|
||||
]),
|
||||
|
@ -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,
|
||||
);
|
||||
}
|
||||
}
|
@ -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,
|
||||
);
|
||||
}
|
||||
}
|
@ -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;
|
||||
}
|
||||
}
|
@ -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,
|
||||
);
|
||||
}
|
||||
}
|
@ -1,13 +1,11 @@
|
||||
import { Module } from '@nestjs/common';
|
||||
|
||||
import { ObjectMetadataRepositoryModule } from 'src/engine/object-metadata-repository/object-metadata-repository.module';
|
||||
import { TwentyORMModule } from 'src/engine/twenty-orm/twenty-orm.module';
|
||||
import { CalendarEventCleanerService } from 'src/modules/calendar/services/calendar-event-cleaner/calendar-event-cleaner.service';
|
||||
import { CalendarEventWorkspaceEntity } from 'src/modules/calendar/standard-objects/calendar-event.workspace-entity';
|
||||
|
||||
@Module({
|
||||
imports: [
|
||||
ObjectMetadataRepositoryModule.forFeature([CalendarEventWorkspaceEntity]),
|
||||
],
|
||||
imports: [TwentyORMModule.forFeature([CalendarEventWorkspaceEntity])],
|
||||
providers: [CalendarEventCleanerService],
|
||||
exports: [CalendarEventCleanerService],
|
||||
})
|
||||
|
@ -1,27 +1,40 @@
|
||||
import { Injectable } from '@nestjs/common';
|
||||
|
||||
import { InjectObjectMetadataRepository } from 'src/engine/object-metadata-repository/object-metadata-repository.decorator';
|
||||
import { CalendarEventRepository } from 'src/modules/calendar/repositories/calendar-event.repository';
|
||||
import { Any, IsNull } from 'typeorm';
|
||||
|
||||
import { InjectWorkspaceRepository } from 'src/engine/twenty-orm/decorators/inject-workspace-repository.decorator';
|
||||
import { WorkspaceRepository } from 'src/engine/twenty-orm/repository/workspace.repository';
|
||||
import { CalendarEventWorkspaceEntity } from 'src/modules/calendar/standard-objects/calendar-event.workspace-entity';
|
||||
import { deleteUsingPagination } from 'src/modules/messaging/message-cleaner/utils/delete-using-pagination.util';
|
||||
|
||||
@Injectable()
|
||||
export class CalendarEventCleanerService {
|
||||
constructor(
|
||||
@InjectObjectMetadataRepository(CalendarEventWorkspaceEntity)
|
||||
private readonly calendarEventRepository: CalendarEventRepository,
|
||||
@InjectWorkspaceRepository(CalendarEventWorkspaceEntity)
|
||||
private readonly calendarEventRepository: WorkspaceRepository<CalendarEventWorkspaceEntity>,
|
||||
) {}
|
||||
|
||||
public async cleanWorkspaceCalendarEvents(workspaceId: string) {
|
||||
await deleteUsingPagination(
|
||||
workspaceId,
|
||||
500,
|
||||
this.calendarEventRepository.getNonAssociatedCalendarEventIdsPaginated.bind(
|
||||
this.calendarEventRepository,
|
||||
),
|
||||
this.calendarEventRepository.deleteByIds.bind(
|
||||
this.calendarEventRepository,
|
||||
),
|
||||
async (limit, offset) => {
|
||||
const nonAssociatedCalendarEvents =
|
||||
await this.calendarEventRepository.find({
|
||||
where: {
|
||||
calendarChannelEventAssociations: {
|
||||
id: IsNull(),
|
||||
},
|
||||
},
|
||||
take: limit,
|
||||
skip: offset,
|
||||
});
|
||||
|
||||
return nonAssociatedCalendarEvents.map(({ id }) => id);
|
||||
},
|
||||
async (ids) => {
|
||||
await this.calendarEventRepository.delete({ id: Any(ids) });
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -1,14 +1,17 @@
|
||||
import { Module } from '@nestjs/common';
|
||||
|
||||
import { ObjectMetadataRepositoryModule } from 'src/engine/object-metadata-repository/object-metadata-repository.module';
|
||||
import { TwentyORMModule } from 'src/engine/twenty-orm/twenty-orm.module';
|
||||
import { WorkspaceDataSourceModule } from 'src/engine/workspace-datasource/workspace-datasource.module';
|
||||
import { AddPersonIdAndWorkspaceMemberIdModule } from 'src/modules/calendar-messaging-participant/services/add-person-id-and-workspace-member-id/add-person-id-and-workspace-member-id.module';
|
||||
import { CalendarEventParticipantService } from 'src/modules/calendar/services/calendar-event-participant/calendar-event-participant.service';
|
||||
import { CalendarEventParticipantWorkspaceEntity } from 'src/modules/calendar/standard-objects/calendar-event-participant.workspace-entity';
|
||||
import { PersonWorkspaceEntity } from 'src/modules/person/standard-objects/person.workspace-entity';
|
||||
|
||||
@Module({
|
||||
imports: [
|
||||
WorkspaceDataSourceModule,
|
||||
TwentyORMModule.forFeature([CalendarEventParticipantWorkspaceEntity]),
|
||||
ObjectMetadataRepositoryModule.forFeature([PersonWorkspaceEntity]),
|
||||
AddPersonIdAndWorkspaceMemberIdModule,
|
||||
],
|
||||
|
@ -1,7 +1,7 @@
|
||||
import { Injectable } from '@nestjs/common';
|
||||
import { EventEmitter2 } from '@nestjs/event-emitter';
|
||||
|
||||
import { EntityManager } from 'typeorm';
|
||||
import { Any, EntityManager } from 'typeorm';
|
||||
|
||||
import { InjectObjectMetadataRepository } from 'src/engine/object-metadata-repository/object-metadata-repository.decorator';
|
||||
import { PersonRepository } from 'src/modules/person/repositories/person.repository';
|
||||
@ -9,17 +9,18 @@ import { PersonWorkspaceEntity } from 'src/modules/person/standard-objects/perso
|
||||
import { WorkspaceDataSourceService } from 'src/engine/workspace-datasource/workspace-datasource.service';
|
||||
import { getFlattenedValuesAndValuesStringForBatchRawQuery } from 'src/modules/calendar/utils/get-flattened-values-and-values-string-for-batch-raw-query.util';
|
||||
import { CalendarEventParticipant } from 'src/modules/calendar/types/calendar-event';
|
||||
import { CalendarEventParticipantRepository } from 'src/modules/calendar/repositories/calendar-event-participant.repository';
|
||||
import { CalendarEventParticipantWorkspaceEntity } from 'src/modules/calendar/standard-objects/calendar-event-participant.workspace-entity';
|
||||
import { AddPersonIdAndWorkspaceMemberIdService } from 'src/modules/calendar-messaging-participant/services/add-person-id-and-workspace-member-id/add-person-id-and-workspace-member-id.service';
|
||||
import { ObjectRecord } from 'src/engine/workspace-manager/workspace-sync-metadata/types/object-record';
|
||||
import { InjectWorkspaceRepository } from 'src/engine/twenty-orm/decorators/inject-workspace-repository.decorator';
|
||||
import { WorkspaceRepository } from 'src/engine/twenty-orm/repository/workspace.repository';
|
||||
|
||||
@Injectable()
|
||||
export class CalendarEventParticipantService {
|
||||
constructor(
|
||||
private readonly workspaceDataSourceService: WorkspaceDataSourceService,
|
||||
@InjectObjectMetadataRepository(CalendarEventParticipantWorkspaceEntity)
|
||||
private readonly calendarEventParticipantRepository: CalendarEventParticipantRepository,
|
||||
@InjectWorkspaceRepository(CalendarEventParticipantWorkspaceEntity)
|
||||
private readonly calendarEventParticipantRepository: WorkspaceRepository<CalendarEventParticipantWorkspaceEntity>,
|
||||
@InjectObjectMetadataRepository(PersonWorkspaceEntity)
|
||||
private readonly personRepository: PersonRepository,
|
||||
private readonly addPersonIdAndWorkspaceMemberIdService: AddPersonIdAndWorkspaceMemberIdService,
|
||||
@ -31,11 +32,11 @@ export class CalendarEventParticipantService {
|
||||
workspaceId: string,
|
||||
transactionManager?: EntityManager,
|
||||
): Promise<ObjectRecord<CalendarEventParticipantWorkspaceEntity>[]> {
|
||||
const participants =
|
||||
await this.calendarEventParticipantRepository.getByHandles(
|
||||
createdPeople.map((person) => person.email),
|
||||
workspaceId,
|
||||
);
|
||||
const participants = await this.calendarEventParticipantRepository.find({
|
||||
where: {
|
||||
handle: Any(createdPeople.map((person) => person.email)),
|
||||
},
|
||||
});
|
||||
|
||||
if (!participants) return [];
|
||||
|
||||
@ -132,33 +133,50 @@ export class CalendarEventParticipantService {
|
||||
workspaceMemberId?: string,
|
||||
) {
|
||||
const calendarEventParticipantsToUpdate =
|
||||
await this.calendarEventParticipantRepository.getByHandles(
|
||||
[email],
|
||||
workspaceId,
|
||||
);
|
||||
await this.calendarEventParticipantRepository.find({
|
||||
where: {
|
||||
handle: email,
|
||||
},
|
||||
});
|
||||
|
||||
const calendarEventParticipantIdsToUpdate =
|
||||
calendarEventParticipantsToUpdate.map((participant) => participant.id);
|
||||
|
||||
if (personId) {
|
||||
await this.calendarEventParticipantRepository.update(
|
||||
{
|
||||
id: Any(calendarEventParticipantIdsToUpdate),
|
||||
},
|
||||
{
|
||||
person: {
|
||||
id: personId,
|
||||
},
|
||||
},
|
||||
);
|
||||
|
||||
const updatedCalendarEventParticipants =
|
||||
await this.calendarEventParticipantRepository.updateParticipantsPersonIdAndReturn(
|
||||
calendarEventParticipantIdsToUpdate,
|
||||
personId,
|
||||
workspaceId,
|
||||
);
|
||||
await this.calendarEventParticipantRepository.find({
|
||||
where: {
|
||||
id: Any(calendarEventParticipantIdsToUpdate),
|
||||
},
|
||||
});
|
||||
|
||||
this.eventEmitter.emit(`calendarEventParticipant.matched`, {
|
||||
workspaceId,
|
||||
userId: null,
|
||||
workspaceMemberId: null,
|
||||
calendarEventParticipants: updatedCalendarEventParticipants,
|
||||
});
|
||||
}
|
||||
if (workspaceMemberId) {
|
||||
await this.calendarEventParticipantRepository.updateParticipantsWorkspaceMemberId(
|
||||
calendarEventParticipantIdsToUpdate,
|
||||
workspaceMemberId,
|
||||
workspaceId,
|
||||
await this.calendarEventParticipantRepository.update(
|
||||
{
|
||||
id: Any(calendarEventParticipantIdsToUpdate),
|
||||
},
|
||||
{
|
||||
workspaceMember: {
|
||||
id: workspaceMemberId,
|
||||
},
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
@ -170,15 +188,23 @@ export class CalendarEventParticipantService {
|
||||
workspaceMemberId?: string,
|
||||
) {
|
||||
if (personId) {
|
||||
await this.calendarEventParticipantRepository.removePersonIdByHandle(
|
||||
handle,
|
||||
workspaceId,
|
||||
await this.calendarEventParticipantRepository.update(
|
||||
{
|
||||
handle,
|
||||
},
|
||||
{
|
||||
person: null,
|
||||
},
|
||||
);
|
||||
}
|
||||
if (workspaceMemberId) {
|
||||
await this.calendarEventParticipantRepository.removeWorkspaceMemberIdByHandle(
|
||||
handle,
|
||||
workspaceId,
|
||||
await this.calendarEventParticipantRepository.update(
|
||||
{
|
||||
handle,
|
||||
},
|
||||
{
|
||||
workspaceMember: null,
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -3,6 +3,7 @@ import { TypeOrmModule } from '@nestjs/typeorm';
|
||||
|
||||
import { FeatureFlagEntity } from 'src/engine/core-modules/feature-flag/feature-flag.entity';
|
||||
import { ObjectMetadataRepositoryModule } from 'src/engine/object-metadata-repository/object-metadata-repository.module';
|
||||
import { TwentyORMModule } from 'src/engine/twenty-orm/twenty-orm.module';
|
||||
import { WorkspaceDataSourceModule } from 'src/engine/workspace-datasource/workspace-datasource.module';
|
||||
import { CalendarEventCleanerModule } from 'src/modules/calendar/services/calendar-event-cleaner/calendar-event-cleaner.module';
|
||||
import { CalendarEventParticipantModule } from 'src/modules/calendar/services/calendar-event-participant/calendar-event-participant.module';
|
||||
@ -20,12 +21,14 @@ import { WorkspaceMemberWorkspaceEntity } from 'src/modules/workspace-member/sta
|
||||
@Module({
|
||||
imports: [
|
||||
CalendarProvidersModule,
|
||||
ObjectMetadataRepositoryModule.forFeature([
|
||||
ConnectedAccountWorkspaceEntity,
|
||||
TwentyORMModule.forFeature([
|
||||
CalendarEventWorkspaceEntity,
|
||||
CalendarChannelWorkspaceEntity,
|
||||
CalendarChannelEventAssociationWorkspaceEntity,
|
||||
CalendarEventParticipantWorkspaceEntity,
|
||||
]),
|
||||
ObjectMetadataRepositoryModule.forFeature([
|
||||
ConnectedAccountWorkspaceEntity,
|
||||
BlocklistWorkspaceEntity,
|
||||
PersonWorkspaceEntity,
|
||||
WorkspaceMemberWorkspaceEntity,
|
||||
|
@ -2,7 +2,7 @@ import { Injectable, Logger } from '@nestjs/common';
|
||||
import { InjectRepository } from '@nestjs/typeorm';
|
||||
import { EventEmitter2 } from '@nestjs/event-emitter';
|
||||
|
||||
import { Repository } from 'typeorm';
|
||||
import { Any, Repository } from 'typeorm';
|
||||
import { calendar_v3 as calendarV3 } from 'googleapis';
|
||||
import { GaxiosError } from 'gaxios';
|
||||
|
||||
@ -13,12 +13,7 @@ import {
|
||||
FeatureFlagKeys,
|
||||
} from 'src/engine/core-modules/feature-flag/feature-flag.entity';
|
||||
import { GoogleCalendarClientProvider } from 'src/modules/calendar/services/providers/google-calendar/google-calendar.provider';
|
||||
import { CalendarChannelEventAssociationRepository } from 'src/modules/calendar/repositories/calendar-channel-event-association.repository';
|
||||
import { CalendarChannelRepository } from 'src/modules/calendar/repositories/calendar-channel.repository';
|
||||
import { WorkspaceDataSourceService } from 'src/engine/workspace-datasource/workspace-datasource.service';
|
||||
import { CalendarEventRepository } from 'src/modules/calendar/repositories/calendar-event.repository';
|
||||
import { formatGoogleCalendarEvent } from 'src/modules/calendar/utils/format-google-calendar-event.util';
|
||||
import { CalendarEventParticipantRepository } from 'src/modules/calendar/repositories/calendar-event-participant.repository';
|
||||
import { ConnectedAccountWorkspaceEntity } from 'src/modules/connected-account/standard-objects/connected-account.workspace-entity';
|
||||
import { InjectObjectMetadataRepository } from 'src/engine/object-metadata-repository/object-metadata-repository.decorator';
|
||||
import { CalendarEventWorkspaceEntity } from 'src/modules/calendar/standard-objects/calendar-event.workspace-entity';
|
||||
@ -28,7 +23,10 @@ import { CalendarEventParticipantWorkspaceEntity } from 'src/modules/calendar/st
|
||||
import { BlocklistWorkspaceEntity } from 'src/modules/connected-account/standard-objects/blocklist.workspace-entity';
|
||||
import { CalendarEventCleanerService } from 'src/modules/calendar/services/calendar-event-cleaner/calendar-event-cleaner.service';
|
||||
import { CalendarEventParticipantService } from 'src/modules/calendar/services/calendar-event-participant/calendar-event-participant.service';
|
||||
import { CalendarEventWithParticipants } from 'src/modules/calendar/types/calendar-event';
|
||||
import {
|
||||
CalendarEventParticipant,
|
||||
CalendarEventWithParticipants,
|
||||
} from 'src/modules/calendar/types/calendar-event';
|
||||
import { filterOutBlocklistedEvents } from 'src/modules/calendar/utils/filter-out-blocklisted-events.util';
|
||||
import { InjectMessageQueue } from 'src/engine/integrations/message-queue/decorators/message-queue.decorator';
|
||||
import { MessageQueue } from 'src/engine/integrations/message-queue/message-queue.constants';
|
||||
@ -37,7 +35,12 @@ import {
|
||||
CreateCompanyAndContactJob,
|
||||
CreateCompanyAndContactJobData,
|
||||
} from 'src/modules/connected-account/auto-companies-and-contacts-creation/jobs/create-company-and-contact.job';
|
||||
import { InjectWorkspaceRepository } from 'src/engine/twenty-orm/decorators/inject-workspace-repository.decorator';
|
||||
import { WorkspaceRepository } from 'src/engine/twenty-orm/repository/workspace.repository';
|
||||
import { ObjectRecord } from 'src/engine/workspace-manager/workspace-sync-metadata/types/object-record';
|
||||
import { isDefined } from 'src/utils/is-defined';
|
||||
import { WorkspaceDataSource } from 'src/engine/twenty-orm/datasource/workspace.datasource';
|
||||
import { InjectWorkspaceDatasource } from 'src/engine/twenty-orm/decorators/inject-workspace-datasource.decorator';
|
||||
|
||||
@Injectable()
|
||||
export class GoogleCalendarSyncService {
|
||||
@ -47,21 +50,20 @@ export class GoogleCalendarSyncService {
|
||||
private readonly googleCalendarClientProvider: GoogleCalendarClientProvider,
|
||||
@InjectObjectMetadataRepository(ConnectedAccountWorkspaceEntity)
|
||||
private readonly connectedAccountRepository: ConnectedAccountRepository,
|
||||
@InjectObjectMetadataRepository(CalendarEventWorkspaceEntity)
|
||||
private readonly calendarEventRepository: CalendarEventRepository,
|
||||
@InjectObjectMetadataRepository(CalendarChannelWorkspaceEntity)
|
||||
private readonly calendarChannelRepository: CalendarChannelRepository,
|
||||
@InjectObjectMetadataRepository(
|
||||
CalendarChannelEventAssociationWorkspaceEntity,
|
||||
)
|
||||
private readonly calendarChannelEventAssociationRepository: CalendarChannelEventAssociationRepository,
|
||||
@InjectObjectMetadataRepository(CalendarEventParticipantWorkspaceEntity)
|
||||
private readonly calendarEventParticipantsRepository: CalendarEventParticipantRepository,
|
||||
@InjectWorkspaceRepository(CalendarEventWorkspaceEntity)
|
||||
private readonly calendarEventRepository: WorkspaceRepository<CalendarEventWorkspaceEntity>,
|
||||
@InjectWorkspaceRepository(CalendarChannelWorkspaceEntity)
|
||||
private readonly calendarChannelRepository: WorkspaceRepository<CalendarChannelWorkspaceEntity>,
|
||||
@InjectWorkspaceRepository(CalendarChannelEventAssociationWorkspaceEntity)
|
||||
private readonly calendarChannelEventAssociationRepository: WorkspaceRepository<CalendarChannelEventAssociationWorkspaceEntity>,
|
||||
@InjectWorkspaceRepository(CalendarEventParticipantWorkspaceEntity)
|
||||
private readonly calendarEventParticipantsRepository: WorkspaceRepository<CalendarEventParticipantWorkspaceEntity>,
|
||||
@InjectObjectMetadataRepository(BlocklistWorkspaceEntity)
|
||||
private readonly blocklistRepository: BlocklistRepository,
|
||||
@InjectRepository(FeatureFlagEntity, 'core')
|
||||
private readonly featureFlagRepository: Repository<FeatureFlagEntity>,
|
||||
private readonly workspaceDataSourceService: WorkspaceDataSourceService,
|
||||
@InjectWorkspaceDatasource()
|
||||
private readonly workspaceDataSource: WorkspaceDataSource,
|
||||
private readonly calendarEventCleanerService: CalendarEventCleanerService,
|
||||
private readonly calendarEventParticipantsService: CalendarEventParticipantService,
|
||||
@InjectMessageQueue(MessageQueue.contactCreationQueue)
|
||||
@ -92,11 +94,11 @@ export class GoogleCalendarSyncService {
|
||||
);
|
||||
}
|
||||
|
||||
const calendarChannel =
|
||||
await this.calendarChannelRepository.getFirstByConnectedAccountId(
|
||||
connectedAccountId,
|
||||
workspaceId,
|
||||
);
|
||||
const calendarChannel = await this.calendarChannelRepository.findOneBy({
|
||||
connectedAccount: {
|
||||
id: connectedAccountId,
|
||||
},
|
||||
});
|
||||
|
||||
const syncToken = calendarChannel?.syncCursor || undefined;
|
||||
|
||||
@ -122,6 +124,12 @@ export class GoogleCalendarSyncService {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!workspaceMemberId) {
|
||||
throw new Error(
|
||||
`Workspace member ID is undefined for connected account ${connectedAccountId} in workspace ${workspaceId}`,
|
||||
);
|
||||
}
|
||||
|
||||
const blocklist = await this.getBlocklist(workspaceMemberId, workspaceId);
|
||||
|
||||
let filteredEvents = filterOutBlocklistedEvents(
|
||||
@ -143,11 +151,18 @@ export class GoogleCalendarSyncService {
|
||||
.filter((event) => event.status === 'cancelled')
|
||||
.map((event) => event.id as string);
|
||||
|
||||
const iCalUIDCalendarEventIdMap =
|
||||
await this.calendarEventRepository.getICalUIDCalendarEventIdMap(
|
||||
filteredEvents.map((calendarEvent) => calendarEvent.iCalUID as string),
|
||||
workspaceId,
|
||||
);
|
||||
const existingCalendarEvents = await this.calendarEventRepository.find({
|
||||
where: {
|
||||
iCalUID: Any(filteredEvents.map((event) => event.iCalUID as string)),
|
||||
},
|
||||
});
|
||||
|
||||
const iCalUIDCalendarEventIdMap = new Map(
|
||||
existingCalendarEvents.map((calendarEvent) => [
|
||||
calendarEvent.iCalUID,
|
||||
calendarEvent.id,
|
||||
]),
|
||||
);
|
||||
|
||||
const formattedEvents = filteredEvents.map((event) =>
|
||||
formatGoogleCalendarEvent(event, iCalUIDCalendarEventIdMap),
|
||||
@ -157,31 +172,34 @@ export class GoogleCalendarSyncService {
|
||||
|
||||
let startTime = Date.now();
|
||||
|
||||
const existingEvents = await this.calendarEventRepository.getByICalUIDs(
|
||||
formattedEvents.map((event) => event.iCalUID),
|
||||
workspaceId,
|
||||
const existingEventsICalUIDs = existingCalendarEvents.map(
|
||||
(calendarEvent) => calendarEvent.iCalUID,
|
||||
);
|
||||
|
||||
const existingEventsICalUIDs = existingEvents.map((event) => event.iCalUID);
|
||||
|
||||
let endTime = Date.now();
|
||||
|
||||
const eventsToSave = formattedEvents.filter(
|
||||
(event) => !existingEventsICalUIDs.includes(event.iCalUID),
|
||||
(calendarEvent) =>
|
||||
!existingEventsICalUIDs.includes(calendarEvent.iCalUID),
|
||||
);
|
||||
|
||||
const eventsToUpdate = formattedEvents.filter((event) =>
|
||||
existingEventsICalUIDs.includes(event.iCalUID),
|
||||
const eventsToUpdate = formattedEvents.filter((calendarEvent) =>
|
||||
existingEventsICalUIDs.includes(calendarEvent.iCalUID),
|
||||
);
|
||||
|
||||
startTime = Date.now();
|
||||
|
||||
const existingCalendarChannelEventAssociations =
|
||||
await this.calendarChannelEventAssociationRepository.getByEventExternalIdsAndCalendarChannelId(
|
||||
formattedEvents.map((event) => event.externalId),
|
||||
calendarChannelId,
|
||||
workspaceId,
|
||||
);
|
||||
await this.calendarChannelEventAssociationRepository.find({
|
||||
where: {
|
||||
eventExternalId: Any(
|
||||
formattedEvents.map((calendarEvent) => calendarEvent.id),
|
||||
),
|
||||
calendarChannel: {
|
||||
id: calendarChannelId,
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
endTime = Date.now();
|
||||
|
||||
@ -193,14 +211,14 @@ export class GoogleCalendarSyncService {
|
||||
|
||||
const calendarChannelEventAssociationsToSave = formattedEvents
|
||||
.filter(
|
||||
(event) =>
|
||||
(calendarEvent) =>
|
||||
!existingCalendarChannelEventAssociations.some(
|
||||
(association) => association.eventExternalId === event.id,
|
||||
(association) => association.eventExternalId === calendarEvent.id,
|
||||
),
|
||||
)
|
||||
.map((event) => ({
|
||||
calendarEventId: event.id,
|
||||
eventExternalId: event.externalId,
|
||||
.map((calendarEvent) => ({
|
||||
calendarEventId: calendarEvent.id,
|
||||
eventExternalId: calendarEvent.externalId,
|
||||
calendarChannelId,
|
||||
}));
|
||||
|
||||
@ -216,11 +234,12 @@ export class GoogleCalendarSyncService {
|
||||
|
||||
startTime = Date.now();
|
||||
|
||||
await this.calendarChannelEventAssociationRepository.deleteByEventExternalIdsAndCalendarChannelId(
|
||||
cancelledEventExternalIds,
|
||||
calendarChannelId,
|
||||
workspaceId,
|
||||
);
|
||||
await this.calendarChannelEventAssociationRepository.delete({
|
||||
eventExternalId: Any(cancelledEventExternalIds),
|
||||
calendarChannel: {
|
||||
id: calendarChannelId,
|
||||
},
|
||||
});
|
||||
|
||||
endTime = Date.now();
|
||||
|
||||
@ -257,10 +276,13 @@ export class GoogleCalendarSyncService {
|
||||
|
||||
startTime = Date.now();
|
||||
|
||||
await this.calendarChannelRepository.updateSyncCursor(
|
||||
nextSyncToken,
|
||||
calendarChannel.id,
|
||||
workspaceId,
|
||||
await this.calendarChannelRepository.update(
|
||||
{
|
||||
id: calendarChannel.id,
|
||||
},
|
||||
{
|
||||
syncCursor: nextSyncToken,
|
||||
},
|
||||
);
|
||||
|
||||
endTime = Date.now();
|
||||
@ -337,10 +359,13 @@ export class GoogleCalendarSyncService {
|
||||
throw error;
|
||||
}
|
||||
|
||||
await this.calendarChannelRepository.updateSyncCursor(
|
||||
null,
|
||||
connectedAccountId,
|
||||
workspaceId,
|
||||
await this.calendarChannelRepository.update(
|
||||
{
|
||||
id: connectedAccountId,
|
||||
},
|
||||
{
|
||||
syncCursor: '',
|
||||
},
|
||||
);
|
||||
|
||||
this.logger.log(
|
||||
@ -395,11 +420,6 @@ export class GoogleCalendarSyncService {
|
||||
calendarChannel: CalendarChannelWorkspaceEntity,
|
||||
workspaceId: string,
|
||||
): Promise<void> {
|
||||
const dataSourceMetadata =
|
||||
await this.workspaceDataSourceService.connectToWorkspaceDataSource(
|
||||
workspaceId,
|
||||
);
|
||||
|
||||
const participantsToSave = eventsToSave.flatMap(
|
||||
(event) => event.participants,
|
||||
);
|
||||
@ -415,103 +435,154 @@ export class GoogleCalendarSyncService {
|
||||
[];
|
||||
|
||||
try {
|
||||
await dataSourceMetadata?.transaction(async (transactionManager) => {
|
||||
startTime = Date.now();
|
||||
await this.workspaceDataSource?.transaction(
|
||||
async (transactionManager) => {
|
||||
startTime = Date.now();
|
||||
|
||||
await this.calendarEventRepository.saveCalendarEvents(
|
||||
eventsToSave,
|
||||
workspaceId,
|
||||
transactionManager,
|
||||
);
|
||||
await this.calendarEventRepository.save(
|
||||
eventsToSave,
|
||||
{},
|
||||
transactionManager,
|
||||
);
|
||||
|
||||
endTime = Date.now();
|
||||
endTime = Date.now();
|
||||
|
||||
this.logger.log(
|
||||
`google calendar sync for workspace ${workspaceId} and account ${
|
||||
connectedAccount.id
|
||||
}: saving ${eventsToSave.length} events in ${endTime - startTime}ms.`,
|
||||
);
|
||||
this.logger.log(
|
||||
`google calendar sync for workspace ${workspaceId} and account ${
|
||||
connectedAccount.id
|
||||
}: saving ${eventsToSave.length} events in ${
|
||||
endTime - startTime
|
||||
}ms.`,
|
||||
);
|
||||
|
||||
startTime = Date.now();
|
||||
startTime = Date.now();
|
||||
|
||||
await this.calendarEventRepository.updateCalendarEvents(
|
||||
eventsToUpdate,
|
||||
workspaceId,
|
||||
transactionManager,
|
||||
);
|
||||
await this.calendarChannelRepository.save(
|
||||
eventsToUpdate,
|
||||
{},
|
||||
transactionManager,
|
||||
);
|
||||
|
||||
endTime = Date.now();
|
||||
endTime = Date.now();
|
||||
|
||||
this.logger.log(
|
||||
`google calendar sync for workspace ${workspaceId} and account ${
|
||||
connectedAccount.id
|
||||
}: updating ${eventsToUpdate.length} events in ${
|
||||
endTime - startTime
|
||||
}ms.`,
|
||||
);
|
||||
this.logger.log(
|
||||
`google calendar sync for workspace ${workspaceId} and account ${
|
||||
connectedAccount.id
|
||||
}: updating ${eventsToUpdate.length} events in ${
|
||||
endTime - startTime
|
||||
}ms.`,
|
||||
);
|
||||
|
||||
startTime = Date.now();
|
||||
startTime = Date.now();
|
||||
|
||||
await this.calendarChannelEventAssociationRepository.saveCalendarChannelEventAssociations(
|
||||
calendarChannelEventAssociationsToSave,
|
||||
workspaceId,
|
||||
transactionManager,
|
||||
);
|
||||
await this.calendarChannelEventAssociationRepository.save(
|
||||
calendarChannelEventAssociationsToSave,
|
||||
{},
|
||||
transactionManager,
|
||||
);
|
||||
|
||||
endTime = Date.now();
|
||||
endTime = Date.now();
|
||||
|
||||
this.logger.log(
|
||||
`google calendar sync for workspace ${workspaceId} and account ${
|
||||
connectedAccount.id
|
||||
}: saving calendar channel event associations in ${
|
||||
endTime - startTime
|
||||
}ms.`,
|
||||
);
|
||||
this.logger.log(
|
||||
`google calendar sync for workspace ${workspaceId} and account ${
|
||||
connectedAccount.id
|
||||
}: saving calendar channel event associations in ${
|
||||
endTime - startTime
|
||||
}ms.`,
|
||||
);
|
||||
|
||||
startTime = Date.now();
|
||||
startTime = Date.now();
|
||||
|
||||
const newCalendarEventParticipants =
|
||||
await this.calendarEventParticipantsRepository.updateCalendarEventParticipantsAndReturnNewOnes(
|
||||
const existingCalendarEventParticipants =
|
||||
await this.calendarEventParticipantsRepository.find({
|
||||
where: {
|
||||
calendarEvent: {
|
||||
id: Any(
|
||||
participantsToUpdate
|
||||
.map((participant) => participant.calendarEventId)
|
||||
.filter(isDefined),
|
||||
),
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
const {
|
||||
calendarEventParticipantsToDelete,
|
||||
newCalendarEventParticipants,
|
||||
} = participantsToUpdate.reduce(
|
||||
(acc, calendarEventParticipant) => {
|
||||
const existingCalendarEventParticipant =
|
||||
existingCalendarEventParticipants.find(
|
||||
(existingCalendarEventParticipant) =>
|
||||
existingCalendarEventParticipant.handle ===
|
||||
calendarEventParticipant.handle,
|
||||
);
|
||||
|
||||
if (existingCalendarEventParticipant) {
|
||||
acc.calendarEventParticipantsToDelete.push(
|
||||
existingCalendarEventParticipant,
|
||||
);
|
||||
} else {
|
||||
acc.newCalendarEventParticipants.push(calendarEventParticipant);
|
||||
}
|
||||
|
||||
return acc;
|
||||
},
|
||||
{
|
||||
calendarEventParticipantsToDelete:
|
||||
[] as CalendarEventParticipantWorkspaceEntity[],
|
||||
newCalendarEventParticipants: [] as CalendarEventParticipant[],
|
||||
},
|
||||
);
|
||||
|
||||
await this.calendarEventParticipantsRepository.delete({
|
||||
id: Any(
|
||||
calendarEventParticipantsToDelete.map(
|
||||
(calendarEventParticipant) => calendarEventParticipant.id,
|
||||
),
|
||||
),
|
||||
});
|
||||
|
||||
await this.calendarEventParticipantsRepository.save(
|
||||
participantsToUpdate,
|
||||
workspaceId,
|
||||
transactionManager,
|
||||
);
|
||||
|
||||
endTime = Date.now();
|
||||
endTime = Date.now();
|
||||
|
||||
participantsToSave.push(...newCalendarEventParticipants);
|
||||
participantsToSave.push(...newCalendarEventParticipants);
|
||||
|
||||
this.logger.log(
|
||||
`google calendar sync for workspace ${workspaceId} and account ${
|
||||
connectedAccount.id
|
||||
}: updating participants in ${endTime - startTime}ms.`,
|
||||
);
|
||||
|
||||
startTime = Date.now();
|
||||
|
||||
const savedCalendarEventParticipants =
|
||||
await this.calendarEventParticipantsService.saveCalendarEventParticipants(
|
||||
participantsToSave,
|
||||
workspaceId,
|
||||
transactionManager,
|
||||
this.logger.log(
|
||||
`google calendar sync for workspace ${workspaceId} and account ${
|
||||
connectedAccount.id
|
||||
}: updating participants in ${endTime - startTime}ms.`,
|
||||
);
|
||||
|
||||
savedCalendarEventParticipantsToEmit.push(
|
||||
...savedCalendarEventParticipants,
|
||||
);
|
||||
startTime = Date.now();
|
||||
|
||||
endTime = Date.now();
|
||||
const savedCalendarEventParticipants =
|
||||
await this.calendarEventParticipantsService.saveCalendarEventParticipants(
|
||||
participantsToSave,
|
||||
workspaceId,
|
||||
transactionManager,
|
||||
);
|
||||
|
||||
this.logger.log(
|
||||
`google calendar sync for workspace ${workspaceId} and account ${
|
||||
connectedAccount.id
|
||||
}: saving participants in ${endTime - startTime}ms.`,
|
||||
);
|
||||
});
|
||||
savedCalendarEventParticipantsToEmit.push(
|
||||
...savedCalendarEventParticipants,
|
||||
);
|
||||
|
||||
endTime = Date.now();
|
||||
|
||||
this.logger.log(
|
||||
`google calendar sync for workspace ${workspaceId} and account ${
|
||||
connectedAccount.id
|
||||
}: saving participants in ${endTime - startTime}ms.`,
|
||||
);
|
||||
},
|
||||
);
|
||||
|
||||
this.eventEmitter.emit(`calendarEventParticipant.matched`, {
|
||||
workspaceId,
|
||||
userId: connectedAccount.accountOwnerId,
|
||||
workspaceMemberId: connectedAccount.accountOwnerId,
|
||||
calendarEventParticipants: savedCalendarEventParticipantsToEmit,
|
||||
});
|
||||
|
||||
|
@ -1,13 +1,11 @@
|
||||
import { Module } from '@nestjs/common';
|
||||
|
||||
import { ObjectMetadataRepositoryModule } from 'src/engine/object-metadata-repository/object-metadata-repository.module';
|
||||
import { TwentyORMModule } from 'src/engine/twenty-orm/twenty-orm.module';
|
||||
import { WorkspaceGoogleCalendarSyncService } from 'src/modules/calendar/services/workspace-google-calendar-sync/workspace-google-calendar-sync.service';
|
||||
import { CalendarChannelWorkspaceEntity } from 'src/modules/calendar/standard-objects/calendar-channel.workspace-entity';
|
||||
|
||||
@Module({
|
||||
imports: [
|
||||
ObjectMetadataRepositoryModule.forFeature([CalendarChannelWorkspaceEntity]),
|
||||
],
|
||||
imports: [TwentyORMModule.forFeature([CalendarChannelWorkspaceEntity])],
|
||||
providers: [WorkspaceGoogleCalendarSyncService],
|
||||
exports: [WorkspaceGoogleCalendarSyncService],
|
||||
})
|
||||
|
@ -3,19 +3,19 @@ import { Injectable } from '@nestjs/common';
|
||||
import { InjectMessageQueue } from 'src/engine/integrations/message-queue/decorators/message-queue.decorator';
|
||||
import { MessageQueue } from 'src/engine/integrations/message-queue/message-queue.constants';
|
||||
import { MessageQueueService } from 'src/engine/integrations/message-queue/services/message-queue.service';
|
||||
import { InjectObjectMetadataRepository } from 'src/engine/object-metadata-repository/object-metadata-repository.decorator';
|
||||
import { InjectWorkspaceRepository } from 'src/engine/twenty-orm/decorators/inject-workspace-repository.decorator';
|
||||
import { WorkspaceRepository } from 'src/engine/twenty-orm/repository/workspace.repository';
|
||||
import {
|
||||
GoogleCalendarSyncJobData,
|
||||
GoogleCalendarSyncJob,
|
||||
} from 'src/modules/calendar/jobs/google-calendar-sync.job';
|
||||
import { CalendarChannelRepository } from 'src/modules/calendar/repositories/calendar-channel.repository';
|
||||
import { CalendarChannelWorkspaceEntity } from 'src/modules/calendar/standard-objects/calendar-channel.workspace-entity';
|
||||
|
||||
@Injectable()
|
||||
export class WorkspaceGoogleCalendarSyncService {
|
||||
constructor(
|
||||
@InjectObjectMetadataRepository(CalendarChannelWorkspaceEntity)
|
||||
private readonly calendarChannelRepository: CalendarChannelRepository,
|
||||
@InjectWorkspaceRepository(CalendarChannelWorkspaceEntity)
|
||||
private readonly calendarChannelRepository: WorkspaceRepository<CalendarChannelWorkspaceEntity>,
|
||||
@InjectMessageQueue(MessageQueue.calendarQueue)
|
||||
private readonly messageQueueService: MessageQueueService,
|
||||
) {}
|
||||
@ -23,8 +23,7 @@ export class WorkspaceGoogleCalendarSyncService {
|
||||
public async startWorkspaceGoogleCalendarSync(
|
||||
workspaceId: string,
|
||||
): Promise<void> {
|
||||
const calendarChannels =
|
||||
await this.calendarChannelRepository.getAll(workspaceId);
|
||||
const calendarChannels = await this.calendarChannelRepository.find({});
|
||||
|
||||
for (const calendarChannel of calendarChannels) {
|
||||
if (!calendarChannel?.isSyncEnabled) {
|
||||
@ -35,7 +34,7 @@ export class WorkspaceGoogleCalendarSyncService {
|
||||
GoogleCalendarSyncJob.name,
|
||||
{
|
||||
workspaceId,
|
||||
connectedAccountId: calendarChannel.connectedAccountId,
|
||||
connectedAccountId: calendarChannel.connectedAccount.id,
|
||||
},
|
||||
{
|
||||
retryLimit: 2,
|
||||
|
@ -120,7 +120,7 @@ export class CalendarEventParticipantWorkspaceEntity extends BaseWorkspaceEntity
|
||||
inverseSideFieldKey: 'calendarEventParticipants',
|
||||
})
|
||||
@WorkspaceIsNullable()
|
||||
person: Relation<PersonWorkspaceEntity>;
|
||||
person: Relation<PersonWorkspaceEntity> | null;
|
||||
|
||||
@WorkspaceRelation({
|
||||
standardId: CALENDAR_EVENT_PARTICIPANT_STANDARD_FIELD_IDS.workspaceMember,
|
||||
@ -133,5 +133,5 @@ export class CalendarEventParticipantWorkspaceEntity extends BaseWorkspaceEntity
|
||||
inverseSideFieldKey: 'calendarEventParticipants',
|
||||
})
|
||||
@WorkspaceIsNullable()
|
||||
workspaceMember: Relation<WorkspaceMemberWorkspaceEntity>;
|
||||
workspaceMember: Relation<WorkspaceMemberWorkspaceEntity> | null;
|
||||
}
|
||||
|
@ -68,7 +68,7 @@ export class CompanyWorkspaceEntity extends BaseWorkspaceEntity {
|
||||
icon: 'IconUsers',
|
||||
})
|
||||
@WorkspaceIsNullable()
|
||||
employees: number;
|
||||
employees: number | null;
|
||||
|
||||
@WorkspaceField({
|
||||
standardId: COMPANY_STANDARD_FIELD_IDS.linkedinLink,
|
||||
@ -78,7 +78,7 @@ export class CompanyWorkspaceEntity extends BaseWorkspaceEntity {
|
||||
icon: 'IconBrandLinkedin',
|
||||
})
|
||||
@WorkspaceIsNullable()
|
||||
linkedinLink: LinkMetadata;
|
||||
linkedinLink: LinkMetadata | null;
|
||||
|
||||
@WorkspaceField({
|
||||
standardId: COMPANY_STANDARD_FIELD_IDS.xLink,
|
||||
@ -88,7 +88,7 @@ export class CompanyWorkspaceEntity extends BaseWorkspaceEntity {
|
||||
icon: 'IconBrandX',
|
||||
})
|
||||
@WorkspaceIsNullable()
|
||||
xLink: LinkMetadata;
|
||||
xLink: LinkMetadata | null;
|
||||
|
||||
@WorkspaceField({
|
||||
standardId: COMPANY_STANDARD_FIELD_IDS.annualRecurringRevenue,
|
||||
@ -99,7 +99,7 @@ export class CompanyWorkspaceEntity extends BaseWorkspaceEntity {
|
||||
icon: 'IconMoneybag',
|
||||
})
|
||||
@WorkspaceIsNullable()
|
||||
annualRecurringRevenue: CurrencyMetadata;
|
||||
annualRecurringRevenue: CurrencyMetadata | null;
|
||||
|
||||
@WorkspaceField({
|
||||
standardId: COMPANY_STANDARD_FIELD_IDS.idealCustomerProfile,
|
||||
@ -121,7 +121,7 @@ export class CompanyWorkspaceEntity extends BaseWorkspaceEntity {
|
||||
})
|
||||
@WorkspaceIsSystem()
|
||||
@WorkspaceIsNullable()
|
||||
position: number;
|
||||
position: number | null;
|
||||
|
||||
// Relations
|
||||
@WorkspaceRelation({
|
||||
@ -149,7 +149,7 @@ export class CompanyWorkspaceEntity extends BaseWorkspaceEntity {
|
||||
onDelete: RelationOnDeleteAction.SET_NULL,
|
||||
})
|
||||
@WorkspaceIsNullable()
|
||||
accountOwner: Relation<WorkspaceMemberWorkspaceEntity>;
|
||||
accountOwner: Relation<WorkspaceMemberWorkspaceEntity> | null;
|
||||
|
||||
@WorkspaceRelation({
|
||||
standardId: COMPANY_STANDARD_FIELD_IDS.activityTargets,
|
||||
|
@ -8,7 +8,6 @@ import { ObjectMetadataRepositoryModule } from 'src/engine/object-metadata-repos
|
||||
import { PersonWorkspaceEntity } from 'src/modules/person/standard-objects/person.workspace-entity';
|
||||
import { WorkspaceMemberWorkspaceEntity } from 'src/modules/workspace-member/standard-objects/workspace-member.workspace-entity';
|
||||
import { WorkspaceDataSourceModule } from 'src/engine/workspace-datasource/workspace-datasource.module';
|
||||
import { CalendarEventParticipantWorkspaceEntity } from 'src/modules/calendar/standard-objects/calendar-event-participant.workspace-entity';
|
||||
import { CalendarEventParticipantModule } from 'src/modules/calendar/services/calendar-event-participant/calendar-event-participant.module';
|
||||
import { FeatureFlagEntity } from 'src/engine/core-modules/feature-flag/feature-flag.entity';
|
||||
import { MessagingCommonModule } from 'src/modules/messaging/common/messaging-common.module';
|
||||
@ -20,7 +19,6 @@ import { MessagingCommonModule } from 'src/modules/messaging/common/messaging-co
|
||||
ObjectMetadataRepositoryModule.forFeature([
|
||||
PersonWorkspaceEntity,
|
||||
WorkspaceMemberWorkspaceEntity,
|
||||
CalendarEventParticipantWorkspaceEntity,
|
||||
]),
|
||||
MessagingCommonModule,
|
||||
WorkspaceDataSourceModule,
|
||||
|
@ -1,13 +1,12 @@
|
||||
import { Process } from 'src/engine/integrations/message-queue/decorators/process.decorator';
|
||||
import { Processor } from 'src/engine/integrations/message-queue/decorators/processor.decorator';
|
||||
import { MessageQueue } from 'src/engine/integrations/message-queue/message-queue.constants';
|
||||
import { ObjectRecord } from 'src/engine/workspace-manager/workspace-sync-metadata/types/object-record';
|
||||
import { CreateCompanyAndContactService } from 'src/modules/connected-account/auto-companies-and-contacts-creation/services/create-company-and-contact.service';
|
||||
import { ConnectedAccountWorkspaceEntity } from 'src/modules/connected-account/standard-objects/connected-account.workspace-entity';
|
||||
|
||||
export type CreateCompanyAndContactJobData = {
|
||||
workspaceId: string;
|
||||
connectedAccount: ObjectRecord<ConnectedAccountWorkspaceEntity>;
|
||||
connectedAccount: ConnectedAccountWorkspaceEntity;
|
||||
contactsToCreate: {
|
||||
displayName: string;
|
||||
handle: string;
|
||||
|
@ -15,14 +15,15 @@ import { PersonWorkspaceEntity } from 'src/modules/person/standard-objects/perso
|
||||
import { WorkspaceMemberWorkspaceEntity } from 'src/modules/workspace-member/standard-objects/workspace-member.workspace-entity';
|
||||
import { getUniqueContactsAndHandles } from 'src/modules/connected-account/auto-companies-and-contacts-creation/utils/get-unique-contacts-and-handles.util';
|
||||
import { Contacts } from 'src/modules/connected-account/auto-companies-and-contacts-creation/types/contact.type';
|
||||
import { WorkspaceDataSourceService } from 'src/engine/workspace-datasource/workspace-datasource.service';
|
||||
import { CalendarEventParticipantService } from 'src/modules/calendar/services/calendar-event-participant/calendar-event-participant.service';
|
||||
import { filterOutContactsFromCompanyOrWorkspace } from 'src/modules/connected-account/auto-companies-and-contacts-creation/utils/filter-out-contacts-from-company-or-workspace.util';
|
||||
import { ObjectRecord } from 'src/engine/workspace-manager/workspace-sync-metadata/types/object-record';
|
||||
import { MessagingMessageParticipantService } from 'src/modules/messaging/common/services/messaging-message-participant.service';
|
||||
import { ConnectedAccountWorkspaceEntity } from 'src/modules/connected-account/standard-objects/connected-account.workspace-entity';
|
||||
import { MessageParticipantWorkspaceEntity } from 'src/modules/messaging/common/standard-objects/message-participant.workspace-entity';
|
||||
import { CalendarEventParticipantWorkspaceEntity } from 'src/modules/calendar/standard-objects/calendar-event-participant.workspace-entity';
|
||||
import { WorkspaceDataSource } from 'src/engine/twenty-orm/datasource/workspace.datasource';
|
||||
import { InjectWorkspaceDatasource } from 'src/engine/twenty-orm/decorators/inject-workspace-datasource.decorator';
|
||||
import { ConnectedAccountWorkspaceEntity } from 'src/modules/connected-account/standard-objects/connected-account.workspace-entity';
|
||||
|
||||
@Injectable()
|
||||
export class CreateCompanyAndContactService {
|
||||
@ -33,7 +34,8 @@ export class CreateCompanyAndContactService {
|
||||
private readonly personRepository: PersonRepository,
|
||||
@InjectObjectMetadataRepository(WorkspaceMemberWorkspaceEntity)
|
||||
private readonly workspaceMemberRepository: WorkspaceMemberRepository,
|
||||
private readonly workspaceDataSourceService: WorkspaceDataSourceService,
|
||||
@InjectWorkspaceDatasource()
|
||||
private readonly workspaceDataSource: WorkspaceDataSource,
|
||||
private readonly messageParticipantService: MessagingMessageParticipantService,
|
||||
private readonly calendarEventParticipantService: CalendarEventParticipantService,
|
||||
private readonly eventEmitter: EventEmitter2,
|
||||
@ -130,21 +132,16 @@ export class CreateCompanyAndContactService {
|
||||
}
|
||||
|
||||
async createCompaniesAndContactsAndUpdateParticipants(
|
||||
connectedAccount: ObjectRecord<ConnectedAccountWorkspaceEntity>,
|
||||
connectedAccount: ConnectedAccountWorkspaceEntity,
|
||||
contactsToCreate: Contacts,
|
||||
workspaceId: string,
|
||||
) {
|
||||
const { dataSource: workspaceDataSource } =
|
||||
await this.workspaceDataSourceService.connectedToWorkspaceDataSourceAndReturnMetadata(
|
||||
workspaceId,
|
||||
);
|
||||
|
||||
let updatedMessageParticipants: ObjectRecord<MessageParticipantWorkspaceEntity>[] =
|
||||
[];
|
||||
let updatedCalendarEventParticipants: ObjectRecord<CalendarEventParticipantWorkspaceEntity>[] =
|
||||
[];
|
||||
|
||||
await workspaceDataSource?.transaction(
|
||||
await this.workspaceDataSource?.transaction(
|
||||
async (transactionManager: EntityManager) => {
|
||||
const createdPeople = await this.createCompaniesAndPeople(
|
||||
connectedAccount.handle,
|
||||
@ -171,13 +168,13 @@ export class CreateCompanyAndContactService {
|
||||
|
||||
this.eventEmitter.emit(`messageParticipant.matched`, {
|
||||
workspaceId,
|
||||
userId: connectedAccount.accountOwnerId,
|
||||
workspaceMemberId: connectedAccount.accountOwnerId,
|
||||
messageParticipants: updatedMessageParticipants,
|
||||
});
|
||||
|
||||
this.eventEmitter.emit(`calendarEventParticipant.matched`, {
|
||||
workspaceId,
|
||||
userId: connectedAccount.accountOwnerId,
|
||||
workspaceMemberId: connectedAccount.accountOwnerId,
|
||||
calendarEventParticipants: updatedCalendarEventParticipants,
|
||||
});
|
||||
}
|
||||
|
@ -80,6 +80,12 @@ export class GoogleAPIRefreshAccessTokenService {
|
||||
workspaceId,
|
||||
);
|
||||
|
||||
if (!messageChannel.connectedAccountId) {
|
||||
throw new Error(
|
||||
`No connected account ID found for message channel ${messageChannel.id} in workspace ${workspaceId}`,
|
||||
);
|
||||
}
|
||||
|
||||
await this.connectedAccountRepository.updateAuthFailedAt(
|
||||
messageChannel.connectedAccountId,
|
||||
workspaceId,
|
||||
|
@ -86,7 +86,7 @@ export class ConnectedAccountWorkspaceEntity extends BaseWorkspaceEntity {
|
||||
icon: 'IconX',
|
||||
})
|
||||
@WorkspaceIsNullable()
|
||||
authFailedAt: Date;
|
||||
authFailedAt: Date | null;
|
||||
|
||||
@WorkspaceRelation({
|
||||
standardId: CONNECTED_ACCOUNT_STANDARD_FIELD_IDS.accountOwner,
|
||||
@ -100,6 +100,8 @@ export class ConnectedAccountWorkspaceEntity extends BaseWorkspaceEntity {
|
||||
})
|
||||
accountOwner: Relation<WorkspaceMemberWorkspaceEntity>;
|
||||
|
||||
accountOwnerId: string;
|
||||
|
||||
@WorkspaceRelation({
|
||||
standardId: CONNECTED_ACCOUNT_STANDARD_FIELD_IDS.messageChannels,
|
||||
type: RelationMetadataType.ONE_TO_MANY,
|
||||
|
@ -63,7 +63,7 @@ export class FavoriteWorkspaceEntity extends BaseWorkspaceEntity {
|
||||
inverseSideFieldKey: 'favorites',
|
||||
})
|
||||
@WorkspaceIsNullable()
|
||||
person: Relation<PersonWorkspaceEntity>;
|
||||
person: Relation<PersonWorkspaceEntity> | null;
|
||||
|
||||
@WorkspaceRelation({
|
||||
standardId: FAVORITE_STANDARD_FIELD_IDS.company,
|
||||
@ -76,7 +76,7 @@ export class FavoriteWorkspaceEntity extends BaseWorkspaceEntity {
|
||||
inverseSideFieldKey: 'favorites',
|
||||
})
|
||||
@WorkspaceIsNullable()
|
||||
company: Relation<CompanyWorkspaceEntity>;
|
||||
company: Relation<CompanyWorkspaceEntity> | null;
|
||||
|
||||
@WorkspaceRelation({
|
||||
standardId: FAVORITE_STANDARD_FIELD_IDS.opportunity,
|
||||
@ -89,7 +89,7 @@ export class FavoriteWorkspaceEntity extends BaseWorkspaceEntity {
|
||||
inverseSideFieldKey: 'favorites',
|
||||
})
|
||||
@WorkspaceIsNullable()
|
||||
opportunity: Relation<OpportunityWorkspaceEntity>;
|
||||
opportunity: Relation<OpportunityWorkspaceEntity> | null;
|
||||
|
||||
@WorkspaceDynamicRelation({
|
||||
type: RelationMetadataType.MANY_TO_ONE,
|
||||
|
@ -56,6 +56,12 @@ export class BlocklistItemDeleteMessagesJob {
|
||||
`Deleting messages from ${handle} in workspace ${workspaceId} for workspace member ${workspaceMemberId}`,
|
||||
);
|
||||
|
||||
if (!workspaceMemberId) {
|
||||
throw new Error(
|
||||
`Workspace member ID is not defined for blocklist item ${blocklistItemId} in workspace ${workspaceId}`,
|
||||
);
|
||||
}
|
||||
|
||||
const messageChannels =
|
||||
await this.messageChannelRepository.getIdsByWorkspaceMemberId(
|
||||
workspaceMemberId,
|
||||
|
@ -9,6 +9,7 @@ import { MessageChannelRepository } from 'src/modules/messaging/common/repositor
|
||||
import { MessageChannelWorkspaceEntity } from 'src/modules/messaging/common/standard-objects/message-channel.workspace-entity';
|
||||
import { WorkspaceMemberRepository } from 'src/modules/workspace-member/repositories/workspace-member.repository';
|
||||
import { WorkspaceMemberWorkspaceEntity } from 'src/modules/workspace-member/standard-objects/workspace-member.workspace-entity';
|
||||
import { isDefined } from 'src/utils/is-defined';
|
||||
|
||||
export class CanAccessMessageThreadService {
|
||||
constructor(
|
||||
@ -46,7 +47,9 @@ export class CanAccessMessageThreadService {
|
||||
|
||||
const messageChannelsConnectedAccounts =
|
||||
await this.connectedAccountRepository.getByIds(
|
||||
messageChannels.map((channel) => channel.connectedAccountId),
|
||||
messageChannels
|
||||
.map((channel) => channel.connectedAccountId)
|
||||
.filter(isDefined),
|
||||
workspaceId,
|
||||
);
|
||||
|
||||
|
@ -211,6 +211,12 @@ export class MessagingErrorHandlingService {
|
||||
workspaceId,
|
||||
);
|
||||
|
||||
if (!messageChannel.connectedAccountId) {
|
||||
throw new Error(
|
||||
`Connected account ID is not defined for message channel ${messageChannel.id} in workspace ${workspaceId}`,
|
||||
);
|
||||
}
|
||||
|
||||
await this.connectedAccountRepository.updateAuthFailedAt(
|
||||
messageChannel.connectedAccountId,
|
||||
workspaceId,
|
||||
|
@ -149,7 +149,7 @@ export class MessagingMessageParticipantService {
|
||||
|
||||
this.eventEmitter.emit(`messageParticipant.matched`, {
|
||||
workspaceId,
|
||||
userId: null,
|
||||
workspaceMemberId: null,
|
||||
messageParticipants: updatedMessageParticipants,
|
||||
});
|
||||
}
|
||||
|
@ -107,7 +107,7 @@ export class MessagingSaveMessagesAndEnqueueContactCreationService {
|
||||
|
||||
this.eventEmitter.emit(`messageParticipant.matched`, {
|
||||
workspaceId,
|
||||
userId: connectedAccount.accountOwnerId,
|
||||
workspaceMemberId: connectedAccount.accountOwnerId,
|
||||
messageParticipants: savedMessageParticipants,
|
||||
});
|
||||
|
||||
|
@ -35,7 +35,7 @@ export class MessageChannelMessageAssociationWorkspaceEntity extends BaseWorkspa
|
||||
icon: 'IconHash',
|
||||
})
|
||||
@WorkspaceIsNullable()
|
||||
messageExternalId: string;
|
||||
messageExternalId: string | null;
|
||||
|
||||
@WorkspaceField({
|
||||
standardId:
|
||||
@ -46,7 +46,7 @@ export class MessageChannelMessageAssociationWorkspaceEntity extends BaseWorkspa
|
||||
icon: 'IconHash',
|
||||
})
|
||||
@WorkspaceIsNullable()
|
||||
messageThreadExternalId: string;
|
||||
messageThreadExternalId: string | null;
|
||||
|
||||
@WorkspaceRelation({
|
||||
standardId:
|
||||
@ -60,7 +60,7 @@ export class MessageChannelMessageAssociationWorkspaceEntity extends BaseWorkspa
|
||||
inverseSideFieldKey: 'messageChannelMessageAssociations',
|
||||
})
|
||||
@WorkspaceIsNullable()
|
||||
messageChannel: Relation<MessageChannelWorkspaceEntity>;
|
||||
messageChannel: Relation<MessageChannelWorkspaceEntity> | null;
|
||||
|
||||
@WorkspaceRelation({
|
||||
standardId: MESSAGE_CHANNEL_MESSAGE_ASSOCIATION_STANDARD_FIELD_IDS.message,
|
||||
@ -73,7 +73,7 @@ export class MessageChannelMessageAssociationWorkspaceEntity extends BaseWorkspa
|
||||
inverseSideFieldKey: 'messageChannelMessageAssociations',
|
||||
})
|
||||
@WorkspaceIsNullable()
|
||||
message: Relation<MessageWorkspaceEntity>;
|
||||
message: Relation<MessageWorkspaceEntity> | null;
|
||||
|
||||
@WorkspaceRelation({
|
||||
standardId:
|
||||
@ -87,5 +87,5 @@ export class MessageChannelMessageAssociationWorkspaceEntity extends BaseWorkspa
|
||||
inverseSideFieldKey: 'messageChannelMessageAssociations',
|
||||
})
|
||||
@WorkspaceIsNullable()
|
||||
messageThread: Relation<MessageThreadWorkspaceEntity>;
|
||||
messageThread: Relation<MessageThreadWorkspaceEntity> | null;
|
||||
}
|
||||
|
@ -162,7 +162,7 @@ export class MessageChannelWorkspaceEntity extends BaseWorkspaceEntity {
|
||||
icon: 'IconHistory',
|
||||
})
|
||||
@WorkspaceIsNullable()
|
||||
syncedAt: string;
|
||||
syncedAt: string | null;
|
||||
|
||||
@WorkspaceField({
|
||||
standardId: MESSAGE_CHANNEL_STANDARD_FIELD_IDS.syncStatus,
|
||||
@ -224,7 +224,7 @@ export class MessageChannelWorkspaceEntity extends BaseWorkspaceEntity {
|
||||
],
|
||||
})
|
||||
@WorkspaceIsNullable()
|
||||
syncStatus: MessageChannelSyncStatus;
|
||||
syncStatus: MessageChannelSyncStatus | null;
|
||||
|
||||
@WorkspaceField({
|
||||
standardId: MESSAGE_CHANNEL_STANDARD_FIELD_IDS.syncStage,
|
||||
@ -282,7 +282,7 @@ export class MessageChannelWorkspaceEntity extends BaseWorkspaceEntity {
|
||||
icon: 'IconHistory',
|
||||
})
|
||||
@WorkspaceIsNullable()
|
||||
syncStageStartedAt: string;
|
||||
syncStageStartedAt: string | null;
|
||||
|
||||
@WorkspaceField({
|
||||
standardId: MESSAGE_CHANNEL_STANDARD_FIELD_IDS.throttleFailureCount,
|
||||
|
@ -83,7 +83,7 @@ export class MessageParticipantWorkspaceEntity extends BaseWorkspaceEntity {
|
||||
inverseSideFieldKey: 'messageParticipants',
|
||||
})
|
||||
@WorkspaceIsNullable()
|
||||
person: Relation<PersonWorkspaceEntity>;
|
||||
person: Relation<PersonWorkspaceEntity> | null;
|
||||
|
||||
@WorkspaceRelation({
|
||||
standardId: MESSAGE_PARTICIPANT_STANDARD_FIELD_IDS.workspaceMember,
|
||||
@ -96,5 +96,5 @@ export class MessageParticipantWorkspaceEntity extends BaseWorkspaceEntity {
|
||||
inverseSideFieldKey: 'messageParticipants',
|
||||
})
|
||||
@WorkspaceIsNullable()
|
||||
workspaceMember: Relation<WorkspaceMemberWorkspaceEntity>;
|
||||
workspaceMember: Relation<WorkspaceMemberWorkspaceEntity> | null;
|
||||
}
|
||||
|
@ -78,7 +78,7 @@ export class MessageWorkspaceEntity extends BaseWorkspaceEntity {
|
||||
icon: 'IconCalendar',
|
||||
})
|
||||
@WorkspaceIsNullable()
|
||||
receivedAt: string;
|
||||
receivedAt: string | null;
|
||||
|
||||
@WorkspaceRelation({
|
||||
standardId: MESSAGE_STANDARD_FIELD_IDS.messageThread,
|
||||
@ -92,7 +92,7 @@ export class MessageWorkspaceEntity extends BaseWorkspaceEntity {
|
||||
onDelete: RelationOnDeleteAction.CASCADE,
|
||||
})
|
||||
@WorkspaceIsNullable()
|
||||
messageThread: Relation<MessageThreadWorkspaceEntity>;
|
||||
messageThread: Relation<MessageThreadWorkspaceEntity> | null;
|
||||
|
||||
@WorkspaceRelation({
|
||||
standardId: MESSAGE_STANDARD_FIELD_IDS.messageParticipants,
|
||||
|
@ -1,4 +1,4 @@
|
||||
import { Logger } from '@nestjs/common';
|
||||
import { Logger, Scope } from '@nestjs/common';
|
||||
|
||||
import { Process } from 'src/engine/integrations/message-queue/decorators/process.decorator';
|
||||
import { Processor } from 'src/engine/integrations/message-queue/decorators/processor.decorator';
|
||||
@ -21,7 +21,10 @@ export type MessagingMessageListFetchJobData = {
|
||||
workspaceId: string;
|
||||
};
|
||||
|
||||
@Processor(MessageQueue.messagingQueue)
|
||||
@Processor({
|
||||
queueName: MessageQueue.messagingQueue,
|
||||
scope: Scope.REQUEST,
|
||||
})
|
||||
export class MessagingMessageListFetchJob {
|
||||
private readonly logger = new Logger(MessagingMessageListFetchJob.name);
|
||||
|
||||
|
@ -1,3 +1,5 @@
|
||||
import { Scope } from '@nestjs/common';
|
||||
|
||||
import { Process } from 'src/engine/integrations/message-queue/decorators/process.decorator';
|
||||
import { Processor } from 'src/engine/integrations/message-queue/decorators/processor.decorator';
|
||||
import { MessageQueue } from 'src/engine/integrations/message-queue/message-queue.constants';
|
||||
@ -18,7 +20,10 @@ export type MessagingMessagesImportJobData = {
|
||||
workspaceId: string;
|
||||
};
|
||||
|
||||
@Processor(MessageQueue.messagingQueue)
|
||||
@Processor({
|
||||
queueName: MessageQueue.messagingQueue,
|
||||
scope: Scope.REQUEST,
|
||||
})
|
||||
export class MessagingMessagesImportJob {
|
||||
constructor(
|
||||
@InjectObjectMetadataRepository(ConnectedAccountWorkspaceEntity)
|
||||
|
@ -25,7 +25,7 @@ export class MessageParticipantListener {
|
||||
@OnEvent('messageParticipant.matched')
|
||||
public async handleMessageParticipantMatched(payload: {
|
||||
workspaceId: string;
|
||||
userId: string;
|
||||
workspaceMemberId: string;
|
||||
messageParticipants: ObjectRecord<MessageParticipantWorkspaceEntity>[];
|
||||
}): Promise<void> {
|
||||
const messageParticipants = payload.messageParticipants ?? [];
|
||||
@ -60,7 +60,7 @@ export class MessageParticipantListener {
|
||||
properties: null,
|
||||
objectName: 'message',
|
||||
recordId: participant.personId,
|
||||
workspaceMemberId: payload.userId,
|
||||
workspaceMemberId: payload.workspaceMemberId,
|
||||
workspaceId: payload.workspaceId,
|
||||
linkedObjectMetadataId: messageObjectMetadata.id,
|
||||
linkedRecordId: participant.messageId,
|
||||
|
@ -67,6 +67,9 @@ export class MessagingMessageChannelSyncStatusMonitoringCronJob {
|
||||
await this.messageChannelRepository.getAll(workspaceId);
|
||||
|
||||
for (const messageChannel of messageChannels) {
|
||||
if (!messageChannel.syncStatus) {
|
||||
continue;
|
||||
}
|
||||
await this.messagingTelemetryService.track({
|
||||
eventName: `message_channel.monitoring.sync_status.${snakeCase(
|
||||
messageChannel.syncStatus,
|
||||
|
@ -49,7 +49,7 @@ export class OpportunityWorkspaceEntity extends BaseWorkspaceEntity {
|
||||
icon: 'IconCurrencyDollar',
|
||||
})
|
||||
@WorkspaceIsNullable()
|
||||
amount: CurrencyMetadata;
|
||||
amount: CurrencyMetadata | null;
|
||||
|
||||
@WorkspaceField({
|
||||
standardId: OPPORTUNITY_STANDARD_FIELD_IDS.closeDate,
|
||||
@ -59,7 +59,7 @@ export class OpportunityWorkspaceEntity extends BaseWorkspaceEntity {
|
||||
icon: 'IconCalendarEvent',
|
||||
})
|
||||
@WorkspaceIsNullable()
|
||||
closeDate: Date;
|
||||
closeDate: Date | null;
|
||||
|
||||
@WorkspaceField({
|
||||
standardId: OPPORTUNITY_STANDARD_FIELD_IDS.probability,
|
||||
@ -102,7 +102,7 @@ export class OpportunityWorkspaceEntity extends BaseWorkspaceEntity {
|
||||
})
|
||||
@WorkspaceIsSystem()
|
||||
@WorkspaceIsNullable()
|
||||
position: number;
|
||||
position: number | null;
|
||||
|
||||
@WorkspaceRelation({
|
||||
standardId: OPPORTUNITY_STANDARD_FIELD_IDS.pointOfContact,
|
||||
@ -116,7 +116,7 @@ export class OpportunityWorkspaceEntity extends BaseWorkspaceEntity {
|
||||
onDelete: RelationOnDeleteAction.SET_NULL,
|
||||
})
|
||||
@WorkspaceIsNullable()
|
||||
pointOfContact: Relation<PersonWorkspaceEntity>;
|
||||
pointOfContact: Relation<PersonWorkspaceEntity> | null;
|
||||
|
||||
@WorkspaceRelation({
|
||||
standardId: OPPORTUNITY_STANDARD_FIELD_IDS.company,
|
||||
@ -130,7 +130,7 @@ export class OpportunityWorkspaceEntity extends BaseWorkspaceEntity {
|
||||
onDelete: RelationOnDeleteAction.SET_NULL,
|
||||
})
|
||||
@WorkspaceIsNullable()
|
||||
company: Relation<CompanyWorkspaceEntity>;
|
||||
company: Relation<CompanyWorkspaceEntity> | null;
|
||||
|
||||
@WorkspaceRelation({
|
||||
standardId: OPPORTUNITY_STANDARD_FIELD_IDS.favorites,
|
||||
|
@ -41,7 +41,7 @@ export class PersonWorkspaceEntity extends BaseWorkspaceEntity {
|
||||
icon: 'IconUser',
|
||||
})
|
||||
@WorkspaceIsNullable()
|
||||
name: FullNameMetadata;
|
||||
name: FullNameMetadata | null;
|
||||
|
||||
@WorkspaceField({
|
||||
standardId: PERSON_STANDARD_FIELD_IDS.email,
|
||||
@ -60,7 +60,7 @@ export class PersonWorkspaceEntity extends BaseWorkspaceEntity {
|
||||
icon: 'IconBrandLinkedin',
|
||||
})
|
||||
@WorkspaceIsNullable()
|
||||
linkedinLink: LinkMetadata;
|
||||
linkedinLink: LinkMetadata | null;
|
||||
|
||||
@WorkspaceField({
|
||||
standardId: PERSON_STANDARD_FIELD_IDS.xLink,
|
||||
@ -70,7 +70,7 @@ export class PersonWorkspaceEntity extends BaseWorkspaceEntity {
|
||||
icon: 'IconBrandX',
|
||||
})
|
||||
@WorkspaceIsNullable()
|
||||
xLink: LinkMetadata;
|
||||
xLink: LinkMetadata | null;
|
||||
|
||||
@WorkspaceField({
|
||||
standardId: PERSON_STANDARD_FIELD_IDS.jobTitle,
|
||||
@ -118,7 +118,7 @@ export class PersonWorkspaceEntity extends BaseWorkspaceEntity {
|
||||
})
|
||||
@WorkspaceIsSystem()
|
||||
@WorkspaceIsNullable()
|
||||
position: number;
|
||||
position: number | null;
|
||||
|
||||
// Relations
|
||||
@WorkspaceRelation({
|
||||
@ -132,7 +132,7 @@ export class PersonWorkspaceEntity extends BaseWorkspaceEntity {
|
||||
inverseSideFieldKey: 'people',
|
||||
})
|
||||
@WorkspaceIsNullable()
|
||||
company: Relation<CompanyWorkspaceEntity>;
|
||||
company: Relation<CompanyWorkspaceEntity> | null;
|
||||
|
||||
@WorkspaceRelation({
|
||||
standardId: PERSON_STANDARD_FIELD_IDS.pointOfContactForOpportunities,
|
||||
|
@ -151,9 +151,9 @@ export class TimelineActivityRepository {
|
||||
name: string;
|
||||
properties: Record<string, any> | null;
|
||||
workspaceMemberId: string | undefined;
|
||||
recordId: string;
|
||||
recordId: string | null;
|
||||
linkedRecordCachedName: string;
|
||||
linkedRecordId: string | undefined;
|
||||
linkedRecordId: string | null | undefined;
|
||||
linkedObjectMetadataId: string | undefined;
|
||||
}[],
|
||||
workspaceId: string,
|
||||
|
@ -39,7 +39,7 @@ export class AuditLogWorkspaceEntity extends BaseWorkspaceEntity {
|
||||
icon: 'IconListDetails',
|
||||
})
|
||||
@WorkspaceIsNullable()
|
||||
properties: JSON;
|
||||
properties: JSON | null;
|
||||
|
||||
@WorkspaceField({
|
||||
standardId: AUDIT_LOGS_STANDARD_FIELD_IDS.context,
|
||||
@ -50,7 +50,7 @@ export class AuditLogWorkspaceEntity extends BaseWorkspaceEntity {
|
||||
icon: 'IconListDetails',
|
||||
})
|
||||
@WorkspaceIsNullable()
|
||||
context: JSON;
|
||||
context: JSON | null;
|
||||
|
||||
@WorkspaceField({
|
||||
standardId: AUDIT_LOGS_STANDARD_FIELD_IDS.objectName,
|
||||
@ -78,7 +78,7 @@ export class AuditLogWorkspaceEntity extends BaseWorkspaceEntity {
|
||||
icon: 'IconAbc',
|
||||
})
|
||||
@WorkspaceIsNullable()
|
||||
recordId: string;
|
||||
recordId: string | null;
|
||||
|
||||
@WorkspaceRelation({
|
||||
standardId: AUDIT_LOGS_STANDARD_FIELD_IDS.workspaceMember,
|
||||
@ -91,5 +91,5 @@ export class AuditLogWorkspaceEntity extends BaseWorkspaceEntity {
|
||||
inverseSideFieldKey: 'auditLogs',
|
||||
})
|
||||
@WorkspaceIsNullable()
|
||||
workspaceMember: Relation<WorkspaceMemberWorkspaceEntity>;
|
||||
workspaceMember: Relation<WorkspaceMemberWorkspaceEntity> | null;
|
||||
}
|
||||
|
@ -56,7 +56,7 @@ export class BehavioralEventWorkspaceEntity extends BaseWorkspaceEntity {
|
||||
icon: 'IconListDetails',
|
||||
})
|
||||
@WorkspaceIsNullable()
|
||||
properties: JSON;
|
||||
properties: JSON | null;
|
||||
|
||||
@WorkspaceField({
|
||||
standardId: BEHAVIORAL_EVENT_STANDARD_FIELD_IDS.context,
|
||||
@ -67,7 +67,7 @@ export class BehavioralEventWorkspaceEntity extends BaseWorkspaceEntity {
|
||||
icon: 'IconListDetails',
|
||||
})
|
||||
@WorkspaceIsNullable()
|
||||
context: JSON;
|
||||
context: JSON | null;
|
||||
|
||||
@WorkspaceField({
|
||||
standardId: BEHAVIORAL_EVENT_STANDARD_FIELD_IDS.objectName,
|
||||
@ -86,5 +86,5 @@ export class BehavioralEventWorkspaceEntity extends BaseWorkspaceEntity {
|
||||
icon: 'IconAbc',
|
||||
})
|
||||
@WorkspaceIsNullable()
|
||||
recordId: string;
|
||||
recordId: string | null;
|
||||
}
|
||||
|
@ -56,7 +56,7 @@ export class TimelineActivityWorkspaceEntity extends BaseWorkspaceEntity {
|
||||
icon: 'IconListDetails',
|
||||
})
|
||||
@WorkspaceIsNullable()
|
||||
properties: JSON;
|
||||
properties: JSON | null;
|
||||
|
||||
// Special objects that don't have their own timeline and are 'link' to the main object
|
||||
@WorkspaceField({
|
||||
@ -76,7 +76,7 @@ export class TimelineActivityWorkspaceEntity extends BaseWorkspaceEntity {
|
||||
icon: 'IconAbc',
|
||||
})
|
||||
@WorkspaceIsNullable()
|
||||
linkedRecordId: string;
|
||||
linkedRecordId: string | null;
|
||||
|
||||
@WorkspaceField({
|
||||
standardId: TIMELINE_ACTIVITY_STANDARD_FIELD_IDS.linkedObjectMetadataId,
|
||||
@ -86,7 +86,7 @@ export class TimelineActivityWorkspaceEntity extends BaseWorkspaceEntity {
|
||||
icon: 'IconAbc',
|
||||
})
|
||||
@WorkspaceIsNullable()
|
||||
linkedObjectMetadataId: string;
|
||||
linkedObjectMetadataId: string | null;
|
||||
|
||||
// Who made the action
|
||||
@WorkspaceRelation({
|
||||
@ -100,7 +100,7 @@ export class TimelineActivityWorkspaceEntity extends BaseWorkspaceEntity {
|
||||
inverseSideFieldKey: 'timelineActivities',
|
||||
})
|
||||
@WorkspaceIsNullable()
|
||||
workspaceMember: Relation<WorkspaceMemberWorkspaceEntity>;
|
||||
workspaceMember: Relation<WorkspaceMemberWorkspaceEntity> | null;
|
||||
|
||||
@WorkspaceRelation({
|
||||
standardId: TIMELINE_ACTIVITY_STANDARD_FIELD_IDS.person,
|
||||
@ -113,7 +113,7 @@ export class TimelineActivityWorkspaceEntity extends BaseWorkspaceEntity {
|
||||
inverseSideFieldKey: 'timelineActivities',
|
||||
})
|
||||
@WorkspaceIsNullable()
|
||||
person: Relation<PersonWorkspaceEntity>;
|
||||
person: Relation<PersonWorkspaceEntity> | null;
|
||||
|
||||
@WorkspaceRelation({
|
||||
standardId: TIMELINE_ACTIVITY_STANDARD_FIELD_IDS.company,
|
||||
@ -126,7 +126,7 @@ export class TimelineActivityWorkspaceEntity extends BaseWorkspaceEntity {
|
||||
inverseSideFieldKey: 'timelineActivities',
|
||||
})
|
||||
@WorkspaceIsNullable()
|
||||
company: Relation<CompanyWorkspaceEntity>;
|
||||
company: Relation<CompanyWorkspaceEntity> | null;
|
||||
|
||||
@WorkspaceRelation({
|
||||
standardId: TIMELINE_ACTIVITY_STANDARD_FIELD_IDS.opportunity,
|
||||
@ -139,7 +139,7 @@ export class TimelineActivityWorkspaceEntity extends BaseWorkspaceEntity {
|
||||
inverseSideFieldKey: 'timelineActivities',
|
||||
})
|
||||
@WorkspaceIsNullable()
|
||||
opportunity: Relation<OpportunityWorkspaceEntity>;
|
||||
opportunity: Relation<OpportunityWorkspaceEntity> | null;
|
||||
|
||||
@WorkspaceDynamicRelation({
|
||||
type: RelationMetadataType.MANY_TO_ONE,
|
||||
|
@ -72,5 +72,5 @@ export class ViewFieldWorkspaceEntity extends BaseWorkspaceEntity {
|
||||
joinColumn: 'viewId',
|
||||
})
|
||||
@WorkspaceIsNullable()
|
||||
view?: ViewWorkspaceEntity;
|
||||
view?: ViewWorkspaceEntity | null;
|
||||
}
|
||||
|
@ -68,5 +68,5 @@ export class ViewFilterWorkspaceEntity extends BaseWorkspaceEntity {
|
||||
inverseSideFieldKey: 'viewFilters',
|
||||
})
|
||||
@WorkspaceIsNullable()
|
||||
view: Relation<ViewWorkspaceEntity>;
|
||||
view: Relation<ViewWorkspaceEntity> | null;
|
||||
}
|
||||
|
@ -53,5 +53,5 @@ export class ViewSortWorkspaceEntity extends BaseWorkspaceEntity {
|
||||
inverseSideFieldKey: 'viewSorts',
|
||||
})
|
||||
@WorkspaceIsNullable()
|
||||
view: Relation<ViewWorkspaceEntity>;
|
||||
view: Relation<ViewWorkspaceEntity> | null;
|
||||
}
|
||||
|
@ -3,10 +3,9 @@ import { Injectable } from '@nestjs/common';
|
||||
import { WorkspacePreQueryHook } from 'src/engine/api/graphql/workspace-query-runner/workspace-pre-query-hook/interfaces/workspace-pre-query-hook.interface';
|
||||
import { DeleteOneResolverArgs } from 'src/engine/api/graphql/workspace-resolver-builder/interfaces/workspace-resolvers-builder.interface';
|
||||
|
||||
import { InjectObjectMetadataRepository } from 'src/engine/object-metadata-repository/object-metadata-repository.decorator';
|
||||
import { CommentRepository } from 'src/modules/activity/repositories/comment.repository';
|
||||
import { InjectWorkspaceRepository } from 'src/engine/twenty-orm/decorators/inject-workspace-repository.decorator';
|
||||
import { WorkspaceRepository } from 'src/engine/twenty-orm/repository/workspace.repository';
|
||||
import { CommentWorkspaceEntity } from 'src/modules/activity/standard-objects/comment.workspace-entity';
|
||||
import { AttachmentRepository } from 'src/modules/attachment/repositories/attachment.repository';
|
||||
import { AttachmentWorkspaceEntity } from 'src/modules/attachment/standard-objects/attachment.workspace-entity';
|
||||
|
||||
@Injectable()
|
||||
@ -14,10 +13,10 @@ export class WorkspaceMemberDeleteOnePreQueryHook
|
||||
implements WorkspacePreQueryHook
|
||||
{
|
||||
constructor(
|
||||
@InjectObjectMetadataRepository(AttachmentWorkspaceEntity)
|
||||
private readonly attachmentRepository: AttachmentRepository,
|
||||
@InjectObjectMetadataRepository(CommentWorkspaceEntity)
|
||||
private readonly commentRepository: CommentRepository,
|
||||
@InjectWorkspaceRepository(AttachmentWorkspaceEntity)
|
||||
private readonly attachmentRepository: WorkspaceRepository<AttachmentWorkspaceEntity>,
|
||||
@InjectWorkspaceRepository(CommentWorkspaceEntity)
|
||||
private readonly commentRepository: WorkspaceRepository<CommentWorkspaceEntity>,
|
||||
) {}
|
||||
|
||||
// There is no need to validate the user's access to the workspace member since we don't have permission yet.
|
||||
@ -26,16 +25,18 @@ export class WorkspaceMemberDeleteOnePreQueryHook
|
||||
workspaceId: string,
|
||||
payload: DeleteOneResolverArgs,
|
||||
): Promise<void> {
|
||||
const workspaceMemberId = payload.id;
|
||||
const authorId = payload.id;
|
||||
|
||||
await this.attachmentRepository.deleteByAuthorId(
|
||||
workspaceMemberId,
|
||||
workspaceId,
|
||||
);
|
||||
await this.attachmentRepository.delete({
|
||||
author: {
|
||||
id: authorId,
|
||||
},
|
||||
});
|
||||
|
||||
await this.commentRepository.deleteByAuthorId(
|
||||
workspaceMemberId,
|
||||
workspaceId,
|
||||
);
|
||||
await this.commentRepository.delete({
|
||||
author: {
|
||||
id: authorId,
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
|
@ -1,6 +1,6 @@
|
||||
import { Module } from '@nestjs/common';
|
||||
|
||||
import { ObjectMetadataRepositoryModule } from 'src/engine/object-metadata-repository/object-metadata-repository.module';
|
||||
import { TwentyORMModule } from 'src/engine/twenty-orm/twenty-orm.module';
|
||||
import { CommentWorkspaceEntity } from 'src/modules/activity/standard-objects/comment.workspace-entity';
|
||||
import { AttachmentWorkspaceEntity } from 'src/modules/attachment/standard-objects/attachment.workspace-entity';
|
||||
import { WorkspaceMemberDeleteManyPreQueryHook } from 'src/modules/workspace-member/query-hooks/workspace-member-delete-many.pre-query.hook';
|
||||
@ -8,7 +8,7 @@ import { WorkspaceMemberDeleteOnePreQueryHook } from 'src/modules/workspace-memb
|
||||
|
||||
@Module({
|
||||
imports: [
|
||||
ObjectMetadataRepositoryModule.forFeature([
|
||||
TwentyORMModule.forFeature([
|
||||
AttachmentWorkspaceEntity,
|
||||
CommentWorkspaceEntity,
|
||||
]),
|
||||
|
3
packages/twenty-server/src/utils/is-defined.ts
Normal file
3
packages/twenty-server/src/utils/is-defined.ts
Normal file
@ -0,0 +1,3 @@
|
||||
export const isDefined = <T>(value: T | null | undefined): value is T => {
|
||||
return value !== null && value !== undefined;
|
||||
};
|
@ -8,6 +8,7 @@ export interface ReflectMetadataTypeMap {
|
||||
['workspace:is-system-metadata-args']: true;
|
||||
['workspace:is-audit-logged-metadata-args']: false;
|
||||
['workspace:is-primary-field-metadata-args']: true;
|
||||
['workspace:join-column']: true;
|
||||
}
|
||||
|
||||
export class TypedReflect {
|
||||
|
Loading…
Reference in New Issue
Block a user