Мне было трудно заставить дочерние объекты автоматически работать с REST API.
У меня есть базовый класс:
class Block {
@PrimaryGeneratedColumn('uuid')
public id: string;
@Column()
public type: string;
}
Затем расширили это на другой блок типы, например:
@Entity('sites_blocks_textblock')
class TextBlock extends Block {
@Column()
public text: string;
}
Я сделал каждый тип блока своим собственным объектом, чтобы столбцы могли сериализоваться в базу данных должным образом и иметь проверки для каждого свойства.
Итак ... I У меня более 10 типов блоков, и я пытаюсь избежать отдельного контроллера и конечных точек для CRUD каждого типа блока. Мне бы просто хотелось, чтобы один BlockController, одна конечная точка / block, POSTing создавались, и PUT on / block /: id для обновления, где он мог бы выводить тип блока из параметра body 'type' запроса.
Проблема в том, что в запросе последний параметр @Body () не будет проверен (запрос не будет проходить через go), если я не использую тип 'any' ... потому что каждый пользовательский тип блока передает свои дополнительные / Пользовательские свойства. В противном случае мне пришлось бы использовать каждый указанный c блочный дочерний класс в качестве типа параметра, требуя настраиваемые методы для каждого типа.
Чтобы добиться этого, я пытаюсь использовать пользовательский валидатор Pipe и дженерики, где я может смотреть на входящий параметр тела 'type' и приводить или создавать входящие данные в качестве заданного c Тип блока.
Обработчик контроллера:
@Post()
@UseGuards(PrincipalGuard)
public create(@Principal() principal: User,
@Param('siteId', ParseUUIDPipe) siteId: string,
@Body(new BlockValidationPipe()) blockCreate: any): Promise<Block> {
return this.blockService.create(principal.organization, siteId, blockCreate);
}
BlockValidationPipe (это предполагается чтобы преобразовать входящий объект данных в указанный тип блока c, а затем проверить его, вернуть входящий объект данных в качестве этого типа):
@Injectable()
export class BlockValidationPipe implements PipeTransform<any> {
async transform(value: any, { metatype }: ArgumentMetadata) {
if (value.type) {
if (value.type.id) {
metatype = getBlockTypeFromId(value.type.id);
}
}
if (!metatype || !this.toValidate(metatype)) {
return value;
}
// MAGIC: ==========>
let object = objectToBlockByType(value, value.type.id, metatype);
const errors = await validate(object);
if (errors.length > 0) {
throw new BadRequestException(errors, 'Validation failed');
}
return object ? object : value;
}
private toValidate(metatype: Function): boolean {
const types: Function[] = [String, Boolean, Number, Array, Object];
return !types.includes(metatype);
}
}
с использованием этого помощника (но он может работать не совсем точно). как и предполагалось, не получили полностью пропущенные типы):
function castOrNull<C extends Block>(value: C, type): C | null {
return value as typeof type;
}
export function objectToBlockByType(object, typeId, metatype) {
switch(typeId) {
case 'text':
return castOrNull<TextBlock>(object, TextBlock);
case 'avatar':
return castOrNull<AvatarBlock>(object, AvatarBlock);
case 'button':
return castOrNull<ButtonBlock>(object, ButtonBlock);
// etc....
default:
return castOrNull<Block>(object, Block);
}
}
... Это все, что должно дать мне правильное создание экземпляров блочного подкласса для использования контроллером, но я не уверен, как передать этот указанный тип подкласса c базовым сервисным вызовам для обновления указанных c блочных репозиториев для каждого типа сущности. Можно ли это сделать с помощью обобщений?
Для экземпляра в BlockService, но я должен передать указанный тип блока c (TextBlock, ButtonBlock и т. Д. c) в метод repository.save (), так что он будет сериализовать типы подклассов в соответствующие таблицы. Я предполагаю, что это возможно сделать, но кто-то, пожалуйста, исправьте меня, если я здесь не прав ...
Я пытаюсь сделать это, где я передаю данные блока как его родительский тип блока и пытаюсь чтобы затем получить его специфицированный c тип класса для передачи, чтобы сохранить, но он не работает ...
public async create(organization: Organization, siteId: string, blockCreate: Block): Promise<Block> {
let blockType: Type<any> = getBlockTypeFromId(blockCreate.type.id);
console.log("create block", typeof blockCreate, blockCreate.constructor.name, blockCreate, typeof blockType, blockType);
///
let r = await this.blockRepository.save<typeof blockCreate>({
organization: organization,
site: await this.siteService.getByIdAndOrganization(siteId, organization),
type: await this.blockTypeService.getById(blockCreate.type.id),
...blockCreate
});
//r.data = JSON.parse(r.data);
return r;
}
Проблема здесь в том, что typeof blockCreate всегда возвращает объект, мне нужно вызвать 'blockCreate.constructor.name', чтобы получить правильное имя типа блока подкласса, но не может передать его как тип T.
Так что мне интересно ... есть ли способ вернуть подкласс Type T Параметр весь путь от помощника контроллера (где он должен приводить и проверять подтип) в хранилище, чтобы я мог передать этот тип T вызову save (entity) ... и правильно ли это зафиксировать? Или есть какой-то другой способ получить этот тип T из самого экземпляра объекта, если 'typeof block' не возвращает указанный тип подкласса c? Я не думаю, что это возможно сделать во время компиляции ...?
Я просто пытаюсь получить сериализацию и валидацию подкласса, работая только с одним набором конечных точек контроллера и сервисным уровнем / хранилище вызывает ... Должен ли я искать Частичные сущности?
Кто-нибудь знает о каком-либо направлении, которое я могу найти, чтобы достичь sh этого?