Migrate to a monorepo structure (#2909)

This commit is contained in:
Charles Bochet
2023-12-10 18:10:54 +01:00
committed by GitHub
parent a70a9281eb
commit 5bdca9de6c
2304 changed files with 37152 additions and 25869 deletions

View File

@ -0,0 +1,56 @@
import { Field, HideField, InputType } from '@nestjs/graphql';
import { BeforeCreateOne } from '@ptc-org/nestjs-query-graphql';
import { IsNotEmpty, IsOptional, IsString, IsUUID } from 'class-validator';
import { BeforeCreateOneObject } from 'src/metadata/object-metadata/hooks/before-create-one-object.hook';
@InputType()
@BeforeCreateOne(BeforeCreateOneObject)
export class CreateObjectInput {
@IsString()
@IsNotEmpty()
@Field()
nameSingular: string;
@IsString()
@IsNotEmpty()
@Field()
namePlural: string;
@IsString()
@IsNotEmpty()
@Field()
labelSingular: string;
@IsString()
@IsNotEmpty()
@Field()
labelPlural: string;
@IsString()
@IsOptional()
@Field({ nullable: true })
description?: string;
@IsString()
@IsOptional()
@Field({ nullable: true })
icon?: string;
@HideField()
dataSourceId: string;
@HideField()
workspaceId: string;
@IsUUID()
@IsOptional()
@Field({ nullable: true })
labelIdentifierFieldMetadataId?: string;
@IsUUID()
@IsOptional()
@Field({ nullable: true })
imageIdentifierFieldMetadataId?: string;
}

View File

@ -0,0 +1,76 @@
import { ObjectType, ID, Field, HideField } from '@nestjs/graphql';
import {
Authorize,
BeforeDeleteOne,
CursorConnection,
FilterableField,
IDField,
QueryOptions,
} from '@ptc-org/nestjs-query-graphql';
import { FieldMetadataDTO } from 'src/metadata/field-metadata/dtos/field-metadata.dto';
import { BeforeDeleteOneObject } from 'src/metadata/object-metadata/hooks/before-delete-one-object.hook';
@ObjectType('object')
@Authorize({
authorize: (context: any) => ({
workspaceId: { eq: context?.req?.user?.workspace?.id },
}),
})
@QueryOptions({
defaultResultSize: 10,
disableSort: true,
maxResultsSize: 1000,
})
@BeforeDeleteOne(BeforeDeleteOneObject)
@CursorConnection('fields', () => FieldMetadataDTO)
export class ObjectMetadataDTO {
@IDField(() => ID)
id: string;
@Field()
dataSourceId: string;
@Field()
nameSingular: string;
@Field()
namePlural: string;
@Field()
labelSingular: string;
@Field()
labelPlural: string;
@Field({ nullable: true })
description: string;
@Field({ nullable: true })
icon: string;
@FilterableField()
isCustom: boolean;
@FilterableField()
isActive: boolean;
@FilterableField()
isSystem: boolean;
@HideField()
workspaceId: string;
@Field()
createdAt: Date;
@Field()
updatedAt: Date;
@Field({ nullable: true })
labelIdentifierFieldMetadataId: string;
@Field({ nullable: true })
imageIdentifierFieldMetadataId: string;
}

View File

@ -0,0 +1,55 @@
import { Field, InputType } from '@nestjs/graphql';
import { BeforeUpdateOne } from '@ptc-org/nestjs-query-graphql';
import { IsBoolean, IsOptional, IsString, IsUUID } from 'class-validator';
import { BeforeUpdateOneObject } from 'src/metadata/object-metadata/hooks/before-update-one-object.hook';
@InputType()
@BeforeUpdateOne(BeforeUpdateOneObject)
export class UpdateObjectInput {
@IsString()
@IsOptional()
@Field({ nullable: true })
labelSingular?: string;
@IsString()
@IsOptional()
@Field({ nullable: true })
labelPlural?: string;
@IsString()
@IsOptional()
@Field({ nullable: true })
nameSingular?: string;
@IsString()
@IsOptional()
@Field({ nullable: true })
namePlural?: string;
@IsString()
@IsOptional()
@Field({ nullable: true })
description?: string;
@IsString()
@IsOptional()
@Field({ nullable: true })
icon?: string;
@IsBoolean()
@IsOptional()
@Field({ nullable: true })
isActive?: boolean;
@IsUUID()
@IsOptional()
@Field({ nullable: true })
labelIdentifierFieldMetadataId?: string;
@IsUUID()
@IsOptional()
@Field({ nullable: true })
imageIdentifierFieldMetadataId?: string;
}

View File

@ -0,0 +1,42 @@
import {
ForbiddenException,
Injectable,
UnauthorizedException,
} from '@nestjs/common';
import {
BeforeCreateOneHook,
CreateOneInputType,
} from '@ptc-org/nestjs-query-graphql';
import { CreateObjectInput } from 'src/metadata/object-metadata/dtos/create-object.input';
const coreObjectNames = ['featureFlag', 'refreshToken', 'workspace', 'user'];
@Injectable()
export class BeforeCreateOneObject<T extends CreateObjectInput>
implements BeforeCreateOneHook<T, any>
{
async run(
instance: CreateOneInputType<T>,
context: any,
): Promise<CreateOneInputType<T>> {
const workspaceId = context?.req?.user?.workspace?.id;
if (!workspaceId) {
throw new UnauthorizedException();
}
if (
coreObjectNames.includes(instance.input.nameSingular) ||
coreObjectNames.includes(instance.input.namePlural)
) {
throw new ForbiddenException(
'You cannot create an object with this name.',
);
}
instance.input.workspaceId = workspaceId;
return instance;
}
}

View File

@ -0,0 +1,49 @@
import {
BadRequestException,
Injectable,
UnauthorizedException,
} from '@nestjs/common';
import {
BeforeDeleteOneHook,
DeleteOneInputType,
} from '@ptc-org/nestjs-query-graphql';
import { ObjectMetadataService } from 'src/metadata/object-metadata/object-metadata.service';
@Injectable()
export class BeforeDeleteOneObject implements BeforeDeleteOneHook<any> {
constructor(readonly objectMetadataService: ObjectMetadataService) {}
async run(
instance: DeleteOneInputType,
context: any,
): Promise<DeleteOneInputType> {
const workspaceId = context?.req?.user?.workspace?.id;
if (!workspaceId) {
throw new UnauthorizedException();
}
const objectMetadata =
await this.objectMetadataService.findOneWithinWorkspace(workspaceId, {
where: {
id: instance.id.toString(),
},
});
if (!objectMetadata) {
throw new BadRequestException('Object does not exist');
}
if (!objectMetadata.isCustom) {
throw new BadRequestException("Standard Objects can't be deleted");
}
if (objectMetadata.isActive) {
throw new BadRequestException("Active objects can't be deleted");
}
return instance;
}
}

View File

@ -0,0 +1,146 @@
import {
BadRequestException,
Injectable,
UnauthorizedException,
} from '@nestjs/common';
import { InjectRepository } from '@nestjs/typeorm';
import {
BeforeUpdateOneHook,
UpdateOneInputType,
} from '@ptc-org/nestjs-query-graphql';
import { Equal, In, Repository } from 'typeorm';
import { FieldMetadataEntity } from 'src/metadata/field-metadata/field-metadata.entity';
import { UpdateObjectInput } from 'src/metadata/object-metadata/dtos/update-object.input';
import { ObjectMetadataService } from 'src/metadata/object-metadata/object-metadata.service';
import { ObjectMetadataEntity } from 'src/metadata/object-metadata/object-metadata.entity';
@Injectable()
export class BeforeUpdateOneObject<T extends UpdateObjectInput>
implements BeforeUpdateOneHook<T, any>
{
constructor(
readonly objectMetadataService: ObjectMetadataService,
// TODO: Should not use the repository here
@InjectRepository(FieldMetadataEntity, 'metadata')
private readonly fieldMetadataRepository: Repository<FieldMetadataEntity>,
) {}
// TODO: this logic could be moved to a policy guard
async run(
instance: UpdateOneInputType<T>,
context: any,
): Promise<UpdateOneInputType<T>> {
const workspaceId = context?.req?.user?.workspace?.id;
if (!workspaceId) {
throw new UnauthorizedException();
}
const objectMetadata =
await this.objectMetadataService.findOneWithinWorkspace(workspaceId, {
where: {
id: instance.id.toString(),
},
});
if (!objectMetadata) {
throw new BadRequestException('Object does not exist');
}
if (!objectMetadata.isCustom) {
if (
Object.keys(instance.update).length === 1 &&
instance.update.hasOwnProperty('isActive') &&
instance.update.isActive !== undefined
) {
return {
id: instance.id,
update: {
isActive: instance.update.isActive,
} as T,
};
}
throw new BadRequestException(
'Only isActive field can be updated for standard objects',
);
}
if (
instance.update.labelIdentifierFieldMetadataId ||
instance.update.imageIdentifierFieldMetadataId
) {
const fields = await this.fieldMetadataRepository.findBy({
workspaceId: Equal(workspaceId),
objectMetadataId: Equal(instance.id.toString()),
id: In(
[
instance.update.labelIdentifierFieldMetadataId,
instance.update.imageIdentifierFieldMetadataId,
].filter((id) => id !== null),
),
});
const fieldIds = fields.map((field) => field.id);
if (
instance.update.labelIdentifierFieldMetadataId &&
!fieldIds.includes(instance.update.labelIdentifierFieldMetadataId)
) {
throw new BadRequestException('This label identifier does not exist');
}
if (
instance.update.imageIdentifierFieldMetadataId &&
!fieldIds.includes(instance.update.imageIdentifierFieldMetadataId)
) {
throw new BadRequestException('This image identifier does not exist');
}
}
this.checkIfFieldIsEditable(instance.update, objectMetadata);
return instance;
}
// This is temporary until we properly use the MigrationRunner to update column names
private checkIfFieldIsEditable(
update: UpdateObjectInput,
objectMetadata: ObjectMetadataEntity,
) {
if (
update.nameSingular &&
update.nameSingular !== objectMetadata.nameSingular
) {
throw new BadRequestException(
"Object's nameSingular can't be updated. Please create a new object instead",
);
}
if (
update.labelSingular &&
update.labelSingular !== objectMetadata.labelSingular
) {
throw new BadRequestException(
"Object's labelSingular can't be updated. Please create a new object instead",
);
}
if (update.namePlural && update.namePlural !== objectMetadata.namePlural) {
throw new BadRequestException(
"Object's namePlural can't be updated. Please create a new object instead",
);
}
if (
update.labelPlural &&
update.labelPlural !== objectMetadata.labelPlural
) {
throw new BadRequestException(
"Object's labelPlural can't be updated. Please create a new object instead",
);
}
}
}

View File

@ -0,0 +1,103 @@
import {
Entity,
Unique,
PrimaryGeneratedColumn,
Column,
OneToMany,
CreateDateColumn,
UpdateDateColumn,
ManyToOne,
} from 'typeorm';
import { ObjectMetadataInterface } from 'src/metadata/field-metadata/interfaces/object-metadata.interface';
import { FieldMetadataEntity } from 'src/metadata/field-metadata/field-metadata.entity';
import { RelationMetadataEntity } from 'src/metadata/relation-metadata/relation-metadata.entity';
import { DataSourceEntity } from 'src/metadata/data-source/data-source.entity';
@Entity('objectMetadata')
@Unique('IndexOnNameSingularAndWorkspaceIdUnique', [
'nameSingular',
'workspaceId',
])
@Unique('IndexOnNamePluralAndWorkspaceIdUnique', ['namePlural', 'workspaceId'])
export class ObjectMetadataEntity implements ObjectMetadataInterface {
@PrimaryGeneratedColumn('uuid')
id: string;
@Column({ nullable: false, type: 'uuid' })
dataSourceId: string;
@Column({ nullable: false })
nameSingular: string;
@Column({ nullable: false })
namePlural: string;
@Column({ nullable: false })
labelSingular: string;
@Column({ nullable: false })
labelPlural: string;
@Column({ nullable: true, type: 'text' })
description: string;
@Column({ nullable: true })
icon: string;
@Column({ nullable: false })
targetTableName: string;
@Column({ default: false })
isCustom: boolean;
@Column({ default: false })
isActive: boolean;
@Column({ default: false })
isSystem: boolean;
@Column({ nullable: true })
labelIdentifierFieldMetadataId?: string;
@Column({ nullable: true })
imageIdentifierFieldMetadataId?: string;
@Column({ nullable: false })
workspaceId: string;
@OneToMany(() => FieldMetadataEntity, (field) => field.object, {
cascade: true,
})
fields: FieldMetadataEntity[];
@OneToMany(
() => RelationMetadataEntity,
(relation: RelationMetadataEntity) => relation.fromObjectMetadata,
{
cascade: true,
},
)
fromRelations: RelationMetadataEntity[];
@OneToMany(
() => RelationMetadataEntity,
(relation: RelationMetadataEntity) => relation.toObjectMetadata,
{
cascade: true,
},
)
toRelations: RelationMetadataEntity[];
@ManyToOne(() => DataSourceEntity, (dataSource) => dataSource.objects, {
onDelete: 'CASCADE',
})
dataSource: DataSourceEntity;
@CreateDateColumn()
createdAt: Date;
@UpdateDateColumn()
updatedAt: Date;
}

View File

@ -0,0 +1,66 @@
import { Module } from '@nestjs/common';
import {
NestjsQueryGraphQLModule,
PagingStrategies,
} from '@ptc-org/nestjs-query-graphql';
import { NestjsQueryTypeOrmModule } from '@ptc-org/nestjs-query-typeorm';
import { SortDirection } from '@ptc-org/nestjs-query-core';
import { DataSourceModule } from 'src/metadata/data-source/data-source.module';
import { WorkspaceMigrationRunnerModule } from 'src/workspace/workspace-migration-runner/workspace-migration-runner.module';
import { WorkspaceMigrationModule } from 'src/metadata/workspace-migration/workspace-migration.module';
import { JwtAuthGuard } from 'src/guards/jwt.auth.guard';
import { TypeORMModule } from 'src/database/typeorm/typeorm.module';
import { FieldMetadataEntity } from 'src/metadata/field-metadata/field-metadata.entity';
import { RelationMetadataEntity } from 'src/metadata/relation-metadata/relation-metadata.entity';
import { ObjectMetadataService } from './object-metadata.service';
import { ObjectMetadataEntity } from './object-metadata.entity';
import { CreateObjectInput } from './dtos/create-object.input';
import { UpdateObjectInput } from './dtos/update-object.input';
import { ObjectMetadataDTO } from './dtos/object-metadata.dto';
@Module({
imports: [
NestjsQueryGraphQLModule.forFeature({
imports: [
TypeORMModule,
NestjsQueryTypeOrmModule.forFeature(
[ObjectMetadataEntity, FieldMetadataEntity, RelationMetadataEntity],
'metadata',
),
DataSourceModule,
WorkspaceMigrationModule,
WorkspaceMigrationRunnerModule,
],
services: [ObjectMetadataService],
resolvers: [
{
EntityClass: ObjectMetadataEntity,
DTOClass: ObjectMetadataDTO,
CreateDTOClass: CreateObjectInput,
UpdateDTOClass: UpdateObjectInput,
ServiceClass: ObjectMetadataService,
enableTotalCount: true,
pagingStrategy: PagingStrategies.CURSOR,
read: {
defaultSort: [{ field: 'id', direction: SortDirection.DESC }],
},
create: {
many: { disabled: true },
},
update: {
many: { disabled: true },
},
delete: { many: { disabled: true } },
guards: [JwtAuthGuard],
},
],
}),
],
providers: [ObjectMetadataService],
exports: [ObjectMetadataService],
})
export class ObjectMetadataModule {}

View File

@ -0,0 +1,346 @@
import { Injectable } from '@nestjs/common';
import { InjectRepository } from '@nestjs/typeorm';
import { FindManyOptions, FindOneOptions, Repository } from 'typeorm';
import { TypeOrmQueryService } from '@ptc-org/nestjs-query-typeorm';
import { WorkspaceMigrationService } from 'src/metadata/workspace-migration/workspace-migration.service';
import { WorkspaceMigrationRunnerService } from 'src/workspace/workspace-migration-runner/workspace-migration-runner.service';
import {
WorkspaceMigrationColumnActionType,
WorkspaceMigrationColumnCreate,
WorkspaceMigrationTableAction,
} from 'src/metadata/workspace-migration/workspace-migration.entity';
import {
FieldMetadataEntity,
FieldMetadataType,
} from 'src/metadata/field-metadata/field-metadata.entity';
import { TypeORMService } from 'src/database/typeorm/typeorm.service';
import { DataSourceService } from 'src/metadata/data-source/data-source.service';
import {
RelationMetadataEntity,
RelationMetadataType,
} from 'src/metadata/relation-metadata/relation-metadata.entity';
import { createCustomColumnName } from 'src/metadata/utils/create-custom-column-name.util';
import { ObjectMetadataEntity } from './object-metadata.entity';
import { CreateObjectInput } from './dtos/create-object.input';
@Injectable()
export class ObjectMetadataService extends TypeOrmQueryService<ObjectMetadataEntity> {
constructor(
@InjectRepository(ObjectMetadataEntity, 'metadata')
private readonly objectMetadataRepository: Repository<ObjectMetadataEntity>,
@InjectRepository(FieldMetadataEntity, 'metadata')
private readonly fieldMetadataRepository: Repository<FieldMetadataEntity>,
@InjectRepository(RelationMetadataEntity, 'metadata')
private readonly relationMetadataRepository: Repository<RelationMetadataEntity>,
private readonly dataSourceService: DataSourceService,
private readonly typeORMService: TypeORMService,
private readonly workspaceMigrationService: WorkspaceMigrationService,
private readonly workspaceMigrationRunnerService: WorkspaceMigrationRunnerService,
) {
super(objectMetadataRepository);
}
override async createOne(
record: CreateObjectInput,
): Promise<ObjectMetadataEntity> {
const lastDataSourceMetadata =
await this.dataSourceService.getLastDataSourceMetadataFromWorkspaceIdOrFail(
record.workspaceId,
);
if (record.nameSingular.toLowerCase() === record.namePlural.toLowerCase()) {
throw new Error(
'The singular and plural name cannot be the same for an object',
);
}
const createdObjectMetadata = await super.createOne({
...record,
dataSourceId: lastDataSourceMetadata.id,
targetTableName: createCustomColumnName(record.nameSingular),
isActive: true,
isCustom: true,
isSystem: false,
fields:
// Creating default fields.
// No need to create a custom migration for this though as the default columns are already
// created with default values which is not supported yet by workspace migrations.
[
{
type: FieldMetadataType.UUID,
name: 'id',
label: 'Id',
targetColumnMap: {
value: 'id',
},
icon: 'Icon123',
description: 'Id',
isNullable: true,
isActive: true,
isCustom: false,
isSystem: true,
workspaceId: record.workspaceId,
defaultValue: { type: 'uuid' },
},
{
type: FieldMetadataType.TEXT,
name: 'name',
label: 'Name',
targetColumnMap: {
value: 'name',
},
icon: 'IconAbc',
description: 'Name',
isNullable: true,
isActive: true,
isCustom: false,
workspaceId: record.workspaceId,
defaultValue: { value: 'Untitled' },
},
{
type: FieldMetadataType.DATE_TIME,
name: 'createdAt',
label: 'Creation date',
targetColumnMap: {
value: 'createdAt',
},
icon: 'IconCalendar',
description: 'Creation date',
isNullable: true,
isActive: true,
isCustom: false,
workspaceId: record.workspaceId,
defaultValue: { type: 'now' },
},
{
type: FieldMetadataType.DATE_TIME,
name: 'updatedAt',
label: 'Update date',
targetColumnMap: {
value: 'updatedAt',
},
icon: 'IconCalendar',
description: 'Update date',
isNullable: true,
isActive: true,
isCustom: false,
isSystem: true,
workspaceId: record.workspaceId,
defaultValue: { type: 'now' },
},
],
});
const activityTargetObjectMetadata =
await this.objectMetadataRepository.findOneByOrFail({
nameSingular: 'activityTarget',
workspaceId: record.workspaceId,
});
const activityTargetRelationFieldMetadata =
await this.fieldMetadataRepository.save([
// FROM
{
objectMetadataId: createdObjectMetadata.id,
workspaceId: record.workspaceId,
isCustom: true,
isActive: true,
type: FieldMetadataType.RELATION,
name: 'activityTargets',
label: 'Activities',
targetColumnMap: {},
description: `Activities tied to the ${record.labelSingular}`,
icon: 'IconCheckbox',
isNullable: true,
},
// TO
{
objectMetadataId: activityTargetObjectMetadata.id,
workspaceId: record.workspaceId,
isCustom: true,
isActive: true,
type: FieldMetadataType.RELATION,
name: record.nameSingular,
label: record.labelSingular,
targetColumnMap: {
value: `${createdObjectMetadata.targetTableName}Id`,
},
description: `ActivityTarget ${record.labelSingular}`,
icon: 'IconBuildingSkyscraper',
isNullable: true,
},
// Foreign key
{
objectMetadataId: activityTargetObjectMetadata.id,
workspaceId: record.workspaceId,
isCustom: true,
isActive: true,
type: FieldMetadataType.UUID,
name: `${createdObjectMetadata.targetTableName}Id`,
label: `${record.labelSingular} ID (foreign key)`,
targetColumnMap: {},
description: `ActivityTarget ${record.labelSingular} id foreign key`,
icon: undefined,
isNullable: true,
isSystem: true,
defaultValue: undefined,
},
]);
const activityTargetRelationFieldMetadataMap =
activityTargetRelationFieldMetadata.reduce(
(acc, fieldMetadata: FieldMetadataEntity) => {
if (fieldMetadata.type === FieldMetadataType.RELATION) {
acc[fieldMetadata.objectMetadataId] = fieldMetadata;
}
return acc;
},
{},
);
await this.relationMetadataRepository.save([
{
workspaceId: record.workspaceId,
relationType: RelationMetadataType.ONE_TO_MANY,
fromObjectMetadataId: createdObjectMetadata.id,
toObjectMetadataId: activityTargetObjectMetadata.id,
fromFieldMetadataId:
activityTargetRelationFieldMetadataMap[createdObjectMetadata.id].id,
toFieldMetadataId:
activityTargetRelationFieldMetadataMap[
activityTargetObjectMetadata.id
].id,
},
]);
await this.workspaceMigrationService.createCustomMigration(
createdObjectMetadata.workspaceId,
[
{
name: createdObjectMetadata.targetTableName,
action: 'create',
} satisfies WorkspaceMigrationTableAction,
// Add activity target relation
{
name: activityTargetObjectMetadata.targetTableName,
action: 'alter',
columns: [
{
action: WorkspaceMigrationColumnActionType.CREATE,
columnName: `${createdObjectMetadata.targetTableName}Id`,
columnType: 'uuid',
} satisfies WorkspaceMigrationColumnCreate,
],
},
{
name: activityTargetObjectMetadata.targetTableName,
action: 'alter',
columns: [
{
action: WorkspaceMigrationColumnActionType.RELATION,
columnName: `${createdObjectMetadata.targetTableName}Id`,
referencedTableName: createdObjectMetadata.targetTableName,
referencedTableColumnName: 'id',
},
],
},
// This is temporary until we implement mainIdentifier
{
name: createdObjectMetadata.targetTableName,
action: 'alter',
columns: [
{
action: WorkspaceMigrationColumnActionType.CREATE,
columnName: 'name',
columnType: 'varchar',
defaultValue: "'Untitled'",
} satisfies WorkspaceMigrationColumnCreate,
],
} satisfies WorkspaceMigrationTableAction,
],
);
await this.workspaceMigrationRunnerService.executeMigrationFromPendingMigrations(
createdObjectMetadata.workspaceId,
);
const dataSourceMetadata =
await this.dataSourceService.getLastDataSourceMetadataFromWorkspaceIdOrFail(
createdObjectMetadata.workspaceId,
);
const workspaceDataSource = await this.typeORMService.connectToDataSource(
dataSourceMetadata,
);
const view = await workspaceDataSource?.query(
`INSERT INTO ${dataSourceMetadata.schema}."view"
("objectMetadataId", "type", "name")
VALUES ('${createdObjectMetadata.id}', 'table', 'All ${createdObjectMetadata.namePlural}') RETURNING *`,
);
createdObjectMetadata.fields.map(async (field, index) => {
if (field.name === 'id') {
return;
}
await workspaceDataSource?.query(
`INSERT INTO ${dataSourceMetadata.schema}."viewField"
("fieldMetadataId", "position", "isVisible", "size", "viewId")
VALUES ('${field.id}', '${index - 1}', true, 180, '${
view[0].id
}') RETURNING *`,
);
});
return createdObjectMetadata;
}
public async findOneWithinWorkspace(
workspaceId: string,
options: FindOneOptions<ObjectMetadataEntity>,
): Promise<ObjectMetadataEntity | null> {
return this.objectMetadataRepository.findOne({
...options,
where: {
...options.where,
workspaceId,
},
relations: [
'fields',
'fields.fromRelationMetadata',
'fields.toRelationMetadata',
],
});
}
public async findManyWithinWorkspace(
workspaceId: string,
options?: FindManyOptions<ObjectMetadataEntity>,
) {
return this.objectMetadataRepository.find({
...options,
where: {
...options?.where,
workspaceId,
},
relations: [
'fields',
'fields.fromRelationMetadata',
'fields.toRelationMetadata',
],
});
}
public async deleteObjectsMetadata(workspaceId: string) {
await this.objectMetadataRepository.delete({ workspaceId });
}
}