feat: drop calendar repository (#5824)

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

---------

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

View File

@ -19,8 +19,8 @@ export class AnalyticsService {
async create(
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,

View File

@ -28,6 +28,8 @@ import { ConnectedAccountWorkspaceEntity } from 'src/modules/connected-account/s
import { CalendarChannelWorkspaceEntity } from 'src/modules/calendar/standard-objects/calendar-channel.workspace-entity';
import { 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,

View File

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

View File

@ -16,9 +16,7 @@ import { GoogleAPIsService } from 'src/engine/core-modules/auth/services/google-
import { TokenService } from 'src/engine/core-modules/auth/services/token.service';
import { 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,
);

View File

@ -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 =

View File

@ -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,
};
}

View File

@ -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({

View File

@ -1,4 +1,4 @@
import { Logger } from '@nestjs/common';
import { Logger, Scope } from '@nestjs/common';
import { BillingService } from 'src/engine/core-modules/billing/billing.service';
import { 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;

View File

@ -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;

View File

@ -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 ||
'',

View File

@ -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 &&

View File

@ -9,6 +9,8 @@ import { UserWorkspaceService } from 'src/engine/core-modules/user-workspace/use
import { DataSourceModule } from 'src/engine/metadata-modules/data-source/data-source.module';
import { 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],

View File

@ -8,11 +8,12 @@ import { UserWorkspace } from 'src/engine/core-modules/user-workspace/user-works
import { TypeORMService } from 'src/database/typeorm/typeorm.service';
import { 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(

View File

@ -29,6 +29,7 @@ import { User } from 'src/engine/core-modules/user/user.entity';
import { WorkspaceMember } from 'src/engine/core-modules/user/dtos/workspace-member.dto';
import { 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);
}
}