forked from Chatbot/v3-api
265 lines
7.9 KiB
TypeScript
265 lines
7.9 KiB
TypeScript
import { HttpException, HttpStatus, Injectable } from '@nestjs/common';
|
|
import { InjectModel } from '@nestjs/mongoose';
|
|
import { Model } from 'mongoose';
|
|
import type { FilterQuery } from 'mongoose';
|
|
import {
|
|
BaseResponseDTO,
|
|
PageMetaDto,
|
|
} from 'src/common/dto/base-response.dto';
|
|
import {
|
|
EXPERT_PREPARED_MESSAGE_CATEGORIES,
|
|
EXPERT_PREPARED_MESSAGE_CATEGORY_LABELS,
|
|
ExpertPreparedMessageCategory,
|
|
} from 'src/common/types/expert-prepared-message-category.type';
|
|
import { ExpertPreparedMessageModel } from 'src/database/model/expert-prepared-message.model';
|
|
import { CreateExpertPreparedMessageDto } from './dto/create-expert-prepared-message.dto';
|
|
import { ListExpertPreparedMessagesQueryDto } from './dto/list-expert-prepared-messages-query.dto';
|
|
import { ReorderExpertPreparedMessagesDto } from './dto/reorder-expert-prepared-messages.dto';
|
|
import { UpdateExpertPreparedMessageDto } from './dto/update-expert-prepared-message.dto';
|
|
|
|
export type ExpertPreparedMessageItem = {
|
|
id: string;
|
|
message: string;
|
|
label: string;
|
|
category: ExpertPreparedMessageCategory;
|
|
sortOrder: number;
|
|
locale: string;
|
|
};
|
|
|
|
export type ExpertPreparedMessageCategorySummary = {
|
|
value: ExpertPreparedMessageCategory;
|
|
label: string;
|
|
messageCount: number;
|
|
};
|
|
|
|
@Injectable()
|
|
export class ExpertPreparedMessagesService {
|
|
constructor(
|
|
@InjectModel(ExpertPreparedMessageModel.name)
|
|
private readonly preparedMessageModel: Model<ExpertPreparedMessageModel>,
|
|
) {}
|
|
|
|
getCategories() {
|
|
return EXPERT_PREPARED_MESSAGE_CATEGORIES.map((value) => ({
|
|
value,
|
|
label: EXPERT_PREPARED_MESSAGE_CATEGORY_LABELS[value],
|
|
}));
|
|
}
|
|
|
|
async findAllForAdmin(query: ListExpertPreparedMessagesQueryDto) {
|
|
const filter: FilterQuery<ExpertPreparedMessageModel> = {};
|
|
if (query.category) filter.category = query.category;
|
|
if (query.isEnabled !== undefined) filter.isEnabled = query.isEnabled;
|
|
if (query.locale) filter.locale = query.locale;
|
|
|
|
const [items, itemCount] = await Promise.all([
|
|
this.preparedMessageModel
|
|
.find(filter)
|
|
.sort({ category: 1, sortOrder: 1, createdAt: 1 })
|
|
.skip(query.skip)
|
|
.limit(query.take)
|
|
.lean()
|
|
.exec(),
|
|
this.preparedMessageModel.countDocuments(filter).exec(),
|
|
]);
|
|
|
|
const meta = new PageMetaDto({ pageOptionsDto: query, itemCount });
|
|
return new BaseResponseDTO(
|
|
HttpStatus.OK,
|
|
'Expert prepared messages retrieved successfully',
|
|
items.map((doc) => this.toAdminDto(doc)),
|
|
meta,
|
|
);
|
|
}
|
|
|
|
async findEnabledCategoriesForExpert(locale = 'fa') {
|
|
const counts = await this.preparedMessageModel
|
|
.aggregate<{ _id: ExpertPreparedMessageCategory; count: number }>([
|
|
{ $match: { isEnabled: true, locale } },
|
|
{ $group: { _id: '$category', count: { $sum: 1 } } },
|
|
])
|
|
.exec();
|
|
|
|
const countByCategory = new Map(
|
|
counts.map((row) => [row._id, row.count]),
|
|
);
|
|
|
|
const categories: ExpertPreparedMessageCategorySummary[] =
|
|
EXPERT_PREPARED_MESSAGE_CATEGORIES.filter((value) =>
|
|
countByCategory.has(value),
|
|
).map((value) => ({
|
|
value,
|
|
label: EXPERT_PREPARED_MESSAGE_CATEGORY_LABELS[value],
|
|
messageCount: countByCategory.get(value) ?? 0,
|
|
}));
|
|
|
|
return new BaseResponseDTO(HttpStatus.OK, 'SUCCESS', {
|
|
locale,
|
|
categories,
|
|
});
|
|
}
|
|
|
|
async findEnabledByCategoryForExpert(
|
|
category: ExpertPreparedMessageCategory,
|
|
locale = 'fa',
|
|
) {
|
|
const docs = await this.preparedMessageModel
|
|
.find({ isEnabled: true, locale, category })
|
|
.sort({ sortOrder: 1, createdAt: 1 })
|
|
.lean()
|
|
.exec();
|
|
|
|
return new BaseResponseDTO(HttpStatus.OK, 'SUCCESS', {
|
|
locale,
|
|
category,
|
|
label: EXPERT_PREPARED_MESSAGE_CATEGORY_LABELS[category],
|
|
messages: docs.map((doc) => this.toExpertItem(doc)),
|
|
});
|
|
}
|
|
|
|
async create(dto: CreateExpertPreparedMessageDto, adminUsername: string) {
|
|
const sortOrder =
|
|
dto.sortOrder ??
|
|
(await this.nextSortOrderForCategory(dto.category, dto.locale ?? 'fa'));
|
|
|
|
const created = await this.preparedMessageModel.create({
|
|
message: dto.message.trim(),
|
|
category: dto.category,
|
|
label: (dto.label ?? '').trim(),
|
|
isEnabled: dto.isEnabled ?? true,
|
|
sortOrder,
|
|
locale: (dto.locale ?? 'fa').trim(),
|
|
createdBy: adminUsername,
|
|
updatedBy: adminUsername,
|
|
});
|
|
|
|
return new BaseResponseDTO(
|
|
HttpStatus.CREATED,
|
|
'Expert prepared message created successfully',
|
|
this.toAdminDto(created.toObject()),
|
|
);
|
|
}
|
|
|
|
async update(
|
|
id: string,
|
|
dto: UpdateExpertPreparedMessageDto,
|
|
adminUsername: string,
|
|
) {
|
|
const existing = await this.preparedMessageModel.findById(id).exec();
|
|
if (!existing) {
|
|
throw new HttpException(
|
|
'prepared_message_not_found',
|
|
HttpStatus.NOT_FOUND,
|
|
);
|
|
}
|
|
|
|
if (dto.message !== undefined) existing.message = dto.message.trim();
|
|
if (dto.category !== undefined) existing.category = dto.category;
|
|
if (dto.label !== undefined) existing.label = dto.label.trim();
|
|
if (dto.isEnabled !== undefined) existing.isEnabled = dto.isEnabled;
|
|
if (dto.sortOrder !== undefined) existing.sortOrder = dto.sortOrder;
|
|
if (dto.locale !== undefined) existing.locale = dto.locale.trim();
|
|
existing.updatedBy = adminUsername;
|
|
|
|
const saved = await existing.save();
|
|
return new BaseResponseDTO(
|
|
HttpStatus.OK,
|
|
'Expert prepared message updated successfully',
|
|
this.toAdminDto(saved.toObject()),
|
|
);
|
|
}
|
|
|
|
async toggleEnabled(id: string, adminUsername: string) {
|
|
const existing = await this.preparedMessageModel.findById(id).exec();
|
|
if (!existing) {
|
|
throw new HttpException(
|
|
'prepared_message_not_found',
|
|
HttpStatus.NOT_FOUND,
|
|
);
|
|
}
|
|
|
|
existing.isEnabled = !existing.isEnabled;
|
|
existing.updatedBy = adminUsername;
|
|
const saved = await existing.save();
|
|
|
|
return new BaseResponseDTO(
|
|
HttpStatus.OK,
|
|
'Expert prepared message toggled successfully',
|
|
this.toAdminDto(saved.toObject()),
|
|
);
|
|
}
|
|
|
|
async reorder(dto: ReorderExpertPreparedMessagesDto, adminUsername: string) {
|
|
const bulk = dto.items.map((item) => ({
|
|
updateOne: {
|
|
filter: { _id: item.id },
|
|
update: { $set: { sortOrder: item.sortOrder, updatedBy: adminUsername } },
|
|
},
|
|
}));
|
|
|
|
await this.preparedMessageModel.bulkWrite(bulk);
|
|
|
|
return new BaseResponseDTO(
|
|
HttpStatus.OK,
|
|
'Expert prepared messages reordered successfully',
|
|
null,
|
|
);
|
|
}
|
|
|
|
async remove(id: string) {
|
|
const result = await this.preparedMessageModel.findByIdAndDelete(id).exec();
|
|
if (!result) {
|
|
throw new HttpException(
|
|
'prepared_message_not_found',
|
|
HttpStatus.NOT_FOUND,
|
|
);
|
|
}
|
|
|
|
return new BaseResponseDTO(
|
|
HttpStatus.OK,
|
|
'Expert prepared message deleted successfully',
|
|
null,
|
|
);
|
|
}
|
|
|
|
private async nextSortOrderForCategory(
|
|
category: ExpertPreparedMessageCategory,
|
|
locale: string,
|
|
): Promise<number> {
|
|
const last = await this.preparedMessageModel
|
|
.findOne({ category, locale })
|
|
.sort({ sortOrder: -1 })
|
|
.select({ sortOrder: 1 })
|
|
.lean()
|
|
.exec();
|
|
return (last?.sortOrder ?? -1) + 1;
|
|
}
|
|
|
|
private toExpertItem(doc: any): ExpertPreparedMessageItem {
|
|
return {
|
|
id: String(doc._id),
|
|
message: doc.message,
|
|
label: doc.label || doc.message.slice(0, 40),
|
|
category: doc.category,
|
|
sortOrder: doc.sortOrder ?? 0,
|
|
locale: doc.locale ?? 'fa',
|
|
};
|
|
}
|
|
|
|
private toAdminDto(doc: any) {
|
|
return {
|
|
id: String(doc._id),
|
|
message: doc.message,
|
|
label: doc.label ?? '',
|
|
category: doc.category,
|
|
isEnabled: doc.isEnabled,
|
|
sortOrder: doc.sortOrder ?? 0,
|
|
locale: doc.locale ?? 'fa',
|
|
createdBy: doc.createdBy ?? null,
|
|
updatedBy: doc.updatedBy ?? null,
|
|
createdAt: doc.createdAt,
|
|
updatedAt: doc.updatedAt,
|
|
};
|
|
}
|
|
}
|