feat(ops): kb doc edit, validation auto-tickets, support batch ops
Add knowledge document GET/PUT and admin edit UI; auto-create support tickets on client 400 validation errors across user/shop/partner apps; batch create tasks and publish from support tickets; restore mini-user store env single-column layout. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -9,8 +9,10 @@ import type {
|
||||
CreateKnowledgeDocumentRequest,
|
||||
KnowledgeBaseDto,
|
||||
KnowledgeBaseOptionDto,
|
||||
KnowledgeDocumentDetailDto,
|
||||
KnowledgeDocumentDto,
|
||||
UpdateKnowledgeBaseRequest,
|
||||
UpdateKnowledgeDocumentRequest,
|
||||
} from '@dukang/shared-types';
|
||||
import { PrismaService } from '../../common/prisma/prisma.module';
|
||||
import { serializeBigInt } from '../../common/decorators/current-user.decorator';
|
||||
@@ -228,6 +230,113 @@ export class AdminKnowledgeBasesService {
|
||||
return this.toDocDto(row);
|
||||
}
|
||||
|
||||
async getDocument(actor: ActorCtx, kbId: bigint, docId: bigint): Promise<KnowledgeDocumentDetailDto> {
|
||||
await this.requireKb(actor, kbId);
|
||||
const doc = await this.prisma.knowledgeDocument.findFirst({
|
||||
where: { id: docId, knowledgeBaseId: kbId },
|
||||
});
|
||||
if (!doc) throw new NotFoundException('文档不存在');
|
||||
return {
|
||||
...this.toDocDto(doc),
|
||||
contentText: doc.contentText,
|
||||
};
|
||||
}
|
||||
|
||||
async updateDocument(
|
||||
actor: ActorCtx,
|
||||
kbId: bigint,
|
||||
docId: bigint,
|
||||
dto: UpdateKnowledgeDocumentRequest,
|
||||
) {
|
||||
const kb = await this.requireKb(actor, kbId);
|
||||
this.requireWrite(actor, kb);
|
||||
const doc = await this.prisma.knowledgeDocument.findFirst({
|
||||
where: { id: docId, knowledgeBaseId: kbId },
|
||||
});
|
||||
if (!doc) throw new NotFoundException('文档不存在');
|
||||
|
||||
const data: {
|
||||
title?: string;
|
||||
fileName?: string | null;
|
||||
fileUrl?: string | null;
|
||||
mimeType?: string | null;
|
||||
sizeBytes?: number | null;
|
||||
contentText?: string | null;
|
||||
status?: 'READY' | 'EMPTY' | 'FAILED';
|
||||
errorMessage?: string | null;
|
||||
} = {};
|
||||
|
||||
if (dto.title !== undefined) {
|
||||
const title = dto.title.trim();
|
||||
if (!title) throw new BadRequestException('请填写标题');
|
||||
data.title = title;
|
||||
}
|
||||
|
||||
const hasContentInput =
|
||||
dto.contentText !== undefined ||
|
||||
dto.fileUrl !== undefined ||
|
||||
dto.fileName !== undefined ||
|
||||
dto.mimeType !== undefined;
|
||||
|
||||
if (hasContentInput) {
|
||||
let contentText = dto.contentText !== undefined ? dto.contentText?.trim() || '' : doc.contentText?.trim() || '';
|
||||
let status: 'READY' | 'EMPTY' | 'FAILED' = doc.status as 'READY' | 'EMPTY' | 'FAILED';
|
||||
let errorMessage: string | null = doc.errorMessage;
|
||||
|
||||
if (dto.contentText !== undefined) {
|
||||
if (contentText) {
|
||||
status = 'READY';
|
||||
errorMessage = null;
|
||||
} else if (!dto.fileUrl?.trim() && !doc.fileUrl) {
|
||||
status = 'EMPTY';
|
||||
errorMessage = null;
|
||||
}
|
||||
data.contentText = contentText || null;
|
||||
}
|
||||
|
||||
if (dto.fileUrl !== undefined) data.fileUrl = dto.fileUrl?.trim() || null;
|
||||
if (dto.fileName !== undefined) data.fileName = dto.fileName?.trim() || null;
|
||||
if (dto.mimeType !== undefined) data.mimeType = dto.mimeType?.trim() || null;
|
||||
if (dto.sizeBytes !== undefined) data.sizeBytes = dto.sizeBytes ?? null;
|
||||
|
||||
const fileUrl = dto.fileUrl?.trim() || doc.fileUrl;
|
||||
const fileName = dto.fileName?.trim() || doc.fileName || '';
|
||||
const mimeType = dto.mimeType?.trim() || doc.mimeType;
|
||||
|
||||
if (dto.fileUrl?.trim() && dto.contentText === undefined) {
|
||||
if (TEXT_EXT.test(fileName) || isLikelyTextMime(mimeType)) {
|
||||
try {
|
||||
contentText = await fetchText(dto.fileUrl.trim());
|
||||
data.contentText = contentText || null;
|
||||
status = contentText.trim() ? 'READY' : 'EMPTY';
|
||||
errorMessage = contentText.trim() ? null : '文件内容为空';
|
||||
} catch (e) {
|
||||
status = 'FAILED';
|
||||
errorMessage = e instanceof Error ? e.message : String(e);
|
||||
}
|
||||
} else {
|
||||
status = 'EMPTY';
|
||||
errorMessage = '非文本文件未抽取正文,请粘贴文本或上传 .txt/.md';
|
||||
}
|
||||
} else if (dto.contentText !== undefined && contentText) {
|
||||
status = 'READY';
|
||||
errorMessage = null;
|
||||
} else if (dto.contentText !== undefined && !contentText && !fileUrl) {
|
||||
status = 'EMPTY';
|
||||
errorMessage = null;
|
||||
}
|
||||
|
||||
data.status = status;
|
||||
data.errorMessage = errorMessage;
|
||||
}
|
||||
|
||||
const updated = await this.prisma.knowledgeDocument.update({
|
||||
where: { id: docId },
|
||||
data,
|
||||
});
|
||||
return this.toDocDto(updated);
|
||||
}
|
||||
|
||||
async removeDocument(actor: ActorCtx, kbId: bigint, docId: bigint) {
|
||||
const kb = await this.requireKb(actor, kbId);
|
||||
this.requireWrite(actor, kb);
|
||||
|
||||
Reference in New Issue
Block a user