Remove usages of connectToDataSource and use workspaceDataSource (#11873)

In this PR we are

1. cleaning typeORM service by removing connectToDataSource method
2. using workspaceDataSource instead of mainDataSource when possible,
and replacing raw SQL with workspaceRepository methods to use
This commit is contained in:
Marie
2025-05-07 10:42:51 +02:00
committed by GitHub
parent b5bacbbd29
commit 463dee3fe6
33 changed files with 324 additions and 441 deletions

View File

@ -1,7 +1,7 @@
import chalk from 'chalk'; import chalk from 'chalk';
import { Option } from 'nest-commander'; import { Option } from 'nest-commander';
import { In, MoreThanOrEqual, Repository } from 'typeorm';
import { WorkspaceActivationStatus } from 'twenty-shared/workspace'; import { WorkspaceActivationStatus } from 'twenty-shared/workspace';
import { In, MoreThanOrEqual, Repository } from 'typeorm';
import { MigrationCommandRunner } from 'src/database/commands/command-runners/migration.command-runner'; import { MigrationCommandRunner } from 'src/database/commands/command-runners/migration.command-runner';
import { Workspace } from 'src/engine/core-modules/workspace/workspace.entity'; import { Workspace } from 'src/engine/core-modules/workspace/workspace.entity';
@ -138,10 +138,10 @@ export abstract class ActiveOrSuspendedWorkspacesMigrationCommandRunner<
try { try {
const dataSource = const dataSource =
await this.twentyORMGlobalManager.getDataSourceForWorkspace( await this.twentyORMGlobalManager.getDataSourceForWorkspace({
workspaceId, workspaceId,
false, shouldFailIfMetadataNotFound: false,
); });
await this.runOnWorkspace({ await this.runOnWorkspace({
options, options,

View File

@ -46,7 +46,7 @@ export class DataSeedDemoWorkspaceService {
const appVersion = this.twentyConfigService.get('APP_VERSION'); const appVersion = this.twentyConfigService.get('APP_VERSION');
await seedCoreSchema({ await seedCoreSchema({
workspaceDataSource: rawDataSource, dataSource: rawDataSource,
workspaceId, workspaceId,
appVersion, appVersion,
seedBilling: false, seedBilling: false,

View File

@ -30,9 +30,6 @@ import { seedPeople } from 'src/database/typeorm-seeds/workspace/seedPeople';
import { seedWorkspaceMember } from 'src/database/typeorm-seeds/workspace/workspace-members'; import { seedWorkspaceMember } from 'src/database/typeorm-seeds/workspace/workspace-members';
import { rawDataSource } from 'src/database/typeorm/raw/raw.datasource'; import { rawDataSource } from 'src/database/typeorm/raw/raw.datasource';
import { TypeORMService } from 'src/database/typeorm/typeorm.service'; import { TypeORMService } from 'src/database/typeorm/typeorm.service';
import { InjectCacheStorage } from 'src/engine/core-modules/cache-storage/decorators/cache-storage.decorator';
import { CacheStorageService } from 'src/engine/core-modules/cache-storage/services/cache-storage.service';
import { CacheStorageNamespace } from 'src/engine/core-modules/cache-storage/types/cache-storage-namespace.enum';
import { TwentyConfigService } from 'src/engine/core-modules/twenty-config/twenty-config.service'; import { TwentyConfigService } from 'src/engine/core-modules/twenty-config/twenty-config.service';
import { DataSourceEntity } from 'src/engine/metadata-modules/data-source/data-source.entity'; import { DataSourceEntity } from 'src/engine/metadata-modules/data-source/data-source.entity';
import { DataSourceService } from 'src/engine/metadata-modules/data-source/data-source.service'; import { DataSourceService } from 'src/engine/metadata-modules/data-source/data-source.service';
@ -45,12 +42,12 @@ import { SURVEY_RESULTS_METADATA_SEEDS } from 'src/engine/seeder/metadata-seeds/
import { SeederService } from 'src/engine/seeder/seeder.service'; import { SeederService } from 'src/engine/seeder/seeder.service';
import { WorkspaceEntityManager } from 'src/engine/twenty-orm/entity-manager/workspace-entity-manager'; import { WorkspaceEntityManager } from 'src/engine/twenty-orm/entity-manager/workspace-entity-manager';
import { shouldSeedWorkspaceFavorite } from 'src/engine/utils/should-seed-workspace-favorite'; import { shouldSeedWorkspaceFavorite } from 'src/engine/utils/should-seed-workspace-favorite';
import { WorkspaceCacheStorageService } from 'src/engine/workspace-cache-storage/workspace-cache-storage.service';
import { createWorkspaceViews } from 'src/engine/workspace-manager/standard-objects-prefill-data/create-workspace-views'; import { createWorkspaceViews } from 'src/engine/workspace-manager/standard-objects-prefill-data/create-workspace-views';
import { seedViewWithDemoData } from 'src/engine/workspace-manager/standard-objects-prefill-data/seed-view-with-demo-data'; import { seedViewWithDemoData } from 'src/engine/workspace-manager/standard-objects-prefill-data/seed-view-with-demo-data';
import { opportunitiesTableByStageView } from 'src/engine/workspace-manager/standard-objects-prefill-data/views/opportunity-table-by-stage.view'; import { opportunitiesTableByStageView } from 'src/engine/workspace-manager/standard-objects-prefill-data/views/opportunity-table-by-stage.view';
import { WorkspaceManagerService } from 'src/engine/workspace-manager/workspace-manager.service'; import { WorkspaceManagerService } from 'src/engine/workspace-manager/workspace-manager.service';
import { STANDARD_OBJECT_IDS } from 'src/engine/workspace-manager/workspace-sync-metadata/constants/standard-object-ids'; import { STANDARD_OBJECT_IDS } from 'src/engine/workspace-manager/workspace-sync-metadata/constants/standard-object-ids';
// TODO: implement dry-run // TODO: implement dry-run
@Command({ @Command({
name: 'workspace:seed:dev', name: 'workspace:seed:dev',
@ -66,11 +63,10 @@ export class DataSeedWorkspaceCommand extends CommandRunner {
private readonly typeORMService: TypeORMService, private readonly typeORMService: TypeORMService,
private readonly fieldMetadataService: FieldMetadataService, private readonly fieldMetadataService: FieldMetadataService,
private readonly objectMetadataService: ObjectMetadataService, private readonly objectMetadataService: ObjectMetadataService,
@InjectCacheStorage(CacheStorageNamespace.EngineWorkspace)
private readonly workspaceSchemaCache: CacheStorageService,
private readonly seederService: SeederService, private readonly seederService: SeederService,
private readonly workspaceManagerService: WorkspaceManagerService, private readonly workspaceManagerService: WorkspaceManagerService,
private readonly twentyConfigService: TwentyConfigService, private readonly twentyConfigService: TwentyConfigService,
private readonly workspaceCacheStorageService: WorkspaceCacheStorageService,
) { ) {
super(); super();
} }
@ -92,7 +88,13 @@ export class DataSeedWorkspaceCommand extends CommandRunner {
} }
async createWorkspaceSchema(workspaceId: string) { async createWorkspaceSchema(workspaceId: string) {
await this.workspaceSchemaCache.flush(); const workspaceCachedMetadataVersion =
await this.workspaceCacheStorageService.getMetadataVersion(workspaceId);
await this.workspaceCacheStorageService.flush(
workspaceId,
workspaceCachedMetadataVersion,
);
await rawDataSource.initialize(); await rawDataSource.initialize();
@ -100,7 +102,7 @@ export class DataSeedWorkspaceCommand extends CommandRunner {
const appVersion = this.twentyConfigService.get('APP_VERSION'); const appVersion = this.twentyConfigService.get('APP_VERSION');
await seedCoreSchema({ await seedCoreSchema({
workspaceDataSource: rawDataSource, dataSource: rawDataSource,
workspaceId, workspaceId,
seedBilling: isBillingEnabled, seedBilling: isBillingEnabled,
appVersion, appVersion,
@ -117,10 +119,9 @@ export class DataSeedWorkspaceCommand extends CommandRunner {
workspaceId, workspaceId,
); );
const workspaceDataSource = const mainDataSource = this.typeORMService.getMainDataSource();
await this.typeORMService.connectToDataSource(dataSourceMetadata);
if (!workspaceDataSource) { if (!mainDataSource) {
throw new Error('Could not connect to workspace data source'); throw new Error('Could not connect to workspace data source');
} }
@ -140,10 +141,7 @@ export class DataSeedWorkspaceCommand extends CommandRunner {
workspaceId, workspaceId,
); );
await this.seedStandardObjectRecords( await this.seedStandardObjectRecords(mainDataSource, dataSourceMetadata);
workspaceDataSource,
dataSourceMetadata,
);
await this.seederService.seedCustomObjects( await this.seederService.seedCustomObjects(
dataSourceMetadata.id, dataSourceMetadata.id,
@ -161,15 +159,13 @@ export class DataSeedWorkspaceCommand extends CommandRunner {
} catch (error) { } catch (error) {
this.logger.error(error); this.logger.error(error);
} }
await this.typeORMService.disconnectFromDataSource(dataSourceMetadata.id);
} }
async seedStandardObjectRecords( async seedStandardObjectRecords(
workspaceDataSource: DataSource, mainDataSource: DataSource,
dataSourceMetadata: DataSourceEntity, dataSourceMetadata: DataSourceEntity,
) { ) {
await workspaceDataSource.transaction( await mainDataSource.transaction(
async (entityManager: WorkspaceEntityManager) => { async (entityManager: WorkspaceEntityManager) => {
const { objectMetadataStandardIdToIdMap } = const { objectMetadataStandardIdToIdMap } =
await this.objectMetadataService.getObjectMetadataStandardIdToIdMap( await this.objectMetadataService.getObjectMetadataStandardIdToIdMap(

View File

@ -8,6 +8,7 @@ import { DataSourceModule } from 'src/engine/metadata-modules/data-source/data-s
import { FieldMetadataModule } from 'src/engine/metadata-modules/field-metadata/field-metadata.module'; import { FieldMetadataModule } from 'src/engine/metadata-modules/field-metadata/field-metadata.module';
import { ObjectMetadataModule } from 'src/engine/metadata-modules/object-metadata/object-metadata.module'; import { ObjectMetadataModule } from 'src/engine/metadata-modules/object-metadata/object-metadata.module';
import { SeederModule } from 'src/engine/seeder/seeder.module'; import { SeederModule } from 'src/engine/seeder/seeder.module';
import { WorkspaceCacheStorageModule } from 'src/engine/workspace-cache-storage/workspace-cache-storage.module';
import { WorkspaceManagerModule } from 'src/engine/workspace-manager/workspace-manager.module'; import { WorkspaceManagerModule } from 'src/engine/workspace-manager/workspace-manager.module';
@Module({ @Module({
@ -21,6 +22,7 @@ import { WorkspaceManagerModule } from 'src/engine/workspace-manager/workspace-m
SeederModule, SeederModule,
WorkspaceManagerModule, WorkspaceManagerModule,
DataSourceModule, DataSourceModule,
WorkspaceCacheStorageModule,
], ],
providers: [DataSeedWorkspaceCommand, ConfirmationQuestion], providers: [DataSeedWorkspaceCommand, ConfirmationQuestion],
}) })

View File

@ -227,12 +227,11 @@ export class MigrateRichTextContentPatchCommand extends ActiveOrSuspendedWorkspa
const schemaName = const schemaName =
this.workspaceDataSourceService.getSchemaName(workspaceId); this.workspaceDataSourceService.getSchemaName(workspaceId);
const failOnMetadataCacheMiss = false;
const workspaceDataSource = const workspaceDataSource =
await this.twentyORMGlobalManager.getDataSourceForWorkspace( await this.twentyORMGlobalManager.getDataSourceForWorkspace({
workspaceId, workspaceId,
failOnMetadataCacheMiss, shouldFailIfMetadataNotFound: false,
); });
const rows = await workspaceDataSource.query( const rows = await workspaceDataSource.query(
`SELECT id, "${richTextField.name}" FROM "${schemaName}"."${computeTableName(objectMetadata.nameSingular, objectMetadata.isCustom)}" WHERE "${richTextField.name}" IS NOT NULL`, `SELECT id, "${richTextField.name}" FROM "${schemaName}"."${computeTableName(objectMetadata.nameSingular, objectMetadata.isCustom)}" WHERE "${richTextField.name}" IS NOT NULL`,

View File

@ -1,18 +1,18 @@
import { InjectRepository } from '@nestjs/typeorm'; import { InjectRepository } from '@nestjs/typeorm';
import { Command } from 'nest-commander'; import { Command } from 'nest-commander';
import { Repository } from 'typeorm';
import { isDefined } from 'twenty-shared/utils'; import { isDefined } from 'twenty-shared/utils';
import { Repository } from 'typeorm';
import { import {
ActiveOrSuspendedWorkspacesMigrationCommandRunner, ActiveOrSuspendedWorkspacesMigrationCommandRunner,
RunOnWorkspaceArgs, RunOnWorkspaceArgs,
} from 'src/database/commands/command-runners/active-or-suspended-workspaces-migration.command-runner'; } from 'src/database/commands/command-runners/active-or-suspended-workspaces-migration.command-runner';
import { WorkspaceDataSourceService } from 'src/engine/workspace-datasource/workspace-datasource.service';
import { Workspace } from 'src/engine/core-modules/workspace/workspace.entity'; import { Workspace } from 'src/engine/core-modules/workspace/workspace.entity';
import { ObjectMetadataEntity } from 'src/engine/metadata-modules/object-metadata/object-metadata.entity';
import { TwentyORMGlobalManager } from 'src/engine/twenty-orm/twenty-orm-global.manager'; import { TwentyORMGlobalManager } from 'src/engine/twenty-orm/twenty-orm-global.manager';
import { computeTableName } from 'src/engine/utils/compute-table-name.util'; import { computeTableName } from 'src/engine/utils/compute-table-name.util';
import { ObjectMetadataEntity } from 'src/engine/metadata-modules/object-metadata/object-metadata.entity'; import { WorkspaceDataSourceService } from 'src/engine/workspace-datasource/workspace-datasource.service';
@Command({ @Command({
name: 'upgrade:0-51:upgrade-created-by-enum', name: 'upgrade:0-51:upgrade-created-by-enum',
@ -43,7 +43,9 @@ export class UpgradeCreatedByEnumCommand extends ActiveOrSuspendedWorkspacesMigr
this.workspaceDataSourceService.getSchemaName(workspaceId); this.workspaceDataSourceService.getSchemaName(workspaceId);
const workspaceDataSource = const workspaceDataSource =
await this.twentyORMGlobalManager.getDataSourceForWorkspace(workspaceId); await this.twentyORMGlobalManager.getDataSourceForWorkspace({
workspaceId,
});
const objectMetadatas = await this.objectMetadataRepository.find({ const objectMetadatas = await this.objectMetadataRepository.find({
where: { where: {

View File

@ -7,7 +7,7 @@ import { seedUsers } from 'src/database/typeorm-seeds/core/users';
import { seedWorkspaces } from 'src/database/typeorm-seeds/core/workspaces'; import { seedWorkspaces } from 'src/database/typeorm-seeds/core/workspaces';
type SeedCoreSchemaArgs = { type SeedCoreSchemaArgs = {
workspaceDataSource: DataSource; dataSource: DataSource;
workspaceId: string; workspaceId: string;
appVersion: string | undefined; appVersion: string | undefined;
seedBilling?: boolean; seedBilling?: boolean;
@ -16,7 +16,7 @@ type SeedCoreSchemaArgs = {
export const seedCoreSchema = async ({ export const seedCoreSchema = async ({
appVersion, appVersion,
workspaceDataSource, dataSource,
workspaceId, workspaceId,
seedBilling = true, seedBilling = true,
seedFeatureFlags: shouldSeedFeatureFlags = true, seedFeatureFlags: shouldSeedFeatureFlags = true,
@ -24,23 +24,19 @@ export const seedCoreSchema = async ({
const schemaName = 'core'; const schemaName = 'core';
await seedWorkspaces({ await seedWorkspaces({
workspaceDataSource, dataSource,
schemaName, schemaName,
workspaceId, workspaceId,
appVersion, appVersion,
}); });
await seedUsers(workspaceDataSource, schemaName); await seedUsers(dataSource, schemaName);
await seedUserWorkspaces(workspaceDataSource, schemaName, workspaceId); await seedUserWorkspaces(dataSource, schemaName, workspaceId);
if (shouldSeedFeatureFlags) { if (shouldSeedFeatureFlags) {
await seedFeatureFlags(workspaceDataSource, schemaName, workspaceId); await seedFeatureFlags(dataSource, schemaName, workspaceId);
} }
if (seedBilling) { if (seedBilling) {
await seedBillingSubscriptions( await seedBillingSubscriptions(dataSource, schemaName, workspaceId);
workspaceDataSource,
schemaName,
workspaceId,
);
} }
}; };

View File

@ -10,7 +10,7 @@ export const SEED_APPLE_WORKSPACE_ID = '20202020-1c25-4d02-bf25-6aeccf7ea419';
export const SEED_ACME_WORKSPACE_ID = '3b8e6458-5fc1-4e63-8563-008ccddaa6db'; export const SEED_ACME_WORKSPACE_ID = '3b8e6458-5fc1-4e63-8563-008ccddaa6db';
export type SeedWorkspaceArgs = { export type SeedWorkspaceArgs = {
workspaceDataSource: DataSource; dataSource: DataSource;
schemaName: string; schemaName: string;
workspaceId: string; workspaceId: string;
appVersion: string | undefined; appVersion: string | undefined;
@ -33,7 +33,7 @@ type WorkspaceSeederFields = Pick<
export const seedWorkspaces = async ({ export const seedWorkspaces = async ({
schemaName, schemaName,
workspaceDataSource, dataSource,
workspaceId, workspaceId,
appVersion, appVersion,
}: SeedWorkspaceArgs) => { }: SeedWorkspaceArgs) => {
@ -60,7 +60,7 @@ export const seedWorkspaces = async ({
}, },
}; };
await workspaceDataSource await dataSource
.createQueryBuilder() .createQueryBuilder()
.insert() .insert()
.into(`${schemaName}.${tableName}`, workspaceSeederFields) .into(`${schemaName}.${tableName}`, workspaceSeederFields)
@ -70,11 +70,11 @@ export const seedWorkspaces = async ({
}; };
export const deleteWorkspaces = async ( export const deleteWorkspaces = async (
workspaceDataSource: DataSource, dataSource: DataSource,
schemaName: string, schemaName: string,
workspaceId: string, workspaceId: string,
) => { ) => {
await workspaceDataSource await dataSource
.createQueryBuilder() .createQueryBuilder()
.delete() .delete()
.from(`${schemaName}.${tableName}`) .from(`${schemaName}.${tableName}`)

View File

@ -22,12 +22,9 @@ import { TwoFactorMethod } from 'src/engine/core-modules/two-factor-method/two-f
import { UserWorkspace } from 'src/engine/core-modules/user-workspace/user-workspace.entity'; import { UserWorkspace } from 'src/engine/core-modules/user-workspace/user-workspace.entity';
import { User } from 'src/engine/core-modules/user/user.entity'; import { User } from 'src/engine/core-modules/user/user.entity';
import { Workspace } from 'src/engine/core-modules/workspace/workspace.entity'; import { Workspace } from 'src/engine/core-modules/workspace/workspace.entity';
import { DataSourceEntity } from 'src/engine/metadata-modules/data-source/data-source.entity';
@Injectable() @Injectable()
export class TypeORMService implements OnModuleInit, OnModuleDestroy { export class TypeORMService implements OnModuleInit, OnModuleDestroy {
private mainDataSource: DataSource; private mainDataSource: DataSource;
private dataSources: Map<string, DataSource> = new Map();
private isDatasourceInitializing: Map<string, boolean> = new Map();
constructor(private readonly twentyConfigService: TwentyConfigService) { constructor(private readonly twentyConfigService: TwentyConfigService) {
this.mainDataSource = new DataSource({ this.mainDataSource = new DataSource({
@ -73,75 +70,6 @@ export class TypeORMService implements OnModuleInit, OnModuleDestroy {
return this.mainDataSource; return this.mainDataSource;
} }
public async connectToDataSource(
dataSource: DataSourceEntity,
): Promise<DataSource | undefined> {
const isMultiDatasourceEnabled = false;
if (isMultiDatasourceEnabled) {
// Wait for a bit before trying again if another initialization is in progress
while (this.isDatasourceInitializing.get(dataSource.id)) {
await new Promise((resolve) => setTimeout(resolve, 10));
}
if (this.dataSources.has(dataSource.id)) {
return this.dataSources.get(dataSource.id);
}
this.isDatasourceInitializing.set(dataSource.id, true);
try {
const dataSourceInstance =
await this.createAndInitializeDataSource(dataSource);
this.dataSources.set(dataSource.id, dataSourceInstance);
return dataSourceInstance;
} finally {
this.isDatasourceInitializing.delete(dataSource.id);
}
}
return this.mainDataSource;
}
private async createAndInitializeDataSource(
dataSource: DataSourceEntity,
): Promise<DataSource> {
const schema = dataSource.schema;
const workspaceDataSource = new DataSource({
url: dataSource.url ?? this.twentyConfigService.get('PG_DATABASE_URL'),
type: 'postgres',
logging:
this.twentyConfigService.get('NODE_ENV') === NodeEnvironment.development
? ['query', 'error']
: ['error'],
schema,
ssl: this.twentyConfigService.get('PG_SSL_ALLOW_SELF_SIGNED')
? {
rejectUnauthorized: false,
}
: undefined,
});
await workspaceDataSource.initialize();
return workspaceDataSource;
}
public async disconnectFromDataSource(dataSourceId: string) {
if (!this.dataSources.has(dataSourceId)) {
return;
}
const dataSource = this.dataSources.get(dataSourceId);
await dataSource?.destroy();
this.dataSources.delete(dataSourceId);
}
public async createSchema(schemaName: string): Promise<string> { public async createSchema(schemaName: string): Promise<string> {
const queryRunner = this.mainDataSource.createQueryRunner(); const queryRunner = this.mainDataSource.createQueryRunner();
@ -168,10 +96,5 @@ export class TypeORMService implements OnModuleInit, OnModuleDestroy {
async onModuleDestroy() { async onModuleDestroy() {
// Destroy main data source "default" schema // Destroy main data source "default" schema
await this.mainDataSource.destroy(); await this.mainDataSource.destroy();
// Destroy all workspace data sources
for (const [, dataSource] of this.dataSources) {
await dataSource.destroy();
}
} }
} }

View File

@ -85,12 +85,13 @@ export abstract class GraphqlQueryBaseResolverService<
await this.validate(args, options); await this.validate(args, options);
const dataSource = const workspaceDataSource =
await this.twentyORMGlobalManager.getDataSourceForWorkspace( await this.twentyORMGlobalManager.getDataSourceForWorkspace({
authContext.workspace.id, workspaceId: authContext.workspace.id,
); shouldFailIfMetadataNotFound: false,
});
const featureFlagsMap = dataSource.featureFlagMap; const featureFlagsMap = workspaceDataSource.featureFlagMap;
const isPermissionsV2Enabled = const isPermissionsV2Enabled =
featureFlagsMap[FeatureFlagKey.IsPermissionsV2Enabled]; featureFlagsMap[FeatureFlagKey.IsPermissionsV2Enabled];
@ -127,7 +128,7 @@ export abstract class GraphqlQueryBaseResolverService<
const executedByApiKey = isDefined(authContext.apiKey); const executedByApiKey = isDefined(authContext.apiKey);
const shouldBypassPermissionChecks = executedByApiKey; const shouldBypassPermissionChecks = executedByApiKey;
const repository = dataSource.getRepository( const repository = workspaceDataSource.getRepository(
objectMetadataItemWithFieldMaps.nameSingular, objectMetadataItemWithFieldMaps.nameSingular,
shouldBypassPermissionChecks, shouldBypassPermissionChecks,
roleId, roleId,
@ -151,7 +152,7 @@ export abstract class GraphqlQueryBaseResolverService<
const graphqlQueryResolverExecutionArgs = { const graphqlQueryResolverExecutionArgs = {
args: computedArgs, args: computedArgs,
options, options,
dataSource, dataSource: workspaceDataSource,
repository, repository,
graphqlQueryParser, graphqlQueryParser,
graphqlQuerySelectedFieldsResult, graphqlQuerySelectedFieldsResult,

View File

@ -156,7 +156,10 @@ export class RestApiCoreServiceV2 {
} }
const dataSource = const dataSource =
await this.twentyORMGlobalManager.getDataSourceForWorkspace(workspace.id); await this.twentyORMGlobalManager.getDataSourceForWorkspace({
workspaceId: workspace.id,
shouldFailIfMetadataNotFound: false,
});
const objectMetadataNameSingular = const objectMetadataNameSingular =
objectMetadata.objectMetadataMapItem.nameSingular; objectMetadata.objectMetadataMapItem.nameSingular;

View File

@ -100,7 +100,9 @@ export class GoogleAPIsService {
); );
const workspaceDataSource = const workspaceDataSource =
await this.twentyORMGlobalManager.getDataSourceForWorkspace(workspaceId); await this.twentyORMGlobalManager.getDataSourceForWorkspace({
workspaceId,
});
const scopes = getGoogleApisOauthScopes(); const scopes = getGoogleApisOauthScopes();

View File

@ -104,7 +104,9 @@ export class MicrosoftAPIsService {
); );
const workspaceDataSource = const workspaceDataSource =
await this.twentyORMGlobalManager.getDataSourceForWorkspace(workspaceId); await this.twentyORMGlobalManager.getDataSourceForWorkspace({
workspaceId,
});
const scopes = getMicrosoftApisOauthScopes(); const scopes = getMicrosoftApisOauthScopes();

View File

@ -14,9 +14,7 @@ describe('JwtAuthStrategy', () => {
let workspaceRepository: any; let workspaceRepository: any;
let userWorkspaceRepository: any; let userWorkspaceRepository: any;
let userRepository: any; let userRepository: any;
let dataSourceService: any; let twentyORMGlobalManager: any;
let typeORMService: any;
const jwt = { const jwt = {
sub: 'sub-default', sub: 'sub-default',
jti: 'jti-default', jti: 'jti-default',
@ -38,6 +36,12 @@ describe('JwtAuthStrategy', () => {
extractJwtFromRequest: jest.fn(() => () => 'token'), extractJwtFromRequest: jest.fn(() => () => 'token'),
}; };
twentyORMGlobalManager = {
getRepositoryForWorkspace: jest.fn(async () => ({
findOne: jest.fn(async () => ({ id: 'api-key-id', revokedAt: null })),
})),
};
// first we test the API_KEY case // first we test the API_KEY case
it('should throw AuthException if type is API_KEY and workspace is not found', async () => { it('should throw AuthException if type is API_KEY and workspace is not found', async () => {
const payload = { const payload = {
@ -50,10 +54,8 @@ describe('JwtAuthStrategy', () => {
}; };
strategy = new JwtAuthStrategy( strategy = new JwtAuthStrategy(
{} as any,
jwtWrapperService, jwtWrapperService,
typeORMService, twentyORMGlobalManager,
dataSourceService,
workspaceRepository, workspaceRepository,
{} as any, {} as any,
userWorkspaceRepository, userWorkspaceRepository,
@ -77,19 +79,15 @@ describe('JwtAuthStrategy', () => {
findOneBy: jest.fn(async () => new Workspace()), findOneBy: jest.fn(async () => new Workspace()),
}; };
dataSourceService = { twentyORMGlobalManager = {
getLastDataSourceMetadataFromWorkspaceIdOrFail: jest.fn(async () => ({})), getRepositoryForWorkspace: jest.fn(async () => ({
}; findOne: jest.fn(async () => null),
})),
typeORMService = {
connectToDataSource: jest.fn(async () => {}),
}; };
strategy = new JwtAuthStrategy( strategy = new JwtAuthStrategy(
{} as any,
jwtWrapperService, jwtWrapperService,
typeORMService, twentyORMGlobalManager,
dataSourceService,
workspaceRepository, workspaceRepository,
{} as any, {} as any,
userWorkspaceRepository, userWorkspaceRepository,
@ -113,21 +111,15 @@ describe('JwtAuthStrategy', () => {
findOneBy: jest.fn(async () => new Workspace()), findOneBy: jest.fn(async () => new Workspace()),
}; };
const mockDataSource = { twentyORMGlobalManager = {
query: jest getRepositoryForWorkspace: jest.fn(async () => ({
.fn() findOne: jest.fn(async () => ({ id: 'api-key-id', revokedAt: null })),
.mockResolvedValue([{ id: 'api-key-id', revokedAt: null }]), })),
}; };
jest
.spyOn(typeORMService, 'connectToDataSource')
.mockResolvedValue(mockDataSource as any);
strategy = new JwtAuthStrategy( strategy = new JwtAuthStrategy(
{} as any,
jwtWrapperService, jwtWrapperService,
typeORMService, twentyORMGlobalManager,
dataSourceService,
workspaceRepository, workspaceRepository,
{} as any, {} as any,
userWorkspaceRepository, userWorkspaceRepository,
@ -140,7 +132,6 @@ describe('JwtAuthStrategy', () => {
}); });
// second we test the ACCESS cases // second we test the ACCESS cases
it('should throw AuthExceptionCode if type is ACCESS, no jti, and user not found', async () => { it('should throw AuthExceptionCode if type is ACCESS, no jti, and user not found', async () => {
const payload = { const payload = {
sub: 'sub-default', sub: 'sub-default',
@ -156,10 +147,8 @@ describe('JwtAuthStrategy', () => {
}; };
strategy = new JwtAuthStrategy( strategy = new JwtAuthStrategy(
{} as any,
jwtWrapperService, jwtWrapperService,
typeORMService, twentyORMGlobalManager,
dataSourceService,
workspaceRepository, workspaceRepository,
userRepository, userRepository,
userWorkspaceRepository, userWorkspaceRepository,
@ -194,10 +183,8 @@ describe('JwtAuthStrategy', () => {
}; };
strategy = new JwtAuthStrategy( strategy = new JwtAuthStrategy(
{} as any,
jwtWrapperService, jwtWrapperService,
typeORMService, twentyORMGlobalManager,
dataSourceService,
workspaceRepository, workspaceRepository,
userRepository, userRepository,
userWorkspaceRepository, userWorkspaceRepository,
@ -235,10 +222,8 @@ describe('JwtAuthStrategy', () => {
}; };
strategy = new JwtAuthStrategy( strategy = new JwtAuthStrategy(
{} as any,
jwtWrapperService, jwtWrapperService,
typeORMService, twentyORMGlobalManager,
dataSourceService,
workspaceRepository, workspaceRepository,
userRepository, userRepository,
userWorkspaceRepository, userWorkspaceRepository,

View File

@ -5,7 +5,6 @@ import { InjectRepository } from '@nestjs/typeorm';
import { Strategy } from 'passport-jwt'; import { Strategy } from 'passport-jwt';
import { Repository } from 'typeorm'; import { Repository } from 'typeorm';
import { TypeORMService } from 'src/database/typeorm/typeorm.service';
import { import {
AuthException, AuthException,
AuthExceptionCode, AuthExceptionCode,
@ -15,20 +14,17 @@ import {
JwtPayload, JwtPayload,
} from 'src/engine/core-modules/auth/types/auth-context.type'; } from 'src/engine/core-modules/auth/types/auth-context.type';
import { JwtWrapperService } from 'src/engine/core-modules/jwt/services/jwt-wrapper.service'; import { JwtWrapperService } from 'src/engine/core-modules/jwt/services/jwt-wrapper.service';
import { TwentyConfigService } from 'src/engine/core-modules/twenty-config/twenty-config.service';
import { UserWorkspace } from 'src/engine/core-modules/user-workspace/user-workspace.entity'; import { UserWorkspace } from 'src/engine/core-modules/user-workspace/user-workspace.entity';
import { User } from 'src/engine/core-modules/user/user.entity'; import { User } from 'src/engine/core-modules/user/user.entity';
import { Workspace } from 'src/engine/core-modules/workspace/workspace.entity'; import { Workspace } from 'src/engine/core-modules/workspace/workspace.entity';
import { DataSourceService } from 'src/engine/metadata-modules/data-source/data-source.service'; import { TwentyORMGlobalManager } from 'src/engine/twenty-orm/twenty-orm-global.manager';
import { ApiKeyWorkspaceEntity } from 'src/modules/api-key/standard-objects/api-key.workspace-entity'; import { ApiKeyWorkspaceEntity } from 'src/modules/api-key/standard-objects/api-key.workspace-entity';
@Injectable() @Injectable()
export class JwtAuthStrategy extends PassportStrategy(Strategy, 'jwt') { export class JwtAuthStrategy extends PassportStrategy(Strategy, 'jwt') {
constructor( constructor(
private readonly twentyConfigService: TwentyConfigService,
private readonly jwtWrapperService: JwtWrapperService, private readonly jwtWrapperService: JwtWrapperService,
private readonly typeORMService: TypeORMService, private readonly twentyORMGlobalManager: TwentyORMGlobalManager,
private readonly dataSourceService: DataSourceService,
@InjectRepository(Workspace, 'core') @InjectRepository(Workspace, 'core')
private readonly workspaceRepository: Repository<Workspace>, private readonly workspaceRepository: Repository<Workspace>,
@InjectRepository(User, 'core') @InjectRepository(User, 'core')
@ -37,7 +33,7 @@ export class JwtAuthStrategy extends PassportStrategy(Strategy, 'jwt') {
private readonly userWorkspaceRepository: Repository<UserWorkspace>, private readonly userWorkspaceRepository: Repository<UserWorkspace>,
) { ) {
const jwtFromRequestFunction = jwtWrapperService.extractJwtFromRequest(); const jwtFromRequestFunction = jwtWrapperService.extractJwtFromRequest();
const secretOrKeyProviderFunction = async (request, rawJwtToken, done) => { const secretOrKeyProviderFunction = async (_request, rawJwtToken, done) => {
try { try {
const decodedToken = jwtWrapperService.decode( const decodedToken = jwtWrapperService.decode(
rawJwtToken, rawJwtToken,
@ -75,20 +71,20 @@ export class JwtAuthStrategy extends PassportStrategy(Strategy, 'jwt') {
); );
} }
const dataSourceMetadata = const apiKeyRepository =
await this.dataSourceService.getLastDataSourceMetadataFromWorkspaceIdOrFail( await this.twentyORMGlobalManager.getRepositoryForWorkspace<ApiKeyWorkspaceEntity>(
workspace.id, workspace.id,
'apiKey',
{
shouldBypassPermissionChecks: true,
},
); );
const workspaceDataSource = apiKey = await apiKeyRepository.findOne({
await this.typeORMService.connectToDataSource(dataSourceMetadata); where: {
id: payload.jti,
const res = await workspaceDataSource?.query( },
`SELECT * FROM ${dataSourceMetadata.schema}."apiKey" WHERE id = $1`, });
[payload.jti],
);
apiKey = res?.[0];
if (!apiKey || apiKey.revokedAt) { if (!apiKey || apiKey.revokedAt) {
throw new AuthException( throw new AuthException(

View File

@ -1,7 +1,7 @@
import { Test, TestingModule } from '@nestjs/testing'; import { Test, TestingModule } from '@nestjs/testing';
import { getRepositoryToken } from '@nestjs/typeorm'; import { getRepositoryToken } from '@nestjs/typeorm';
import { Repository } from 'typeorm'; import { DataSource, Repository } from 'typeorm';
import { TypeORMService } from 'src/database/typeorm/typeorm.service'; import { TypeORMService } from 'src/database/typeorm/typeorm.service';
import { DatabaseEventAction } from 'src/engine/api/graphql/graphql-query-runner/enums/database-event-action'; import { DatabaseEventAction } from 'src/engine/api/graphql/graphql-query-runner/enums/database-event-action';
@ -13,7 +13,6 @@ import { UserWorkspaceService } from 'src/engine/core-modules/user-workspace/use
import { User } from 'src/engine/core-modules/user/user.entity'; import { User } from 'src/engine/core-modules/user/user.entity';
import { WorkspaceInvitationService } from 'src/engine/core-modules/workspace-invitation/services/workspace-invitation.service'; import { WorkspaceInvitationService } from 'src/engine/core-modules/workspace-invitation/services/workspace-invitation.service';
import { Workspace } from 'src/engine/core-modules/workspace/workspace.entity'; import { Workspace } from 'src/engine/core-modules/workspace/workspace.entity';
import { DataSourceEntity } from 'src/engine/metadata-modules/data-source/data-source.entity';
import { DataSourceService } from 'src/engine/metadata-modules/data-source/data-source.service'; import { DataSourceService } from 'src/engine/metadata-modules/data-source/data-source.service';
import { ObjectMetadataEntity } from 'src/engine/metadata-modules/object-metadata/object-metadata.entity'; import { ObjectMetadataEntity } from 'src/engine/metadata-modules/object-metadata/object-metadata.entity';
import { PermissionsException } from 'src/engine/metadata-modules/permissions/permissions.exception'; import { PermissionsException } from 'src/engine/metadata-modules/permissions/permissions.exception';
@ -27,7 +26,6 @@ describe('UserWorkspaceService', () => {
let userWorkspaceRepository: Repository<UserWorkspace>; let userWorkspaceRepository: Repository<UserWorkspace>;
let userRepository: Repository<User>; let userRepository: Repository<User>;
let objectMetadataRepository: Repository<ObjectMetadataEntity>; let objectMetadataRepository: Repository<ObjectMetadataEntity>;
let dataSourceService: DataSourceService;
let typeORMService: TypeORMService; let typeORMService: TypeORMService;
let workspaceInvitationService: WorkspaceInvitationService; let workspaceInvitationService: WorkspaceInvitationService;
let workspaceEventEmitter: WorkspaceEventEmitter; let workspaceEventEmitter: WorkspaceEventEmitter;
@ -71,7 +69,7 @@ describe('UserWorkspaceService', () => {
{ {
provide: TypeORMService, provide: TypeORMService,
useValue: { useValue: {
connectToDataSource: jest.fn(), getMainDataSource: jest.fn(),
}, },
}, },
{ {
@ -116,7 +114,6 @@ describe('UserWorkspaceService', () => {
objectMetadataRepository = module.get( objectMetadataRepository = module.get(
getRepositoryToken(ObjectMetadataEntity, 'metadata'), getRepositoryToken(ObjectMetadataEntity, 'metadata'),
); );
dataSourceService = module.get<DataSourceService>(DataSourceService);
typeORMService = module.get<TypeORMService>(TypeORMService); typeORMService = module.get<TypeORMService>(TypeORMService);
workspaceInvitationService = module.get<WorkspaceInvitationService>( workspaceInvitationService = module.get<WorkspaceInvitationService>(
WorkspaceInvitationService, WorkspaceInvitationService,
@ -179,12 +176,9 @@ describe('UserWorkspaceService', () => {
defaultAvatarUrl: 'avatar-url', defaultAvatarUrl: 'avatar-url',
locale: 'en', locale: 'en',
} as User; } as User;
const dataSourceMetadata = { const mainDataSource = {
schema: 'public',
} as DataSourceEntity;
const workspaceDataSource = {
query: jest.fn(), query: jest.fn(),
}; } as unknown as DataSource;
const workspaceMember = [ const workspaceMember = [
{ {
id: 'workspace-member-id', id: 'workspace-member-id',
@ -197,17 +191,16 @@ describe('UserWorkspaceService', () => {
const objectMetadata = { const objectMetadata = {
nameSingular: 'workspaceMember', nameSingular: 'workspaceMember',
} as ObjectMetadataEntity; } as ObjectMetadataEntity;
const workspaceMemberRepository = {
insert: jest.fn(),
find: jest.fn().mockResolvedValue(workspaceMember),
};
jest jest
.spyOn( .spyOn(typeORMService, 'getMainDataSource')
dataSourceService, .mockReturnValue(mainDataSource);
'getLastDataSourceMetadataFromWorkspaceIdOrFail',
)
.mockResolvedValue(dataSourceMetadata);
jest jest
.spyOn(typeORMService, 'connectToDataSource') .spyOn(mainDataSource, 'query')
.mockResolvedValue(workspaceDataSource as any);
workspaceDataSource.query
.mockResolvedValueOnce(undefined) .mockResolvedValueOnce(undefined)
.mockResolvedValueOnce(workspaceMember); .mockResolvedValueOnce(workspaceMember);
jest jest
@ -217,15 +210,23 @@ describe('UserWorkspaceService', () => {
.spyOn(workspaceEventEmitter, 'emitDatabaseBatchEvent') .spyOn(workspaceEventEmitter, 'emitDatabaseBatchEvent')
.mockImplementation(); .mockImplementation();
jest
.spyOn(twentyORMGlobalManager, 'getRepositoryForWorkspace')
.mockResolvedValue(workspaceMemberRepository as any);
await service.createWorkspaceMember(workspaceId, user); await service.createWorkspaceMember(workspaceId, user);
expect( expect(workspaceMemberRepository.insert).toHaveBeenCalledWith({
dataSourceService.getLastDataSourceMetadataFromWorkspaceIdOrFail, name: {
).toHaveBeenCalledWith(workspaceId); firstName: user.firstName,
expect(typeORMService.connectToDataSource).toHaveBeenCalledWith( lastName: user.lastName,
dataSourceMetadata, },
); colorScheme: 'System',
expect(workspaceDataSource.query).toHaveBeenCalledTimes(2); userId: user.id,
userEmail: user.email,
locale: 'en',
avatarUrl: 'avatar-url',
});
expect(objectMetadataRepository.findOneOrFail).toHaveBeenCalledWith({ expect(objectMetadataRepository.findOneOrFail).toHaveBeenCalledWith({
where: { where: {
nameSingular: 'workspaceMember', nameSingular: 'workspaceMember',

View File

@ -2,7 +2,7 @@
import { InjectRepository } from '@nestjs/typeorm'; import { InjectRepository } from '@nestjs/typeorm';
import { TypeOrmQueryService } from '@ptc-org/nestjs-query-typeorm'; import { TypeOrmQueryService } from '@ptc-org/nestjs-query-typeorm';
import { SOURCE_LOCALE } from 'twenty-shared/translations'; import { APP_LOCALES, SOURCE_LOCALE } from 'twenty-shared/translations';
import { isDefined } from 'twenty-shared/utils'; import { isDefined } from 'twenty-shared/utils';
import { Repository } from 'typeorm'; import { Repository } from 'typeorm';
@ -69,33 +69,35 @@ export class UserWorkspaceService extends TypeOrmQueryService<UserWorkspace> {
} }
async createWorkspaceMember(workspaceId: string, user: User) { async createWorkspaceMember(workspaceId: string, user: User) {
const dataSourceMetadata = const workspaceMemberRepository =
await this.dataSourceService.getLastDataSourceMetadataFromWorkspaceIdOrFail( await this.twentyORMGlobalManager.getRepositoryForWorkspace<WorkspaceMemberWorkspaceEntity>(
workspaceId, workspaceId,
'workspaceMember',
{
shouldBypassPermissionChecks: true,
},
); );
const workspaceDataSource = await workspaceMemberRepository.insert({
await this.typeORMService.connectToDataSource(dataSourceMetadata); name: {
firstName: user.firstName,
lastName: user.lastName,
},
colorScheme: 'System',
userId: user.id,
userEmail: user.email,
avatarUrl: user.defaultAvatarUrl ?? '',
locale: (user.locale ?? SOURCE_LOCALE) as keyof typeof APP_LOCALES,
});
await workspaceDataSource?.query( const workspaceMember = await workspaceMemberRepository.find({
`INSERT INTO ${dataSourceMetadata.schema}."workspaceMember" where: {
("nameFirstName", "nameLastName", "colorScheme", "userId", "userEmail", "avatarUrl", "locale") userId: user.id,
VALUES ($1, $2, 'System', $3, $4, $5, $6)`, },
[ });
user.firstName,
user.lastName,
user.id,
user.email,
user.defaultAvatarUrl ?? '',
user.locale ?? SOURCE_LOCALE,
],
);
const workspaceMember = await workspaceDataSource?.query(
`SELECT * FROM ${dataSourceMetadata.schema}."workspaceMember" WHERE "userId"='${user.id}'`,
);
assert( assert(
workspaceMember.length === 1, workspaceMember?.length === 1,
`Error while creating workspace member ${user.email} on workspace ${workspaceId}`, `Error while creating workspace member ${user.email} on workspace ${workspaceId}`,
); );
const objectMetadata = await this.objectMetadataRepository.findOneOrFail({ const objectMetadata = await this.objectMetadataRepository.findOneOrFail({

View File

@ -6,13 +6,11 @@ import { TypeOrmQueryService } from '@ptc-org/nestjs-query-typeorm';
import { isWorkspaceActiveOrSuspended } from 'twenty-shared/workspace'; import { isWorkspaceActiveOrSuspended } from 'twenty-shared/workspace';
import { Repository } from 'typeorm'; import { Repository } from 'typeorm';
import { TypeORMService } from 'src/database/typeorm/typeorm.service';
import { DatabaseEventAction } from 'src/engine/api/graphql/graphql-query-runner/enums/database-event-action'; import { DatabaseEventAction } from 'src/engine/api/graphql/graphql-query-runner/enums/database-event-action';
import { import {
AuthException, AuthException,
AuthExceptionCode, AuthExceptionCode,
} from 'src/engine/core-modules/auth/auth.exception'; } from 'src/engine/core-modules/auth/auth.exception';
import { FeatureFlagService } from 'src/engine/core-modules/feature-flag/services/feature-flag.service';
import { UserWorkspaceService } from 'src/engine/core-modules/user-workspace/user-workspace.service'; import { UserWorkspaceService } from 'src/engine/core-modules/user-workspace/user-workspace.service';
import { User } from 'src/engine/core-modules/user/user.entity'; import { User } from 'src/engine/core-modules/user/user.entity';
import { userValidator } from 'src/engine/core-modules/user/user.validate'; import { userValidator } from 'src/engine/core-modules/user/user.validate';
@ -38,13 +36,11 @@ export class UserService extends TypeOrmQueryService<User> {
@InjectRepository(ObjectMetadataEntity, 'metadata') @InjectRepository(ObjectMetadataEntity, 'metadata')
private readonly objectMetadataRepository: Repository<ObjectMetadataEntity>, private readonly objectMetadataRepository: Repository<ObjectMetadataEntity>,
private readonly dataSourceService: DataSourceService, private readonly dataSourceService: DataSourceService,
private readonly typeORMService: TypeORMService,
private readonly workspaceEventEmitter: WorkspaceEventEmitter, private readonly workspaceEventEmitter: WorkspaceEventEmitter,
private readonly workspaceService: WorkspaceService, private readonly workspaceService: WorkspaceService,
private readonly twentyORMGlobalManager: TwentyORMGlobalManager, private readonly twentyORMGlobalManager: TwentyORMGlobalManager,
private readonly userRoleService: UserRoleService, private readonly userRoleService: UserRoleService,
private readonly userWorkspaceService: UserWorkspaceService, private readonly userWorkspaceService: UserWorkspaceService,
private readonly featureFlagService: FeatureFlagService,
) { ) {
super(userRepository); super(userRepository);
} }
@ -88,17 +84,16 @@ export class UserService extends TypeOrmQueryService<User> {
userId: string; userId: string;
workspaceId: string; workspaceId: string;
}) { }) {
const dataSourceMetadata = const workspaceMemberRepository =
await this.dataSourceService.getLastDataSourceMetadataFromWorkspaceIdOrFail( await this.twentyORMGlobalManager.getRepositoryForWorkspace<WorkspaceMemberWorkspaceEntity>(
workspaceId, workspaceId,
'workspaceMember',
{
shouldBypassPermissionChecks: true,
},
); );
const workspaceDataSource = const workspaceMembers = await workspaceMemberRepository.find();
await this.typeORMService.connectToDataSource(dataSourceMetadata);
const workspaceMembers = await workspaceDataSource?.query(
`SELECT * FROM ${dataSourceMetadata.schema}."workspaceMember"`,
);
if (workspaceMembers.length > 1) { if (workspaceMembers.length > 1) {
const userWorkspace = const userWorkspace =
@ -119,9 +114,7 @@ export class UserService extends TypeOrmQueryService<User> {
assert(workspaceMember, 'WorkspaceMember not found'); assert(workspaceMember, 'WorkspaceMember not found');
await workspaceDataSource?.query( await workspaceMemberRepository.delete({ userId });
`DELETE FROM ${dataSourceMetadata.schema}."workspaceMember" WHERE "userId" = '${userId}'`,
);
const objectMetadata = await this.objectMetadataRepository.findOneOrFail({ const objectMetadata = await this.objectMetadataRepository.findOneOrFail({
where: { where: {

View File

@ -61,11 +61,11 @@ import {
} from 'src/engine/metadata-modules/workspace-migration/workspace-migration.entity'; } from 'src/engine/metadata-modules/workspace-migration/workspace-migration.entity';
import { WorkspaceMigrationFactory } from 'src/engine/metadata-modules/workspace-migration/workspace-migration.factory'; import { WorkspaceMigrationFactory } from 'src/engine/metadata-modules/workspace-migration/workspace-migration.factory';
import { WorkspaceMigrationService } from 'src/engine/metadata-modules/workspace-migration/workspace-migration.service'; import { WorkspaceMigrationService } from 'src/engine/metadata-modules/workspace-migration/workspace-migration.service';
import { WorkspaceEntityManager } from 'src/engine/twenty-orm/entity-manager/workspace-entity-manager';
import { TwentyORMGlobalManager } from 'src/engine/twenty-orm/twenty-orm-global.manager'; import { TwentyORMGlobalManager } from 'src/engine/twenty-orm/twenty-orm-global.manager';
import { computeObjectTargetTable } from 'src/engine/utils/compute-object-target-table.util'; import { computeObjectTargetTable } from 'src/engine/utils/compute-object-target-table.util';
import { WorkspaceMigrationRunnerService } from 'src/engine/workspace-manager/workspace-migration-runner/workspace-migration-runner.service'; import { WorkspaceMigrationRunnerService } from 'src/engine/workspace-manager/workspace-migration-runner/workspace-migration-runner.service';
import { ViewService } from 'src/modules/view/services/view.service'; import { ViewService } from 'src/modules/view/services/view.service';
import { ViewFieldWorkspaceEntity } from 'src/modules/view/standard-objects/view-field.workspace-entity';
import { FieldMetadataValidationService } from './field-metadata-validation.service'; import { FieldMetadataValidationService } from './field-metadata-validation.service';
import { FieldMetadataEntity } from './field-metadata.entity'; import { FieldMetadataEntity } from './field-metadata.entity';
@ -910,74 +910,70 @@ export class FieldMetadataService extends TypeOrmQueryService<FieldMetadataEntit
createdFieldMetadatas: FieldMetadataEntity[], createdFieldMetadatas: FieldMetadataEntity[],
workspaceId: string, workspaceId: string,
) { ) {
const dataSourceMetadata =
await this.dataSourceService.getLastDataSourceMetadataFromWorkspaceIdOrFail(
workspaceId,
);
const workspaceDataSource = const workspaceDataSource =
await this.typeORMService.connectToDataSource(dataSourceMetadata); await this.twentyORMGlobalManager.getDataSourceForWorkspace({
workspaceId,
});
const workspaceQueryRunner = workspaceDataSource?.createQueryRunner(); await workspaceDataSource.transaction(
async (workspaceEntityManager: WorkspaceEntityManager) => {
const viewsRepository = workspaceEntityManager.getRepository('view', {
shouldBypassPermissionChecks: true,
});
if (!workspaceQueryRunner) { const viewFieldsRepository = workspaceEntityManager.getRepository(
throw new FieldMetadataException( 'viewField',
'Could not create workspace query runner', {
FieldMetadataExceptionCode.INTERNAL_SERVER_ERROR, shouldBypassPermissionChecks: true,
); },
}
await workspaceQueryRunner.connect();
await workspaceQueryRunner.startTransaction();
try {
for (const createdFieldMetadata of createdFieldMetadatas) {
const view = await workspaceQueryRunner?.query(
`SELECT id FROM ${dataSourceMetadata.schema}."view"
WHERE "objectMetadataId" = '${createdFieldMetadata.objectMetadataId}'`,
); );
if (!isEmpty(view)) { for (const createdFieldMetadata of createdFieldMetadatas) {
const existingViewFields = (await workspaceQueryRunner?.query( const views = await viewsRepository.find({
`SELECT * FROM ${dataSourceMetadata.schema}."viewField" where: {
WHERE "viewId" = '${view[0].id}'`, objectMetadataId: createdFieldMetadata.objectMetadataId,
)) as ViewFieldWorkspaceEntity[]; },
const isVisible = });
existingViewFields.length < settings.maxVisibleViewFields;
const createdFieldIsAlreadyInView = existingViewFields.some( if (!isEmpty(views)) {
(existingViewField) => const view = views[0];
existingViewField.fieldMetadataId === createdFieldMetadata.id, const existingViewFields = await viewFieldsRepository.find({
); where: {
viewId: view.id,
},
});
if (!createdFieldIsAlreadyInView) { const isVisible =
const lastPosition = existingViewFields existingViewFields.length < settings.maxVisibleViewFields;
.map((viewField) => viewField.position)
.reduce((acc, position) => {
if (position > acc) {
return position;
}
return acc; const createdFieldIsAlreadyInView = existingViewFields.some(
}, -1); (existingViewField) =>
existingViewField.fieldMetadataId === createdFieldMetadata.id,
await workspaceQueryRunner?.query(
`INSERT INTO ${dataSourceMetadata.schema}."viewField"
("fieldMetadataId", "position", "isVisible", "size", "viewId")
VALUES ('${createdFieldMetadata.id}', '${
lastPosition + 1
}', ${isVisible}, 180, '${view[0].id}')`,
); );
if (!createdFieldIsAlreadyInView) {
const lastPosition = existingViewFields
.map((viewField) => viewField.position)
.reduce((acc, position) => {
if (position > acc) {
return position;
}
return acc;
}, -1);
await viewFieldsRepository.insert({
fieldMetadataId: createdFieldMetadata.id,
position: lastPosition + 1,
isVisible,
size: 180,
viewId: view.id,
});
}
} }
} }
} },
await workspaceQueryRunner.commitTransaction(); );
} catch (error) {
await workspaceQueryRunner.rollbackTransaction();
throw error;
} finally {
await workspaceQueryRunner.release();
}
} }
async getFieldMetadataItemsByBatch( async getFieldMetadataItemsByBatch(

View File

@ -8,15 +8,15 @@ import {
RemoteServerEntity, RemoteServerEntity,
RemoteServerType, RemoteServerType,
} from 'src/engine/metadata-modules/remote-server/remote-server.entity'; } from 'src/engine/metadata-modules/remote-server/remote-server.entity';
import { WorkspaceDataSourceService } from 'src/engine/workspace-datasource/workspace-datasource.service';
import { DistantTables } from 'src/engine/metadata-modules/remote-server/remote-table/distant-table/types/distant-table';
import { STRIPE_DISTANT_TABLES } from 'src/engine/metadata-modules/remote-server/remote-table/distant-table/utils/stripe-distant-tables.util';
import { PostgresTableSchemaColumn } from 'src/engine/metadata-modules/remote-server/types/postgres-table-schema-column';
import { isQueryTimeoutError } from 'src/engine/utils/query-timeout.util';
import { import {
DistantTableException, DistantTableException,
DistantTableExceptionCode, DistantTableExceptionCode,
} from 'src/engine/metadata-modules/remote-server/remote-table/distant-table/distant-table.exception'; } from 'src/engine/metadata-modules/remote-server/remote-table/distant-table/distant-table.exception';
import { DistantTables } from 'src/engine/metadata-modules/remote-server/remote-table/distant-table/types/distant-table';
import { STRIPE_DISTANT_TABLES } from 'src/engine/metadata-modules/remote-server/remote-table/distant-table/utils/stripe-distant-tables.util';
import { PostgresTableSchemaColumn } from 'src/engine/metadata-modules/remote-server/types/postgres-table-schema-column';
import { isQueryTimeoutError } from 'src/engine/utils/query-timeout.util';
import { WorkspaceDataSourceService } from 'src/engine/workspace-datasource/workspace-datasource.service';
@Injectable() @Injectable()
export class DistantTableService { export class DistantTableService {
@ -73,13 +73,11 @@ export class DistantTableService {
const tmpSchemaId = v4(); const tmpSchemaId = v4();
const tmpSchemaName = `${workspaceId}_${remoteServer.id}_${tmpSchemaId}`; const tmpSchemaName = `${workspaceId}_${remoteServer.id}_${tmpSchemaId}`;
const workspaceDataSource = const mainDataSource =
await this.workspaceDataSourceService.connectToWorkspaceDataSource( await this.workspaceDataSourceService.connectToMainDataSource();
workspaceId,
);
try { try {
const distantTables = await workspaceDataSource.transaction( const distantTables = await mainDataSource.transaction(
async (entityManager: EntityManager) => { async (entityManager: EntityManager) => {
await entityManager.query(`CREATE SCHEMA "${tmpSchemaName}"`); await entityManager.query(`CREATE SCHEMA "${tmpSchemaName}"`);

View File

@ -37,13 +37,11 @@ export class ForeignTableService {
workspaceId: string, workspaceId: string,
foreignDataWrapperId: string, foreignDataWrapperId: string,
): Promise<string[]> { ): Promise<string[]> {
const workspaceDataSource = const mainDataSource =
await this.workspaceDataSourceService.connectToWorkspaceDataSource( await this.workspaceDataSourceService.connectToMainDataSource();
workspaceId,
);
return ( return (
await workspaceDataSource.query( await mainDataSource.query(
`SELECT foreign_table_name, foreign_server_name FROM information_schema.foreign_tables WHERE foreign_server_name = $1`, `SELECT foreign_table_name, foreign_server_name FROM information_schema.foreign_tables WHERE foreign_server_name = $1`,
[foreignDataWrapperId], [foreignDataWrapperId],
) )

View File

@ -182,16 +182,14 @@ export class RemoteTableService {
workspaceId, workspaceId,
); );
const workspaceDataSource = const mainDataSource =
await this.workspaceDataSourceService.connectToWorkspaceDataSource( await this.workspaceDataSourceService.connectToMainDataSource();
workspaceId,
);
const { baseName: localTableBaseName, suffix: localTableSuffix } = const { baseName: localTableBaseName, suffix: localTableSuffix } =
await getRemoteTableLocalName( await getRemoteTableLocalName(
input.name, input.name,
dataSourceMetatada.schema, dataSourceMetatada.schema,
workspaceDataSource, mainDataSource,
); );
const localTableName = localTableSuffix const localTableName = localTableSuffix

View File

@ -61,13 +61,10 @@ export class SeederService {
const schemaName = const schemaName =
this.workspaceDataSourceService.getSchemaName(workspaceId); this.workspaceDataSourceService.getSchemaName(workspaceId);
const workspaceDataSource: DataSource = const mainDataSource: DataSource =
await this.workspaceDataSourceService.connectToWorkspaceDataSource( await this.workspaceDataSourceService.connectToMainDataSource();
workspaceId,
);
const entityManager: EntityManager = const entityManager: EntityManager = mainDataSource.createEntityManager();
workspaceDataSource.createEntityManager();
const filteredFieldMetadataSeeds = objectMetadataSeed.fields.filter( const filteredFieldMetadataSeeds = objectMetadataSeed.fields.filter(
(field) => (field) =>

View File

@ -47,15 +47,15 @@ export class WorkspaceDataSource extends DataSource {
roleId?: string, roleId?: string,
): WorkspaceRepository<Entity> { ): WorkspaceRepository<Entity> {
if (shouldBypassPermissionChecks === true) { if (shouldBypassPermissionChecks === true) {
return this.manager.getRepository(target, shouldBypassPermissionChecks); return this.manager.getRepository(target, {
shouldBypassPermissionChecks: true,
});
} }
if (roleId) { if (roleId) {
return this.manager.getRepository( return this.manager.getRepository(target, {
target,
shouldBypassPermissionChecks,
roleId, roleId,
); });
} }
return this.manager.getRepository(target); return this.manager.getRepository(target);

View File

@ -2,6 +2,7 @@ import { ObjectRecordsPermissions } from 'twenty-shared/types';
import { import {
EntityManager, EntityManager,
EntityTarget, EntityTarget,
FindManyOptions,
InsertResult, InsertResult,
ObjectLiteral, ObjectLiteral,
QueryRunner, QueryRunner,
@ -44,16 +45,19 @@ export class WorkspaceEntityManager extends EntityManager {
override getRepository<Entity extends ObjectLiteral>( override getRepository<Entity extends ObjectLiteral>(
target: EntityTarget<Entity>, target: EntityTarget<Entity>,
shouldBypassPermissionChecks = false, permissionOptions?: {
roleId?: string, shouldBypassPermissionChecks?: boolean;
roleId?: string;
},
): WorkspaceRepository<Entity> { ): WorkspaceRepository<Entity> {
const dataSource = this.connection; const dataSource = this.connection;
const repositoryKey = this.getRepositoryKey({ const repositoryKey = this.getRepositoryKey({
target, target,
dataSource, dataSource,
roleId, roleId: permissionOptions?.roleId,
shouldBypassPermissionChecks, shouldBypassPermissionChecks:
permissionOptions?.shouldBypassPermissionChecks ?? false,
}); });
const repoFromMap = this.repositories.get(repositoryKey); const repoFromMap = this.repositories.get(repositoryKey);
@ -63,10 +67,11 @@ export class WorkspaceEntityManager extends EntityManager {
let objectPermissions = {}; let objectPermissions = {};
if (roleId) { if (permissionOptions?.roleId) {
const objectPermissionsByRoleId = dataSource.permissionsPerRoleId; const objectPermissionsByRoleId = dataSource.permissionsPerRoleId;
objectPermissions = objectPermissionsByRoleId?.[roleId] ?? {}; objectPermissions =
objectPermissionsByRoleId?.[permissionOptions?.roleId] ?? {};
} }
const newRepository = new WorkspaceRepository<Entity>( const newRepository = new WorkspaceRepository<Entity>(
@ -76,7 +81,7 @@ export class WorkspaceEntityManager extends EntityManager {
dataSource.featureFlagMap, dataSource.featureFlagMap,
this.queryRunner, this.queryRunner,
objectPermissions, objectPermissions,
shouldBypassPermissionChecks, permissionOptions?.shouldBypassPermissionChecks,
); );
this.repositories.set(repositoryKey, newRepository); this.repositories.set(repositoryKey, newRepository);
@ -135,17 +140,30 @@ export class WorkspaceEntityManager extends EntityManager {
} }
} }
override find<Entity extends ObjectLiteral>(
target: EntityTarget<Entity>,
options?: FindManyOptions<Entity>,
permissionOptions?: {
shouldBypassPermissionChecks?: boolean;
objectRecordsPermissions?: ObjectRecordsPermissions;
},
): Promise<Entity[]> {
this.validatePermissions(target, 'select', permissionOptions);
return super.find(target, options);
}
override insert<Entity extends ObjectLiteral>( override insert<Entity extends ObjectLiteral>(
target: EntityTarget<Entity>, target: EntityTarget<Entity>,
entityOrEntities: entityOrEntities:
| QueryDeepPartialEntity<Entity> | QueryDeepPartialEntity<Entity>
| QueryDeepPartialEntity<Entity>[], | QueryDeepPartialEntity<Entity>[],
options?: { permissionOptions?: {
shouldBypassPermissionChecks?: boolean; shouldBypassPermissionChecks?: boolean;
objectRecordsPermissions?: ObjectRecordsPermissions; objectRecordsPermissions?: ObjectRecordsPermissions;
}, },
): Promise<InsertResult> { ): Promise<InsertResult> {
this.validatePermissions(target, 'insert', options); this.validatePermissions(target, 'insert', permissionOptions);
return super.insert(target, entityOrEntities); return super.insert(target, entityOrEntities);
} }
@ -156,12 +174,12 @@ export class WorkspaceEntityManager extends EntityManager {
| QueryDeepPartialEntity<Entity> | QueryDeepPartialEntity<Entity>
| QueryDeepPartialEntity<Entity>[], | QueryDeepPartialEntity<Entity>[],
conflictPathsOrOptions: string[] | UpsertOptions<Entity>, conflictPathsOrOptions: string[] | UpsertOptions<Entity>,
options?: { permissionOptions?: {
shouldBypassPermissionChecks?: boolean; shouldBypassPermissionChecks?: boolean;
objectRecordsPermissions?: ObjectRecordsPermissions; objectRecordsPermissions?: ObjectRecordsPermissions;
}, },
): Promise<InsertResult> { ): Promise<InsertResult> {
this.validatePermissions(target, 'update', options); this.validatePermissions(target, 'update', permissionOptions);
return super.upsert(target, entityOrEntities, conflictPathsOrOptions); return super.upsert(target, entityOrEntities, conflictPathsOrOptions);
} }
@ -194,7 +212,7 @@ export class WorkspaceEntityManager extends EntityManager {
private validatePermissions<Entity extends ObjectLiteral>( private validatePermissions<Entity extends ObjectLiteral>(
target: EntityTarget<Entity>, target: EntityTarget<Entity>,
operationType: OperationType, operationType: OperationType,
options?: { permissionOptions?: {
shouldBypassPermissionChecks?: boolean; shouldBypassPermissionChecks?: boolean;
objectRecordsPermissions?: ObjectRecordsPermissions; objectRecordsPermissions?: ObjectRecordsPermissions;
}, },
@ -208,14 +226,15 @@ export class WorkspaceEntityManager extends EntityManager {
return; return;
} }
if (options?.shouldBypassPermissionChecks === true) { if (permissionOptions?.shouldBypassPermissionChecks === true) {
return; return;
} }
validateOperationIsPermittedOrThrow({ validateOperationIsPermittedOrThrow({
entityName: this.extractTargetNameSingularFromEntityTarget(target), entityName: this.extractTargetNameSingularFromEntityTarget(target),
operationType, operationType,
objectRecordsPermissions: options?.objectRecordsPermissions ?? {}, objectRecordsPermissions:
permissionOptions?.objectRecordsPermissions ?? {},
objectMetadataMaps: this.internalContext.objectMetadataMaps, objectMetadataMaps: this.internalContext.objectMetadataMaps,
}); });
} }

View File

@ -65,10 +65,13 @@ export class TwentyORMGlobalManager {
return repository; return repository;
} }
async getDataSourceForWorkspace( async getDataSourceForWorkspace({
workspaceId: string, workspaceId,
shouldFailIfMetadataNotFound = true, shouldFailIfMetadataNotFound = true,
) { }: {
workspaceId: string;
shouldFailIfMetadataNotFound?: boolean;
}) {
return await this.workspaceDataSourceFactory.create( return await this.workspaceDataSourceFactory.create(
workspaceId, workspaceId,
null, null,

View File

@ -2,6 +2,7 @@ import { Injectable } from '@nestjs/common';
import crypto from 'crypto'; import crypto from 'crypto';
import { isDefined } from 'twenty-shared/utils';
import { EntitySchemaOptions } from 'typeorm'; import { EntitySchemaOptions } from 'typeorm';
import { FeatureFlagMap } from 'src/engine/core-modules/feature-flag/interfaces/feature-flag-map.interface'; import { FeatureFlagMap } from 'src/engine/core-modules/feature-flag/interfaces/feature-flag-map.interface';
@ -277,8 +278,13 @@ export class WorkspaceCacheStorageService {
); );
} }
async flush(workspaceId: string, metadataVersion: number): Promise<void> { async flush(
await this.flushVersionedMetadata(workspaceId, metadataVersion); workspaceId: string,
metadataVersion: number | undefined,
): Promise<void> {
if (isDefined(metadataVersion)) {
await this.flushVersionedMetadata(workspaceId, metadataVersion);
}
await this.cacheStorageService.del( await this.cacheStorageService.del(
`${WorkspaceCacheKeys.MetadataPermissionsRolesPermissions}:${workspaceId}`, `${WorkspaceCacheKeys.MetadataPermissionsRolesPermissions}:${workspaceId}`,

View File

@ -3,7 +3,6 @@ import { Injectable } from '@nestjs/common';
import { DataSource, EntityManager } from 'typeorm'; import { DataSource, EntityManager } from 'typeorm';
import { TypeORMService } from 'src/database/typeorm/typeorm.service'; import { TypeORMService } from 'src/database/typeorm/typeorm.service';
import { DataSourceEntity } from 'src/engine/metadata-modules/data-source/data-source.entity';
import { DataSourceService } from 'src/engine/metadata-modules/data-source/data-source.service'; import { DataSourceService } from 'src/engine/metadata-modules/data-source/data-source.service';
@Injectable() @Injectable()
@ -20,11 +19,12 @@ export class WorkspaceDataSourceService {
* @param workspaceId * @param workspaceId
* @returns * @returns
*/ */
public async connectToWorkspaceDataSource( public async connectToMainDataSource(): Promise<DataSource> {
workspaceId: string, const dataSource = this.typeormService.getMainDataSource();
): Promise<DataSource> {
const { dataSource } = if (!dataSource) {
await this.connectedToWorkspaceDataSourceAndReturnMetadata(workspaceId); throw new Error(`Could not connect to workspace data source`);
}
return dataSource; return dataSource;
} }
@ -38,26 +38,6 @@ export class WorkspaceDataSourceService {
return dataSource.length > 0; return dataSource.length > 0;
} }
public async connectedToWorkspaceDataSourceAndReturnMetadata(
workspaceId: string,
): Promise<{ dataSource: DataSource; dataSourceMetadata: DataSourceEntity }> {
const dataSourceMetadata =
await this.dataSourceService.getLastDataSourceMetadataFromWorkspaceIdOrFail(
workspaceId,
);
const dataSource =
await this.typeormService.connectToDataSource(dataSourceMetadata);
if (!dataSource) {
throw new Error(
`Could not connect to workspace data source for workspace ${workspaceId}`,
);
}
return { dataSource, dataSourceMetadata };
}
/** /**
* *
* Create a new DB schema for a workspace * Create a new DB schema for a workspace
@ -128,10 +108,9 @@ export class WorkspaceDataSourceService {
if (transactionManager) { if (transactionManager) {
return await transactionManager.query(query, parameters); return await transactionManager.query(query, parameters);
} }
const workspaceDataSource = const dataSource = await this.connectToMainDataSource();
await this.connectToWorkspaceDataSource(workspaceId);
return await workspaceDataSource.query(query, parameters); return await dataSource.query(query, parameters);
} catch (error) { } catch (error) {
throw new Error( throw new Error(
`Error executing raw query for workspace ${workspaceId}: ${error.message}`, `Error executing raw query for workspace ${workspaceId}: ${error.message}`,

View File

@ -9,7 +9,7 @@ import { personPrefillData } from 'src/engine/workspace-manager/standard-objects
import { seedViewWithDemoData } from 'src/engine/workspace-manager/standard-objects-prefill-data/seed-view-with-demo-data'; import { seedViewWithDemoData } from 'src/engine/workspace-manager/standard-objects-prefill-data/seed-view-with-demo-data';
export const standardObjectsPrefillData = async ( export const standardObjectsPrefillData = async (
workspaceDataSource: DataSource, mainDataSource: DataSource,
schemaName: string, schemaName: string,
objectMetadata: ObjectMetadataEntity[], objectMetadata: ObjectMetadataEntity[],
) => { ) => {
@ -34,30 +34,28 @@ export const standardObjectsPrefillData = async (
return acc; return acc;
}, {}); }, {});
workspaceDataSource.transaction( mainDataSource.transaction(async (entityManager: WorkspaceEntityManager) => {
async (entityManager: WorkspaceEntityManager) => { await companyPrefillData(entityManager, schemaName);
await companyPrefillData(entityManager, schemaName); await personPrefillData(entityManager, schemaName);
await personPrefillData(entityManager, schemaName); const viewDefinitionsWithId = await seedViewWithDemoData(
const viewDefinitionsWithId = await seedViewWithDemoData( entityManager,
entityManager, schemaName,
schemaName, objectMetadataMap,
objectMetadataMap, );
);
await seedWorkspaceFavorites( await seedWorkspaceFavorites(
viewDefinitionsWithId viewDefinitionsWithId
.filter( .filter(
(view) => (view) =>
view.key === 'INDEX' && view.key === 'INDEX' &&
shouldSeedWorkspaceFavorite( shouldSeedWorkspaceFavorite(
view.objectMetadataId, view.objectMetadataId,
objectMetadataMap, objectMetadataMap,
), ),
) )
.map((view) => view.id), .map((view) => view.id),
entityManager, entityManager,
schemaName, schemaName,
); );
}, });
);
}; };

View File

@ -10,7 +10,6 @@ import {
WorkspaceHealthOptions, WorkspaceHealthOptions,
} from 'src/engine/workspace-manager/workspace-health/interfaces/workspace-health-options.interface'; } from 'src/engine/workspace-manager/workspace-health/interfaces/workspace-health-options.interface';
import { TypeORMService } from 'src/database/typeorm/typeorm.service';
import { DataSourceService } from 'src/engine/metadata-modules/data-source/data-source.service'; import { DataSourceService } from 'src/engine/metadata-modules/data-source/data-source.service';
import { ObjectMetadataService } from 'src/engine/metadata-modules/object-metadata/object-metadata.service'; import { ObjectMetadataService } from 'src/engine/metadata-modules/object-metadata/object-metadata.service';
import { WorkspaceMigrationEntity } from 'src/engine/metadata-modules/workspace-migration/workspace-migration.entity'; import { WorkspaceMigrationEntity } from 'src/engine/metadata-modules/workspace-migration/workspace-migration.entity';
@ -31,7 +30,6 @@ export class WorkspaceHealthService {
@InjectDataSource('metadata') @InjectDataSource('metadata')
private readonly metadataDataSource: DataSource, private readonly metadataDataSource: DataSource,
private readonly dataSourceService: DataSourceService, private readonly dataSourceService: DataSourceService,
private readonly typeORMService: TypeORMService,
private readonly objectMetadataService: ObjectMetadataService, private readonly objectMetadataService: ObjectMetadataService,
private readonly databaseStructureService: DatabaseStructureService, private readonly databaseStructureService: DatabaseStructureService,
private readonly workspaceDataSourceService: WorkspaceDataSourceService, private readonly workspaceDataSourceService: WorkspaceDataSourceService,
@ -62,9 +60,6 @@ export class WorkspaceHealthService {
); );
} }
// Try to connect to the data source
await this.typeORMService.connectToDataSource(dataSourceMetadata);
const objectMetadataCollection = const objectMetadataCollection =
await this.objectMetadataService.findManyWithinWorkspace(workspaceId); await this.objectMetadataService.findManyWithinWorkspace(workspaceId);

View File

@ -196,20 +196,18 @@ export class WorkspaceManagerService {
dataSourceMetadata: DataSourceEntity, dataSourceMetadata: DataSourceEntity,
workspaceId: string, workspaceId: string,
) { ) {
const workspaceDataSource = const mainDataSource =
await this.workspaceDataSourceService.connectToWorkspaceDataSource( await this.workspaceDataSourceService.connectToMainDataSource();
workspaceId,
);
if (!workspaceDataSource) { if (!mainDataSource) {
throw new Error('Could not connect to workspace data source'); throw new Error('Could not connect to main data source');
} }
const createdObjectMetadata = const createdObjectMetadata =
await this.objectMetadataService.findManyWithinWorkspace(workspaceId); await this.objectMetadataService.findManyWithinWorkspace(workspaceId);
await standardObjectsPrefillData( await standardObjectsPrefillData(
workspaceDataSource, mainDataSource,
dataSourceMetadata.schema, dataSourceMetadata.schema,
createdObjectMetadata, createdObjectMetadata,
); );
@ -226,20 +224,18 @@ export class WorkspaceManagerService {
dataSourceMetadata: DataSourceEntity, dataSourceMetadata: DataSourceEntity,
workspaceId: string, workspaceId: string,
) { ) {
const workspaceDataSource = const mainDataSource =
await this.workspaceDataSourceService.connectToWorkspaceDataSource( await this.workspaceDataSourceService.connectToMainDataSource();
workspaceId,
);
if (!workspaceDataSource) { if (!mainDataSource) {
throw new Error('Could not connect to workspace data source'); throw new Error('Could not connect to main data source');
} }
const createdObjectMetadata = const createdObjectMetadata =
await this.objectMetadataService.findManyWithinWorkspace(workspaceId); await this.objectMetadataService.findManyWithinWorkspace(workspaceId);
await seedWorkspaceWithDemoData( await seedWorkspaceWithDemoData(
workspaceDataSource, mainDataSource,
dataSourceMetadata.schema, dataSourceMetadata.schema,
createdObjectMetadata, createdObjectMetadata,
); );

View File

@ -55,13 +55,11 @@ export class WorkspaceMigrationRunnerService {
public async executeMigrationFromPendingMigrations( public async executeMigrationFromPendingMigrations(
workspaceId: string, workspaceId: string,
): Promise<WorkspaceMigrationTableAction[]> { ): Promise<WorkspaceMigrationTableAction[]> {
const workspaceDataSource = const mainDataSource =
await this.workspaceDataSourceService.connectToWorkspaceDataSource( await this.workspaceDataSourceService.connectToMainDataSource();
workspaceId,
);
if (!workspaceDataSource) { if (!mainDataSource) {
throw new Error('Workspace data source not found'); throw new Error('Main data source not found');
} }
const pendingMigrations = const pendingMigrations =
@ -76,7 +74,7 @@ export class WorkspaceMigrationRunnerService {
return [...acc, ...pendingMigration.migrations]; return [...acc, ...pendingMigration.migrations];
}, []); }, []);
const queryRunner = workspaceDataSource?.createQueryRunner(); const queryRunner = mainDataSource.createQueryRunner();
await queryRunner.connect(); await queryRunner.connect();
await queryRunner.startTransaction(); await queryRunner.startTransaction();

View File

@ -28,7 +28,6 @@ describe('destroyOneObjectRecordsPermissions', () => {
}); });
it('should throw a permission error when user does not have permission (guest role)', async () => { it('should throw a permission error when user does not have permission (guest role)', async () => {
const personId = randomUUID();
const graphqlOperation = destroyOneOperationFactory({ const graphqlOperation = destroyOneOperationFactory({
objectMetadataSingularName: 'person', objectMetadataSingularName: 'person',
gqlFields: PERSON_GQL_FIELDS, gqlFields: PERSON_GQL_FIELDS,