fix: code review — 4 critical bugs in KnowledgeItem/KB modules
All checks were successful
Deploy API Server / build-and-deploy (push) Successful in 42s
All checks were successful
Deploy API Server / build-and-deploy (push) Successful in 42s
1. enrichItem: remove MIME type → sourceType mapping (contentType is text/markdown not markdown) 2. update(): add field whitelist to prevent mass assignment (userId/deletedAt/etc) 3. createFolder: add userId permission check + parent folder validation 4. deleteFolder: cascade soft-delete to corresponding KnowledgeItem (itemType=folder) Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
parent
4b8653080e
commit
4b21c98835
@ -59,8 +59,12 @@ export class KnowledgeBaseController {
|
||||
|
||||
@Post(':id/folders')
|
||||
@ApiOperation({ summary: '创建文件夹' })
|
||||
async createFolder(@Param('id') id: string, @Body() dto: { name: string; parentId?: string }) {
|
||||
return this.service.createFolder(id, dto);
|
||||
async createFolder(
|
||||
@CurrentUser() user: UserPayload,
|
||||
@Param('id') id: string,
|
||||
@Body() dto: { name: string; parentId?: string },
|
||||
) {
|
||||
return this.service.createFolder(String(user?.id || 'anonymous'), id, dto);
|
||||
}
|
||||
|
||||
@Patch(':id/folders/:folderId')
|
||||
|
||||
@ -119,10 +119,18 @@ export class KnowledgeBaseService {
|
||||
|
||||
// ── Folder CRUD ──
|
||||
|
||||
async createFolder(kbId: string, dto: { name: string; parentId?: string }) {
|
||||
// Also create a KnowledgeItem with itemType='folder' for iOS list compatibility
|
||||
async createFolder(userId: string, kbId: string, dto: { name: string; parentId?: string }) {
|
||||
const kb = await this.repository.findById(kbId);
|
||||
if (!kb) throw new NotFoundException('知识库不存在');
|
||||
if (!kb || kb.deletedAt) throw new NotFoundException('知识库不存在');
|
||||
if (String(kb.userId) !== userId) throw new ForbiddenException('无权操作该知识库');
|
||||
|
||||
// Validate parent folder belongs to same KB
|
||||
if (dto.parentId) {
|
||||
const parentFolder = await this.prisma.knowledgeFolder.findUnique({ where: { id: dto.parentId } });
|
||||
if (!parentFolder || parentFolder.knowledgeBaseId !== kbId) {
|
||||
throw new BadRequestException('父文件夹不存在或不属于该知识库');
|
||||
}
|
||||
}
|
||||
|
||||
const [folder] = await Promise.all([
|
||||
this.prisma.knowledgeFolder.create({
|
||||
@ -155,11 +163,26 @@ export class KnowledgeBaseService {
|
||||
}
|
||||
|
||||
async deleteFolder(folderId: string) {
|
||||
const folder = await this.prisma.knowledgeFolder.findUnique({ where: { id: folderId } });
|
||||
if (!folder) throw new NotFoundException('文件夹不存在');
|
||||
|
||||
// Soft-delete folder and children
|
||||
await this.prisma.knowledgeFolder.updateMany({
|
||||
where: { OR: [{ id: folderId }, { parentId: folderId }] },
|
||||
data: { deletedAt: new Date() },
|
||||
});
|
||||
|
||||
// Also soft-delete the corresponding KnowledgeItem (itemType='folder')
|
||||
await this.prisma.knowledgeItem.updateMany({
|
||||
where: {
|
||||
knowledgeBaseId: folder.knowledgeBaseId,
|
||||
title: folder.name,
|
||||
itemType: 'folder',
|
||||
deletedAt: null,
|
||||
},
|
||||
data: { deletedAt: new Date() },
|
||||
});
|
||||
|
||||
return { success: true };
|
||||
}
|
||||
}
|
||||
|
||||
@ -110,9 +110,15 @@ export class KnowledgeItemsRepository {
|
||||
}
|
||||
|
||||
async update(id: string, dto: Record<string, any>) {
|
||||
// Whitelist allowed fields to prevent mass assignment
|
||||
const allowed = ['title', 'content', 'summary', 'parentId', 'itemType', 'sourceType', 'orderIndex', 'status', 'durationSeconds'];
|
||||
const data: Record<string, any> = {};
|
||||
for (const key of allowed) {
|
||||
if (dto[key] !== undefined) data[key] = dto[key];
|
||||
}
|
||||
return this.prisma.knowledgeItem.update({
|
||||
where: { id },
|
||||
data: dto,
|
||||
data,
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@ -47,7 +47,6 @@ export class KnowledgeItemsService {
|
||||
const enriched = { ...item, content: freshUrl };
|
||||
if (headInfo) {
|
||||
enriched.fileSize = Number(headInfo.size);
|
||||
enriched.sourceType = enriched.sourceType || headInfo.contentType;
|
||||
}
|
||||
return enriched;
|
||||
} catch {
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user