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:
@ -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>;
|
||||
|
||||
Reference in New Issue
Block a user