forked from Chatbot/v3-api
first commit v3 initialiazed a temporary repository for darmanet client
This commit is contained in:
@@ -0,0 +1,59 @@
|
||||
import { ApiProperty, ApiPropertyOptional } from '@nestjs/swagger';
|
||||
import {
|
||||
IsBoolean,
|
||||
IsEnum,
|
||||
IsInt,
|
||||
IsNotEmpty,
|
||||
IsOptional,
|
||||
IsString,
|
||||
MaxLength,
|
||||
Min,
|
||||
} from 'class-validator';
|
||||
import {
|
||||
ExpertPreparedMessageCategory,
|
||||
EXPERT_PREPARED_MESSAGE_CATEGORIES,
|
||||
} from 'src/common/types/expert-prepared-message-category.type';
|
||||
|
||||
export class CreateExpertPreparedMessageDto {
|
||||
@ApiProperty({
|
||||
example: 'سلام، چطور میتونم کمکتون کنم؟',
|
||||
description: 'Full text inserted into the chat when the expert selects this snippet',
|
||||
})
|
||||
@IsString()
|
||||
@IsNotEmpty()
|
||||
@MaxLength(2000)
|
||||
message: string;
|
||||
|
||||
@ApiProperty({
|
||||
enum: EXPERT_PREPARED_MESSAGE_CATEGORIES,
|
||||
example: ExpertPreparedMessageCategory.Greetings,
|
||||
})
|
||||
@IsEnum(ExpertPreparedMessageCategory)
|
||||
category: ExpertPreparedMessageCategory;
|
||||
|
||||
@ApiPropertyOptional({
|
||||
example: 'سلام',
|
||||
description: 'Short label shown on the chip/button in the expert chat UI',
|
||||
})
|
||||
@IsOptional()
|
||||
@IsString()
|
||||
@MaxLength(120)
|
||||
label?: string;
|
||||
|
||||
@ApiPropertyOptional({ default: true })
|
||||
@IsOptional()
|
||||
@IsBoolean()
|
||||
isEnabled?: boolean;
|
||||
|
||||
@ApiPropertyOptional({ default: 0, description: 'Lower values appear first within a category' })
|
||||
@IsOptional()
|
||||
@IsInt()
|
||||
@Min(0)
|
||||
sortOrder?: number;
|
||||
|
||||
@ApiPropertyOptional({ default: 'fa', example: 'fa' })
|
||||
@IsOptional()
|
||||
@IsString()
|
||||
@MaxLength(10)
|
||||
locale?: string;
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
import { ApiProperty, ApiPropertyOptional } from '@nestjs/swagger';
|
||||
import { IsEnum, IsOptional, IsString, MaxLength } from 'class-validator';
|
||||
import {
|
||||
ExpertPreparedMessageCategory,
|
||||
EXPERT_PREPARED_MESSAGE_CATEGORIES,
|
||||
} from 'src/common/types/expert-prepared-message-category.type';
|
||||
|
||||
export class ExpertPreparedMessagesLocaleQueryDto {
|
||||
@ApiPropertyOptional({ default: 'fa', example: 'fa' })
|
||||
@IsOptional()
|
||||
@IsString()
|
||||
@MaxLength(10)
|
||||
locale?: string;
|
||||
}
|
||||
|
||||
export class ExpertPreparedMessagesByCategoryQueryDto extends ExpertPreparedMessagesLocaleQueryDto {
|
||||
@ApiProperty({
|
||||
enum: EXPERT_PREPARED_MESSAGE_CATEGORIES,
|
||||
example: ExpertPreparedMessageCategory.Greetings,
|
||||
})
|
||||
@IsEnum(ExpertPreparedMessageCategory)
|
||||
category: ExpertPreparedMessageCategory;
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
import { ApiPropertyOptional } from '@nestjs/swagger';
|
||||
import { Transform, Type } from 'class-transformer';
|
||||
import { IsBoolean, IsEnum, IsOptional, IsString } from 'class-validator';
|
||||
import { PageOptionsDto } from 'src/common/dto/base-response.dto';
|
||||
import {
|
||||
ExpertPreparedMessageCategory,
|
||||
EXPERT_PREPARED_MESSAGE_CATEGORIES,
|
||||
} from 'src/common/types/expert-prepared-message-category.type';
|
||||
|
||||
export class ListExpertPreparedMessagesQueryDto extends PageOptionsDto {
|
||||
@ApiPropertyOptional({ enum: EXPERT_PREPARED_MESSAGE_CATEGORIES })
|
||||
@IsOptional()
|
||||
@IsEnum(ExpertPreparedMessageCategory)
|
||||
category?: ExpertPreparedMessageCategory;
|
||||
|
||||
@ApiPropertyOptional({ description: 'Filter by enabled flag' })
|
||||
@IsOptional()
|
||||
@Transform(({ value }) => {
|
||||
if (value === 'true' || value === true) return true;
|
||||
if (value === 'false' || value === false) return false;
|
||||
return value;
|
||||
})
|
||||
@IsBoolean()
|
||||
isEnabled?: boolean;
|
||||
|
||||
@ApiPropertyOptional({ example: 'fa' })
|
||||
@IsOptional()
|
||||
@IsString()
|
||||
locale?: string;
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
import { ApiProperty } from '@nestjs/swagger';
|
||||
import { Type } from 'class-transformer';
|
||||
import {
|
||||
ArrayMinSize,
|
||||
IsArray,
|
||||
IsInt,
|
||||
IsMongoId,
|
||||
IsNotEmpty,
|
||||
Min,
|
||||
ValidateNested,
|
||||
} from 'class-validator';
|
||||
|
||||
export class ReorderExpertPreparedMessageItemDto {
|
||||
@ApiProperty({ example: '507f1f77bcf86cd799439011' })
|
||||
@IsMongoId()
|
||||
@IsNotEmpty()
|
||||
id: string;
|
||||
|
||||
@ApiProperty({ example: 0 })
|
||||
@IsInt()
|
||||
@Min(0)
|
||||
sortOrder: number;
|
||||
}
|
||||
|
||||
export class ReorderExpertPreparedMessagesDto {
|
||||
@ApiProperty({ type: [ReorderExpertPreparedMessageItemDto] })
|
||||
@IsArray()
|
||||
@ArrayMinSize(1)
|
||||
@ValidateNested({ each: true })
|
||||
@Type(() => ReorderExpertPreparedMessageItemDto)
|
||||
items: ReorderExpertPreparedMessageItemDto[];
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
import { PartialType } from '@nestjs/swagger';
|
||||
import { CreateExpertPreparedMessageDto } from './create-expert-prepared-message.dto';
|
||||
|
||||
export class UpdateExpertPreparedMessageDto extends PartialType(
|
||||
CreateExpertPreparedMessageDto,
|
||||
) {}
|
||||
@@ -0,0 +1,126 @@
|
||||
import {
|
||||
Body,
|
||||
Controller,
|
||||
Delete,
|
||||
Get,
|
||||
HttpException,
|
||||
HttpStatus,
|
||||
Param,
|
||||
Patch,
|
||||
Post,
|
||||
Put,
|
||||
Query,
|
||||
UseGuards,
|
||||
} from '@nestjs/common';
|
||||
import {
|
||||
ApiBearerAuth,
|
||||
ApiOperation,
|
||||
ApiParam,
|
||||
ApiTags,
|
||||
} from '@nestjs/swagger';
|
||||
import { AdminGuard } from 'src/auth/guards/admin.guard';
|
||||
import { AdminIdentity } from 'src/common/decorators/Identity.decorator';
|
||||
import { Permissions } from 'src/common/decorators/permission.decorator';
|
||||
import { Permission } from 'src/common/types/permissions.catalog';
|
||||
import { AdminModel } from 'src/database/model/admin.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';
|
||||
import { ExpertPreparedMessagesService } from './expert-prepared-messages.service';
|
||||
|
||||
@ApiBearerAuth()
|
||||
@UseGuards(AdminGuard)
|
||||
@Permissions(Permission.PreparedMessagesManage)
|
||||
@ApiTags('expert-prepared-messages (admin)')
|
||||
@Controller('admin/expert-prepared-messages')
|
||||
export class ExpertPreparedMessagesAdminController {
|
||||
constructor(
|
||||
private readonly preparedMessagesService: ExpertPreparedMessagesService,
|
||||
) {}
|
||||
|
||||
@Get('categories')
|
||||
@ApiOperation({ summary: 'List available prepared-message categories' })
|
||||
getCategories() {
|
||||
return {
|
||||
statusCode: HttpStatus.OK,
|
||||
message: 'SUCCESS',
|
||||
data: this.preparedMessagesService.getCategories(),
|
||||
};
|
||||
}
|
||||
|
||||
@Get()
|
||||
@ApiOperation({
|
||||
summary: 'List all prepared messages for expert online chat (admin management)',
|
||||
})
|
||||
findAll(@Query() query: ListExpertPreparedMessagesQueryDto) {
|
||||
return this.preparedMessagesService.findAllForAdmin(query);
|
||||
}
|
||||
|
||||
@Post()
|
||||
@ApiOperation({ summary: 'Create a prepared message snippet' })
|
||||
create(
|
||||
@Body() dto: CreateExpertPreparedMessageDto,
|
||||
@AdminIdentity() admin: AdminModel,
|
||||
) {
|
||||
return this.preparedMessagesService.create(dto, admin.username);
|
||||
}
|
||||
|
||||
@Put(':id')
|
||||
@ApiOperation({ summary: 'Update a prepared message snippet' })
|
||||
@ApiParam({ name: 'id' })
|
||||
update(
|
||||
@Param('id') id: string,
|
||||
@Body() dto: UpdateExpertPreparedMessageDto,
|
||||
@AdminIdentity() admin: AdminModel,
|
||||
) {
|
||||
try {
|
||||
return this.preparedMessagesService.update(id, dto, admin.username);
|
||||
} catch (error) {
|
||||
if (error instanceof HttpException) throw error;
|
||||
throw new HttpException(
|
||||
error.message || 'Failed to update prepared message',
|
||||
HttpStatus.INTERNAL_SERVER_ERROR,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@Patch('reorder')
|
||||
@ApiOperation({ summary: 'Bulk update sort order for prepared messages' })
|
||||
reorder(
|
||||
@Body() dto: ReorderExpertPreparedMessagesDto,
|
||||
@AdminIdentity() admin: AdminModel,
|
||||
) {
|
||||
return this.preparedMessagesService.reorder(dto, admin.username);
|
||||
}
|
||||
|
||||
@Patch(':id/toggle')
|
||||
@ApiOperation({ summary: 'Enable or disable a prepared message snippet' })
|
||||
@ApiParam({ name: 'id' })
|
||||
toggle(@Param('id') id: string, @AdminIdentity() admin: AdminModel) {
|
||||
try {
|
||||
return this.preparedMessagesService.toggleEnabled(id, admin.username);
|
||||
} catch (error) {
|
||||
if (error instanceof HttpException) throw error;
|
||||
throw new HttpException(
|
||||
error.message || 'Failed to toggle prepared message',
|
||||
HttpStatus.INTERNAL_SERVER_ERROR,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@Delete(':id')
|
||||
@ApiOperation({ summary: 'Delete a prepared message snippet' })
|
||||
@ApiParam({ name: 'id' })
|
||||
remove(@Param('id') id: string) {
|
||||
try {
|
||||
return this.preparedMessagesService.remove(id);
|
||||
} catch (error) {
|
||||
if (error instanceof HttpException) throw error;
|
||||
throw new HttpException(
|
||||
error.message || 'Failed to delete prepared message',
|
||||
HttpStatus.INTERNAL_SERVER_ERROR,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,72 @@
|
||||
import { Controller, Get, Query, UseGuards } from '@nestjs/common';
|
||||
import {
|
||||
ApiBearerAuth,
|
||||
ApiOperation,
|
||||
ApiQuery,
|
||||
ApiTags,
|
||||
} from '@nestjs/swagger';
|
||||
import { AdminGuard } from 'src/auth/guards/admin.guard';
|
||||
import { Permissions } from 'src/common/decorators/permission.decorator';
|
||||
import { Permission } from 'src/common/types/permissions.catalog';
|
||||
import {
|
||||
ExpertPreparedMessagesByCategoryQueryDto,
|
||||
ExpertPreparedMessagesLocaleQueryDto,
|
||||
} from './dto/expert-prepared-messages-query.dto';
|
||||
import { ExpertPreparedMessagesService } from './expert-prepared-messages.service';
|
||||
|
||||
@ApiBearerAuth()
|
||||
@UseGuards(AdminGuard)
|
||||
@Permissions(Permission.PreparedMessagesRead)
|
||||
@ApiTags('expert-prepared-messages (expert)')
|
||||
@Controller('expert/prepared-messages')
|
||||
export class ExpertPreparedMessagesExpertController {
|
||||
constructor(
|
||||
private readonly preparedMessagesService: ExpertPreparedMessagesService,
|
||||
) {}
|
||||
|
||||
@Get('categories')
|
||||
@ApiOperation({
|
||||
summary:
|
||||
'List prepared-message categories that have at least one enabled message',
|
||||
})
|
||||
@ApiQuery({
|
||||
name: 'locale',
|
||||
required: false,
|
||||
example: 'fa',
|
||||
description: 'Locale filter (defaults to fa)',
|
||||
})
|
||||
findCategories(@Query() query: ExpertPreparedMessagesLocaleQueryDto) {
|
||||
return this.preparedMessagesService.findEnabledCategoriesForExpert(
|
||||
query.locale || 'fa',
|
||||
);
|
||||
}
|
||||
|
||||
@Get()
|
||||
@ApiOperation({
|
||||
summary: 'Get enabled prepared messages for a specific category',
|
||||
})
|
||||
@ApiQuery({
|
||||
name: 'category',
|
||||
required: true,
|
||||
enum: [
|
||||
'greetings',
|
||||
'quick_answers',
|
||||
'closing',
|
||||
'follow_up',
|
||||
'apology',
|
||||
'other',
|
||||
],
|
||||
})
|
||||
@ApiQuery({
|
||||
name: 'locale',
|
||||
required: false,
|
||||
example: 'fa',
|
||||
description: 'Locale filter (defaults to fa)',
|
||||
})
|
||||
findByCategory(@Query() query: ExpertPreparedMessagesByCategoryQueryDto) {
|
||||
return this.preparedMessagesService.findEnabledByCategoryForExpert(
|
||||
query.category,
|
||||
query.locale || 'fa',
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
import { Module } from '@nestjs/common';
|
||||
import { MongooseModule } from '@nestjs/mongoose';
|
||||
import { DatabaseModule } from 'src/database/database.module';
|
||||
import {
|
||||
ExpertPreparedMessageModel,
|
||||
ExpertPreparedMessageSchema,
|
||||
} from 'src/database/model/expert-prepared-message.model';
|
||||
import { ExpertPreparedMessagesAdminController } from './expert-prepared-messages-admin.controller';
|
||||
import { ExpertPreparedMessagesExpertController } from './expert-prepared-messages-expert.controller';
|
||||
import { ExpertPreparedMessagesService } from './expert-prepared-messages.service';
|
||||
|
||||
@Module({
|
||||
imports: [
|
||||
DatabaseModule,
|
||||
MongooseModule.forFeature([
|
||||
{
|
||||
name: ExpertPreparedMessageModel.name,
|
||||
schema: ExpertPreparedMessageSchema,
|
||||
},
|
||||
]),
|
||||
],
|
||||
controllers: [
|
||||
ExpertPreparedMessagesAdminController,
|
||||
ExpertPreparedMessagesExpertController,
|
||||
],
|
||||
providers: [ExpertPreparedMessagesService],
|
||||
exports: [ExpertPreparedMessagesService],
|
||||
})
|
||||
export class ExpertPreparedMessagesModule {}
|
||||
263
src/expert-prepared-messages/expert-prepared-messages.service.ts
Normal file
263
src/expert-prepared-messages/expert-prepared-messages.service.ts
Normal file
@@ -0,0 +1,263 @@
|
||||
import { HttpException, HttpStatus, Injectable } from '@nestjs/common';
|
||||
import { InjectModel } from '@nestjs/mongoose';
|
||||
import { FilterQuery, Model } 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,
|
||||
};
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user