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:
Weiko
2023-11-21 18:41:48 +01:00
committed by GitHub
parent 726e375616
commit c74bde28b8
17 changed files with 408 additions and 94 deletions

View File

@ -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)

View File

@ -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;
}

View File

@ -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;
}

View File

@ -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;
}
}

View File

@ -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");
}
}
}

View File

@ -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;

View File

@ -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,

View File

@ -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,