import { BadRequestException, Injectable, NotFoundException } from '@nestjs/common'; import { PrismaService } from '../../prisma/prisma.service'; function slugify(name: string): string { return name.toLowerCase().trim().replace(/[^a-z0-9]+/g, '-').replace(/^-+|-+$/g, '') || 'board'; } @Injectable() export class KnowledgeService { constructor(private readonly prisma: PrismaService) {} // ── Admin: boards ────────────────────────────────────────────────────────── async listBoardsAdmin(tenantId: string) { return this.prisma.knowledgeBoard.findMany({ where: { tenantId }, orderBy: [{ sortOrder: 'asc' }, { createdAt: 'asc' }], include: { _count: { select: { items: true } } }, }); } async createBoard(tenantId: string, dto: { name: string; description?: string; sortOrder?: number }) { let slug = slugify(dto.name); const existing = await this.prisma.knowledgeBoard.findFirst({ where: { tenantId, slug } }); if (existing) slug = `${slug}-${Date.now().toString(36)}`; return this.prisma.knowledgeBoard.create({ data: { tenantId, name: dto.name, description: dto.description ?? null, slug, sortOrder: dto.sortOrder ?? 0, }, }); } async updateBoard(tenantId: string, id: string, dto: { name?: string; description?: string; sortOrder?: number }) { const board = await this.prisma.knowledgeBoard.findFirst({ where: { id, tenantId } }); if (!board) throw new NotFoundException('Board not found'); return this.prisma.knowledgeBoard.update({ where: { id }, data: { ...(dto.name !== undefined && { name: dto.name }), ...(dto.description !== undefined && { description: dto.description }), ...(dto.sortOrder !== undefined && { sortOrder: dto.sortOrder }), }, }); } async removeBoard(tenantId: string, id: string) { const board = await this.prisma.knowledgeBoard.findFirst({ where: { id, tenantId } }); if (!board) throw new NotFoundException('Board not found'); await this.prisma.knowledgeBoard.delete({ where: { id } }); return { ok: true }; } // ── Admin: items ───────────────────────────────────────────────────────────── async listItemsAdmin(tenantId: string, boardId: string) { const board = await this.prisma.knowledgeBoard.findFirst({ where: { id: boardId, tenantId } }); if (!board) throw new NotFoundException('Board not found'); return this.prisma.knowledgeItem.findMany({ where: { boardId }, orderBy: [{ sortOrder: 'asc' }, { createdAt: 'asc' }], }); } async createItem(tenantId: string, adminId: string, boardId: string, dto: { question: string; answer: string; tags?: string[]; status?: 'DRAFT' | 'PUBLISHED' | 'ARCHIVED'; sortOrder?: number; }) { const board = await this.prisma.knowledgeBoard.findFirst({ where: { id: boardId, tenantId } }); if (!board) throw new NotFoundException('Board not found'); return this.prisma.knowledgeItem.create({ data: { boardId, tenantId, question: dto.question, answer: dto.answer, tags: dto.tags ?? [], status: dto.status ?? 'DRAFT', sortOrder: dto.sortOrder ?? 0, createdBy: adminId, }, }); } async updateItem(tenantId: string, itemId: string, dto: { question?: string; answer?: string; tags?: string[]; status?: 'DRAFT' | 'PUBLISHED' | 'ARCHIVED'; sortOrder?: number; }) { const item = await this.prisma.knowledgeItem.findFirst({ where: { id: itemId, tenantId } }); if (!item) throw new NotFoundException('Item not found'); return this.prisma.knowledgeItem.update({ where: { id: itemId }, data: { ...(dto.question !== undefined && { question: dto.question }), ...(dto.answer !== undefined && { answer: dto.answer }), ...(dto.tags !== undefined && { tags: dto.tags }), ...(dto.status !== undefined && { status: dto.status }), ...(dto.sortOrder !== undefined && { sortOrder: dto.sortOrder }), }, }); } async removeItem(tenantId: string, itemId: string) { const item = await this.prisma.knowledgeItem.findFirst({ where: { id: itemId, tenantId } }); if (!item) throw new NotFoundException('Item not found'); await this.prisma.knowledgeItem.delete({ where: { id: itemId } }); return { ok: true }; } // ── Member: read published knowledge grouped by board ──────────────────────── async listForMember(tenantId: string, q?: string) { const boards = await this.prisma.knowledgeBoard.findMany({ where: { tenantId }, orderBy: [{ sortOrder: 'asc' }, { createdAt: 'asc' }], include: { items: { where: { status: 'PUBLISHED', ...(q && q.trim() ? { OR: [ { question: { contains: q.trim(), mode: 'insensitive' } }, { answer: { contains: q.trim(), mode: 'insensitive' } }, ], } : {}), }, orderBy: [{ sortOrder: 'asc' }, { createdAt: 'asc' }], select: { id: true, question: true, answer: true, tags: true }, }, }, }); return boards .filter((b) => b.items.length > 0) .map((b) => ({ id: b.id, name: b.name, description: b.description, items: b.items, })); } }