Remove DataSourceService and clean up datasource migration logic (#19532)

## Summary

- **Drop the `objectMetadata.dataSourceId` foreign key and index** via a
1-22 fast instance command — column kept nullable for data preservation
- **Delete `DataSourceService`, `DataSourceModule`, and
`DataSourceException`** — all code now uses `workspace.databaseSchema`
directly
- **Remove `IS_DATASOURCE_MIGRATED` feature flag** from default flags
and all branching logic
- **Simplify workspace/object creation pipelines** —
`WorkspaceManagerService`, `DevSeederService`, and the object creation
action handler no longer route through `DataSourceService`
- **Keep `DataSourceEntity` and the `dataSource` table** for historical
data — entity stripped of all ORM relations
This commit is contained in:
Charles Bochet
2026-04-10 09:34:05 +02:00
committed by GitHub
parent 4fd721a3c9
commit f6423f5925
55 changed files with 75 additions and 388 deletions
@@ -3,8 +3,6 @@ import { Module } from '@nestjs/common';
import { ScalarsExplorerService } from 'src/engine/api/graphql/services/scalars-explorer.service';
import { WorkspaceSchemaBuilderModule } from 'src/engine/api/graphql/workspace-schema-builder/workspace-schema-builder.module';
import { WorkspaceGraphqlSchemaSDLService } from 'src/engine/api/graphql/workspace-graphql-schema-sdl/workspace-graphql-schema-sdl.service';
import { FeatureFlagModule } from 'src/engine/core-modules/feature-flag/feature-flag.module';
import { DataSourceModule } from 'src/engine/metadata-modules/data-source/data-source.module';
import { WorkspaceManyOrAllFlatEntityMapsCacheModule } from 'src/engine/metadata-modules/flat-entity/services/workspace-many-or-all-flat-entity-maps-cache.module';
import { WorkspaceCacheStorageModule } from 'src/engine/workspace-cache-storage/workspace-cache-storage.module';
@@ -13,8 +11,6 @@ import { WorkspaceCacheStorageModule } from 'src/engine/workspace-cache-storage/
WorkspaceSchemaBuilderModule,
WorkspaceCacheStorageModule,
WorkspaceManyOrAllFlatEntityMapsCacheModule,
FeatureFlagModule,
DataSourceModule,
],
providers: [WorkspaceGraphqlSchemaSDLService, ScalarsExplorerService],
exports: [WorkspaceGraphqlSchemaSDLService],
@@ -2,14 +2,11 @@ import { Injectable } from '@nestjs/common';
import { isNonEmptyString } from '@sniptt/guards';
import { printSchema } from 'graphql';
import { FeatureFlagKey } from 'twenty-shared/types';
import { isDefined } from 'twenty-shared/utils';
import { ScalarsExplorerService } from 'src/engine/api/graphql/services/scalars-explorer.service';
import { WorkspaceGraphQLSchemaGenerator } from 'src/engine/api/graphql/workspace-schema-builder/workspace-graphql-schema.factory';
import { FeatureFlagService } from 'src/engine/core-modules/feature-flag/services/feature-flag.service';
import { FlatWorkspace } from 'src/engine/core-modules/workspace/types/flat-workspace.type';
import { DataSourceService } from 'src/engine/metadata-modules/data-source/data-source.service';
import {
FlatEntityMapsException,
FlatEntityMapsExceptionCode,
@@ -37,28 +34,13 @@ export class WorkspaceGraphqlSchemaSDLService {
private readonly workspaceGraphQLSchemaGenerator: WorkspaceGraphQLSchemaGenerator,
private readonly workspaceCacheStorageService: WorkspaceCacheStorageService,
private readonly workspaceManyOrAllFlatEntityMapsCacheService: WorkspaceManyOrAllFlatEntityMapsCacheService,
private readonly featureFlagService: FeatureFlagService,
private readonly dataSourceService: DataSourceService,
) {}
async getOrComputeSchemaSDL(
workspace: FlatWorkspace,
applicationId?: string,
): Promise<WorkspaceGraphqlSchemaSDLResult | null> {
const isDataSourceMigrated = await this.featureFlagService.isFeatureEnabled(
FeatureFlagKey.IS_DATASOURCE_MIGRATED,
workspace.id,
);
const hasSchema = isDataSourceMigrated
? isNonEmptyString(workspace.databaseSchema)
: (
await this.dataSourceService.getDataSourcesMetadataFromWorkspaceId(
workspace.id,
)
).length > 0;
if (!hasSchema) {
if (!isNonEmptyString(workspace.databaseSchema)) {
return null;
}
@@ -16,7 +16,6 @@ import { UserWorkspaceEntity } from 'src/engine/core-modules/user-workspace/user
import { UserEntity } from 'src/engine/core-modules/user/user.entity';
import { WorkspaceEntity } from 'src/engine/core-modules/workspace/workspace.entity';
import { CoreEntityCacheModule } from 'src/engine/core-entity-cache/core-entity-cache.module';
import { DataSourceModule } from 'src/engine/metadata-modules/data-source/data-source.module';
import { PermissionsModule } from 'src/engine/metadata-modules/permissions/permissions.module';
import { WorkspaceCacheModule } from 'src/engine/workspace-cache/workspace-cache.module';
@@ -31,7 +30,6 @@ import { WorkspaceCacheModule } from 'src/engine/workspace-cache/workspace-cache
ApplicationEntity,
]),
TypeORMModule,
DataSourceModule,
PermissionsModule,
WorkspaceCacheModule,
CoreEntityCacheModule,
@@ -42,7 +42,6 @@ import { UserWorkspaceEntity } from 'src/engine/core-modules/user-workspace/user
import { WorkspaceEntity } from 'src/engine/core-modules/workspace/workspace.entity';
import { AiBillingModule } from 'src/engine/metadata-modules/ai/ai-billing/ai-billing.module';
import { AiModelsModule } from 'src/engine/metadata-modules/ai/ai-models/ai-models.module';
import { DataSourceModule } from 'src/engine/metadata-modules/data-source/data-source.module';
import { PermissionsModule } from 'src/engine/metadata-modules/permissions/permissions.module';
@Module({
@@ -66,7 +65,6 @@ import { PermissionsModule } from 'src/engine/metadata-modules/permissions/permi
UserWorkspaceEntity,
FeatureFlagEntity,
]),
DataSourceModule,
MetricsModule,
EnterpriseModule,
WorkspaceIteratorModule,
@@ -21,7 +21,6 @@ import { WorkspaceEntity } from 'src/engine/core-modules/workspace/workspace.ent
import { WorkspaceModule } from 'src/engine/core-modules/workspace/workspace.module';
import { AiAgentMonitorModule } from 'src/engine/metadata-modules/ai/ai-agent-monitor/ai-agent-monitor.module';
import { AiChatModule } from 'src/engine/metadata-modules/ai/ai-chat/ai-chat.module';
import { DataSourceModule } from 'src/engine/metadata-modules/data-source/data-source.module';
import { LogicFunctionModule } from 'src/engine/metadata-modules/logic-function/logic-function.module';
import { NavigationMenuItemModule } from 'src/engine/metadata-modules/navigation-menu-item/navigation-menu-item.module';
import { ObjectMetadataModule } from 'src/engine/metadata-modules/object-metadata/object-metadata.module';
@@ -45,7 +44,6 @@ import { WorkflowModule } from 'src/modules/workflow/workflow.module';
@Module({
imports: [
TypeOrmModule.forFeature([WorkspaceEntity, BillingSubscriptionEntity]),
DataSourceModule,
ObjectMetadataModule,
TypeORMModule,
UserModule,
@@ -18,7 +18,6 @@ import { UserWorkspaceService } from 'src/engine/core-modules/user-workspace/use
import { UserEntity } from 'src/engine/core-modules/user/user.entity';
import { WorkspaceInvitationModule } from 'src/engine/core-modules/workspace-invitation/workspace-invitation.module';
import { WorkspaceEntity } from 'src/engine/core-modules/workspace/workspace.entity';
import { DataSourceModule } from 'src/engine/metadata-modules/data-source/data-source.module';
import { ObjectMetadataEntity } from 'src/engine/metadata-modules/object-metadata/object-metadata.entity';
import { PermissionsModule } from 'src/engine/metadata-modules/permissions/permissions.module';
import { RoleTargetEntity } from 'src/engine/metadata-modules/role-target/role-target.entity';
@@ -40,7 +39,6 @@ import { WorkspaceDataSourceModule } from 'src/engine/workspace-datasource/works
RoleValidationModule,
NestjsQueryTypeOrmModule.forFeature([ObjectMetadataEntity]),
TypeORMModule,
DataSourceModule,
WorkspaceDataSourceModule,
ApprovedAccessDomainModule,
WorkspaceInvitationModule,
@@ -19,7 +19,6 @@ import { UserWorkspaceService } from 'src/engine/core-modules/user-workspace/use
import { UserEntity } from 'src/engine/core-modules/user/user.entity';
import { WorkspaceInvitationService } from 'src/engine/core-modules/workspace-invitation/services/workspace-invitation.service';
import { type WorkspaceEntity } from 'src/engine/core-modules/workspace/workspace.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';
import { RoleTargetEntity } from 'src/engine/metadata-modules/role-target/role-target.entity';
@@ -78,12 +77,6 @@ describe('UserWorkspaceService', () => {
validateRoleAssignableToUsersOrThrow: jest.fn(),
},
},
{
provide: DataSourceService,
useValue: {
getLastDataSourceMetadataFromWorkspaceIdOrFail: jest.fn(),
},
},
{
provide: WorkspaceInvitationService,
useValue: {
@@ -22,7 +22,6 @@ import { UserVarsModule } from 'src/engine/core-modules/user/user-vars/user-vars
import { UserEntity } from 'src/engine/core-modules/user/user.entity';
import { UserResolver } from 'src/engine/core-modules/user/user.resolver';
import { WorkspaceModule } from 'src/engine/core-modules/workspace/workspace.module';
import { DataSourceModule } from 'src/engine/metadata-modules/data-source/data-source.module';
import { ObjectMetadataEntity } from 'src/engine/metadata-modules/object-metadata/object-metadata.entity';
import { PermissionsModule } from 'src/engine/metadata-modules/permissions/permissions.module';
import { UserRoleModule } from 'src/engine/metadata-modules/user-role/user-role.module';
@@ -44,7 +43,6 @@ import { UserService } from './services/user.service';
resolvers: userAutoResolverOpts,
}),
NestjsQueryTypeOrmModule.forFeature([ObjectMetadataEntity]),
DataSourceModule,
WorkspaceModule,
OnboardingModule,
TypeOrmModule.forFeature([KeyValuePairEntity, UserWorkspaceEntity]),
@@ -34,7 +34,6 @@ import { WorkspaceEntity } from 'src/engine/core-modules/workspace/workspace.ent
import { WorkspaceResolver } from 'src/engine/core-modules/workspace/workspace.resolver';
import { BillingDisabledGuard } from 'src/engine/guards/billing-disabled.guard';
import { AiAgentModule } from 'src/engine/metadata-modules/ai/ai-agent/ai-agent.module';
import { DataSourceModule } from 'src/engine/metadata-modules/data-source/data-source.module';
import { WorkspaceManyOrAllFlatEntityMapsCacheModule } from 'src/engine/metadata-modules/flat-entity/services/workspace-many-or-all-flat-entity-maps-cache.module';
import { ObjectMetadataModule } from 'src/engine/metadata-modules/object-metadata/object-metadata.module';
import { PermissionsModule } from 'src/engine/metadata-modules/permissions/permissions.module';
@@ -67,7 +66,6 @@ import { StandardObjectsPrefillModule } from 'src/engine/workspace-manager/stand
UserWorkspaceModule,
WorkspaceManagerModule,
FeatureFlagModule,
DataSourceModule,
OnboardingModule,
WorkspaceDataSourceModule,
TypeORMModule,
@@ -4,19 +4,16 @@ import {
type DataSourceOptions,
Entity,
Index,
OneToMany,
PrimaryGeneratedColumn,
UpdateDateColumn,
} from 'typeorm';
import { ObjectMetadataEntity } from 'src/engine/metadata-modules/object-metadata/object-metadata.entity';
import { WorkspaceRelatedEntity } from 'src/engine/workspace-manager/types/workspace-related-entity';
export type DataSourceType = DataSourceOptions['type'];
// @deprecated - This entity is being deprecated in favor of storing
// databaseSchema directly on WorkspaceEntity.
// During the transition, writes go to both tables (dual-write).
// @deprecated - This entity is kept only to preserve the dataSource table.
// All code should use workspace.databaseSchema instead.
@Entity('dataSource')
@Index('IDX_DATA_SOURCE_WORKSPACE_ID_CREATED_AT', ['workspaceId', 'createdAt'])
export class DataSourceEntity extends WorkspaceRelatedEntity {
@@ -38,11 +35,6 @@ export class DataSourceEntity extends WorkspaceRelatedEntity {
@Column({ default: false })
isRemote: boolean;
@OneToMany(() => ObjectMetadataEntity, (object) => object.dataSource, {
cascade: true,
})
objects: ObjectMetadataEntity[];
@CreateDateColumn({ type: 'timestamptz' })
createdAt: Date;
@@ -1,33 +0,0 @@
import { type MessageDescriptor } from '@lingui/core';
import { assertUnreachable } from 'twenty-shared/utils';
import { STANDARD_ERROR_MESSAGE } from 'src/engine/api/common/common-query-runners/errors/standard-error-message.constant';
import { CustomException } from 'src/utils/custom-exception';
export enum DataSourceExceptionCode {
DATA_SOURCE_NOT_FOUND = 'DATA_SOURCE_NOT_FOUND',
}
const getDataSourceExceptionUserFriendlyMessage = (
code: DataSourceExceptionCode,
) => {
switch (code) {
case DataSourceExceptionCode.DATA_SOURCE_NOT_FOUND:
return STANDARD_ERROR_MESSAGE;
default:
assertUnreachable(code);
}
};
export class DataSourceException extends CustomException<DataSourceExceptionCode> {
constructor(
message: string,
code: DataSourceExceptionCode,
{ userFriendlyMessage }: { userFriendlyMessage?: MessageDescriptor } = {},
) {
super(message, code, {
userFriendlyMessage:
userFriendlyMessage ?? getDataSourceExceptionUserFriendlyMessage(code),
});
}
}
@@ -1,14 +0,0 @@
import { Module } from '@nestjs/common';
import { TypeOrmModule } from '@nestjs/typeorm';
import { WorkspaceEntity } from 'src/engine/core-modules/workspace/workspace.entity';
import { DataSourceEntity } from './data-source.entity';
import { DataSourceService } from './data-source.service';
@Module({
imports: [TypeOrmModule.forFeature([DataSourceEntity, WorkspaceEntity])],
providers: [DataSourceService],
exports: [DataSourceService],
})
export class DataSourceModule {}
@@ -1,106 +0,0 @@
import { Injectable } from '@nestjs/common';
import { InjectRepository } from '@nestjs/typeorm';
import { type FindManyOptions, Repository } from 'typeorm';
import { WorkspaceEntity } from 'src/engine/core-modules/workspace/workspace.entity';
import {
DataSourceException,
DataSourceExceptionCode,
} from 'src/engine/metadata-modules/data-source/data-source.exception';
import { DataSourceEntity } from './data-source.entity';
// @deprecated - This service is being deprecated. During the transition,
// writes go to both the dataSource table and workspace table (dual-write).
// Reads should progressively migrate to use workspace.databaseSchema
// or the deterministic getWorkspaceSchemaName(workspaceId) utility.
@Injectable()
export class DataSourceService {
constructor(
@InjectRepository(DataSourceEntity)
private readonly dataSourceMetadataRepository: Repository<DataSourceEntity>,
@InjectRepository(WorkspaceEntity)
private readonly workspaceRepository: Repository<WorkspaceEntity>,
) {}
async createDataSourceMetadata(
workspaceId: string,
workspaceSchema: string,
): Promise<DataSourceEntity> {
const dataSource = await this.dataSourceMetadataRepository.findOne({
where: { workspaceId },
});
// Dual-write: always keep workspace.databaseSchema in sync
await this.workspaceRepository.update(workspaceId, {
databaseSchema: workspaceSchema,
});
if (dataSource) {
return dataSource;
}
return this.dataSourceMetadataRepository.save({
workspaceId,
schema: workspaceSchema,
});
}
// @deprecated - Use workspace.activationStatus or workspace.databaseSchema
// to check if a workspace has been initialized instead.
async getManyDataSourceMetadata(
options: FindManyOptions<DataSourceEntity> = {},
): Promise<DataSourceEntity[]> {
return this.dataSourceMetadataRepository.find(options);
}
// @deprecated - Use workspace.databaseSchema or
// getWorkspaceSchemaName(workspaceId) instead.
async getDataSourcesMetadataFromWorkspaceId(
workspaceId: string,
): Promise<DataSourceEntity[]> {
return this.dataSourceMetadataRepository.find({
where: { workspaceId },
order: { createdAt: 'DESC' },
});
}
// @deprecated - Use workspace.databaseSchema or
// getWorkspaceSchemaName(workspaceId) instead.
async getLastDataSourceMetadataFromWorkspaceId(
workspaceId: string,
): Promise<DataSourceEntity | null> {
return this.dataSourceMetadataRepository.findOne({
where: { workspaceId },
order: { createdAt: 'DESC' },
});
}
// @deprecated - Use workspace.databaseSchema or
// getWorkspaceSchemaName(workspaceId) instead.
async getLastDataSourceMetadataFromWorkspaceIdOrFail(
workspaceId: string,
): Promise<DataSourceEntity> {
try {
return this.dataSourceMetadataRepository.findOneOrFail({
where: { workspaceId },
order: { createdAt: 'DESC' },
});
} catch (error) {
throw new DataSourceException(
`Data source not found for workspace ${workspaceId}: ${error}`,
DataSourceExceptionCode.DATA_SOURCE_NOT_FOUND,
);
}
}
async delete(workspaceId: string): Promise<void> {
await this.dataSourceMetadataRepository.delete({ workspaceId });
// Dual-write: clear workspace.databaseSchema on delete
await this.workspaceRepository.update(workspaceId, {
databaseSchema: null,
});
}
}
@@ -12,7 +12,6 @@ import { ActorModule } from 'src/engine/core-modules/actor/actor.module';
import { ApplicationModule } from 'src/engine/core-modules/application/application.module';
import { FeatureFlagModule } from 'src/engine/core-modules/feature-flag/feature-flag.module';
import { WorkspaceAuthGuard } from 'src/engine/guards/workspace-auth.guard';
import { DataSourceModule } from 'src/engine/metadata-modules/data-source/data-source.module';
import { FieldMetadataDTO } from 'src/engine/metadata-modules/field-metadata/dtos/field-metadata.dto';
import { FieldMetadataResolver } from 'src/engine/metadata-modules/field-metadata/field-metadata.resolver';
import { FieldMetadataGraphqlApiExceptionInterceptor } from 'src/engine/metadata-modules/field-metadata/interceptors/field-metadata-graphql-api-exception.interceptor';
@@ -49,7 +48,6 @@ import { UpdateFieldInput } from './dtos/update-field.input';
WorkspaceMetadataVersionModule,
WorkspaceCacheStorageModule,
ObjectMetadataModule,
DataSourceModule,
TypeORMModule,
ActorModule,
ApplicationModule,
@@ -79,7 +79,6 @@ export const ALL_MANY_TO_ONE_METADATA_FOREIGN_KEY = {
},
},
objectMetadata: {
dataSource: null,
workspace: null,
application: null,
},
@@ -139,7 +139,6 @@ export const ALL_MANY_TO_ONE_METADATA_RELATIONS = {
},
},
objectMetadata: {
dataSource: null,
workspace: null,
application: null,
},
@@ -8,7 +8,6 @@ import { AiGenerateTextModule } from 'src/engine/metadata-modules/ai/ai-generate
import { CalendarChannelMetadataModule } from 'src/engine/metadata-modules/calendar-channel/calendar-channel-metadata.module';
import { ConnectedAccountMetadataModule } from 'src/engine/metadata-modules/connected-account/connected-account-metadata.module';
import { CommandMenuItemModule } from 'src/engine/metadata-modules/command-menu-item/command-menu-item.module';
import { DataSourceModule } from 'src/engine/metadata-modules/data-source/data-source.module';
import { FieldMetadataModule } from 'src/engine/metadata-modules/field-metadata/field-metadata.module';
import { FlatEntityMapsGraphqlApiExceptionFilter } from 'src/engine/metadata-modules/flat-entity/filters/flat-entity-maps-graphql-api-exception.filter';
import { FrontComponentModule } from 'src/engine/metadata-modules/front-component/front-component.module';
@@ -30,7 +29,6 @@ import { WorkspaceMetadataVersionModule } from 'src/engine/metadata-modules/work
@Module({
imports: [
DataSourceModule,
FieldMetadataModule,
FrontComponentModule,
ObjectMetadataModule,
@@ -63,7 +61,6 @@ import { WorkspaceMetadataVersionModule } from 'src/engine/metadata-modules/work
},
],
exports: [
DataSourceModule,
FieldMetadataModule,
FrontComponentModule,
ObjectMetadataModule,
@@ -60,9 +60,6 @@ export class CreateObjectInput {
@Field({ nullable: true })
color?: string;
@HideField()
dataSourceId: string;
@HideField()
applicationId?: string;
@@ -2,8 +2,6 @@ import {
Column,
CreateDateColumn,
Entity,
Index,
ManyToOne,
OneToMany,
PrimaryGeneratedColumn,
type Relation,
@@ -12,7 +10,6 @@ import {
} from 'typeorm';
import { type WorkspaceEntityDuplicateCriteria } from 'src/engine/api/graphql/workspace-query-builder/types/workspace-entity-duplicate-criteria.type';
import { DataSourceEntity } from 'src/engine/metadata-modules/data-source/data-source.entity';
import { FieldMetadataEntity } from 'src/engine/metadata-modules/field-metadata/field-metadata.entity';
import { IndexMetadataEntity } from 'src/engine/metadata-modules/index-metadata/index-metadata.entity';
import { type ObjectStandardOverridesDTO } from 'src/engine/metadata-modules/object-metadata/dtos/object-standard-overrides.dto';
@@ -31,7 +28,6 @@ import { type JsonbProperty } from 'src/engine/workspace-manager/workspace-migra
'namePlural',
'workspaceId',
])
@Index('IDX_OBJECT_METADATA_DATA_SOURCE_ID', ['dataSourceId'])
export class ObjectMetadataEntity
extends SyncableEntity
implements Required<ObjectMetadataEntity>
@@ -39,7 +35,8 @@ export class ObjectMetadataEntity
@PrimaryGeneratedColumn('uuid')
id: string;
@Column({ nullable: false, type: 'uuid' })
// @deprecated - FK dropped, column kept for data preservation only
@Column({ nullable: true, type: 'uuid' })
dataSourceId: string;
@Column({ nullable: false })
@@ -120,11 +117,6 @@ export class ObjectMetadataEntity
})
indexMetadatas: Relation<IndexMetadataEntity[]>;
@ManyToOne(() => DataSourceEntity, (dataSource) => dataSource.objects, {
onDelete: 'CASCADE',
})
dataSource: Relation<DataSourceEntity>;
@CreateDateColumn({ type: 'timestamptz' })
createdAt: Date;
@@ -13,7 +13,6 @@ import { ApplicationModule } from 'src/engine/core-modules/application/applicati
import { FeatureFlagEntity } from 'src/engine/core-modules/feature-flag/feature-flag.entity';
import { FeatureFlagModule } from 'src/engine/core-modules/feature-flag/feature-flag.module';
import { WorkspaceAuthGuard } from 'src/engine/guards/workspace-auth.guard';
import { DataSourceModule } from 'src/engine/metadata-modules/data-source/data-source.module';
import { FieldMetadataEntity } from 'src/engine/metadata-modules/field-metadata/field-metadata.entity';
import { WorkspaceManyOrAllFlatEntityMapsCacheModule } from 'src/engine/metadata-modules/flat-entity/services/workspace-many-or-all-flat-entity-maps-cache.module';
import { FlatFieldMetadataTypeValidatorService } from 'src/engine/metadata-modules/flat-field-metadata/services/flat-field-metadata-type-validator.service';
@@ -50,7 +49,6 @@ import { WorkspaceMigrationModule } from 'src/engine/workspace-manager/workspace
]),
TypeOrmModule.forFeature([FeatureFlagEntity, ViewEntity]),
ApplicationModule,
DataSourceModule,
WorkspaceMetadataVersionModule,
IndexMetadataModule,
PermissionsModule,
@@ -1,17 +1,13 @@
import { Module } from '@nestjs/common';
import { TokenModule } from 'src/engine/core-modules/auth/token/token.module';
import { FeatureFlagModule } from 'src/engine/core-modules/feature-flag/feature-flag.module';
import { JwtModule } from 'src/engine/core-modules/jwt/jwt.module';
import { DataSourceModule } from 'src/engine/metadata-modules/data-source/data-source.module';
import { WorkspaceManyOrAllFlatEntityMapsCacheModule } from 'src/engine/metadata-modules/flat-entity/services/workspace-many-or-all-flat-entity-maps-cache.module';
import { MiddlewareService } from 'src/engine/middlewares/middleware.service';
import { WorkspaceCacheStorageModule } from 'src/engine/workspace-cache-storage/workspace-cache-storage.module';
@Module({
imports: [
DataSourceModule,
FeatureFlagModule,
WorkspaceCacheStorageModule,
WorkspaceManyOrAllFlatEntityMapsCacheModule,
TokenModule,
@@ -2,7 +2,6 @@ import { Injectable } from '@nestjs/common';
import { isNonEmptyString } from '@sniptt/guards';
import { type Request, type Response } from 'express';
import { FeatureFlagKey } from 'twenty-shared/types';
import { type APP_LOCALES, SOURCE_LOCALE } from 'twenty-shared/translations';
import { isDefined } from 'twenty-shared/utils';
@@ -11,10 +10,8 @@ import { AuthGraphqlApiExceptionFilter } from 'src/engine/core-modules/auth/filt
import { AccessTokenService } from 'src/engine/core-modules/auth/token/services/access-token.service';
import { getAuthExceptionRestStatus } from 'src/engine/core-modules/auth/utils/get-auth-exception-rest-status.util';
import { ExceptionHandlerService } from 'src/engine/core-modules/exception-handler/exception-handler.service';
import { FeatureFlagService } from 'src/engine/core-modules/feature-flag/services/feature-flag.service';
import { ErrorCode } from 'src/engine/core-modules/graphql/utils/graphql-errors.util';
import { JwtWrapperService } from 'src/engine/core-modules/jwt/services/jwt-wrapper.service';
import { DataSourceService } from 'src/engine/metadata-modules/data-source/data-source.service';
import { WorkspaceManyOrAllFlatEntityMapsCacheService } from 'src/engine/metadata-modules/flat-entity/services/workspace-many-or-all-flat-entity-maps-cache.service';
import { INTERNAL_SERVER_ERROR } from 'src/engine/middlewares/constants/default-error-message.constant';
import { bindDataToRequestObject } from 'src/engine/utils/bind-data-to-request-object.util';
@@ -31,8 +28,6 @@ export class MiddlewareService {
private readonly accessTokenService: AccessTokenService,
private readonly workspaceStorageCacheService: WorkspaceCacheStorageService,
private readonly flatEntityMapsCacheService: WorkspaceManyOrAllFlatEntityMapsCacheService,
private readonly dataSourceService: DataSourceService,
private readonly featureFlagService: FeatureFlagService,
private readonly exceptionHandlerService: ExceptionHandlerService,
private readonly jwtWrapperService: JwtWrapperService,
) {}
@@ -114,20 +109,7 @@ export class MiddlewareService {
throw new Error('No data sources found');
}
const isDataSourceMigrated = await this.featureFlagService.isFeatureEnabled(
FeatureFlagKey.IS_DATASOURCE_MIGRATED,
data.workspace.id,
);
const hasSchema = isDataSourceMigrated
? isNonEmptyString(data.workspace.databaseSchema)
: (
await this.dataSourceService.getDataSourcesMetadataFromWorkspaceId(
data.workspace.id,
)
).length > 0;
if (!hasSchema) {
if (!isNonEmptyString(data.workspace.databaseSchema)) {
throw new Error('No data sources found');
}
@@ -3,7 +3,6 @@ import { TypeOrmModule } from '@nestjs/typeorm';
import { TwentyConfigModule } from 'src/engine/core-modules/twenty-config/twenty-config.module';
import { WorkspaceEntity } from 'src/engine/core-modules/workspace/workspace.entity';
import { DataSourceModule } from 'src/engine/metadata-modules/data-source/data-source.module';
import { FieldMetadataEntity } from 'src/engine/metadata-modules/field-metadata/field-metadata.entity';
import { WorkspaceManyOrAllFlatEntityMapsCacheModule } from 'src/engine/metadata-modules/flat-entity/services/workspace-many-or-all-flat-entity-maps-cache.module';
import { ObjectMetadataEntity } from 'src/engine/metadata-modules/object-metadata/object-metadata.entity';
@@ -27,7 +26,6 @@ import { WorkspaceEventEmitterModule } from 'src/engine/workspace-event-emitter/
ObjectMetadataEntity,
FieldMetadataEntity,
]),
DataSourceModule,
WorkspaceCacheStorageModule,
WorkspaceManyOrAllFlatEntityMapsCacheModule,
WorkspaceFeatureFlagsMapCacheModule,
@@ -4,7 +4,6 @@ import { TypeOrmModule } from '@nestjs/typeorm';
import { FeatureFlagModule } from 'src/engine/core-modules/feature-flag/feature-flag.module';
import { TwentyConfigModule } from 'src/engine/core-modules/twenty-config/twenty-config.module';
import { WorkspaceEntity } from 'src/engine/core-modules/workspace/workspace.entity';
import { DataSourceModule } from 'src/engine/metadata-modules/data-source/data-source.module';
import { WorkspaceManyOrAllFlatEntityMapsCacheModule } from 'src/engine/metadata-modules/flat-entity/services/workspace-many-or-all-flat-entity-maps-cache.module';
import { ObjectMetadataEntity } from 'src/engine/metadata-modules/object-metadata/object-metadata.entity';
import { PermissionsModule } from 'src/engine/metadata-modules/permissions/permissions.module';
@@ -23,7 +22,6 @@ import { WorkspaceCacheModule } from 'src/engine/workspace-cache/workspace-cache
RoleTargetEntity,
WorkspaceEntity,
]),
DataSourceModule,
WorkspaceCacheStorageModule,
WorkspaceManyOrAllFlatEntityMapsCacheModule,
PermissionsModule,
@@ -2,19 +2,12 @@ import { Module } from '@nestjs/common';
import { TypeOrmModule } from '@nestjs/typeorm';
import { TypeORMModule } from 'src/database/typeorm/typeorm.module';
import { FeatureFlagModule } from 'src/engine/core-modules/feature-flag/feature-flag.module';
import { WorkspaceEntity } from 'src/engine/core-modules/workspace/workspace.entity';
import { DataSourceModule } from 'src/engine/metadata-modules/data-source/data-source.module';
import { WorkspaceDataSourceService } from './workspace-datasource.service';
@Module({
imports: [
TypeOrmModule.forFeature([WorkspaceEntity]),
TypeORMModule,
FeatureFlagModule,
DataSourceModule,
],
imports: [TypeOrmModule.forFeature([WorkspaceEntity]), TypeORMModule],
exports: [WorkspaceDataSourceService],
providers: [WorkspaceDataSourceService],
})
@@ -3,13 +3,10 @@ import { InjectDataSource, InjectRepository } from '@nestjs/typeorm';
import { msg } from '@lingui/core/macro';
import { isNonEmptyString } from '@sniptt/guards';
import { FeatureFlagKey } from 'twenty-shared/types';
import { type DataSource, type EntityManager, Repository } from 'typeorm';
import { FeatureFlagService } from 'src/engine/core-modules/feature-flag/services/feature-flag.service';
import { TwentyConfigService } from 'src/engine/core-modules/twenty-config/twenty-config.service';
import { WorkspaceEntity } from 'src/engine/core-modules/workspace/workspace.entity';
import { DataSourceService } from 'src/engine/metadata-modules/data-source/data-source.service';
import {
PermissionsException,
PermissionsExceptionCode,
@@ -27,8 +24,6 @@ export class WorkspaceDataSourceService {
private readonly workspaceRepository: Repository<WorkspaceEntity>,
@InjectDataSource()
private readonly coreDataSource: DataSource,
private readonly featureFlagService: FeatureFlagService,
private readonly dataSourceService: DataSourceService,
private readonly twentyConfigService: TwentyConfigService,
) {}
@@ -43,26 +38,12 @@ export class WorkspaceDataSourceService {
}
public async checkSchemaExists(workspaceId: string) {
const isDataSourceMigrated = await this.featureFlagService.isFeatureEnabled(
FeatureFlagKey.IS_DATASOURCE_MIGRATED,
workspaceId,
);
const workspace = await this.workspaceRepository.findOne({
select: ['databaseSchema'],
where: { id: workspaceId },
});
if (isDataSourceMigrated) {
const workspace = await this.workspaceRepository.findOne({
select: ['databaseSchema'],
where: { id: workspaceId },
});
return isNonEmptyString(workspace?.databaseSchema);
}
const dataSources =
await this.dataSourceService.getDataSourcesMetadataFromWorkspaceId(
workspaceId,
);
return dataSources.length > 0;
return isNonEmptyString(workspace?.databaseSchema);
}
/**
@@ -9,7 +9,6 @@ import { FeatureFlagModule } from 'src/engine/core-modules/feature-flag/feature-
import { SdkClientModule } from 'src/engine/core-modules/sdk-client/sdk-client.module';
import { FileStorageModule } from 'src/engine/core-modules/file-storage/file-storage.module';
import { WorkspaceEntity } from 'src/engine/core-modules/workspace/workspace.entity';
import { DataSourceModule } from 'src/engine/metadata-modules/data-source/data-source.module';
import { FieldMetadataModule } from 'src/engine/metadata-modules/field-metadata/field-metadata.module';
import { WorkspaceManyOrAllFlatEntityMapsCacheModule } from 'src/engine/metadata-modules/flat-entity/services/workspace-many-or-all-flat-entity-maps-cache.module';
import { ObjectMetadataEntity } from 'src/engine/metadata-modules/object-metadata/object-metadata.entity';
@@ -37,7 +36,6 @@ import { WorkspaceMigrationModule } from 'src/engine/workspace-manager/workspace
WorkspaceDataSourceModule,
WorkspaceCacheStorageModule,
TypeORMModule,
DataSourceModule,
RoleModule,
RoleTargetModule,
UserRoleModule,
@@ -3,7 +3,6 @@ import { Injectable } from '@nestjs/common';
import { FieldMetadataType, RelationType } from 'twenty-shared/types';
import { isDefined } from 'twenty-shared/utils';
import { type DataSourceEntity } from 'src/engine/metadata-modules/data-source/data-source.entity';
import { FieldMetadataService } from 'src/engine/metadata-modules/field-metadata/services/field-metadata.service';
import { WorkspaceManyOrAllFlatEntityMapsCacheService } from 'src/engine/metadata-modules/flat-entity/services/workspace-many-or-all-flat-entity-maps-cache.service';
import { type FlatEntityMaps } from 'src/engine/metadata-modules/flat-entity/types/flat-entity-maps.type';
@@ -195,11 +194,9 @@ export class DevSeederMetadataService {
}
public async seed({
dataSourceMetadata,
workspaceId,
light = false,
}: {
dataSourceMetadata: DataSourceEntity;
workspaceId: string;
light?: boolean;
}) {
@@ -207,7 +204,6 @@ export class DevSeederMetadataService {
for (const obj of config.objects) {
await this.seedCustomObject({
dataSourceId: dataSourceMetadata.id,
workspaceId,
objectMetadataSeed: obj.seed,
});
@@ -231,19 +227,14 @@ export class DevSeederMetadataService {
}
private async seedCustomObject({
dataSourceId,
workspaceId,
objectMetadataSeed,
}: {
dataSourceId: string;
workspaceId: string;
objectMetadataSeed: ObjectMetadataSeed;
}): Promise<void> {
await this.objectMetadataService.createOneObject({
createObjectInput: {
...objectMetadataSeed,
dataSourceId,
},
createObjectInput: objectMetadataSeed,
workspaceId,
});
}
@@ -2,7 +2,7 @@ import { type CreateObjectInput } from 'src/engine/metadata-modules/object-metad
export type ObjectMetadataSeed = Omit<
CreateObjectInput,
'workspaceId' | 'dataSourceId' | 'fields'
'workspaceId' | 'fields'
> & {
skipNameField?: boolean;
};
@@ -1,14 +1,14 @@
import { Injectable, Logger } from '@nestjs/common';
import { InjectDataSource } from '@nestjs/typeorm';
import { InjectDataSource, InjectRepository } from '@nestjs/typeorm';
import { ALL_METADATA_NAME } from 'twenty-shared/metadata';
import { DataSource } from 'typeorm';
import { DataSource, Repository } from 'typeorm';
import { ApplicationRegistrationService } from 'src/engine/core-modules/application/application-registration/application-registration.service';
import { ApplicationService } from 'src/engine/core-modules/application/application.service';
import { SdkClientGenerationService } from 'src/engine/core-modules/sdk-client/sdk-client-generation.service';
import { TwentyConfigService } from 'src/engine/core-modules/twenty-config/twenty-config.service';
import { DataSourceService } from 'src/engine/metadata-modules/data-source/data-source.service';
import { WorkspaceEntity } from 'src/engine/core-modules/workspace/workspace.entity';
import { getMetadataFlatEntityMapsKey } from 'src/engine/metadata-modules/flat-entity/utils/get-metadata-flat-entity-maps-key.util';
import { getMetadataRelatedMetadataNames } from 'src/engine/metadata-modules/flat-entity/utils/get-metadata-related-metadata-names.util';
import { ObjectMetadataEntity } from 'src/engine/metadata-modules/object-metadata/object-metadata.entity';
@@ -33,7 +33,6 @@ export class DevSeederService {
private readonly workspaceCacheStorageService: WorkspaceCacheStorageService,
private readonly twentyConfigService: TwentyConfigService,
private readonly workspaceDataSourceService: WorkspaceDataSourceService,
private readonly dataSourceService: DataSourceService,
private readonly twentyStandardApplicationService: TwentyStandardApplicationService,
private readonly devSeederMetadataService: DevSeederMetadataService,
private readonly devSeederPermissionsService: DevSeederPermissionsService,
@@ -44,6 +43,8 @@ export class DevSeederService {
private readonly sdkClientGenerationService: SdkClientGenerationService,
@InjectDataSource()
private readonly coreDataSource: DataSource,
@InjectRepository(WorkspaceEntity)
private readonly workspaceRepository: Repository<WorkspaceEntity>,
) {}
public async seedDev(
@@ -74,11 +75,9 @@ export class DevSeederService {
['flatApplicationMaps', 'featureFlagsMap'],
);
const dataSourceMetadata =
await this.dataSourceService.createDataSourceMetadata(
workspaceId,
schemaName,
);
await this.workspaceRepository.update(workspaceId, {
databaseSchema: schemaName,
});
const { workspaceCustomFlatApplication, twentyStandardFlatApplication } =
await this.applicationService.findWorkspaceTwentyStandardAndCustomApplicationOrThrow(
@@ -101,7 +100,6 @@ export class DevSeederService {
});
await this.devSeederMetadataService.seed({
dataSourceMetadata,
workspaceId,
light,
});
@@ -165,7 +163,7 @@ export class DevSeederService {
);
await this.devSeederDataService.seed({
schemaName: dataSourceMetadata.schema,
schemaName,
workspaceId,
featureFlags: featureFlagsMap,
light,
@@ -10,7 +10,6 @@ import { UserVarsModule } from 'src/engine/core-modules/user/user-vars/user-vars
import { UserModule } from 'src/engine/core-modules/user/user.module';
import { WorkspaceEntity } from 'src/engine/core-modules/workspace/workspace.entity';
import { WorkspaceModule } from 'src/engine/core-modules/workspace/workspace.module';
import { DataSourceModule } from 'src/engine/metadata-modules/data-source/data-source.module';
import { CleanOnboardingWorkspacesCommand } from 'src/engine/workspace-manager/workspace-cleaner/commands/clean-onboarding-workspaces.command';
import { CleanOnboardingWorkspacesCronCommand } from 'src/engine/workspace-manager/workspace-cleaner/commands/clean-onboarding-workspaces.cron.command';
import { CleanSuspendedWorkspacesCommand } from 'src/engine/workspace-manager/workspace-cleaner/commands/clean-suspended-workspaces.command';
@@ -26,7 +25,6 @@ import { CleanerWorkspaceService } from 'src/engine/workspace-manager/workspace-
BillingSubscriptionEntity,
]),
WorkspaceModule,
DataSourceModule,
UserVarsModule,
UserModule,
EmailModule,
@@ -6,7 +6,6 @@ import { FeatureFlagModule } from 'src/engine/core-modules/feature-flag/feature-
import { UserWorkspaceEntity } from 'src/engine/core-modules/user-workspace/user-workspace.entity';
import { WorkspaceEntity } from 'src/engine/core-modules/workspace/workspace.entity';
import { AiAgentModule } from 'src/engine/metadata-modules/ai/ai-agent/ai-agent.module';
import { DataSourceModule } from 'src/engine/metadata-modules/data-source/data-source.module';
import { FieldMetadataEntity } from 'src/engine/metadata-modules/field-metadata/field-metadata.entity';
import { WorkspaceManyOrAllFlatEntityMapsCacheModule } from 'src/engine/metadata-modules/flat-entity/services/workspace-many-or-all-flat-entity-maps-cache.module';
import { LogicFunctionEntity } from 'src/engine/metadata-modules/logic-function/logic-function.entity';
@@ -29,7 +28,6 @@ import { WorkspaceManagerService } from './workspace-manager.service';
WorkspaceMigrationModule,
ObjectMetadataModule,
DevSeederModule,
DataSourceModule,
FeatureFlagModule,
PermissionsModule,
AiAgentModule,
@@ -14,7 +14,6 @@ import {
} from 'src/engine/core-modules/sdk-client/jobs/generate-sdk-client.job';
import { UserWorkspaceEntity } from 'src/engine/core-modules/user-workspace/user-workspace.entity';
import { WorkspaceEntity } from 'src/engine/core-modules/workspace/workspace.entity';
import { DataSourceService } from 'src/engine/metadata-modules/data-source/data-source.service';
import { RoleEntity } from 'src/engine/metadata-modules/role/role.entity';
import { RoleService } from 'src/engine/metadata-modules/role/role.service';
import { UserRoleService } from 'src/engine/metadata-modules/user-role/user-role.service';
@@ -28,7 +27,6 @@ export class WorkspaceManagerService {
constructor(
private readonly workspaceDataSourceService: WorkspaceDataSourceService,
private readonly dataSourceService: DataSourceService,
@InjectRepository(UserWorkspaceEntity)
private readonly userWorkspaceRepository: Repository<UserWorkspaceEntity>,
private readonly roleService: RoleService,
@@ -65,10 +63,9 @@ export class WorkspaceManagerService {
const dataSourceMetadataCreationStart = performance.now();
await this.dataSourceService.createDataSourceMetadata(
workspaceId,
schemaName,
);
await this.workspaceRepository.update(workspaceId, {
databaseSchema: schemaName,
});
await this.applicationService.createTwentyStandardApplication({
workspaceId,
@@ -1,5 +1,3 @@
import { FeatureFlagKey } from 'twenty-shared/types';
export const DEFAULT_FEATURE_FLAGS = [
FeatureFlagKey.IS_DATASOURCE_MIGRATED,
] as const satisfies FeatureFlagKey[];
export const DEFAULT_FEATURE_FLAGS = [] as const satisfies FeatureFlagKey[];
@@ -4,7 +4,6 @@ import { v4 } from 'uuid';
import { WorkspaceMigrationRunnerActionHandler } from 'src/engine/workspace-manager/workspace-migration/workspace-migration-runner/interfaces/workspace-migration-runner-action-handler-service.interface';
import { DataSourceEntity } from 'src/engine/metadata-modules/data-source/data-source.entity';
import { ALL_METADATA_ENTITY_BY_METADATA_NAME } from 'src/engine/metadata-modules/flat-entity/constant/all-metadata-entity-by-metadata-name.constant';
import { isCompositeFlatFieldMetadata } from 'src/engine/metadata-modules/flat-field-metadata/utils/is-composite-flat-field-metadata.util';
import { isEnumFlatFieldMetadata } from 'src/engine/metadata-modules/flat-field-metadata/utils/is-enum-flat-field-metadata.util';
@@ -42,19 +41,9 @@ export class CreateObjectActionHandlerService extends WorkspaceMigrationRunnerAc
override async transpileUniversalActionToFlatAction(
context: WorkspaceMigrationActionRunnerArgs<UniversalCreateObjectAction>,
): Promise<FlatCreateObjectAction> {
const { action, queryRunner, workspaceId, allFlatEntityMaps } = context;
const { action, workspaceId, allFlatEntityMaps } = context;
const { fieldIdByUniversalIdentifier, id: providedObjectId } = action;
const dataSourceRepository =
queryRunner.manager.getRepository<DataSourceEntity>(DataSourceEntity);
const lastDataSourceMetadata = await dataSourceRepository.findOneOrFail({
where: {
workspaceId,
},
order: { createdAt: 'DESC' },
});
const allFieldIdToBeCreatedInActionByUniversalIdentifierMap = new Map<
string,
string
@@ -77,7 +66,6 @@ export class CreateObjectActionHandlerService extends WorkspaceMigrationRunnerAc
allFieldIdToBeCreatedInActionByUniversalIdentifierMap,
allFlatEntityMaps,
context,
dataSourceId: lastDataSourceMetadata.id,
generatedId: providedObjectId ?? v4(),
universalFlatObjectMetadata: action.flatEntity,
});
@@ -20,7 +20,6 @@ export type FromUniversalFlatObjectMetadataToFlatObjectMetadataArgs = {
WorkspaceMigrationActionRunnerArgs<AllUniversalWorkspaceMigrationAction>,
'workspaceId' | 'flatApplication'
>;
dataSourceId: string;
};
const findFieldMetadataIdInCreateObjectContext = ({
@@ -49,7 +48,6 @@ const findFieldMetadataIdInCreateObjectContext = ({
export const fromUniversalFlatObjectMetadataToFlatObjectMetadata = ({
universalFlatObjectMetadata,
dataSourceId,
generatedId,
allFlatEntityMaps,
allFieldIdToBeCreatedInActionByUniversalIdentifierMap,
@@ -57,9 +55,7 @@ export const fromUniversalFlatObjectMetadataToFlatObjectMetadata = ({
flatApplication: { id: applicationId },
workspaceId,
},
}: FromUniversalFlatObjectMetadataToFlatObjectMetadataArgs): FlatObjectMetadata & {
dataSourceId: string;
} => {
}: FromUniversalFlatObjectMetadataToFlatObjectMetadataArgs): FlatObjectMetadata => {
const {
universalIdentifier,
applicationUniversalIdentifier,
@@ -107,7 +103,6 @@ export const fromUniversalFlatObjectMetadataToFlatObjectMetadata = ({
return {
...restProperties,
dataSourceId,
id: generatedId,
workspaceId,
applicationId,
@@ -6,7 +6,6 @@ import { WorkspaceIteratorModule } from 'src/database/commands/command-runners/w
import { TypeORMModule } from 'src/database/typeorm/typeorm.module';
import { FeatureFlagModule } from 'src/engine/core-modules/feature-flag/feature-flag.module';
import { WorkspaceEntity } from 'src/engine/core-modules/workspace/workspace.entity';
import { DataSourceModule } from 'src/engine/metadata-modules/data-source/data-source.module';
import { WorkspaceManyOrAllFlatEntityMapsCacheModule } from 'src/engine/metadata-modules/flat-entity/services/workspace-many-or-all-flat-entity-maps-cache.module';
import { WorkspaceMetadataVersionModule } from 'src/engine/metadata-modules/workspace-metadata-version/workspace-metadata-version.module';
import { WorkspaceCacheStorageModule } from 'src/engine/workspace-cache-storage/workspace-cache-storage.module';
@@ -20,7 +19,6 @@ import { WorkspaceMigrationRunnerService } from 'src/engine/workspace-manager/wo
imports: [
FeatureFlagModule,
TypeORMModule,
DataSourceModule,
WorkspaceMetadataVersionModule,
WorkspaceSchemaMigrationRunnerActionHandlersModule,
WorkspaceManyOrAllFlatEntityMapsCacheModule,