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:
@ -1,7 +1,7 @@
|
||||
import chalk from 'chalk';
|
||||
import { Option } from 'nest-commander';
|
||||
import { In, MoreThanOrEqual, Repository } from 'typeorm';
|
||||
import { WorkspaceActivationStatus } from 'twenty-shared/workspace';
|
||||
import { In, MoreThanOrEqual, Repository } from 'typeorm';
|
||||
|
||||
import { MigrationCommandRunner } from 'src/database/commands/command-runners/migration.command-runner';
|
||||
import { Workspace } from 'src/engine/core-modules/workspace/workspace.entity';
|
||||
@ -138,10 +138,10 @@ export abstract class ActiveOrSuspendedWorkspacesMigrationCommandRunner<
|
||||
|
||||
try {
|
||||
const dataSource =
|
||||
await this.twentyORMGlobalManager.getDataSourceForWorkspace(
|
||||
await this.twentyORMGlobalManager.getDataSourceForWorkspace({
|
||||
workspaceId,
|
||||
false,
|
||||
);
|
||||
shouldFailIfMetadataNotFound: false,
|
||||
});
|
||||
|
||||
await this.runOnWorkspace({
|
||||
options,
|
||||
|
||||
@ -46,7 +46,7 @@ export class DataSeedDemoWorkspaceService {
|
||||
const appVersion = this.twentyConfigService.get('APP_VERSION');
|
||||
|
||||
await seedCoreSchema({
|
||||
workspaceDataSource: rawDataSource,
|
||||
dataSource: rawDataSource,
|
||||
workspaceId,
|
||||
appVersion,
|
||||
seedBilling: false,
|
||||
|
||||
@ -30,9 +30,6 @@ import { seedPeople } from 'src/database/typeorm-seeds/workspace/seedPeople';
|
||||
import { seedWorkspaceMember } from 'src/database/typeorm-seeds/workspace/workspace-members';
|
||||
import { rawDataSource } from 'src/database/typeorm/raw/raw.datasource';
|
||||
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 { DataSourceEntity } from 'src/engine/metadata-modules/data-source/data-source.entity';
|
||||
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 { WorkspaceEntityManager } from 'src/engine/twenty-orm/entity-manager/workspace-entity-manager';
|
||||
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 { 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 { 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';
|
||||
|
||||
// TODO: implement dry-run
|
||||
@Command({
|
||||
name: 'workspace:seed:dev',
|
||||
@ -66,11 +63,10 @@ export class DataSeedWorkspaceCommand extends CommandRunner {
|
||||
private readonly typeORMService: TypeORMService,
|
||||
private readonly fieldMetadataService: FieldMetadataService,
|
||||
private readonly objectMetadataService: ObjectMetadataService,
|
||||
@InjectCacheStorage(CacheStorageNamespace.EngineWorkspace)
|
||||
private readonly workspaceSchemaCache: CacheStorageService,
|
||||
private readonly seederService: SeederService,
|
||||
private readonly workspaceManagerService: WorkspaceManagerService,
|
||||
private readonly twentyConfigService: TwentyConfigService,
|
||||
private readonly workspaceCacheStorageService: WorkspaceCacheStorageService,
|
||||
) {
|
||||
super();
|
||||
}
|
||||
@ -92,7 +88,13 @@ export class DataSeedWorkspaceCommand extends CommandRunner {
|
||||
}
|
||||
|
||||
async createWorkspaceSchema(workspaceId: string) {
|
||||
await this.workspaceSchemaCache.flush();
|
||||
const workspaceCachedMetadataVersion =
|
||||
await this.workspaceCacheStorageService.getMetadataVersion(workspaceId);
|
||||
|
||||
await this.workspaceCacheStorageService.flush(
|
||||
workspaceId,
|
||||
workspaceCachedMetadataVersion,
|
||||
);
|
||||
|
||||
await rawDataSource.initialize();
|
||||
|
||||
@ -100,7 +102,7 @@ export class DataSeedWorkspaceCommand extends CommandRunner {
|
||||
const appVersion = this.twentyConfigService.get('APP_VERSION');
|
||||
|
||||
await seedCoreSchema({
|
||||
workspaceDataSource: rawDataSource,
|
||||
dataSource: rawDataSource,
|
||||
workspaceId,
|
||||
seedBilling: isBillingEnabled,
|
||||
appVersion,
|
||||
@ -117,10 +119,9 @@ export class DataSeedWorkspaceCommand extends CommandRunner {
|
||||
workspaceId,
|
||||
);
|
||||
|
||||
const workspaceDataSource =
|
||||
await this.typeORMService.connectToDataSource(dataSourceMetadata);
|
||||
const mainDataSource = this.typeORMService.getMainDataSource();
|
||||
|
||||
if (!workspaceDataSource) {
|
||||
if (!mainDataSource) {
|
||||
throw new Error('Could not connect to workspace data source');
|
||||
}
|
||||
|
||||
@ -140,10 +141,7 @@ export class DataSeedWorkspaceCommand extends CommandRunner {
|
||||
workspaceId,
|
||||
);
|
||||
|
||||
await this.seedStandardObjectRecords(
|
||||
workspaceDataSource,
|
||||
dataSourceMetadata,
|
||||
);
|
||||
await this.seedStandardObjectRecords(mainDataSource, dataSourceMetadata);
|
||||
|
||||
await this.seederService.seedCustomObjects(
|
||||
dataSourceMetadata.id,
|
||||
@ -161,15 +159,13 @@ export class DataSeedWorkspaceCommand extends CommandRunner {
|
||||
} catch (error) {
|
||||
this.logger.error(error);
|
||||
}
|
||||
|
||||
await this.typeORMService.disconnectFromDataSource(dataSourceMetadata.id);
|
||||
}
|
||||
|
||||
async seedStandardObjectRecords(
|
||||
workspaceDataSource: DataSource,
|
||||
mainDataSource: DataSource,
|
||||
dataSourceMetadata: DataSourceEntity,
|
||||
) {
|
||||
await workspaceDataSource.transaction(
|
||||
await mainDataSource.transaction(
|
||||
async (entityManager: WorkspaceEntityManager) => {
|
||||
const { objectMetadataStandardIdToIdMap } =
|
||||
await this.objectMetadataService.getObjectMetadataStandardIdToIdMap(
|
||||
|
||||
@ -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 { ObjectMetadataModule } from 'src/engine/metadata-modules/object-metadata/object-metadata.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';
|
||||
|
||||
@Module({
|
||||
@ -21,6 +22,7 @@ import { WorkspaceManagerModule } from 'src/engine/workspace-manager/workspace-m
|
||||
SeederModule,
|
||||
WorkspaceManagerModule,
|
||||
DataSourceModule,
|
||||
WorkspaceCacheStorageModule,
|
||||
],
|
||||
providers: [DataSeedWorkspaceCommand, ConfirmationQuestion],
|
||||
})
|
||||
|
||||
@ -227,12 +227,11 @@ export class MigrateRichTextContentPatchCommand extends ActiveOrSuspendedWorkspa
|
||||
const schemaName =
|
||||
this.workspaceDataSourceService.getSchemaName(workspaceId);
|
||||
|
||||
const failOnMetadataCacheMiss = false;
|
||||
const workspaceDataSource =
|
||||
await this.twentyORMGlobalManager.getDataSourceForWorkspace(
|
||||
await this.twentyORMGlobalManager.getDataSourceForWorkspace({
|
||||
workspaceId,
|
||||
failOnMetadataCacheMiss,
|
||||
);
|
||||
shouldFailIfMetadataNotFound: false,
|
||||
});
|
||||
|
||||
const rows = await workspaceDataSource.query(
|
||||
`SELECT id, "${richTextField.name}" FROM "${schemaName}"."${computeTableName(objectMetadata.nameSingular, objectMetadata.isCustom)}" WHERE "${richTextField.name}" IS NOT NULL`,
|
||||
|
||||
@ -1,18 +1,18 @@
|
||||
import { InjectRepository } from '@nestjs/typeorm';
|
||||
|
||||
import { Command } from 'nest-commander';
|
||||
import { Repository } from 'typeorm';
|
||||
import { isDefined } from 'twenty-shared/utils';
|
||||
import { Repository } from 'typeorm';
|
||||
|
||||
import {
|
||||
ActiveOrSuspendedWorkspacesMigrationCommandRunner,
|
||||
RunOnWorkspaceArgs,
|
||||
} 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 { ObjectMetadataEntity } from 'src/engine/metadata-modules/object-metadata/object-metadata.entity';
|
||||
import { TwentyORMGlobalManager } from 'src/engine/twenty-orm/twenty-orm-global.manager';
|
||||
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({
|
||||
name: 'upgrade:0-51:upgrade-created-by-enum',
|
||||
@ -43,7 +43,9 @@ export class UpgradeCreatedByEnumCommand extends ActiveOrSuspendedWorkspacesMigr
|
||||
this.workspaceDataSourceService.getSchemaName(workspaceId);
|
||||
|
||||
const workspaceDataSource =
|
||||
await this.twentyORMGlobalManager.getDataSourceForWorkspace(workspaceId);
|
||||
await this.twentyORMGlobalManager.getDataSourceForWorkspace({
|
||||
workspaceId,
|
||||
});
|
||||
|
||||
const objectMetadatas = await this.objectMetadataRepository.find({
|
||||
where: {
|
||||
|
||||
@ -7,7 +7,7 @@ import { seedUsers } from 'src/database/typeorm-seeds/core/users';
|
||||
import { seedWorkspaces } from 'src/database/typeorm-seeds/core/workspaces';
|
||||
|
||||
type SeedCoreSchemaArgs = {
|
||||
workspaceDataSource: DataSource;
|
||||
dataSource: DataSource;
|
||||
workspaceId: string;
|
||||
appVersion: string | undefined;
|
||||
seedBilling?: boolean;
|
||||
@ -16,7 +16,7 @@ type SeedCoreSchemaArgs = {
|
||||
|
||||
export const seedCoreSchema = async ({
|
||||
appVersion,
|
||||
workspaceDataSource,
|
||||
dataSource,
|
||||
workspaceId,
|
||||
seedBilling = true,
|
||||
seedFeatureFlags: shouldSeedFeatureFlags = true,
|
||||
@ -24,23 +24,19 @@ export const seedCoreSchema = async ({
|
||||
const schemaName = 'core';
|
||||
|
||||
await seedWorkspaces({
|
||||
workspaceDataSource,
|
||||
dataSource,
|
||||
schemaName,
|
||||
workspaceId,
|
||||
appVersion,
|
||||
});
|
||||
await seedUsers(workspaceDataSource, schemaName);
|
||||
await seedUserWorkspaces(workspaceDataSource, schemaName, workspaceId);
|
||||
await seedUsers(dataSource, schemaName);
|
||||
await seedUserWorkspaces(dataSource, schemaName, workspaceId);
|
||||
|
||||
if (shouldSeedFeatureFlags) {
|
||||
await seedFeatureFlags(workspaceDataSource, schemaName, workspaceId);
|
||||
await seedFeatureFlags(dataSource, schemaName, workspaceId);
|
||||
}
|
||||
|
||||
if (seedBilling) {
|
||||
await seedBillingSubscriptions(
|
||||
workspaceDataSource,
|
||||
schemaName,
|
||||
workspaceId,
|
||||
);
|
||||
await seedBillingSubscriptions(dataSource, schemaName, workspaceId);
|
||||
}
|
||||
};
|
||||
|
||||
@ -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 type SeedWorkspaceArgs = {
|
||||
workspaceDataSource: DataSource;
|
||||
dataSource: DataSource;
|
||||
schemaName: string;
|
||||
workspaceId: string;
|
||||
appVersion: string | undefined;
|
||||
@ -33,7 +33,7 @@ type WorkspaceSeederFields = Pick<
|
||||
|
||||
export const seedWorkspaces = async ({
|
||||
schemaName,
|
||||
workspaceDataSource,
|
||||
dataSource,
|
||||
workspaceId,
|
||||
appVersion,
|
||||
}: SeedWorkspaceArgs) => {
|
||||
@ -60,7 +60,7 @@ export const seedWorkspaces = async ({
|
||||
},
|
||||
};
|
||||
|
||||
await workspaceDataSource
|
||||
await dataSource
|
||||
.createQueryBuilder()
|
||||
.insert()
|
||||
.into(`${schemaName}.${tableName}`, workspaceSeederFields)
|
||||
@ -70,11 +70,11 @@ export const seedWorkspaces = async ({
|
||||
};
|
||||
|
||||
export const deleteWorkspaces = async (
|
||||
workspaceDataSource: DataSource,
|
||||
dataSource: DataSource,
|
||||
schemaName: string,
|
||||
workspaceId: string,
|
||||
) => {
|
||||
await workspaceDataSource
|
||||
await dataSource
|
||||
.createQueryBuilder()
|
||||
.delete()
|
||||
.from(`${schemaName}.${tableName}`)
|
||||
|
||||
@ -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 { User } from 'src/engine/core-modules/user/user.entity';
|
||||
import { Workspace } from 'src/engine/core-modules/workspace/workspace.entity';
|
||||
import { DataSourceEntity } from 'src/engine/metadata-modules/data-source/data-source.entity';
|
||||
@Injectable()
|
||||
export class TypeORMService implements OnModuleInit, OnModuleDestroy {
|
||||
private mainDataSource: DataSource;
|
||||
private dataSources: Map<string, DataSource> = new Map();
|
||||
private isDatasourceInitializing: Map<string, boolean> = new Map();
|
||||
|
||||
constructor(private readonly twentyConfigService: TwentyConfigService) {
|
||||
this.mainDataSource = new DataSource({
|
||||
@ -73,75 +70,6 @@ export class TypeORMService implements OnModuleInit, OnModuleDestroy {
|
||||
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> {
|
||||
const queryRunner = this.mainDataSource.createQueryRunner();
|
||||
|
||||
@ -168,10 +96,5 @@ export class TypeORMService implements OnModuleInit, OnModuleDestroy {
|
||||
async onModuleDestroy() {
|
||||
// Destroy main data source "default" schema
|
||||
await this.mainDataSource.destroy();
|
||||
|
||||
// Destroy all workspace data sources
|
||||
for (const [, dataSource] of this.dataSources) {
|
||||
await dataSource.destroy();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -85,12 +85,13 @@ export abstract class GraphqlQueryBaseResolverService<
|
||||
|
||||
await this.validate(args, options);
|
||||
|
||||
const dataSource =
|
||||
await this.twentyORMGlobalManager.getDataSourceForWorkspace(
|
||||
authContext.workspace.id,
|
||||
);
|
||||
const workspaceDataSource =
|
||||
await this.twentyORMGlobalManager.getDataSourceForWorkspace({
|
||||
workspaceId: authContext.workspace.id,
|
||||
shouldFailIfMetadataNotFound: false,
|
||||
});
|
||||
|
||||
const featureFlagsMap = dataSource.featureFlagMap;
|
||||
const featureFlagsMap = workspaceDataSource.featureFlagMap;
|
||||
|
||||
const isPermissionsV2Enabled =
|
||||
featureFlagsMap[FeatureFlagKey.IsPermissionsV2Enabled];
|
||||
@ -127,7 +128,7 @@ export abstract class GraphqlQueryBaseResolverService<
|
||||
const executedByApiKey = isDefined(authContext.apiKey);
|
||||
const shouldBypassPermissionChecks = executedByApiKey;
|
||||
|
||||
const repository = dataSource.getRepository(
|
||||
const repository = workspaceDataSource.getRepository(
|
||||
objectMetadataItemWithFieldMaps.nameSingular,
|
||||
shouldBypassPermissionChecks,
|
||||
roleId,
|
||||
@ -151,7 +152,7 @@ export abstract class GraphqlQueryBaseResolverService<
|
||||
const graphqlQueryResolverExecutionArgs = {
|
||||
args: computedArgs,
|
||||
options,
|
||||
dataSource,
|
||||
dataSource: workspaceDataSource,
|
||||
repository,
|
||||
graphqlQueryParser,
|
||||
graphqlQuerySelectedFieldsResult,
|
||||
|
||||
@ -156,7 +156,10 @@ export class RestApiCoreServiceV2 {
|
||||
}
|
||||
|
||||
const dataSource =
|
||||
await this.twentyORMGlobalManager.getDataSourceForWorkspace(workspace.id);
|
||||
await this.twentyORMGlobalManager.getDataSourceForWorkspace({
|
||||
workspaceId: workspace.id,
|
||||
shouldFailIfMetadataNotFound: false,
|
||||
});
|
||||
|
||||
const objectMetadataNameSingular =
|
||||
objectMetadata.objectMetadataMapItem.nameSingular;
|
||||
|
||||
@ -100,7 +100,9 @@ export class GoogleAPIsService {
|
||||
);
|
||||
|
||||
const workspaceDataSource =
|
||||
await this.twentyORMGlobalManager.getDataSourceForWorkspace(workspaceId);
|
||||
await this.twentyORMGlobalManager.getDataSourceForWorkspace({
|
||||
workspaceId,
|
||||
});
|
||||
|
||||
const scopes = getGoogleApisOauthScopes();
|
||||
|
||||
|
||||
@ -104,7 +104,9 @@ export class MicrosoftAPIsService {
|
||||
);
|
||||
|
||||
const workspaceDataSource =
|
||||
await this.twentyORMGlobalManager.getDataSourceForWorkspace(workspaceId);
|
||||
await this.twentyORMGlobalManager.getDataSourceForWorkspace({
|
||||
workspaceId,
|
||||
});
|
||||
|
||||
const scopes = getMicrosoftApisOauthScopes();
|
||||
|
||||
|
||||
@ -14,9 +14,7 @@ describe('JwtAuthStrategy', () => {
|
||||
let workspaceRepository: any;
|
||||
let userWorkspaceRepository: any;
|
||||
let userRepository: any;
|
||||
let dataSourceService: any;
|
||||
let typeORMService: any;
|
||||
|
||||
let twentyORMGlobalManager: any;
|
||||
const jwt = {
|
||||
sub: 'sub-default',
|
||||
jti: 'jti-default',
|
||||
@ -38,6 +36,12 @@ describe('JwtAuthStrategy', () => {
|
||||
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
|
||||
it('should throw AuthException if type is API_KEY and workspace is not found', async () => {
|
||||
const payload = {
|
||||
@ -50,10 +54,8 @@ describe('JwtAuthStrategy', () => {
|
||||
};
|
||||
|
||||
strategy = new JwtAuthStrategy(
|
||||
{} as any,
|
||||
jwtWrapperService,
|
||||
typeORMService,
|
||||
dataSourceService,
|
||||
twentyORMGlobalManager,
|
||||
workspaceRepository,
|
||||
{} as any,
|
||||
userWorkspaceRepository,
|
||||
@ -77,19 +79,15 @@ describe('JwtAuthStrategy', () => {
|
||||
findOneBy: jest.fn(async () => new Workspace()),
|
||||
};
|
||||
|
||||
dataSourceService = {
|
||||
getLastDataSourceMetadataFromWorkspaceIdOrFail: jest.fn(async () => ({})),
|
||||
};
|
||||
|
||||
typeORMService = {
|
||||
connectToDataSource: jest.fn(async () => {}),
|
||||
twentyORMGlobalManager = {
|
||||
getRepositoryForWorkspace: jest.fn(async () => ({
|
||||
findOne: jest.fn(async () => null),
|
||||
})),
|
||||
};
|
||||
|
||||
strategy = new JwtAuthStrategy(
|
||||
{} as any,
|
||||
jwtWrapperService,
|
||||
typeORMService,
|
||||
dataSourceService,
|
||||
twentyORMGlobalManager,
|
||||
workspaceRepository,
|
||||
{} as any,
|
||||
userWorkspaceRepository,
|
||||
@ -113,21 +111,15 @@ describe('JwtAuthStrategy', () => {
|
||||
findOneBy: jest.fn(async () => new Workspace()),
|
||||
};
|
||||
|
||||
const mockDataSource = {
|
||||
query: jest
|
||||
.fn()
|
||||
.mockResolvedValue([{ id: 'api-key-id', revokedAt: null }]),
|
||||
twentyORMGlobalManager = {
|
||||
getRepositoryForWorkspace: jest.fn(async () => ({
|
||||
findOne: jest.fn(async () => ({ id: 'api-key-id', revokedAt: null })),
|
||||
})),
|
||||
};
|
||||
|
||||
jest
|
||||
.spyOn(typeORMService, 'connectToDataSource')
|
||||
.mockResolvedValue(mockDataSource as any);
|
||||
|
||||
strategy = new JwtAuthStrategy(
|
||||
{} as any,
|
||||
jwtWrapperService,
|
||||
typeORMService,
|
||||
dataSourceService,
|
||||
twentyORMGlobalManager,
|
||||
workspaceRepository,
|
||||
{} as any,
|
||||
userWorkspaceRepository,
|
||||
@ -140,7 +132,6 @@ describe('JwtAuthStrategy', () => {
|
||||
});
|
||||
|
||||
// second we test the ACCESS cases
|
||||
|
||||
it('should throw AuthExceptionCode if type is ACCESS, no jti, and user not found', async () => {
|
||||
const payload = {
|
||||
sub: 'sub-default',
|
||||
@ -156,10 +147,8 @@ describe('JwtAuthStrategy', () => {
|
||||
};
|
||||
|
||||
strategy = new JwtAuthStrategy(
|
||||
{} as any,
|
||||
jwtWrapperService,
|
||||
typeORMService,
|
||||
dataSourceService,
|
||||
twentyORMGlobalManager,
|
||||
workspaceRepository,
|
||||
userRepository,
|
||||
userWorkspaceRepository,
|
||||
@ -194,10 +183,8 @@ describe('JwtAuthStrategy', () => {
|
||||
};
|
||||
|
||||
strategy = new JwtAuthStrategy(
|
||||
{} as any,
|
||||
jwtWrapperService,
|
||||
typeORMService,
|
||||
dataSourceService,
|
||||
twentyORMGlobalManager,
|
||||
workspaceRepository,
|
||||
userRepository,
|
||||
userWorkspaceRepository,
|
||||
@ -235,10 +222,8 @@ describe('JwtAuthStrategy', () => {
|
||||
};
|
||||
|
||||
strategy = new JwtAuthStrategy(
|
||||
{} as any,
|
||||
jwtWrapperService,
|
||||
typeORMService,
|
||||
dataSourceService,
|
||||
twentyORMGlobalManager,
|
||||
workspaceRepository,
|
||||
userRepository,
|
||||
userWorkspaceRepository,
|
||||
|
||||
@ -5,7 +5,6 @@ import { InjectRepository } from '@nestjs/typeorm';
|
||||
import { Strategy } from 'passport-jwt';
|
||||
import { Repository } from 'typeorm';
|
||||
|
||||
import { TypeORMService } from 'src/database/typeorm/typeorm.service';
|
||||
import {
|
||||
AuthException,
|
||||
AuthExceptionCode,
|
||||
@ -15,20 +14,17 @@ import {
|
||||
JwtPayload,
|
||||
} from 'src/engine/core-modules/auth/types/auth-context.type';
|
||||
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 { User } from 'src/engine/core-modules/user/user.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';
|
||||
|
||||
@Injectable()
|
||||
export class JwtAuthStrategy extends PassportStrategy(Strategy, 'jwt') {
|
||||
constructor(
|
||||
private readonly twentyConfigService: TwentyConfigService,
|
||||
private readonly jwtWrapperService: JwtWrapperService,
|
||||
private readonly typeORMService: TypeORMService,
|
||||
private readonly dataSourceService: DataSourceService,
|
||||
private readonly twentyORMGlobalManager: TwentyORMGlobalManager,
|
||||
@InjectRepository(Workspace, 'core')
|
||||
private readonly workspaceRepository: Repository<Workspace>,
|
||||
@InjectRepository(User, 'core')
|
||||
@ -37,7 +33,7 @@ export class JwtAuthStrategy extends PassportStrategy(Strategy, 'jwt') {
|
||||
private readonly userWorkspaceRepository: Repository<UserWorkspace>,
|
||||
) {
|
||||
const jwtFromRequestFunction = jwtWrapperService.extractJwtFromRequest();
|
||||
const secretOrKeyProviderFunction = async (request, rawJwtToken, done) => {
|
||||
const secretOrKeyProviderFunction = async (_request, rawJwtToken, done) => {
|
||||
try {
|
||||
const decodedToken = jwtWrapperService.decode(
|
||||
rawJwtToken,
|
||||
@ -75,20 +71,20 @@ export class JwtAuthStrategy extends PassportStrategy(Strategy, 'jwt') {
|
||||
);
|
||||
}
|
||||
|
||||
const dataSourceMetadata =
|
||||
await this.dataSourceService.getLastDataSourceMetadataFromWorkspaceIdOrFail(
|
||||
const apiKeyRepository =
|
||||
await this.twentyORMGlobalManager.getRepositoryForWorkspace<ApiKeyWorkspaceEntity>(
|
||||
workspace.id,
|
||||
'apiKey',
|
||||
{
|
||||
shouldBypassPermissionChecks: true,
|
||||
},
|
||||
);
|
||||
|
||||
const workspaceDataSource =
|
||||
await this.typeORMService.connectToDataSource(dataSourceMetadata);
|
||||
|
||||
const res = await workspaceDataSource?.query(
|
||||
`SELECT * FROM ${dataSourceMetadata.schema}."apiKey" WHERE id = $1`,
|
||||
[payload.jti],
|
||||
);
|
||||
|
||||
apiKey = res?.[0];
|
||||
apiKey = await apiKeyRepository.findOne({
|
||||
where: {
|
||||
id: payload.jti,
|
||||
},
|
||||
});
|
||||
|
||||
if (!apiKey || apiKey.revokedAt) {
|
||||
throw new AuthException(
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
import { Test, TestingModule } from '@nestjs/testing';
|
||||
import { getRepositoryToken } from '@nestjs/typeorm';
|
||||
|
||||
import { Repository } from 'typeorm';
|
||||
import { DataSource, 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';
|
||||
@ -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 { WorkspaceInvitationService } from 'src/engine/core-modules/workspace-invitation/services/workspace-invitation.service';
|
||||
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 { ObjectMetadataEntity } from 'src/engine/metadata-modules/object-metadata/object-metadata.entity';
|
||||
import { PermissionsException } from 'src/engine/metadata-modules/permissions/permissions.exception';
|
||||
@ -27,7 +26,6 @@ describe('UserWorkspaceService', () => {
|
||||
let userWorkspaceRepository: Repository<UserWorkspace>;
|
||||
let userRepository: Repository<User>;
|
||||
let objectMetadataRepository: Repository<ObjectMetadataEntity>;
|
||||
let dataSourceService: DataSourceService;
|
||||
let typeORMService: TypeORMService;
|
||||
let workspaceInvitationService: WorkspaceInvitationService;
|
||||
let workspaceEventEmitter: WorkspaceEventEmitter;
|
||||
@ -71,7 +69,7 @@ describe('UserWorkspaceService', () => {
|
||||
{
|
||||
provide: TypeORMService,
|
||||
useValue: {
|
||||
connectToDataSource: jest.fn(),
|
||||
getMainDataSource: jest.fn(),
|
||||
},
|
||||
},
|
||||
{
|
||||
@ -116,7 +114,6 @@ describe('UserWorkspaceService', () => {
|
||||
objectMetadataRepository = module.get(
|
||||
getRepositoryToken(ObjectMetadataEntity, 'metadata'),
|
||||
);
|
||||
dataSourceService = module.get<DataSourceService>(DataSourceService);
|
||||
typeORMService = module.get<TypeORMService>(TypeORMService);
|
||||
workspaceInvitationService = module.get<WorkspaceInvitationService>(
|
||||
WorkspaceInvitationService,
|
||||
@ -179,12 +176,9 @@ describe('UserWorkspaceService', () => {
|
||||
defaultAvatarUrl: 'avatar-url',
|
||||
locale: 'en',
|
||||
} as User;
|
||||
const dataSourceMetadata = {
|
||||
schema: 'public',
|
||||
} as DataSourceEntity;
|
||||
const workspaceDataSource = {
|
||||
const mainDataSource = {
|
||||
query: jest.fn(),
|
||||
};
|
||||
} as unknown as DataSource;
|
||||
const workspaceMember = [
|
||||
{
|
||||
id: 'workspace-member-id',
|
||||
@ -197,17 +191,16 @@ describe('UserWorkspaceService', () => {
|
||||
const objectMetadata = {
|
||||
nameSingular: 'workspaceMember',
|
||||
} as ObjectMetadataEntity;
|
||||
const workspaceMemberRepository = {
|
||||
insert: jest.fn(),
|
||||
find: jest.fn().mockResolvedValue(workspaceMember),
|
||||
};
|
||||
|
||||
jest
|
||||
.spyOn(
|
||||
dataSourceService,
|
||||
'getLastDataSourceMetadataFromWorkspaceIdOrFail',
|
||||
)
|
||||
.mockResolvedValue(dataSourceMetadata);
|
||||
.spyOn(typeORMService, 'getMainDataSource')
|
||||
.mockReturnValue(mainDataSource);
|
||||
jest
|
||||
.spyOn(typeORMService, 'connectToDataSource')
|
||||
.mockResolvedValue(workspaceDataSource as any);
|
||||
workspaceDataSource.query
|
||||
.spyOn(mainDataSource, 'query')
|
||||
.mockResolvedValueOnce(undefined)
|
||||
.mockResolvedValueOnce(workspaceMember);
|
||||
jest
|
||||
@ -217,15 +210,23 @@ describe('UserWorkspaceService', () => {
|
||||
.spyOn(workspaceEventEmitter, 'emitDatabaseBatchEvent')
|
||||
.mockImplementation();
|
||||
|
||||
jest
|
||||
.spyOn(twentyORMGlobalManager, 'getRepositoryForWorkspace')
|
||||
.mockResolvedValue(workspaceMemberRepository as any);
|
||||
|
||||
await service.createWorkspaceMember(workspaceId, user);
|
||||
|
||||
expect(
|
||||
dataSourceService.getLastDataSourceMetadataFromWorkspaceIdOrFail,
|
||||
).toHaveBeenCalledWith(workspaceId);
|
||||
expect(typeORMService.connectToDataSource).toHaveBeenCalledWith(
|
||||
dataSourceMetadata,
|
||||
);
|
||||
expect(workspaceDataSource.query).toHaveBeenCalledTimes(2);
|
||||
expect(workspaceMemberRepository.insert).toHaveBeenCalledWith({
|
||||
name: {
|
||||
firstName: user.firstName,
|
||||
lastName: user.lastName,
|
||||
},
|
||||
colorScheme: 'System',
|
||||
userId: user.id,
|
||||
userEmail: user.email,
|
||||
locale: 'en',
|
||||
avatarUrl: 'avatar-url',
|
||||
});
|
||||
expect(objectMetadataRepository.findOneOrFail).toHaveBeenCalledWith({
|
||||
where: {
|
||||
nameSingular: 'workspaceMember',
|
||||
|
||||
@ -2,7 +2,7 @@
|
||||
import { InjectRepository } from '@nestjs/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 { Repository } from 'typeorm';
|
||||
|
||||
@ -69,33 +69,35 @@ export class UserWorkspaceService extends TypeOrmQueryService<UserWorkspace> {
|
||||
}
|
||||
|
||||
async createWorkspaceMember(workspaceId: string, user: User) {
|
||||
const dataSourceMetadata =
|
||||
await this.dataSourceService.getLastDataSourceMetadataFromWorkspaceIdOrFail(
|
||||
const workspaceMemberRepository =
|
||||
await this.twentyORMGlobalManager.getRepositoryForWorkspace<WorkspaceMemberWorkspaceEntity>(
|
||||
workspaceId,
|
||||
'workspaceMember',
|
||||
{
|
||||
shouldBypassPermissionChecks: true,
|
||||
},
|
||||
);
|
||||
|
||||
const workspaceDataSource =
|
||||
await this.typeORMService.connectToDataSource(dataSourceMetadata);
|
||||
await workspaceMemberRepository.insert({
|
||||
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(
|
||||
`INSERT INTO ${dataSourceMetadata.schema}."workspaceMember"
|
||||
("nameFirstName", "nameLastName", "colorScheme", "userId", "userEmail", "avatarUrl", "locale")
|
||||
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}'`,
|
||||
);
|
||||
const workspaceMember = await workspaceMemberRepository.find({
|
||||
where: {
|
||||
userId: user.id,
|
||||
},
|
||||
});
|
||||
|
||||
assert(
|
||||
workspaceMember.length === 1,
|
||||
workspaceMember?.length === 1,
|
||||
`Error while creating workspace member ${user.email} on workspace ${workspaceId}`,
|
||||
);
|
||||
const objectMetadata = await this.objectMetadataRepository.findOneOrFail({
|
||||
|
||||
@ -6,13 +6,11 @@ import { TypeOrmQueryService } from '@ptc-org/nestjs-query-typeorm';
|
||||
import { isWorkspaceActiveOrSuspended } from 'twenty-shared/workspace';
|
||||
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 {
|
||||
AuthException,
|
||||
AuthExceptionCode,
|
||||
} 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 { User } from 'src/engine/core-modules/user/user.entity';
|
||||
import { userValidator } from 'src/engine/core-modules/user/user.validate';
|
||||
@ -38,13 +36,11 @@ export class UserService extends TypeOrmQueryService<User> {
|
||||
@InjectRepository(ObjectMetadataEntity, 'metadata')
|
||||
private readonly objectMetadataRepository: Repository<ObjectMetadataEntity>,
|
||||
private readonly dataSourceService: DataSourceService,
|
||||
private readonly typeORMService: TypeORMService,
|
||||
private readonly workspaceEventEmitter: WorkspaceEventEmitter,
|
||||
private readonly workspaceService: WorkspaceService,
|
||||
private readonly twentyORMGlobalManager: TwentyORMGlobalManager,
|
||||
private readonly userRoleService: UserRoleService,
|
||||
private readonly userWorkspaceService: UserWorkspaceService,
|
||||
private readonly featureFlagService: FeatureFlagService,
|
||||
) {
|
||||
super(userRepository);
|
||||
}
|
||||
@ -88,17 +84,16 @@ export class UserService extends TypeOrmQueryService<User> {
|
||||
userId: string;
|
||||
workspaceId: string;
|
||||
}) {
|
||||
const dataSourceMetadata =
|
||||
await this.dataSourceService.getLastDataSourceMetadataFromWorkspaceIdOrFail(
|
||||
const workspaceMemberRepository =
|
||||
await this.twentyORMGlobalManager.getRepositoryForWorkspace<WorkspaceMemberWorkspaceEntity>(
|
||||
workspaceId,
|
||||
'workspaceMember',
|
||||
{
|
||||
shouldBypassPermissionChecks: true,
|
||||
},
|
||||
);
|
||||
|
||||
const workspaceDataSource =
|
||||
await this.typeORMService.connectToDataSource(dataSourceMetadata);
|
||||
|
||||
const workspaceMembers = await workspaceDataSource?.query(
|
||||
`SELECT * FROM ${dataSourceMetadata.schema}."workspaceMember"`,
|
||||
);
|
||||
const workspaceMembers = await workspaceMemberRepository.find();
|
||||
|
||||
if (workspaceMembers.length > 1) {
|
||||
const userWorkspace =
|
||||
@ -119,9 +114,7 @@ export class UserService extends TypeOrmQueryService<User> {
|
||||
|
||||
assert(workspaceMember, 'WorkspaceMember not found');
|
||||
|
||||
await workspaceDataSource?.query(
|
||||
`DELETE FROM ${dataSourceMetadata.schema}."workspaceMember" WHERE "userId" = '${userId}'`,
|
||||
);
|
||||
await workspaceMemberRepository.delete({ userId });
|
||||
|
||||
const objectMetadata = await this.objectMetadataRepository.findOneOrFail({
|
||||
where: {
|
||||
|
||||
@ -61,11 +61,11 @@ import {
|
||||
} from 'src/engine/metadata-modules/workspace-migration/workspace-migration.entity';
|
||||
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 { WorkspaceEntityManager } from 'src/engine/twenty-orm/entity-manager/workspace-entity-manager';
|
||||
import { TwentyORMGlobalManager } from 'src/engine/twenty-orm/twenty-orm-global.manager';
|
||||
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 { 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 { FieldMetadataEntity } from './field-metadata.entity';
|
||||
@ -910,74 +910,70 @@ export class FieldMetadataService extends TypeOrmQueryService<FieldMetadataEntit
|
||||
createdFieldMetadatas: FieldMetadataEntity[],
|
||||
workspaceId: string,
|
||||
) {
|
||||
const dataSourceMetadata =
|
||||
await this.dataSourceService.getLastDataSourceMetadataFromWorkspaceIdOrFail(
|
||||
workspaceId,
|
||||
);
|
||||
|
||||
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) {
|
||||
throw new FieldMetadataException(
|
||||
'Could not create workspace query runner',
|
||||
FieldMetadataExceptionCode.INTERNAL_SERVER_ERROR,
|
||||
);
|
||||
}
|
||||
|
||||
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}'`,
|
||||
const viewFieldsRepository = workspaceEntityManager.getRepository(
|
||||
'viewField',
|
||||
{
|
||||
shouldBypassPermissionChecks: true,
|
||||
},
|
||||
);
|
||||
|
||||
if (!isEmpty(view)) {
|
||||
const existingViewFields = (await workspaceQueryRunner?.query(
|
||||
`SELECT * FROM ${dataSourceMetadata.schema}."viewField"
|
||||
WHERE "viewId" = '${view[0].id}'`,
|
||||
)) as ViewFieldWorkspaceEntity[];
|
||||
const isVisible =
|
||||
existingViewFields.length < settings.maxVisibleViewFields;
|
||||
for (const createdFieldMetadata of createdFieldMetadatas) {
|
||||
const views = await viewsRepository.find({
|
||||
where: {
|
||||
objectMetadataId: createdFieldMetadata.objectMetadataId,
|
||||
},
|
||||
});
|
||||
|
||||
const createdFieldIsAlreadyInView = existingViewFields.some(
|
||||
(existingViewField) =>
|
||||
existingViewField.fieldMetadataId === createdFieldMetadata.id,
|
||||
);
|
||||
if (!isEmpty(views)) {
|
||||
const view = views[0];
|
||||
const existingViewFields = await viewFieldsRepository.find({
|
||||
where: {
|
||||
viewId: view.id,
|
||||
},
|
||||
});
|
||||
|
||||
if (!createdFieldIsAlreadyInView) {
|
||||
const lastPosition = existingViewFields
|
||||
.map((viewField) => viewField.position)
|
||||
.reduce((acc, position) => {
|
||||
if (position > acc) {
|
||||
return position;
|
||||
}
|
||||
const isVisible =
|
||||
existingViewFields.length < settings.maxVisibleViewFields;
|
||||
|
||||
return acc;
|
||||
}, -1);
|
||||
|
||||
await workspaceQueryRunner?.query(
|
||||
`INSERT INTO ${dataSourceMetadata.schema}."viewField"
|
||||
("fieldMetadataId", "position", "isVisible", "size", "viewId")
|
||||
VALUES ('${createdFieldMetadata.id}', '${
|
||||
lastPosition + 1
|
||||
}', ${isVisible}, 180, '${view[0].id}')`,
|
||||
const createdFieldIsAlreadyInView = existingViewFields.some(
|
||||
(existingViewField) =>
|
||||
existingViewField.fieldMetadataId === createdFieldMetadata.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(
|
||||
|
||||
@ -8,15 +8,15 @@ import {
|
||||
RemoteServerEntity,
|
||||
RemoteServerType,
|
||||
} 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 {
|
||||
DistantTableException,
|
||||
DistantTableExceptionCode,
|
||||
} 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()
|
||||
export class DistantTableService {
|
||||
@ -73,13 +73,11 @@ export class DistantTableService {
|
||||
const tmpSchemaId = v4();
|
||||
const tmpSchemaName = `${workspaceId}_${remoteServer.id}_${tmpSchemaId}`;
|
||||
|
||||
const workspaceDataSource =
|
||||
await this.workspaceDataSourceService.connectToWorkspaceDataSource(
|
||||
workspaceId,
|
||||
);
|
||||
const mainDataSource =
|
||||
await this.workspaceDataSourceService.connectToMainDataSource();
|
||||
|
||||
try {
|
||||
const distantTables = await workspaceDataSource.transaction(
|
||||
const distantTables = await mainDataSource.transaction(
|
||||
async (entityManager: EntityManager) => {
|
||||
await entityManager.query(`CREATE SCHEMA "${tmpSchemaName}"`);
|
||||
|
||||
|
||||
@ -37,13 +37,11 @@ export class ForeignTableService {
|
||||
workspaceId: string,
|
||||
foreignDataWrapperId: string,
|
||||
): Promise<string[]> {
|
||||
const workspaceDataSource =
|
||||
await this.workspaceDataSourceService.connectToWorkspaceDataSource(
|
||||
workspaceId,
|
||||
);
|
||||
const mainDataSource =
|
||||
await this.workspaceDataSourceService.connectToMainDataSource();
|
||||
|
||||
return (
|
||||
await workspaceDataSource.query(
|
||||
await mainDataSource.query(
|
||||
`SELECT foreign_table_name, foreign_server_name FROM information_schema.foreign_tables WHERE foreign_server_name = $1`,
|
||||
[foreignDataWrapperId],
|
||||
)
|
||||
|
||||
@ -182,16 +182,14 @@ export class RemoteTableService {
|
||||
workspaceId,
|
||||
);
|
||||
|
||||
const workspaceDataSource =
|
||||
await this.workspaceDataSourceService.connectToWorkspaceDataSource(
|
||||
workspaceId,
|
||||
);
|
||||
const mainDataSource =
|
||||
await this.workspaceDataSourceService.connectToMainDataSource();
|
||||
|
||||
const { baseName: localTableBaseName, suffix: localTableSuffix } =
|
||||
await getRemoteTableLocalName(
|
||||
input.name,
|
||||
dataSourceMetatada.schema,
|
||||
workspaceDataSource,
|
||||
mainDataSource,
|
||||
);
|
||||
|
||||
const localTableName = localTableSuffix
|
||||
|
||||
@ -61,13 +61,10 @@ export class SeederService {
|
||||
const schemaName =
|
||||
this.workspaceDataSourceService.getSchemaName(workspaceId);
|
||||
|
||||
const workspaceDataSource: DataSource =
|
||||
await this.workspaceDataSourceService.connectToWorkspaceDataSource(
|
||||
workspaceId,
|
||||
);
|
||||
const mainDataSource: DataSource =
|
||||
await this.workspaceDataSourceService.connectToMainDataSource();
|
||||
|
||||
const entityManager: EntityManager =
|
||||
workspaceDataSource.createEntityManager();
|
||||
const entityManager: EntityManager = mainDataSource.createEntityManager();
|
||||
|
||||
const filteredFieldMetadataSeeds = objectMetadataSeed.fields.filter(
|
||||
(field) =>
|
||||
|
||||
@ -47,15 +47,15 @@ export class WorkspaceDataSource extends DataSource {
|
||||
roleId?: string,
|
||||
): WorkspaceRepository<Entity> {
|
||||
if (shouldBypassPermissionChecks === true) {
|
||||
return this.manager.getRepository(target, shouldBypassPermissionChecks);
|
||||
return this.manager.getRepository(target, {
|
||||
shouldBypassPermissionChecks: true,
|
||||
});
|
||||
}
|
||||
|
||||
if (roleId) {
|
||||
return this.manager.getRepository(
|
||||
target,
|
||||
shouldBypassPermissionChecks,
|
||||
return this.manager.getRepository(target, {
|
||||
roleId,
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
return this.manager.getRepository(target);
|
||||
|
||||
@ -2,6 +2,7 @@ import { ObjectRecordsPermissions } from 'twenty-shared/types';
|
||||
import {
|
||||
EntityManager,
|
||||
EntityTarget,
|
||||
FindManyOptions,
|
||||
InsertResult,
|
||||
ObjectLiteral,
|
||||
QueryRunner,
|
||||
@ -44,16 +45,19 @@ export class WorkspaceEntityManager extends EntityManager {
|
||||
|
||||
override getRepository<Entity extends ObjectLiteral>(
|
||||
target: EntityTarget<Entity>,
|
||||
shouldBypassPermissionChecks = false,
|
||||
roleId?: string,
|
||||
permissionOptions?: {
|
||||
shouldBypassPermissionChecks?: boolean;
|
||||
roleId?: string;
|
||||
},
|
||||
): WorkspaceRepository<Entity> {
|
||||
const dataSource = this.connection;
|
||||
|
||||
const repositoryKey = this.getRepositoryKey({
|
||||
target,
|
||||
dataSource,
|
||||
roleId,
|
||||
shouldBypassPermissionChecks,
|
||||
roleId: permissionOptions?.roleId,
|
||||
shouldBypassPermissionChecks:
|
||||
permissionOptions?.shouldBypassPermissionChecks ?? false,
|
||||
});
|
||||
const repoFromMap = this.repositories.get(repositoryKey);
|
||||
|
||||
@ -63,10 +67,11 @@ export class WorkspaceEntityManager extends EntityManager {
|
||||
|
||||
let objectPermissions = {};
|
||||
|
||||
if (roleId) {
|
||||
if (permissionOptions?.roleId) {
|
||||
const objectPermissionsByRoleId = dataSource.permissionsPerRoleId;
|
||||
|
||||
objectPermissions = objectPermissionsByRoleId?.[roleId] ?? {};
|
||||
objectPermissions =
|
||||
objectPermissionsByRoleId?.[permissionOptions?.roleId] ?? {};
|
||||
}
|
||||
|
||||
const newRepository = new WorkspaceRepository<Entity>(
|
||||
@ -76,7 +81,7 @@ export class WorkspaceEntityManager extends EntityManager {
|
||||
dataSource.featureFlagMap,
|
||||
this.queryRunner,
|
||||
objectPermissions,
|
||||
shouldBypassPermissionChecks,
|
||||
permissionOptions?.shouldBypassPermissionChecks,
|
||||
);
|
||||
|
||||
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>(
|
||||
target: EntityTarget<Entity>,
|
||||
entityOrEntities:
|
||||
| QueryDeepPartialEntity<Entity>
|
||||
| QueryDeepPartialEntity<Entity>[],
|
||||
options?: {
|
||||
permissionOptions?: {
|
||||
shouldBypassPermissionChecks?: boolean;
|
||||
objectRecordsPermissions?: ObjectRecordsPermissions;
|
||||
},
|
||||
): Promise<InsertResult> {
|
||||
this.validatePermissions(target, 'insert', options);
|
||||
this.validatePermissions(target, 'insert', permissionOptions);
|
||||
|
||||
return super.insert(target, entityOrEntities);
|
||||
}
|
||||
@ -156,12 +174,12 @@ export class WorkspaceEntityManager extends EntityManager {
|
||||
| QueryDeepPartialEntity<Entity>
|
||||
| QueryDeepPartialEntity<Entity>[],
|
||||
conflictPathsOrOptions: string[] | UpsertOptions<Entity>,
|
||||
options?: {
|
||||
permissionOptions?: {
|
||||
shouldBypassPermissionChecks?: boolean;
|
||||
objectRecordsPermissions?: ObjectRecordsPermissions;
|
||||
},
|
||||
): Promise<InsertResult> {
|
||||
this.validatePermissions(target, 'update', options);
|
||||
this.validatePermissions(target, 'update', permissionOptions);
|
||||
|
||||
return super.upsert(target, entityOrEntities, conflictPathsOrOptions);
|
||||
}
|
||||
@ -194,7 +212,7 @@ export class WorkspaceEntityManager extends EntityManager {
|
||||
private validatePermissions<Entity extends ObjectLiteral>(
|
||||
target: EntityTarget<Entity>,
|
||||
operationType: OperationType,
|
||||
options?: {
|
||||
permissionOptions?: {
|
||||
shouldBypassPermissionChecks?: boolean;
|
||||
objectRecordsPermissions?: ObjectRecordsPermissions;
|
||||
},
|
||||
@ -208,14 +226,15 @@ export class WorkspaceEntityManager extends EntityManager {
|
||||
return;
|
||||
}
|
||||
|
||||
if (options?.shouldBypassPermissionChecks === true) {
|
||||
if (permissionOptions?.shouldBypassPermissionChecks === true) {
|
||||
return;
|
||||
}
|
||||
|
||||
validateOperationIsPermittedOrThrow({
|
||||
entityName: this.extractTargetNameSingularFromEntityTarget(target),
|
||||
operationType,
|
||||
objectRecordsPermissions: options?.objectRecordsPermissions ?? {},
|
||||
objectRecordsPermissions:
|
||||
permissionOptions?.objectRecordsPermissions ?? {},
|
||||
objectMetadataMaps: this.internalContext.objectMetadataMaps,
|
||||
});
|
||||
}
|
||||
|
||||
@ -65,10 +65,13 @@ export class TwentyORMGlobalManager {
|
||||
return repository;
|
||||
}
|
||||
|
||||
async getDataSourceForWorkspace(
|
||||
workspaceId: string,
|
||||
async getDataSourceForWorkspace({
|
||||
workspaceId,
|
||||
shouldFailIfMetadataNotFound = true,
|
||||
) {
|
||||
}: {
|
||||
workspaceId: string;
|
||||
shouldFailIfMetadataNotFound?: boolean;
|
||||
}) {
|
||||
return await this.workspaceDataSourceFactory.create(
|
||||
workspaceId,
|
||||
null,
|
||||
|
||||
@ -2,6 +2,7 @@ import { Injectable } from '@nestjs/common';
|
||||
|
||||
import crypto from 'crypto';
|
||||
|
||||
import { isDefined } from 'twenty-shared/utils';
|
||||
import { EntitySchemaOptions } from 'typeorm';
|
||||
|
||||
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> {
|
||||
await this.flushVersionedMetadata(workspaceId, metadataVersion);
|
||||
async flush(
|
||||
workspaceId: string,
|
||||
metadataVersion: number | undefined,
|
||||
): Promise<void> {
|
||||
if (isDefined(metadataVersion)) {
|
||||
await this.flushVersionedMetadata(workspaceId, metadataVersion);
|
||||
}
|
||||
|
||||
await this.cacheStorageService.del(
|
||||
`${WorkspaceCacheKeys.MetadataPermissionsRolesPermissions}:${workspaceId}`,
|
||||
|
||||
@ -3,7 +3,6 @@ import { Injectable } from '@nestjs/common';
|
||||
import { DataSource, EntityManager } from 'typeorm';
|
||||
|
||||
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';
|
||||
|
||||
@Injectable()
|
||||
@ -20,11 +19,12 @@ export class WorkspaceDataSourceService {
|
||||
* @param workspaceId
|
||||
* @returns
|
||||
*/
|
||||
public async connectToWorkspaceDataSource(
|
||||
workspaceId: string,
|
||||
): Promise<DataSource> {
|
||||
const { dataSource } =
|
||||
await this.connectedToWorkspaceDataSourceAndReturnMetadata(workspaceId);
|
||||
public async connectToMainDataSource(): Promise<DataSource> {
|
||||
const dataSource = this.typeormService.getMainDataSource();
|
||||
|
||||
if (!dataSource) {
|
||||
throw new Error(`Could not connect to workspace data source`);
|
||||
}
|
||||
|
||||
return dataSource;
|
||||
}
|
||||
@ -38,26 +38,6 @@ export class WorkspaceDataSourceService {
|
||||
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
|
||||
@ -128,10 +108,9 @@ export class WorkspaceDataSourceService {
|
||||
if (transactionManager) {
|
||||
return await transactionManager.query(query, parameters);
|
||||
}
|
||||
const workspaceDataSource =
|
||||
await this.connectToWorkspaceDataSource(workspaceId);
|
||||
const dataSource = await this.connectToMainDataSource();
|
||||
|
||||
return await workspaceDataSource.query(query, parameters);
|
||||
return await dataSource.query(query, parameters);
|
||||
} catch (error) {
|
||||
throw new Error(
|
||||
`Error executing raw query for workspace ${workspaceId}: ${error.message}`,
|
||||
|
||||
@ -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';
|
||||
|
||||
export const standardObjectsPrefillData = async (
|
||||
workspaceDataSource: DataSource,
|
||||
mainDataSource: DataSource,
|
||||
schemaName: string,
|
||||
objectMetadata: ObjectMetadataEntity[],
|
||||
) => {
|
||||
@ -34,30 +34,28 @@ export const standardObjectsPrefillData = async (
|
||||
return acc;
|
||||
}, {});
|
||||
|
||||
workspaceDataSource.transaction(
|
||||
async (entityManager: WorkspaceEntityManager) => {
|
||||
await companyPrefillData(entityManager, schemaName);
|
||||
await personPrefillData(entityManager, schemaName);
|
||||
const viewDefinitionsWithId = await seedViewWithDemoData(
|
||||
entityManager,
|
||||
schemaName,
|
||||
objectMetadataMap,
|
||||
);
|
||||
mainDataSource.transaction(async (entityManager: WorkspaceEntityManager) => {
|
||||
await companyPrefillData(entityManager, schemaName);
|
||||
await personPrefillData(entityManager, schemaName);
|
||||
const viewDefinitionsWithId = await seedViewWithDemoData(
|
||||
entityManager,
|
||||
schemaName,
|
||||
objectMetadataMap,
|
||||
);
|
||||
|
||||
await seedWorkspaceFavorites(
|
||||
viewDefinitionsWithId
|
||||
.filter(
|
||||
(view) =>
|
||||
view.key === 'INDEX' &&
|
||||
shouldSeedWorkspaceFavorite(
|
||||
view.objectMetadataId,
|
||||
objectMetadataMap,
|
||||
),
|
||||
)
|
||||
.map((view) => view.id),
|
||||
entityManager,
|
||||
schemaName,
|
||||
);
|
||||
},
|
||||
);
|
||||
await seedWorkspaceFavorites(
|
||||
viewDefinitionsWithId
|
||||
.filter(
|
||||
(view) =>
|
||||
view.key === 'INDEX' &&
|
||||
shouldSeedWorkspaceFavorite(
|
||||
view.objectMetadataId,
|
||||
objectMetadataMap,
|
||||
),
|
||||
)
|
||||
.map((view) => view.id),
|
||||
entityManager,
|
||||
schemaName,
|
||||
);
|
||||
});
|
||||
};
|
||||
|
||||
@ -10,7 +10,6 @@ import {
|
||||
WorkspaceHealthOptions,
|
||||
} 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 { ObjectMetadataService } from 'src/engine/metadata-modules/object-metadata/object-metadata.service';
|
||||
import { WorkspaceMigrationEntity } from 'src/engine/metadata-modules/workspace-migration/workspace-migration.entity';
|
||||
@ -31,7 +30,6 @@ export class WorkspaceHealthService {
|
||||
@InjectDataSource('metadata')
|
||||
private readonly metadataDataSource: DataSource,
|
||||
private readonly dataSourceService: DataSourceService,
|
||||
private readonly typeORMService: TypeORMService,
|
||||
private readonly objectMetadataService: ObjectMetadataService,
|
||||
private readonly databaseStructureService: DatabaseStructureService,
|
||||
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 =
|
||||
await this.objectMetadataService.findManyWithinWorkspace(workspaceId);
|
||||
|
||||
|
||||
@ -196,20 +196,18 @@ export class WorkspaceManagerService {
|
||||
dataSourceMetadata: DataSourceEntity,
|
||||
workspaceId: string,
|
||||
) {
|
||||
const workspaceDataSource =
|
||||
await this.workspaceDataSourceService.connectToWorkspaceDataSource(
|
||||
workspaceId,
|
||||
);
|
||||
const mainDataSource =
|
||||
await this.workspaceDataSourceService.connectToMainDataSource();
|
||||
|
||||
if (!workspaceDataSource) {
|
||||
throw new Error('Could not connect to workspace data source');
|
||||
if (!mainDataSource) {
|
||||
throw new Error('Could not connect to main data source');
|
||||
}
|
||||
|
||||
const createdObjectMetadata =
|
||||
await this.objectMetadataService.findManyWithinWorkspace(workspaceId);
|
||||
|
||||
await standardObjectsPrefillData(
|
||||
workspaceDataSource,
|
||||
mainDataSource,
|
||||
dataSourceMetadata.schema,
|
||||
createdObjectMetadata,
|
||||
);
|
||||
@ -226,20 +224,18 @@ export class WorkspaceManagerService {
|
||||
dataSourceMetadata: DataSourceEntity,
|
||||
workspaceId: string,
|
||||
) {
|
||||
const workspaceDataSource =
|
||||
await this.workspaceDataSourceService.connectToWorkspaceDataSource(
|
||||
workspaceId,
|
||||
);
|
||||
const mainDataSource =
|
||||
await this.workspaceDataSourceService.connectToMainDataSource();
|
||||
|
||||
if (!workspaceDataSource) {
|
||||
throw new Error('Could not connect to workspace data source');
|
||||
if (!mainDataSource) {
|
||||
throw new Error('Could not connect to main data source');
|
||||
}
|
||||
|
||||
const createdObjectMetadata =
|
||||
await this.objectMetadataService.findManyWithinWorkspace(workspaceId);
|
||||
|
||||
await seedWorkspaceWithDemoData(
|
||||
workspaceDataSource,
|
||||
mainDataSource,
|
||||
dataSourceMetadata.schema,
|
||||
createdObjectMetadata,
|
||||
);
|
||||
|
||||
@ -55,13 +55,11 @@ export class WorkspaceMigrationRunnerService {
|
||||
public async executeMigrationFromPendingMigrations(
|
||||
workspaceId: string,
|
||||
): Promise<WorkspaceMigrationTableAction[]> {
|
||||
const workspaceDataSource =
|
||||
await this.workspaceDataSourceService.connectToWorkspaceDataSource(
|
||||
workspaceId,
|
||||
);
|
||||
const mainDataSource =
|
||||
await this.workspaceDataSourceService.connectToMainDataSource();
|
||||
|
||||
if (!workspaceDataSource) {
|
||||
throw new Error('Workspace data source not found');
|
||||
if (!mainDataSource) {
|
||||
throw new Error('Main data source not found');
|
||||
}
|
||||
|
||||
const pendingMigrations =
|
||||
@ -76,7 +74,7 @@ export class WorkspaceMigrationRunnerService {
|
||||
return [...acc, ...pendingMigration.migrations];
|
||||
}, []);
|
||||
|
||||
const queryRunner = workspaceDataSource?.createQueryRunner();
|
||||
const queryRunner = mainDataSource.createQueryRunner();
|
||||
|
||||
await queryRunner.connect();
|
||||
await queryRunner.startTransaction();
|
||||
|
||||
@ -28,7 +28,6 @@ describe('destroyOneObjectRecordsPermissions', () => {
|
||||
});
|
||||
|
||||
it('should throw a permission error when user does not have permission (guest role)', async () => {
|
||||
const personId = randomUUID();
|
||||
const graphqlOperation = destroyOneOperationFactory({
|
||||
objectMetadataSingularName: 'person',
|
||||
gqlFields: PERSON_GQL_FIELDS,
|
||||
|
||||
Reference in New Issue
Block a user