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:
@ -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,
|
||||
|
||||
Reference in New Issue
Block a user