Add identifier fields to ObjectMetadata (#2616)
* Add indentifier fields to ObjectMetadata * Add indentifier fields to ObjectMetadata * Add indentifier fields to ObjectMetadata * temporarily block name/label edition
This commit is contained in:
@ -0,0 +1,25 @@
|
||||
import { MigrationInterface, QueryRunner } from 'typeorm';
|
||||
|
||||
export class AddIdentifierFieldToObjectMetadata1700565712112
|
||||
implements MigrationInterface
|
||||
{
|
||||
name = 'AddIdentifierFieldToObjectMetadata1700565712112';
|
||||
|
||||
public async up(queryRunner: QueryRunner): Promise<void> {
|
||||
await queryRunner.query(
|
||||
`ALTER TABLE "metadata"."objectMetadata" ADD "labelIdentifierFieldMetadataId" character varying`,
|
||||
);
|
||||
await queryRunner.query(
|
||||
`ALTER TABLE "metadata"."objectMetadata" ADD "imageIdentifierFieldMetadataId" character varying`,
|
||||
);
|
||||
}
|
||||
|
||||
public async down(queryRunner: QueryRunner): Promise<void> {
|
||||
await queryRunner.query(
|
||||
`ALTER TABLE "metadata"."objectMetadata" DROP COLUMN "imageIdentifierFieldMetadataId"`,
|
||||
);
|
||||
await queryRunner.query(
|
||||
`ALTER TABLE "metadata"."objectMetadata" DROP COLUMN "labelIdentifierFieldMetadataId"`,
|
||||
);
|
||||
}
|
||||
}
|
||||
@ -8,6 +8,7 @@ import {
|
||||
|
||||
import {
|
||||
Authorize,
|
||||
BeforeDeleteOne,
|
||||
FilterableField,
|
||||
IDField,
|
||||
QueryOptions,
|
||||
@ -16,6 +17,7 @@ import {
|
||||
|
||||
import { RelationMetadataDTO } from 'src/metadata/relation-metadata/dtos/relation-metadata.dto';
|
||||
import { FieldMetadataType } from 'src/metadata/field-metadata/field-metadata.entity';
|
||||
import { BeforeDeleteOneField } from 'src/metadata/field-metadata/hooks/before-delete-one-field.hook';
|
||||
|
||||
registerEnumType(FieldMetadataType, {
|
||||
name: 'FieldMetadataType',
|
||||
@ -33,6 +35,7 @@ registerEnumType(FieldMetadataType, {
|
||||
disableSort: true,
|
||||
maxResultsSize: 1000,
|
||||
})
|
||||
@BeforeDeleteOne(BeforeDeleteOneField)
|
||||
@Relation('toRelationMetadata', () => RelationMetadataDTO, {
|
||||
nullable: true,
|
||||
})
|
||||
|
||||
@ -1,8 +1,12 @@
|
||||
import { Field, InputType } from '@nestjs/graphql';
|
||||
|
||||
import { BeforeUpdateOne } from '@ptc-org/nestjs-query-graphql';
|
||||
import { IsBoolean, IsOptional, IsString } from 'class-validator';
|
||||
|
||||
import { BeforeUpdateOneField } from 'src/metadata/field-metadata/hooks/before-update-one-field.hook';
|
||||
|
||||
@InputType()
|
||||
@BeforeUpdateOne(BeforeUpdateOneField)
|
||||
export class UpdateFieldInput {
|
||||
@IsString()
|
||||
@IsOptional()
|
||||
|
||||
@ -1,5 +1,4 @@
|
||||
import {
|
||||
BadRequestException,
|
||||
ConflictException,
|
||||
Injectable,
|
||||
NotFoundException,
|
||||
@ -8,12 +7,10 @@ import { InjectRepository } from '@nestjs/typeorm';
|
||||
|
||||
import { Repository } from 'typeorm';
|
||||
import { TypeOrmQueryService } from '@ptc-org/nestjs-query-typeorm';
|
||||
import { DeleteOneOptions } from '@ptc-org/nestjs-query-core';
|
||||
|
||||
import { WorkspaceMigrationRunnerService } from 'src/workspace/workspace-migration-runner/workspace-migration-runner.service';
|
||||
import { WorkspaceMigrationService } from 'src/metadata/workspace-migration/workspace-migration.service';
|
||||
import { ObjectMetadataService } from 'src/metadata/object-metadata/object-metadata.service';
|
||||
import { FieldMetadataDTO } from 'src/metadata/field-metadata/dtos/field-metadata.dto';
|
||||
import { CreateFieldInput } from 'src/metadata/field-metadata/dtos/create-field.input';
|
||||
import { WorkspaceMigrationTableAction } from 'src/metadata/workspace-migration/workspace-migration.entity';
|
||||
import { generateTargetColumnMap } from 'src/metadata/field-metadata/utils/generate-target-column-map.util';
|
||||
@ -34,30 +31,6 @@ export class FieldMetadataService extends TypeOrmQueryService<FieldMetadataEntit
|
||||
super(fieldMetadataRepository);
|
||||
}
|
||||
|
||||
override async deleteOne(
|
||||
id: string,
|
||||
opts?: DeleteOneOptions<FieldMetadataDTO> | undefined,
|
||||
): Promise<FieldMetadataEntity> {
|
||||
const fieldMetadata = await this.fieldMetadataRepository.findOne({
|
||||
where: { id },
|
||||
});
|
||||
if (!fieldMetadata) {
|
||||
throw new NotFoundException('Field does not exist');
|
||||
}
|
||||
|
||||
if (!fieldMetadata.isCustom) {
|
||||
throw new BadRequestException("Standard fields can't be deleted");
|
||||
}
|
||||
|
||||
if (fieldMetadata.isActive) {
|
||||
throw new BadRequestException("Active fields can't be deleted");
|
||||
}
|
||||
|
||||
// TODO: delete associated relation-metadata and field-metadata from the relation
|
||||
|
||||
return super.deleteOne(id, opts);
|
||||
}
|
||||
|
||||
override async createOne(
|
||||
record: CreateFieldInput,
|
||||
): Promise<FieldMetadataEntity> {
|
||||
@ -108,6 +81,15 @@ export class FieldMetadataService extends TypeOrmQueryService<FieldMetadataEntit
|
||||
return createdFieldMetadata;
|
||||
}
|
||||
|
||||
public async findOneWithinWorkspace(
|
||||
fieldMetadataId: string,
|
||||
workspaceId: string,
|
||||
) {
|
||||
return this.fieldMetadataRepository.findOne({
|
||||
where: { id: fieldMetadataId, workspaceId },
|
||||
});
|
||||
}
|
||||
|
||||
public async deleteFieldsMetadata(workspaceId: string) {
|
||||
await this.fieldMetadataRepository.delete({ workspaceId });
|
||||
}
|
||||
|
||||
@ -0,0 +1,55 @@
|
||||
import {
|
||||
BadRequestException,
|
||||
Injectable,
|
||||
UnauthorizedException,
|
||||
} from '@nestjs/common';
|
||||
|
||||
import {
|
||||
BeforeDeleteOneHook,
|
||||
DeleteOneInputType,
|
||||
} from '@ptc-org/nestjs-query-graphql';
|
||||
|
||||
import { FieldMetadataType } from 'src/metadata/field-metadata/field-metadata.entity';
|
||||
import { FieldMetadataService } from 'src/metadata/field-metadata/field-metadata.service';
|
||||
|
||||
@Injectable()
|
||||
export class BeforeDeleteOneField implements BeforeDeleteOneHook<any> {
|
||||
constructor(readonly fieldMetadataService: FieldMetadataService) {}
|
||||
|
||||
async run(
|
||||
instance: DeleteOneInputType,
|
||||
context: any,
|
||||
): Promise<DeleteOneInputType> {
|
||||
const workspaceId = context?.req?.user?.workspace?.id;
|
||||
|
||||
if (!workspaceId) {
|
||||
throw new UnauthorizedException();
|
||||
}
|
||||
|
||||
const fieldMetadata =
|
||||
await this.fieldMetadataService.findOneWithinWorkspace(
|
||||
instance.id.toString(),
|
||||
workspaceId,
|
||||
);
|
||||
|
||||
if (!fieldMetadata) {
|
||||
throw new BadRequestException('Field does not exist');
|
||||
}
|
||||
|
||||
if (!fieldMetadata.isCustom) {
|
||||
throw new BadRequestException("Standard Fields can't be deleted");
|
||||
}
|
||||
|
||||
if (fieldMetadata.isActive) {
|
||||
throw new BadRequestException("Active fields can't be deleted");
|
||||
}
|
||||
|
||||
if (fieldMetadata.type === FieldMetadataType.RELATION) {
|
||||
throw new BadRequestException(
|
||||
"Relation fields can't be deleted, you need to delete the RelationMetadata instead",
|
||||
);
|
||||
}
|
||||
|
||||
return instance;
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,57 @@
|
||||
import {
|
||||
BadRequestException,
|
||||
Injectable,
|
||||
UnauthorizedException,
|
||||
} from '@nestjs/common';
|
||||
|
||||
import {
|
||||
BeforeUpdateOneHook,
|
||||
UpdateOneInputType,
|
||||
} from '@ptc-org/nestjs-query-graphql';
|
||||
|
||||
import { UpdateFieldInput } from 'src/metadata/field-metadata/dtos/update-field.input';
|
||||
import { FieldMetadataService } from 'src/metadata/field-metadata/field-metadata.service';
|
||||
|
||||
@Injectable()
|
||||
export class BeforeUpdateOneField<T extends UpdateFieldInput>
|
||||
implements BeforeUpdateOneHook<T, any>
|
||||
{
|
||||
constructor(readonly fieldMetadataService: FieldMetadataService) {}
|
||||
|
||||
// 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 fieldMetadata =
|
||||
await this.fieldMetadataService.findOneWithinWorkspace(
|
||||
instance.id.toString(),
|
||||
workspaceId,
|
||||
);
|
||||
|
||||
if (!fieldMetadata) {
|
||||
throw new BadRequestException('Field does not exist');
|
||||
}
|
||||
|
||||
if (!fieldMetadata.isCustom) {
|
||||
throw new BadRequestException("Standard Fields can't be updated");
|
||||
}
|
||||
|
||||
this.checkIfFieldIsEditable(instance.update);
|
||||
|
||||
return instance;
|
||||
}
|
||||
|
||||
// This is temporary until we properly use the MigrationRunner to update column names
|
||||
private checkIfFieldIsEditable(update: UpdateFieldInput) {
|
||||
if (update.name || update.label) {
|
||||
throw new BadRequestException("Field's name and label can't be updated");
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -2,6 +2,7 @@ import { ObjectType, ID, Field, HideField } from '@nestjs/graphql';
|
||||
|
||||
import {
|
||||
Authorize,
|
||||
BeforeDeleteOne,
|
||||
CursorConnection,
|
||||
FilterableField,
|
||||
IDField,
|
||||
@ -9,6 +10,7 @@ import {
|
||||
} 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({
|
||||
@ -21,6 +23,7 @@ import { FieldMetadataDTO } from 'src/metadata/field-metadata/dtos/field-metadat
|
||||
disableSort: true,
|
||||
maxResultsSize: 1000,
|
||||
})
|
||||
@BeforeDeleteOne(BeforeDeleteOneObject)
|
||||
@CursorConnection('fields', () => FieldMetadataDTO)
|
||||
export class ObjectMetadataDTO {
|
||||
@IDField(() => ID)
|
||||
|
||||
@ -1,8 +1,12 @@
|
||||
import { Field, InputType } from '@nestjs/graphql';
|
||||
|
||||
import { IsBoolean, IsOptional, IsString } from 'class-validator';
|
||||
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()
|
||||
@ -38,4 +42,14 @@ export class UpdateObjectInput {
|
||||
@IsOptional()
|
||||
@Field({ nullable: true })
|
||||
isActive?: boolean;
|
||||
|
||||
@IsUUID()
|
||||
@IsOptional()
|
||||
@Field({ nullable: true })
|
||||
labelIdentifierFieldMetadataId?: string;
|
||||
|
||||
@IsUUID()
|
||||
@IsOptional()
|
||||
@Field({ nullable: true })
|
||||
imageIdentifierFieldMetadataId?: string;
|
||||
}
|
||||
|
||||
@ -5,15 +5,12 @@ import {
|
||||
CreateOneInputType,
|
||||
} from '@ptc-org/nestjs-query-graphql';
|
||||
|
||||
import { DataSourceService } from 'src/metadata/data-source/data-source.service';
|
||||
import { CreateObjectInput } from 'src/metadata/object-metadata/dtos/create-object.input';
|
||||
|
||||
@Injectable()
|
||||
export class BeforeCreateOneObject<T extends CreateObjectInput>
|
||||
implements BeforeCreateOneHook<T, any>
|
||||
{
|
||||
constructor(readonly dataSourceService: DataSourceService) {}
|
||||
|
||||
async run(
|
||||
instance: CreateOneInputType<T>,
|
||||
context: any,
|
||||
@ -24,12 +21,6 @@ export class BeforeCreateOneObject<T extends CreateObjectInput>
|
||||
throw new UnauthorizedException();
|
||||
}
|
||||
|
||||
const lastDataSourceMetadata =
|
||||
await this.dataSourceService.getLastDataSourceMetadataFromWorkspaceIdOrFail(
|
||||
workspaceId,
|
||||
);
|
||||
|
||||
instance.input.dataSourceId = lastDataSourceMetadata.id;
|
||||
instance.input.workspaceId = workspaceId;
|
||||
return instance;
|
||||
}
|
||||
|
||||
@ -0,0 +1,48 @@
|
||||
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(
|
||||
instance.id.toString(),
|
||||
workspaceId,
|
||||
);
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,102 @@
|
||||
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';
|
||||
|
||||
@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(
|
||||
instance.id.toString(),
|
||||
workspaceId,
|
||||
);
|
||||
|
||||
if (!objectMetadata) {
|
||||
throw new BadRequestException('Object does not exist');
|
||||
}
|
||||
|
||||
if (!objectMetadata.isCustom) {
|
||||
throw new BadRequestException("Standard Objects can't be updated");
|
||||
}
|
||||
|
||||
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);
|
||||
|
||||
return instance;
|
||||
}
|
||||
|
||||
// This is temporary until we properly use the MigrationRunner to update column names
|
||||
private checkIfFieldIsEditable(update: UpdateObjectInput) {
|
||||
if (
|
||||
update.nameSingular ||
|
||||
update.namePlural ||
|
||||
update.labelSingular ||
|
||||
update.labelPlural
|
||||
) {
|
||||
throw new BadRequestException("Object's name and label can't be updated");
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -58,6 +58,12 @@ export class ObjectMetadataEntity implements ObjectMetadataInterface {
|
||||
@Column({ default: false })
|
||||
isSystem: boolean;
|
||||
|
||||
@Column({ nullable: true })
|
||||
labelIdentifierFieldMetadataId?: string;
|
||||
|
||||
@Column({ nullable: true })
|
||||
imageIdentifierFieldMetadataId?: string;
|
||||
|
||||
@Column({ nullable: false })
|
||||
workspaceId: string;
|
||||
|
||||
|
||||
@ -12,6 +12,7 @@ import { WorkspaceMigrationRunnerModule } from 'src/workspace/workspace-migratio
|
||||
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 { ObjectMetadataService } from './object-metadata.service';
|
||||
import { ObjectMetadataEntity } from './object-metadata.entity';
|
||||
@ -25,7 +26,10 @@ import { ObjectMetadataDTO } from './dtos/object-metadata.dto';
|
||||
NestjsQueryGraphQLModule.forFeature({
|
||||
imports: [
|
||||
TypeORMModule,
|
||||
NestjsQueryTypeOrmModule.forFeature([ObjectMetadataEntity], 'metadata'),
|
||||
NestjsQueryTypeOrmModule.forFeature(
|
||||
[ObjectMetadataEntity, FieldMetadataEntity],
|
||||
'metadata',
|
||||
),
|
||||
DataSourceModule,
|
||||
WorkspaceMigrationModule,
|
||||
WorkspaceMigrationRunnerModule,
|
||||
|
||||
@ -1,8 +1,4 @@
|
||||
import {
|
||||
BadRequestException,
|
||||
Injectable,
|
||||
NotFoundException,
|
||||
} from '@nestjs/common';
|
||||
import { Injectable } from '@nestjs/common';
|
||||
import { InjectRepository } from '@nestjs/typeorm';
|
||||
|
||||
import { Equal, In, Repository } from 'typeorm';
|
||||
@ -37,31 +33,17 @@ export class ObjectMetadataService extends TypeOrmQueryService<ObjectMetadataEnt
|
||||
super(objectMetadataRepository);
|
||||
}
|
||||
|
||||
override async deleteOne(id: string): Promise<ObjectMetadataEntity> {
|
||||
const objectMetadata = await this.objectMetadataRepository.findOne({
|
||||
where: { id },
|
||||
});
|
||||
|
||||
if (!objectMetadata) {
|
||||
throw new NotFoundException('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 super.deleteOne(id);
|
||||
}
|
||||
|
||||
override async createOne(
|
||||
record: CreateObjectInput,
|
||||
): Promise<ObjectMetadataEntity> {
|
||||
const lastDataSourceMetadata =
|
||||
await this.dataSourceService.getLastDataSourceMetadataFromWorkspaceIdOrFail(
|
||||
record.workspaceId,
|
||||
);
|
||||
|
||||
const createdObjectMetadata = await super.createOne({
|
||||
...record,
|
||||
dataSourceId: lastDataSourceMetadata.id,
|
||||
targetTableName: `_${record.nameSingular}`,
|
||||
isActive: true,
|
||||
isCustom: true,
|
||||
@ -208,23 +190,6 @@ export class ObjectMetadataService extends TypeOrmQueryService<ObjectMetadataEnt
|
||||
});
|
||||
}
|
||||
|
||||
public async getObjectMetadataFromDataSourceId(dataSourceId: string) {
|
||||
return this.objectMetadataRepository.find({
|
||||
where: { dataSourceId },
|
||||
relations: [
|
||||
'fields',
|
||||
'fields.fromRelationMetadata',
|
||||
'fields.fromRelationMetadata.fromObjectMetadata',
|
||||
'fields.fromRelationMetadata.toObjectMetadata',
|
||||
'fields.fromRelationMetadata.toObjectMetadata.fields',
|
||||
'fields.toRelationMetadata',
|
||||
'fields.toRelationMetadata.fromObjectMetadata',
|
||||
'fields.toRelationMetadata.fromObjectMetadata.fields',
|
||||
'fields.toRelationMetadata.toObjectMetadata',
|
||||
],
|
||||
});
|
||||
}
|
||||
|
||||
public async findOneWithinWorkspace(
|
||||
objectMetadataId: string,
|
||||
workspaceId: string,
|
||||
|
||||
@ -9,6 +9,7 @@ import {
|
||||
import { CreateDateColumn, UpdateDateColumn } from 'typeorm';
|
||||
import {
|
||||
Authorize,
|
||||
BeforeDeleteOne,
|
||||
IDField,
|
||||
QueryOptions,
|
||||
Relation,
|
||||
@ -16,6 +17,7 @@ import {
|
||||
|
||||
import { ObjectMetadataDTO } from 'src/metadata/object-metadata/dtos/object-metadata.dto';
|
||||
import { RelationMetadataType } from 'src/metadata/relation-metadata/relation-metadata.entity';
|
||||
import { BeforeDeleteOneRelation } from 'src/metadata/relation-metadata/hooks/before-delete-one-field.hook';
|
||||
|
||||
registerEnumType(RelationMetadataType, {
|
||||
name: 'RelationMetadataType',
|
||||
@ -34,6 +36,7 @@ registerEnumType(RelationMetadataType, {
|
||||
disableSort: true,
|
||||
maxResultsSize: 1000,
|
||||
})
|
||||
@BeforeDeleteOne(BeforeDeleteOneRelation)
|
||||
@Relation('fromObjectMetadata', () => ObjectMetadataDTO)
|
||||
@Relation('toObjectMetadata', () => ObjectMetadataDTO)
|
||||
export class RelationMetadataDTO {
|
||||
|
||||
@ -0,0 +1,54 @@
|
||||
import {
|
||||
BadRequestException,
|
||||
Injectable,
|
||||
UnauthorizedException,
|
||||
} from '@nestjs/common';
|
||||
|
||||
import {
|
||||
BeforeDeleteOneHook,
|
||||
DeleteOneInputType,
|
||||
} from '@ptc-org/nestjs-query-graphql';
|
||||
|
||||
import { RelationMetadataService } from 'src/metadata/relation-metadata/relation-metadata.service';
|
||||
|
||||
@Injectable()
|
||||
export class BeforeDeleteOneRelation implements BeforeDeleteOneHook<any> {
|
||||
constructor(readonly relationMetadataService: RelationMetadataService) {}
|
||||
|
||||
async run(
|
||||
instance: DeleteOneInputType,
|
||||
context: any,
|
||||
): Promise<DeleteOneInputType> {
|
||||
const workspaceId = context?.req?.user?.workspace?.id;
|
||||
|
||||
if (!workspaceId) {
|
||||
throw new UnauthorizedException();
|
||||
}
|
||||
|
||||
const relationMetadata =
|
||||
await this.relationMetadataService.findOneWithinWorkspace(
|
||||
instance.id.toString(),
|
||||
workspaceId,
|
||||
);
|
||||
|
||||
if (!relationMetadata) {
|
||||
throw new BadRequestException('Relation does not exist');
|
||||
}
|
||||
|
||||
if (
|
||||
!relationMetadata.toFieldMetadata.isCustom ||
|
||||
!relationMetadata.fromFieldMetadata.isCustom
|
||||
) {
|
||||
throw new BadRequestException("Standard Relations can't be deleted");
|
||||
}
|
||||
|
||||
if (
|
||||
relationMetadata.toFieldMetadata.isActive ||
|
||||
relationMetadata.fromFieldMetadata.isActive
|
||||
) {
|
||||
throw new BadRequestException("Active relations can't be deleted");
|
||||
}
|
||||
|
||||
return instance;
|
||||
}
|
||||
}
|
||||
@ -36,6 +36,7 @@ export class RelationMetadataService extends TypeOrmQueryService<RelationMetadat
|
||||
}
|
||||
|
||||
override async deleteOne(id: string): Promise<RelationMetadataEntity> {
|
||||
// TODO: This logic is duplicated with the BeforeDeleteOneRelation hook
|
||||
const relationMetadata = await this.relationMetadataRepository.findOne({
|
||||
where: { id },
|
||||
relations: ['fromFieldMetadata', 'toFieldMetadata'],
|
||||
@ -45,22 +46,9 @@ export class RelationMetadataService extends TypeOrmQueryService<RelationMetadat
|
||||
throw new NotFoundException('Relation does not exist');
|
||||
}
|
||||
|
||||
if (
|
||||
!relationMetadata.toFieldMetadata.isCustom ||
|
||||
!relationMetadata.fromFieldMetadata.isCustom
|
||||
) {
|
||||
throw new BadRequestException("Standard Relations can't be deleted");
|
||||
}
|
||||
|
||||
if (
|
||||
relationMetadata.toFieldMetadata.isActive ||
|
||||
relationMetadata.fromFieldMetadata.isActive
|
||||
) {
|
||||
throw new BadRequestException("Active relations can't be deleted");
|
||||
}
|
||||
|
||||
const deletedRelationMetadata = super.deleteOne(id);
|
||||
|
||||
// TODO: Move to a cdc scheduler
|
||||
this.fieldMetadataService.deleteMany({
|
||||
id: {
|
||||
in: [
|
||||
@ -213,4 +201,14 @@ export class RelationMetadataService extends TypeOrmQueryService<RelationMetadat
|
||||
|
||||
return createdRelationMetadata;
|
||||
}
|
||||
|
||||
public async findOneWithinWorkspace(
|
||||
relationMetadataId: string,
|
||||
workspaceId: string,
|
||||
) {
|
||||
return this.relationMetadataRepository.findOne({
|
||||
where: { id: relationMetadataId, workspaceId },
|
||||
relations: ['fromFieldMetadata', 'toFieldMetadata'],
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user