Convert metadata tables to camel_case (#2420)

* Convert metadata tables to camelcase

* refactor folder structure

* rename datasourcemetadata

* regenerate metadata schema

* rename dataSourceMetadata to dataSource
This commit is contained in:
Weiko
2023-11-10 15:33:25 +01:00
committed by GitHub
parent 6e7ad5eabc
commit 04c618284f
98 changed files with 1189 additions and 1735 deletions
@@ -0,0 +1,40 @@
import {
Column,
CreateDateColumn,
Entity,
PrimaryGeneratedColumn,
UpdateDateColumn,
DataSourceOptions,
} from 'typeorm';
type DataSourceType = DataSourceOptions['type'];
@Entity('dataSource')
export class DataSourceEntity {
@PrimaryGeneratedColumn('uuid')
id: string;
@Column({ nullable: true })
url: string;
@Column({ nullable: true })
schema: string;
@Column({ type: 'enum', enum: ['postgres'], default: 'postgres' })
type: DataSourceType;
@Column({ nullable: true, name: 'label' })
label: string;
@Column({ default: false })
isRemote: boolean;
@Column({ nullable: false })
workspaceId: string;
@CreateDateColumn()
createdAt: Date;
@UpdateDateColumn()
updatedAt: Date;
}
@@ -1,11 +1,11 @@
import { Module } from '@nestjs/common';
import { DataSourceMetadataModule } from 'src/metadata/data-source-metadata/data-source-metadata.module';
import { TypeOrmModule } from '@nestjs/typeorm';
import { DataSourceService } from './data-source.service';
import { DataSourceEntity } from './data-source.entity';
@Module({
imports: [DataSourceMetadataModule],
imports: [TypeOrmModule.forFeature([DataSourceEntity], 'metadata')],
providers: [DataSourceService],
exports: [DataSourceService],
})
@@ -1,34 +0,0 @@
import { Test, TestingModule } from '@nestjs/testing';
import { EnvironmentService } from 'src/integrations/environment/environment.service';
import { DataSourceMetadataService } from 'src/metadata/data-source-metadata/data-source-metadata.service';
import { DataSourceService } from './data-source.service';
describe('DataSourceService', () => {
let service: DataSourceService;
beforeEach(async () => {
const module: TestingModule = await Test.createTestingModule({
providers: [
DataSourceService,
{
provide: EnvironmentService,
useValue: {
getPGDatabaseUrl: () => '',
},
},
{
provide: DataSourceMetadataService,
useValue: {},
},
],
}).compile();
service = module.get<DataSourceService>(DataSourceService);
});
it('should be defined', () => {
expect(service).toBeDefined();
});
});
@@ -1,153 +1,48 @@
import { Injectable, OnModuleDestroy, OnModuleInit } from '@nestjs/common';
import { Injectable } from '@nestjs/common';
import { InjectRepository } from '@nestjs/typeorm';
import { DataSource } from 'typeorm';
import { Repository } from 'typeorm';
import { EnvironmentService } from 'src/integrations/environment/environment.service';
import { DataSourceMetadataService } from 'src/metadata/data-source-metadata/data-source-metadata.service';
import { TenantMigration } from 'src/metadata/tenant-migration/tenant-migration.entity';
import { uuidToBase36 } from './data-source.util';
import { DataSourceEntity } from './data-source.entity';
@Injectable()
export class DataSourceService implements OnModuleInit, OnModuleDestroy {
private mainDataSource: DataSource;
private dataSources: Map<string, DataSource> = new Map();
export class DataSourceService {
constructor(
private readonly environmentService: EnvironmentService,
private readonly dataSourceMetadataService: DataSourceMetadataService,
) {
this.mainDataSource = new DataSource({
url: environmentService.getPGDatabaseUrl(),
type: 'postgres',
logging: false,
schema: 'public',
@InjectRepository(DataSourceEntity, 'metadata')
private readonly dataSourceMetadataRepository: Repository<DataSourceEntity>,
) {}
async createDataSourceMetadata(workspaceId: string, workspaceSchema: string) {
// TODO: Double check if this is the correct way to do this
const dataSource = await this.dataSourceMetadataRepository.findOne({
where: { workspaceId },
});
if (dataSource) {
return dataSource;
}
return this.dataSourceMetadataRepository.save({
workspaceId,
schema: workspaceSchema,
});
}
/**
* Creates a new schema for a given workspaceId
* @param workspaceId
* @returns Promise<void>
*/
public async createWorkspaceSchema(workspaceId: string): Promise<string> {
const schemaName = this.getSchemaName(workspaceId);
const queryRunner = this.mainDataSource.createQueryRunner();
const schemaAlreadyExists = await queryRunner.hasSchema(schemaName);
if (schemaAlreadyExists) {
throw new Error(`Schema ${schemaName} already exists`);
}
await queryRunner.createSchema(schemaName, true);
await queryRunner.release();
return schemaName;
}
/**
* Connects to a workspace data source using the workspace metadata. Returns a cached connection if it exists.
* @param workspaceId
* @returns Promise<DataSource | undefined>
*/
public async connectToWorkspaceDataSource(
workspaceId: string,
): Promise<DataSource | undefined> {
if (this.dataSources.has(workspaceId)) {
const cachedDataSource = this.dataSources.get(workspaceId);
return cachedDataSource;
}
// We only want the first one for now, we will handle multiple data sources later with remote datasources.
// However, we will need to differentiate the data sources because we won't run migrations on remote data sources for example.
const dataSourceMetadata =
await this.dataSourceMetadataService.getLastDataSourceMetadataFromWorkspaceIdOrFail(
workspaceId,
);
const schema = dataSourceMetadata.schema;
// Probably not needed as we will ask for the schema name OR store public by default if it's remote
if (!schema && !dataSourceMetadata.isRemote) {
throw Error(
"No schema found for this non-remote data source, we don't want to fallback to public for workspace data sources.",
);
}
const workspaceDataSource = new DataSource({
// TODO: We should use later dataSourceMetadata.type and use a switch case condition to create the right data source
url: dataSourceMetadata.url ?? this.environmentService.getPGDatabaseUrl(),
type: 'postgres',
logging: ['query'],
schema,
entities: {
TenantMigration,
},
async getDataSourcesMetadataFromWorkspaceId(workspaceId: string) {
return this.dataSourceMetadataRepository.find({
where: { workspaceId },
order: { createdAt: 'DESC' },
});
await workspaceDataSource.initialize();
this.dataSources.set(workspaceId, workspaceDataSource);
return workspaceDataSource;
}
/**
* Disconnects from a workspace data source.
* @param workspaceId
* @returns Promise<void>
*
*/
public async disconnectFromWorkspaceDataSource(workspaceId: string) {
if (!this.dataSources.has(workspaceId)) {
return;
}
const dataSource = this.dataSources.get(workspaceId);
await dataSource?.destroy();
this.dataSources.delete(workspaceId);
async getLastDataSourceMetadataFromWorkspaceIdOrFail(workspaceId: string) {
return this.dataSourceMetadataRepository.findOneOrFail({
where: { workspaceId },
order: { createdAt: 'DESC' },
});
}
/**
*
* Returns the schema name for a given workspaceId
* @param workspaceId
* @returns string
*/
public getSchemaName(workspaceId: string): string {
return `workspace_${uuidToBase36(workspaceId)}`;
}
public async deleteWorkspaceSchema(workspaceId: string) {
const schemaName = this.getSchemaName(workspaceId);
const queryRunner = this.mainDataSource.createQueryRunner();
const schemaAlreadyExists = await queryRunner.hasSchema(schemaName);
if (!schemaAlreadyExists) {
await queryRunner.release();
return;
}
await queryRunner.dropSchema(schemaName, true, true);
await queryRunner.release();
}
async onModuleInit() {
// Init main data source "default" schema
await this.mainDataSource.initialize();
}
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();
}
async delete(workspaceId: string) {
await this.dataSourceMetadataRepository.delete({ workspaceId });
}
}
@@ -1,21 +0,0 @@
/**
* Converts a UUID to a base 36 string.
* This is used to generate the schema name since hyphens from workspace uuid are not allowed in postgres schema names.
*
* @param uuid
* @returns
*/
export function uuidToBase36(uuid: string): string {
let devId = false;
if (uuid.startsWith('twenty-')) {
devId = true;
// Clean dev uuids (twenty-)
uuid = uuid.replace('twenty-', '');
}
const hexString = uuid.replace(/-/g, '');
const base10Number = BigInt('0x' + hexString);
const base36String = base10Number.toString(36);
return `${devId ? 'twenty_' : ''}${base36String}`;
}