120 lines
3.6 KiB
TypeScript
120 lines
3.6 KiB
TypeScript
import { Injectable, NotFoundException } from '@nestjs/common';
|
|
import type {
|
|
CreateFaqArticleRequest,
|
|
FaqArticleDto,
|
|
UpdateFaqArticleRequest,
|
|
} from '@mento/shared';
|
|
import { Prisma } from '@prisma/client';
|
|
import { PrismaService } from '../prisma/prisma.service';
|
|
import { buildSearchWhere, ci } from '../common/search';
|
|
|
|
@Injectable()
|
|
export class FaqService {
|
|
constructor(private readonly prisma: PrismaService) {}
|
|
|
|
async list(
|
|
organizationId: string,
|
|
opts: { q?: string; category?: string; publishedOnly?: boolean },
|
|
): Promise<FaqArticleDto[]> {
|
|
const where: Prisma.FaqArticleWhereInput = {
|
|
organizationId,
|
|
...(opts.publishedOnly ? { isPublished: true } : {}),
|
|
...(opts.category?.trim()
|
|
? { category: opts.category.trim() }
|
|
: {}),
|
|
};
|
|
const search = buildSearchWhere<Prisma.FaqArticleWhereInput>(opts.q, (t) => [
|
|
{ question: ci(t) },
|
|
{ answer: ci(t) },
|
|
]);
|
|
if (search) where.AND = [search];
|
|
const rows = await this.prisma.faqArticle.findMany({
|
|
where,
|
|
orderBy: [{ position: 'asc' }, { createdAt: 'asc' }],
|
|
});
|
|
return rows.map(toDto);
|
|
}
|
|
|
|
async get(organizationId: string, id: string): Promise<FaqArticleDto> {
|
|
const row = await this.prisma.faqArticle.findFirst({
|
|
where: { id, organizationId },
|
|
});
|
|
if (!row) throw new NotFoundException('مقاله FAQ پیدا نشد');
|
|
return toDto(row);
|
|
}
|
|
|
|
async create(
|
|
organizationId: string,
|
|
dto: CreateFaqArticleRequest,
|
|
): Promise<FaqArticleDto> {
|
|
const row = await this.prisma.faqArticle.create({
|
|
data: {
|
|
organizationId,
|
|
question: dto.question.trim(),
|
|
answer: dto.answer.trim(),
|
|
category: emptyToNull(dto.category ?? null),
|
|
isPublished: dto.isPublished ?? true,
|
|
position: dto.position ?? 0,
|
|
},
|
|
});
|
|
return toDto(row);
|
|
}
|
|
|
|
async update(
|
|
organizationId: string,
|
|
id: string,
|
|
dto: UpdateFaqArticleRequest,
|
|
): Promise<FaqArticleDto> {
|
|
await this.ensureExists(organizationId, id);
|
|
const data: Prisma.FaqArticleUpdateInput = {};
|
|
if (dto.question !== undefined) data.question = dto.question.trim();
|
|
if (dto.answer !== undefined) data.answer = dto.answer.trim();
|
|
if ('category' in dto) data.category = emptyToNull(dto.category ?? null);
|
|
if (dto.isPublished !== undefined) data.isPublished = dto.isPublished;
|
|
if (dto.position !== undefined) data.position = dto.position;
|
|
const row = await this.prisma.faqArticle.update({ where: { id }, data });
|
|
return toDto(row);
|
|
}
|
|
|
|
async remove(organizationId: string, id: string): Promise<void> {
|
|
await this.ensureExists(organizationId, id);
|
|
await this.prisma.faqArticle.delete({ where: { id } });
|
|
}
|
|
|
|
private async ensureExists(organizationId: string, id: string) {
|
|
const found = await this.prisma.faqArticle.findFirst({
|
|
where: { id, organizationId },
|
|
select: { id: true },
|
|
});
|
|
if (!found) throw new NotFoundException('مقاله FAQ پیدا نشد');
|
|
}
|
|
}
|
|
|
|
function toDto(row: {
|
|
id: string;
|
|
question: string;
|
|
answer: string;
|
|
category: string | null;
|
|
isPublished: boolean;
|
|
position: number;
|
|
createdAt: Date;
|
|
updatedAt: Date;
|
|
}): FaqArticleDto {
|
|
return {
|
|
id: row.id,
|
|
question: row.question,
|
|
answer: row.answer,
|
|
category: row.category,
|
|
isPublished: row.isPublished,
|
|
position: row.position,
|
|
createdAt: row.createdAt.toISOString(),
|
|
updatedAt: row.updatedAt.toISOString(),
|
|
};
|
|
}
|
|
|
|
function emptyToNull(value?: string | null): string | null {
|
|
if (value == null) return null;
|
|
const trimmed = value.trim();
|
|
return trimmed.length ? trimmed : null;
|
|
}
|