mirror of
https://github.com/hexastack/hexabot
synced 2025-06-26 18:27:28 +00:00
fix: refactor logic
This commit is contained in:
parent
a3b92da470
commit
e592a781b3
@ -30,6 +30,7 @@ import { PageQueryPipe } from '@/utils/pagination/pagination-query.pipe';
|
||||
import { PopulatePipe } from '@/utils/pipes/populate.pipe';
|
||||
import { SearchFilterPipe } from '@/utils/pipes/search-filter.pipe';
|
||||
import { TFilterQuery } from '@/utils/types/filter.types';
|
||||
import { Format } from '@/utils/types/format.types';
|
||||
|
||||
import { NlpValueCreateDto, NlpValueUpdateDto } from '../dto/nlp-value.dto';
|
||||
import {
|
||||
@ -147,9 +148,11 @@ export class NlpValueController extends BaseController<
|
||||
)
|
||||
filters: TFilterQuery<NlpValue>,
|
||||
) {
|
||||
return this.canPopulate(populate)
|
||||
? await this.nlpValueService.findAndPopulateWithCount(pageQuery, filters)
|
||||
: await this.nlpValueService.findWithCount(pageQuery, filters);
|
||||
return await this.nlpValueService.findWithCount(
|
||||
this.canPopulate(populate) ? Format.FULL : Format.STUB,
|
||||
pageQuery,
|
||||
filters,
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -20,6 +20,7 @@ import mongoose, {
|
||||
import { BaseRepository, DeleteResult } from '@/utils/generics/base-repository';
|
||||
import { PageQueryDto } from '@/utils/pagination/pagination-query.dto';
|
||||
import { TFilterQuery } from '@/utils/types/filter.types';
|
||||
import { Format } from '@/utils/types/format.types';
|
||||
|
||||
import { NlpValueDto } from '../dto/nlp-value.dto';
|
||||
import { NlpEntity, NlpEntityModel } from '../schemas/nlp-entity.schema';
|
||||
@ -31,7 +32,7 @@ import {
|
||||
NlpValueFullWithCount,
|
||||
NlpValuePopulate,
|
||||
NlpValueWithCount,
|
||||
TNlpValueCountFormat,
|
||||
TNlpValueCount,
|
||||
} from '../schemas/nlp-value.schema';
|
||||
|
||||
import { NlpSampleEntityRepository } from './nlp-sample-entity.repository';
|
||||
@ -119,31 +120,38 @@ export class NlpValueRepository extends BaseRepository<
|
||||
}
|
||||
}
|
||||
|
||||
private async aggregateWithCount<T extends 'full' | 'stub' = 'stub'>(
|
||||
/**
|
||||
* Performs an aggregation to retrieve NLP values with their sample counts.
|
||||
*
|
||||
* @param pageQuery - The pagination parameters
|
||||
* @param filterQuery - The filter criteria
|
||||
* @param populatePipelineStages - Optional additional pipeline stages for populating related data
|
||||
* @returns Aggregated results with sample counts
|
||||
*/
|
||||
private async aggregateWithCount<F extends Format>(
|
||||
format: F,
|
||||
{
|
||||
limit = 10,
|
||||
skip = 0,
|
||||
sort = ['createdAt', 'desc'],
|
||||
}: PageQueryDto<NlpValue>,
|
||||
{ $and = [], ...rest }: TFilterQuery<NlpValue>,
|
||||
populatePipelineStages: PipelineStage[] = [],
|
||||
) {
|
||||
): Promise<TNlpValueCount<F>[]> {
|
||||
const pipeline: PipelineStage[] = [
|
||||
{
|
||||
// support filters
|
||||
$match: {
|
||||
...rest,
|
||||
...($and.length && {
|
||||
$and:
|
||||
$and.map(({ entity, ...rest }) =>
|
||||
entity
|
||||
? {
|
||||
...rest,
|
||||
entity: new Types.ObjectId(String(entity)),
|
||||
}
|
||||
: rest,
|
||||
) || [],
|
||||
}),
|
||||
...($and.length
|
||||
? {
|
||||
$and: $and.map(({ entity, ...rest }) => ({
|
||||
...rest,
|
||||
...(entity
|
||||
? { entity: new Types.ObjectId(String(entity)) }
|
||||
: {}),
|
||||
})),
|
||||
}
|
||||
: {}),
|
||||
},
|
||||
},
|
||||
// support pageQuery
|
||||
@ -196,92 +204,96 @@ export class NlpValueRepository extends BaseRepository<
|
||||
nlpSamplesCount: 1,
|
||||
},
|
||||
},
|
||||
...populatePipelineStages,
|
||||
...(format === Format.FULL
|
||||
? [
|
||||
{
|
||||
$lookup: {
|
||||
from: 'nlpentities',
|
||||
localField: 'entity',
|
||||
foreignField: '_id',
|
||||
as: 'entity',
|
||||
},
|
||||
},
|
||||
{
|
||||
$unwind: '$entity',
|
||||
},
|
||||
]
|
||||
: []),
|
||||
{
|
||||
$sort: {
|
||||
[sort[0]]: sort[1].toString().startsWith('desc') ? -1 : 1,
|
||||
_id: sort[1].toString().startsWith('desc') ? -1 : 1,
|
||||
[sort[0]]:
|
||||
typeof sort[1] === 'number'
|
||||
? sort[1]
|
||||
: sort[1].toString().toLowerCase() === 'desc'
|
||||
? -1
|
||||
: 1,
|
||||
_id:
|
||||
typeof sort[1] === 'number'
|
||||
? sort[1]
|
||||
: sort[1].toString().toLowerCase() === 'desc'
|
||||
? -1
|
||||
: 1,
|
||||
},
|
||||
},
|
||||
];
|
||||
|
||||
return await this.model.aggregate<TNlpValueCountFormat<T>>(pipeline).exec();
|
||||
return await this.model.aggregate<TNlpValueCount<F>>(pipeline).exec();
|
||||
}
|
||||
|
||||
private async plainToClass<T extends 'full' | 'stub'>(
|
||||
format: 'full' | 'stub',
|
||||
aggregatedResults: (NlpValueWithCount | NlpValueFullWithCount)[],
|
||||
): Promise<TNlpValueCountFormat<T>[]> {
|
||||
if (format === 'full') {
|
||||
const nestedNlpEntities: NlpValueFullWithCount[] = [];
|
||||
for (const { entity, ...rest } of aggregatedResults) {
|
||||
const plainNlpValue = {
|
||||
private async plainToClass<F extends Format>(
|
||||
format: F,
|
||||
aggregatedResults: TNlpValueCount<F>[],
|
||||
): Promise<TNlpValueCount<F>[]> {
|
||||
const result: typeof aggregatedResults = [];
|
||||
|
||||
for (const item of aggregatedResults as TNlpValueCount<F>[]) {
|
||||
if (format === Format.FULL) {
|
||||
const { entity, ...rest } = item;
|
||||
const entityData = await mongoose
|
||||
.model(NlpEntityModel.name, NlpEntityModel.schema)
|
||||
.findById(entity)
|
||||
.lean();
|
||||
|
||||
const plainNlpValue: NlpValueFull = {
|
||||
...rest,
|
||||
entity: plainToClass(
|
||||
NlpEntity,
|
||||
await mongoose
|
||||
.model(NlpEntityModel.name, NlpEntityModel.schema)
|
||||
.findById(entity)
|
||||
.lean(),
|
||||
{
|
||||
excludePrefixes: ['_'],
|
||||
},
|
||||
),
|
||||
entity: plainToClass(NlpEntity, entityData, {
|
||||
excludePrefixes: ['_'],
|
||||
}),
|
||||
};
|
||||
nestedNlpEntities.push(
|
||||
|
||||
result.push(
|
||||
plainToClass(NlpValueFullWithCount, plainNlpValue, {
|
||||
excludePrefixes: ['_'],
|
||||
}),
|
||||
}) as TNlpValueCount<F>,
|
||||
);
|
||||
}
|
||||
return nestedNlpEntities as TNlpValueCountFormat<T>[];
|
||||
} else {
|
||||
const nestedNlpEntities: NlpValueWithCount[] = [];
|
||||
for (const aggregatedResult of aggregatedResults) {
|
||||
nestedNlpEntities.push(
|
||||
plainToClass(NlpValueWithCount, aggregatedResult, {
|
||||
} else {
|
||||
result.push(
|
||||
plainToClass(NlpValueWithCount, item, {
|
||||
excludePrefixes: ['_'],
|
||||
}),
|
||||
}) as TNlpValueCount<F>,
|
||||
);
|
||||
}
|
||||
return nestedNlpEntities as TNlpValueCountFormat<T>[];
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
async findWithCount<F extends Format>(
|
||||
format: F,
|
||||
pageQuery: PageQueryDto<NlpValue>,
|
||||
filterQuery: TFilterQuery<NlpValue>,
|
||||
): Promise<TNlpValueCount<F>[]> {
|
||||
try {
|
||||
const aggregatedResults = await this.aggregateWithCount(
|
||||
format,
|
||||
pageQuery,
|
||||
filterQuery,
|
||||
);
|
||||
|
||||
return await this.plainToClass(format, aggregatedResults);
|
||||
} catch (error) {
|
||||
this.logger.error(`Error in findWithCount: ${error.message}`, error);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
async findWithCount(
|
||||
pageQuery: PageQueryDto<NlpValue>,
|
||||
filterQuery: TFilterQuery<NlpValue>,
|
||||
): Promise<NlpValueWithCount[]> {
|
||||
const aggregatedResults = await this.aggregateWithCount<'stub'>(
|
||||
pageQuery,
|
||||
filterQuery,
|
||||
);
|
||||
|
||||
return await this.plainToClass<'stub'>('stub', aggregatedResults);
|
||||
}
|
||||
|
||||
async findAndPopulateWithCount(
|
||||
pageQuery: PageQueryDto<NlpValue>,
|
||||
filterQuery: TFilterQuery<NlpValue>,
|
||||
): Promise<NlpValueFullWithCount[]> {
|
||||
const aggregatedResults = await this.aggregateWithCount<'full'>(
|
||||
pageQuery,
|
||||
filterQuery,
|
||||
[
|
||||
{
|
||||
$lookup: {
|
||||
from: 'nlpentities',
|
||||
localField: 'entity',
|
||||
foreignField: '_id',
|
||||
as: 'entity',
|
||||
},
|
||||
},
|
||||
{
|
||||
$unwind: '$entity',
|
||||
},
|
||||
],
|
||||
);
|
||||
|
||||
return await this.plainToClass<'full'>('full', aggregatedResults);
|
||||
}
|
||||
}
|
||||
|
@ -16,6 +16,7 @@ import {
|
||||
TFilterPopulateFields,
|
||||
THydratedDocument,
|
||||
} from '@/utils/types/filter.types';
|
||||
import { TStubOrFull } from '@/utils/types/format.types';
|
||||
|
||||
import { NlpEntity, NlpEntityFull } from './nlp-entity.schema';
|
||||
import { NlpValueMap } from './types';
|
||||
@ -114,10 +115,6 @@ export class NlpValueFullWithCount extends NlpValueFull {
|
||||
nlpSamplesCount: number;
|
||||
}
|
||||
|
||||
export class NlpValueFullWithCountDto {
|
||||
nlpSamplesCount: number;
|
||||
}
|
||||
|
||||
export type NlpValueDocument = THydratedDocument<NlpValue>;
|
||||
|
||||
export const NlpValueModel: ModelDefinition = LifecycleHookManager.attach({
|
||||
@ -134,6 +131,8 @@ export type NlpValuePopulate = keyof TFilterPopulateFields<
|
||||
|
||||
export const NLP_VALUE_POPULATE: NlpValuePopulate[] = ['entity'];
|
||||
|
||||
export type TNlpValueCountFormat<T> = T extends 'stub'
|
||||
? NlpValueWithCount
|
||||
: NlpValueFullWithCount;
|
||||
export type TNlpValueCount<T> = TStubOrFull<
|
||||
T,
|
||||
NlpValueWithCount,
|
||||
NlpValueFullWithCount
|
||||
>;
|
||||
|
@ -12,6 +12,7 @@ import { DeleteResult } from '@/utils/generics/base-repository';
|
||||
import { BaseService } from '@/utils/generics/base-service';
|
||||
import { PageQueryDto } from '@/utils/pagination/pagination-query.dto';
|
||||
import { TFilterQuery } from '@/utils/types/filter.types';
|
||||
import { Format } from '@/utils/types/format.types';
|
||||
|
||||
import { NlpValueCreateDto, NlpValueDto } from '../dto/nlp-value.dto';
|
||||
import { NlpValueRepository } from '../repositories/nlp-value.repository';
|
||||
@ -19,9 +20,8 @@ import { NlpEntity } from '../schemas/nlp-entity.schema';
|
||||
import {
|
||||
NlpValue,
|
||||
NlpValueFull,
|
||||
NlpValueFullWithCount,
|
||||
NlpValuePopulate,
|
||||
NlpValueWithCount,
|
||||
TNlpValueCount,
|
||||
} from '../schemas/nlp-value.schema';
|
||||
import { NlpSampleEntityValue } from '../schemas/types';
|
||||
|
||||
@ -223,17 +223,11 @@ export class NlpValueService extends BaseService<
|
||||
return Promise.all(promises);
|
||||
}
|
||||
|
||||
async findWithCount(
|
||||
async findWithCount<F extends Format>(
|
||||
format: F,
|
||||
pageQuery: PageQueryDto<NlpValue>,
|
||||
filters: TFilterQuery<NlpValue>,
|
||||
): Promise<NlpValueWithCount[]> {
|
||||
return await this.repository.findWithCount(pageQuery, filters);
|
||||
}
|
||||
|
||||
async findAndPopulateWithCount(
|
||||
pageQuery: PageQueryDto<NlpValue>,
|
||||
filters: TFilterQuery<NlpValue>,
|
||||
): Promise<NlpValueFullWithCount[]> {
|
||||
return await this.repository.findAndPopulateWithCount(pageQuery, filters);
|
||||
): Promise<TNlpValueCount<F>[]> {
|
||||
return await this.repository.findWithCount(format, pageQuery, filters);
|
||||
}
|
||||
}
|
||||
|
18
api/src/utils/types/format.types.ts
Normal file
18
api/src/utils/types/format.types.ts
Normal file
@ -0,0 +1,18 @@
|
||||
/*
|
||||
* Copyright © 2025 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).
|
||||
*/
|
||||
|
||||
export enum Format {
|
||||
NONE = 0,
|
||||
STUB = 1,
|
||||
BASIC = 2,
|
||||
FULL = 3,
|
||||
}
|
||||
|
||||
export type TStubOrFull<TF, TStub, TFull> = TF extends Format.STUB
|
||||
? TStub
|
||||
: TFull;
|
Loading…
Reference in New Issue
Block a user