mirror of
https://github.com/hexastack/hexabot
synced 2024-11-24 04:53:41 +00:00
refactor(api): content logic
This commit is contained in:
parent
7b5846d721
commit
7c74348001
@ -6,13 +6,7 @@
|
||||
* 2. All derivative works must include clear attribution to the original creator and software, Hexastack and Hexabot, in a prominent location (e.g., in the software's "About" section, documentation, and README file).
|
||||
*/
|
||||
|
||||
import {
|
||||
forwardRef,
|
||||
MiddlewareConsumer,
|
||||
Module,
|
||||
NestModule,
|
||||
RequestMethod,
|
||||
} from '@nestjs/common';
|
||||
import { forwardRef, Module } from '@nestjs/common';
|
||||
import { MongooseModule } from '@nestjs/mongoose';
|
||||
|
||||
import { AttachmentModule } from '@/attachment/attachment.module';
|
||||
@ -21,7 +15,6 @@ import { ChatModule } from '@/chat/chat.module';
|
||||
import { ContentTypeController } from './controllers/content-type.controller';
|
||||
import { ContentController } from './controllers/content.controller';
|
||||
import { MenuController } from './controllers/menu.controller';
|
||||
import { ContentMiddleWare } from './middlewares/content.middleware';
|
||||
import { ContentTypeRepository } from './repositories/content-type.repository';
|
||||
import { ContentRepository } from './repositories/content.repository';
|
||||
import { MenuRepository } from './repositories/menu.repository';
|
||||
@ -55,13 +48,4 @@ import { AttachmentModel } from '../attachment/schemas/attachment.schema';
|
||||
],
|
||||
exports: [MenuService, ContentService, ContentTypeService],
|
||||
})
|
||||
export class CmsModule implements NestModule {
|
||||
configure(consumer: MiddlewareConsumer) {
|
||||
consumer
|
||||
.apply(ContentMiddleWare)
|
||||
.forRoutes(
|
||||
{ path: 'content', method: RequestMethod.POST },
|
||||
{ path: 'content/:id', method: RequestMethod.PATCH },
|
||||
);
|
||||
}
|
||||
}
|
||||
export class CmsModule {}
|
||||
|
@ -35,7 +35,6 @@ import {
|
||||
|
||||
import { ContentController } from './content.controller';
|
||||
import { ContentCreateDto } from '../dto/content.dto';
|
||||
import { ContentTransformInterceptor } from '../interceptors/content.interceptor';
|
||||
import { ContentTypeRepository } from '../repositories/content-type.repository';
|
||||
import { ContentRepository } from '../repositories/content.repository';
|
||||
import { ContentType, ContentTypeModel } from '../schemas/content-type.schema';
|
||||
@ -48,7 +47,6 @@ describe('ContentController', () => {
|
||||
let contentService: ContentService;
|
||||
let contentTypeService: ContentTypeService;
|
||||
let attachmentService: AttachmentService;
|
||||
let transformInterceptor: ContentTransformInterceptor;
|
||||
let contentType: ContentType;
|
||||
let content: Content;
|
||||
let attachment: Attachment;
|
||||
@ -74,7 +72,6 @@ describe('ContentController', () => {
|
||||
AttachmentService,
|
||||
ContentTypeRepository,
|
||||
AttachmentRepository,
|
||||
ContentTransformInterceptor,
|
||||
EventEmitter2,
|
||||
],
|
||||
}).compile();
|
||||
@ -82,9 +79,6 @@ describe('ContentController', () => {
|
||||
contentService = module.get<ContentService>(ContentService);
|
||||
attachmentService = module.get<AttachmentService>(AttachmentService);
|
||||
contentTypeService = module.get<ContentTypeService>(ContentTypeService);
|
||||
transformInterceptor = module.get<ContentTransformInterceptor>(
|
||||
ContentTransformInterceptor,
|
||||
);
|
||||
contentType = await contentTypeService.findOne({ name: 'Product' });
|
||||
content = await contentService.findOne({
|
||||
title: 'Jean',
|
||||
@ -237,28 +231,6 @@ describe('ContentController', () => {
|
||||
});
|
||||
});
|
||||
|
||||
describe('filterDynamicFields', () => {
|
||||
it('should flatten dynamic fields', () => {
|
||||
const result = transformInterceptor.transformDynamicFields(
|
||||
contentFixtures[0],
|
||||
);
|
||||
|
||||
expect(result).toEqualPayload(
|
||||
{
|
||||
title: 'Jean',
|
||||
status: true,
|
||||
subtitle: 'Jean Droit Taille Normale',
|
||||
image: {
|
||||
payload: {
|
||||
url: 'https://images-na.ssl-images-amazon.com/images/I/31DY09uzLDL._SX38_SY50_CR,0,0,38,50_.jpg',
|
||||
},
|
||||
},
|
||||
},
|
||||
['entity', 'rag', ...IGNORED_TEST_FIELDS],
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe('count', () => {
|
||||
it('should return the number of contents', async () => {
|
||||
jest.spyOn(contentService, 'count');
|
||||
|
@ -40,7 +40,6 @@ import { SearchFilterPipe } from '@/utils/pipes/search-filter.pipe';
|
||||
import { ContentTypeService } from './../services/content-type.service';
|
||||
import { ContentService } from './../services/content.service';
|
||||
import { ContentCreateDto, ContentUpdateDto } from '../dto/content.dto';
|
||||
import { ContentTransformInterceptor } from '../interceptors/content.interceptor';
|
||||
import { ContentType } from '../schemas/content-type.schema';
|
||||
import {
|
||||
Content,
|
||||
@ -48,9 +47,8 @@ import {
|
||||
ContentPopulate,
|
||||
ContentStub,
|
||||
} from '../schemas/content.schema';
|
||||
import { preprocessDynamicFields } from '../utilities';
|
||||
|
||||
@UseInterceptors(ContentTransformInterceptor, CsrfInterceptor)
|
||||
@UseInterceptors(CsrfInterceptor)
|
||||
@Controller('content')
|
||||
export class ContentController extends BaseController<
|
||||
Content,
|
||||
@ -116,8 +114,7 @@ export class ContentController extends BaseController<
|
||||
entity: contentType?.id,
|
||||
},
|
||||
});
|
||||
const newContent = this.filterDynamicFields(contentDto, contentType);
|
||||
return await this.contentService.create(newContent);
|
||||
return await this.contentService.create(contentDto);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -186,12 +183,22 @@ export class ContentController extends BaseController<
|
||||
});
|
||||
}
|
||||
|
||||
const contentsDto = result.data.map((content) => {
|
||||
content.entity = targetContentType;
|
||||
const dto = preprocessDynamicFields(content);
|
||||
// Match headers against entity fields
|
||||
return this.filterDynamicFields(dto, contentType);
|
||||
});
|
||||
const contentsDto = result.data.reduce(
|
||||
(acc, { title, status, ...rest }) => [
|
||||
...acc,
|
||||
{
|
||||
title,
|
||||
status,
|
||||
entity: targetContentType,
|
||||
dynamicFields: Object.keys(rest)
|
||||
.filter((key) =>
|
||||
contentType.fields.map((field) => field.name).includes(key),
|
||||
)
|
||||
.reduce((filtered, key) => ({ ...filtered, [key]: rest[key] }), {}),
|
||||
},
|
||||
],
|
||||
[],
|
||||
);
|
||||
|
||||
// Create content
|
||||
return await this.contentService.createMany(contentsDto);
|
||||
|
@ -43,4 +43,8 @@ export class ContentUpdateDto {
|
||||
@IsBoolean()
|
||||
@IsOptional()
|
||||
status?: boolean;
|
||||
|
||||
@ApiPropertyOptional({ description: 'Content dynamic fields', type: Object })
|
||||
@IsOptional()
|
||||
dynamicFields?: Record<string, any>;
|
||||
}
|
||||
|
@ -1,54 +0,0 @@
|
||||
/*
|
||||
* Copyright © 2024 Hexastack. All rights reserved.
|
||||
*
|
||||
* Licensed under the GNU Affero General Public License v3.0 (AGPLv3) with the following additional terms:
|
||||
* 1. The name "Hexabot" is a trademark of Hexastack. You may not use this name in derivative works without express written permission.
|
||||
* 2. All derivative works must include clear attribution to the original creator and software, Hexastack and Hexabot, in a prominent location (e.g., in the software's "About" section, documentation, and README file).
|
||||
*/
|
||||
|
||||
import {
|
||||
Injectable,
|
||||
NestInterceptor,
|
||||
ExecutionContext,
|
||||
CallHandler,
|
||||
} from '@nestjs/common';
|
||||
import { Observable, map } from 'rxjs';
|
||||
|
||||
import { Content } from '../schemas/content.schema';
|
||||
|
||||
@Injectable()
|
||||
export class ContentTransformInterceptor
|
||||
implements NestInterceptor<Content, Content>
|
||||
{
|
||||
/*
|
||||
-This interceptor is designed to provide a flattened representation of the 'dynamicFields'.
|
||||
-The incoming data contains a 'dynamicField' object, and the interceptor is expanding it,
|
||||
extracting its content as separate entries.
|
||||
-After the expansion, the 'dynamicFields' property is removed.
|
||||
-The interceptor will be applied on each endpoint of this controller.
|
||||
*/
|
||||
transformDynamicFields(data) {
|
||||
if (data.dynamicFields) {
|
||||
Object.keys(data.dynamicFields).forEach((key) => {
|
||||
data[key] = data.dynamicFields[key];
|
||||
});
|
||||
delete data.dynamicFields;
|
||||
}
|
||||
return data;
|
||||
}
|
||||
|
||||
intercept(context: ExecutionContext, next: CallHandler): Observable<Content> {
|
||||
return next.handle().pipe(
|
||||
map((data) => {
|
||||
// If the data is not an array, the 'transformDynamicFields' method is applied once
|
||||
if (!Array.isArray(data)) {
|
||||
return this.transformDynamicFields(data);
|
||||
}
|
||||
|
||||
return data.map((content) => {
|
||||
return this.transformDynamicFields(content);
|
||||
});
|
||||
}),
|
||||
);
|
||||
}
|
||||
}
|
@ -1,20 +0,0 @@
|
||||
/*
|
||||
* Copyright © 2024 Hexastack. All rights reserved.
|
||||
*
|
||||
* Licensed under the GNU Affero General Public License v3.0 (AGPLv3) with the following additional terms:
|
||||
* 1. The name "Hexabot" is a trademark of Hexastack. You may not use this name in derivative works without express written permission.
|
||||
* 2. All derivative works must include clear attribution to the original creator and software, Hexastack and Hexabot, in a prominent location (e.g., in the software's "About" section, documentation, and README file).
|
||||
*/
|
||||
|
||||
import { Injectable, NestMiddleware } from '@nestjs/common';
|
||||
import { NextFunction, Request, Response } from 'express';
|
||||
|
||||
import { preprocessDynamicFields } from '../utilities';
|
||||
|
||||
@Injectable()
|
||||
export class ContentMiddleWare implements NestMiddleware {
|
||||
use(req: Request, _res: Response, next: NextFunction) {
|
||||
req.body = preprocessDynamicFields(req.body);
|
||||
next();
|
||||
}
|
||||
}
|
@ -1,24 +0,0 @@
|
||||
/*
|
||||
* Copyright © 2024 Hexastack. All rights reserved.
|
||||
*
|
||||
* Licensed under the GNU Affero General Public License v3.0 (AGPLv3) with the following additional terms:
|
||||
* 1. The name "Hexabot" is a trademark of Hexastack. You may not use this name in derivative works without express written permission.
|
||||
* 2. All derivative works must include clear attribution to the original creator and software, Hexastack and Hexabot, in a prominent location (e.g., in the software's "About" section, documentation, and README file).
|
||||
*/
|
||||
|
||||
import { ContentCreateDto } from '../dto/content.dto';
|
||||
|
||||
export const preprocessDynamicFields = (
|
||||
content: Record<string, string | boolean | number>,
|
||||
) => {
|
||||
const { _csrf, title, status, entity, ...dynamicFields } = content;
|
||||
const processed: ContentCreateDto & { _csrf?: string } = {
|
||||
_csrf: _csrf?.toString(),
|
||||
entity: entity?.toString(),
|
||||
status: !!status,
|
||||
title: title?.toString(),
|
||||
dynamicFields,
|
||||
};
|
||||
|
||||
return processed;
|
||||
};
|
Loading…
Reference in New Issue
Block a user