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 {
|
import {
|
||||||
Authorize,
|
Authorize,
|
||||||
|
BeforeDeleteOne,
|
||||||
FilterableField,
|
FilterableField,
|
||||||
IDField,
|
IDField,
|
||||||
QueryOptions,
|
QueryOptions,
|
||||||
@ -16,6 +17,7 @@ import {
|
|||||||
|
|
||||||
import { RelationMetadataDTO } from 'src/metadata/relation-metadata/dtos/relation-metadata.dto';
|
import { RelationMetadataDTO } from 'src/metadata/relation-metadata/dtos/relation-metadata.dto';
|
||||||
import { FieldMetadataType } from 'src/metadata/field-metadata/field-metadata.entity';
|
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, {
|
registerEnumType(FieldMetadataType, {
|
||||||
name: 'FieldMetadataType',
|
name: 'FieldMetadataType',
|
||||||
@ -33,6 +35,7 @@ registerEnumType(FieldMetadataType, {
|
|||||||
disableSort: true,
|
disableSort: true,
|
||||||
maxResultsSize: 1000,
|
maxResultsSize: 1000,
|
||||||
})
|
})
|
||||||
|
@BeforeDeleteOne(BeforeDeleteOneField)
|
||||||
@Relation('toRelationMetadata', () => RelationMetadataDTO, {
|
@Relation('toRelationMetadata', () => RelationMetadataDTO, {
|
||||||
nullable: true,
|
nullable: true,
|
||||||
})
|
})
|
||||||
|
|||||||
@ -1,8 +1,12 @@
|
|||||||
import { Field, InputType } from '@nestjs/graphql';
|
import { Field, InputType } from '@nestjs/graphql';
|
||||||
|
|
||||||
|
import { BeforeUpdateOne } from '@ptc-org/nestjs-query-graphql';
|
||||||
import { IsBoolean, IsOptional, IsString } from 'class-validator';
|
import { IsBoolean, IsOptional, IsString } from 'class-validator';
|
||||||
|
|
||||||
|
import { BeforeUpdateOneField } from 'src/metadata/field-metadata/hooks/before-update-one-field.hook';
|
||||||
|
|
||||||
@InputType()
|
@InputType()
|
||||||
|
@BeforeUpdateOne(BeforeUpdateOneField)
|
||||||
export class UpdateFieldInput {
|
export class UpdateFieldInput {
|
||||||
@IsString()
|
@IsString()
|
||||||
@IsOptional()
|
@IsOptional()
|
||||||
|
|||||||
@ -1,5 +1,4 @@
|
|||||||
import {
|
import {
|
||||||
BadRequestException,
|
|
||||||
ConflictException,
|
ConflictException,
|
||||||
Injectable,
|
Injectable,
|
||||||
NotFoundException,
|
NotFoundException,
|
||||||
@ -8,12 +7,10 @@ import { InjectRepository } from '@nestjs/typeorm';
|
|||||||
|
|
||||||
import { Repository } from 'typeorm';
|
import { Repository } from 'typeorm';
|
||||||
import { TypeOrmQueryService } from '@ptc-org/nestjs-query-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 { WorkspaceMigrationRunnerService } from 'src/workspace/workspace-migration-runner/workspace-migration-runner.service';
|
||||||
import { WorkspaceMigrationService } from 'src/metadata/workspace-migration/workspace-migration.service';
|
import { WorkspaceMigrationService } from 'src/metadata/workspace-migration/workspace-migration.service';
|
||||||
import { ObjectMetadataService } from 'src/metadata/object-metadata/object-metadata.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 { CreateFieldInput } from 'src/metadata/field-metadata/dtos/create-field.input';
|
||||||
import { WorkspaceMigrationTableAction } from 'src/metadata/workspace-migration/workspace-migration.entity';
|
import { WorkspaceMigrationTableAction } from 'src/metadata/workspace-migration/workspace-migration.entity';
|
||||||
import { generateTargetColumnMap } from 'src/metadata/field-metadata/utils/generate-target-column-map.util';
|
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);
|
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(
|
override async createOne(
|
||||||
record: CreateFieldInput,
|
record: CreateFieldInput,
|
||||||
): Promise<FieldMetadataEntity> {
|
): Promise<FieldMetadataEntity> {
|
||||||
@ -108,6 +81,15 @@ export class FieldMetadataService extends TypeOrmQueryService<FieldMetadataEntit
|
|||||||
return createdFieldMetadata;
|
return createdFieldMetadata;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public async findOneWithinWorkspace(
|
||||||
|
fieldMetadataId: string,
|
||||||
|
workspaceId: string,
|
||||||
|
) {
|
||||||
|
return this.fieldMetadataRepository.findOne({
|
||||||
|
where: { id: fieldMetadataId, workspaceId },
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
public async deleteFieldsMetadata(workspaceId: string) {
|
public async deleteFieldsMetadata(workspaceId: string) {
|
||||||
await this.fieldMetadataRepository.delete({ workspaceId });
|
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 {
|
import {
|
||||||
Authorize,
|
Authorize,
|
||||||
|
BeforeDeleteOne,
|
||||||
CursorConnection,
|
CursorConnection,
|
||||||
FilterableField,
|
FilterableField,
|
||||||
IDField,
|
IDField,
|
||||||
@ -9,6 +10,7 @@ import {
|
|||||||
} from '@ptc-org/nestjs-query-graphql';
|
} from '@ptc-org/nestjs-query-graphql';
|
||||||
|
|
||||||
import { FieldMetadataDTO } from 'src/metadata/field-metadata/dtos/field-metadata.dto';
|
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')
|
@ObjectType('object')
|
||||||
@Authorize({
|
@Authorize({
|
||||||
@ -21,6 +23,7 @@ import { FieldMetadataDTO } from 'src/metadata/field-metadata/dtos/field-metadat
|
|||||||
disableSort: true,
|
disableSort: true,
|
||||||
maxResultsSize: 1000,
|
maxResultsSize: 1000,
|
||||||
})
|
})
|
||||||
|
@BeforeDeleteOne(BeforeDeleteOneObject)
|
||||||
@CursorConnection('fields', () => FieldMetadataDTO)
|
@CursorConnection('fields', () => FieldMetadataDTO)
|
||||||
export class ObjectMetadataDTO {
|
export class ObjectMetadataDTO {
|
||||||
@IDField(() => ID)
|
@IDField(() => ID)
|
||||||
|
|||||||
@ -1,8 +1,12 @@
|
|||||||
import { Field, InputType } from '@nestjs/graphql';
|
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()
|
@InputType()
|
||||||
|
@BeforeUpdateOne(BeforeUpdateOneObject)
|
||||||
export class UpdateObjectInput {
|
export class UpdateObjectInput {
|
||||||
@IsString()
|
@IsString()
|
||||||
@IsOptional()
|
@IsOptional()
|
||||||
@ -38,4 +42,14 @@ export class UpdateObjectInput {
|
|||||||
@IsOptional()
|
@IsOptional()
|
||||||
@Field({ nullable: true })
|
@Field({ nullable: true })
|
||||||
isActive?: boolean;
|
isActive?: boolean;
|
||||||
|
|
||||||
|
@IsUUID()
|
||||||
|
@IsOptional()
|
||||||
|
@Field({ nullable: true })
|
||||||
|
labelIdentifierFieldMetadataId?: string;
|
||||||
|
|
||||||
|
@IsUUID()
|
||||||
|
@IsOptional()
|
||||||
|
@Field({ nullable: true })
|
||||||
|
imageIdentifierFieldMetadataId?: string;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -5,15 +5,12 @@ import {
|
|||||||
CreateOneInputType,
|
CreateOneInputType,
|
||||||
} from '@ptc-org/nestjs-query-graphql';
|
} 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';
|
import { CreateObjectInput } from 'src/metadata/object-metadata/dtos/create-object.input';
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class BeforeCreateOneObject<T extends CreateObjectInput>
|
export class BeforeCreateOneObject<T extends CreateObjectInput>
|
||||||
implements BeforeCreateOneHook<T, any>
|
implements BeforeCreateOneHook<T, any>
|
||||||
{
|
{
|
||||||
constructor(readonly dataSourceService: DataSourceService) {}
|
|
||||||
|
|
||||||
async run(
|
async run(
|
||||||
instance: CreateOneInputType<T>,
|
instance: CreateOneInputType<T>,
|
||||||
context: any,
|
context: any,
|
||||||
@ -24,12 +21,6 @@ export class BeforeCreateOneObject<T extends CreateObjectInput>
|
|||||||
throw new UnauthorizedException();
|
throw new UnauthorizedException();
|
||||||
}
|
}
|
||||||
|
|
||||||
const lastDataSourceMetadata =
|
|
||||||
await this.dataSourceService.getLastDataSourceMetadataFromWorkspaceIdOrFail(
|
|
||||||
workspaceId,
|
|
||||||
);
|
|
||||||
|
|
||||||
instance.input.dataSourceId = lastDataSourceMetadata.id;
|
|
||||||
instance.input.workspaceId = workspaceId;
|
instance.input.workspaceId = workspaceId;
|
||||||
return instance;
|
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 })
|
@Column({ default: false })
|
||||||
isSystem: boolean;
|
isSystem: boolean;
|
||||||
|
|
||||||
|
@Column({ nullable: true })
|
||||||
|
labelIdentifierFieldMetadataId?: string;
|
||||||
|
|
||||||
|
@Column({ nullable: true })
|
||||||
|
imageIdentifierFieldMetadataId?: string;
|
||||||
|
|
||||||
@Column({ nullable: false })
|
@Column({ nullable: false })
|
||||||
workspaceId: string;
|
workspaceId: string;
|
||||||
|
|
||||||
|
|||||||
@ -12,6 +12,7 @@ import { WorkspaceMigrationRunnerModule } from 'src/workspace/workspace-migratio
|
|||||||
import { WorkspaceMigrationModule } from 'src/metadata/workspace-migration/workspace-migration.module';
|
import { WorkspaceMigrationModule } from 'src/metadata/workspace-migration/workspace-migration.module';
|
||||||
import { JwtAuthGuard } from 'src/guards/jwt.auth.guard';
|
import { JwtAuthGuard } from 'src/guards/jwt.auth.guard';
|
||||||
import { TypeORMModule } from 'src/database/typeorm/typeorm.module';
|
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 { ObjectMetadataService } from './object-metadata.service';
|
||||||
import { ObjectMetadataEntity } from './object-metadata.entity';
|
import { ObjectMetadataEntity } from './object-metadata.entity';
|
||||||
@ -25,7 +26,10 @@ import { ObjectMetadataDTO } from './dtos/object-metadata.dto';
|
|||||||
NestjsQueryGraphQLModule.forFeature({
|
NestjsQueryGraphQLModule.forFeature({
|
||||||
imports: [
|
imports: [
|
||||||
TypeORMModule,
|
TypeORMModule,
|
||||||
NestjsQueryTypeOrmModule.forFeature([ObjectMetadataEntity], 'metadata'),
|
NestjsQueryTypeOrmModule.forFeature(
|
||||||
|
[ObjectMetadataEntity, FieldMetadataEntity],
|
||||||
|
'metadata',
|
||||||
|
),
|
||||||
DataSourceModule,
|
DataSourceModule,
|
||||||
WorkspaceMigrationModule,
|
WorkspaceMigrationModule,
|
||||||
WorkspaceMigrationRunnerModule,
|
WorkspaceMigrationRunnerModule,
|
||||||
|
|||||||
@ -1,8 +1,4 @@
|
|||||||
import {
|
import { Injectable } from '@nestjs/common';
|
||||||
BadRequestException,
|
|
||||||
Injectable,
|
|
||||||
NotFoundException,
|
|
||||||
} from '@nestjs/common';
|
|
||||||
import { InjectRepository } from '@nestjs/typeorm';
|
import { InjectRepository } from '@nestjs/typeorm';
|
||||||
|
|
||||||
import { Equal, In, Repository } from 'typeorm';
|
import { Equal, In, Repository } from 'typeorm';
|
||||||
@ -37,31 +33,17 @@ export class ObjectMetadataService extends TypeOrmQueryService<ObjectMetadataEnt
|
|||||||
super(objectMetadataRepository);
|
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(
|
override async createOne(
|
||||||
record: CreateObjectInput,
|
record: CreateObjectInput,
|
||||||
): Promise<ObjectMetadataEntity> {
|
): Promise<ObjectMetadataEntity> {
|
||||||
|
const lastDataSourceMetadata =
|
||||||
|
await this.dataSourceService.getLastDataSourceMetadataFromWorkspaceIdOrFail(
|
||||||
|
record.workspaceId,
|
||||||
|
);
|
||||||
|
|
||||||
const createdObjectMetadata = await super.createOne({
|
const createdObjectMetadata = await super.createOne({
|
||||||
...record,
|
...record,
|
||||||
|
dataSourceId: lastDataSourceMetadata.id,
|
||||||
targetTableName: `_${record.nameSingular}`,
|
targetTableName: `_${record.nameSingular}`,
|
||||||
isActive: true,
|
isActive: true,
|
||||||
isCustom: 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(
|
public async findOneWithinWorkspace(
|
||||||
objectMetadataId: string,
|
objectMetadataId: string,
|
||||||
workspaceId: string,
|
workspaceId: string,
|
||||||
|
|||||||
@ -9,6 +9,7 @@ import {
|
|||||||
import { CreateDateColumn, UpdateDateColumn } from 'typeorm';
|
import { CreateDateColumn, UpdateDateColumn } from 'typeorm';
|
||||||
import {
|
import {
|
||||||
Authorize,
|
Authorize,
|
||||||
|
BeforeDeleteOne,
|
||||||
IDField,
|
IDField,
|
||||||
QueryOptions,
|
QueryOptions,
|
||||||
Relation,
|
Relation,
|
||||||
@ -16,6 +17,7 @@ import {
|
|||||||
|
|
||||||
import { ObjectMetadataDTO } from 'src/metadata/object-metadata/dtos/object-metadata.dto';
|
import { ObjectMetadataDTO } from 'src/metadata/object-metadata/dtos/object-metadata.dto';
|
||||||
import { RelationMetadataType } from 'src/metadata/relation-metadata/relation-metadata.entity';
|
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, {
|
registerEnumType(RelationMetadataType, {
|
||||||
name: 'RelationMetadataType',
|
name: 'RelationMetadataType',
|
||||||
@ -34,6 +36,7 @@ registerEnumType(RelationMetadataType, {
|
|||||||
disableSort: true,
|
disableSort: true,
|
||||||
maxResultsSize: 1000,
|
maxResultsSize: 1000,
|
||||||
})
|
})
|
||||||
|
@BeforeDeleteOne(BeforeDeleteOneRelation)
|
||||||
@Relation('fromObjectMetadata', () => ObjectMetadataDTO)
|
@Relation('fromObjectMetadata', () => ObjectMetadataDTO)
|
||||||
@Relation('toObjectMetadata', () => ObjectMetadataDTO)
|
@Relation('toObjectMetadata', () => ObjectMetadataDTO)
|
||||||
export class RelationMetadataDTO {
|
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> {
|
override async deleteOne(id: string): Promise<RelationMetadataEntity> {
|
||||||
|
// TODO: This logic is duplicated with the BeforeDeleteOneRelation hook
|
||||||
const relationMetadata = await this.relationMetadataRepository.findOne({
|
const relationMetadata = await this.relationMetadataRepository.findOne({
|
||||||
where: { id },
|
where: { id },
|
||||||
relations: ['fromFieldMetadata', 'toFieldMetadata'],
|
relations: ['fromFieldMetadata', 'toFieldMetadata'],
|
||||||
@ -45,22 +46,9 @@ export class RelationMetadataService extends TypeOrmQueryService<RelationMetadat
|
|||||||
throw new NotFoundException('Relation does not exist');
|
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);
|
const deletedRelationMetadata = super.deleteOne(id);
|
||||||
|
|
||||||
|
// TODO: Move to a cdc scheduler
|
||||||
this.fieldMetadataService.deleteMany({
|
this.fieldMetadataService.deleteMany({
|
||||||
id: {
|
id: {
|
||||||
in: [
|
in: [
|
||||||
@ -213,4 +201,14 @@ export class RelationMetadataService extends TypeOrmQueryService<RelationMetadat
|
|||||||
|
|
||||||
return createdRelationMetadata;
|
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