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:
+2
-10
@@ -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;
|
||||
|
||||
|
||||
-33
@@ -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 {}
|
||||
-106
@@ -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,
|
||||
});
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user