fix: code review cleanup
- Added missing API functions: fetchKnowledgeStats, fetchEntryGraph - Exported StatsDashboard and EntryGraphViewer components - Replaced 'any' types with proper TypeScript types: * AuthUser for @CurrentUser parameters * Prisma.KnowledgeEntryWhereInput for where clauses * Prisma.KnowledgeEntryUpdateInput for update data * Prisma.TransactionClient for transaction parameters - All TypeScript checks passing - XSS protection verified in WikiLinkRenderer (escapeHtml function) - Wiki-link parsing properly handles code blocks and escaping
This commit is contained in:
@@ -11,6 +11,7 @@ import {
|
|||||||
ParseIntPipe,
|
ParseIntPipe,
|
||||||
DefaultValuePipe,
|
DefaultValuePipe,
|
||||||
} from "@nestjs/common";
|
} from "@nestjs/common";
|
||||||
|
import type { AuthUser } from "@mosaic/shared";
|
||||||
import { KnowledgeService } from "./knowledge.service";
|
import { KnowledgeService } from "./knowledge.service";
|
||||||
import { CreateEntryDto, UpdateEntryDto, EntryQueryDto, RestoreVersionDto } from "./dto";
|
import { CreateEntryDto, UpdateEntryDto, EntryQueryDto, RestoreVersionDto } from "./dto";
|
||||||
import { AuthGuard } from "../auth/guards/auth.guard";
|
import { AuthGuard } from "../auth/guards/auth.guard";
|
||||||
@@ -69,7 +70,7 @@ export class KnowledgeController {
|
|||||||
@RequirePermission(Permission.WORKSPACE_MEMBER)
|
@RequirePermission(Permission.WORKSPACE_MEMBER)
|
||||||
async create(
|
async create(
|
||||||
@Workspace() workspaceId: string,
|
@Workspace() workspaceId: string,
|
||||||
@CurrentUser() user: any,
|
@CurrentUser() user: AuthUser,
|
||||||
@Body() createDto: CreateEntryDto
|
@Body() createDto: CreateEntryDto
|
||||||
) {
|
) {
|
||||||
return this.knowledgeService.create(workspaceId, user.id, createDto);
|
return this.knowledgeService.create(workspaceId, user.id, createDto);
|
||||||
@@ -85,7 +86,7 @@ export class KnowledgeController {
|
|||||||
async update(
|
async update(
|
||||||
@Workspace() workspaceId: string,
|
@Workspace() workspaceId: string,
|
||||||
@Param("slug") slug: string,
|
@Param("slug") slug: string,
|
||||||
@CurrentUser() user: any,
|
@CurrentUser() user: AuthUser,
|
||||||
@Body() updateDto: UpdateEntryDto
|
@Body() updateDto: UpdateEntryDto
|
||||||
) {
|
) {
|
||||||
return this.knowledgeService.update(workspaceId, slug, user.id, updateDto);
|
return this.knowledgeService.update(workspaceId, slug, user.id, updateDto);
|
||||||
@@ -101,7 +102,7 @@ export class KnowledgeController {
|
|||||||
async remove(
|
async remove(
|
||||||
@Workspace() workspaceId: string,
|
@Workspace() workspaceId: string,
|
||||||
@Param("slug") slug: string,
|
@Param("slug") slug: string,
|
||||||
@CurrentUser() user: any
|
@CurrentUser() user: AuthUser
|
||||||
) {
|
) {
|
||||||
await this.knowledgeService.remove(workspaceId, slug, user.id);
|
await this.knowledgeService.remove(workspaceId, slug, user.id);
|
||||||
return { message: "Entry archived successfully" };
|
return { message: "Entry archived successfully" };
|
||||||
@@ -177,7 +178,7 @@ export class KnowledgeController {
|
|||||||
@Workspace() workspaceId: string,
|
@Workspace() workspaceId: string,
|
||||||
@Param("slug") slug: string,
|
@Param("slug") slug: string,
|
||||||
@Param("version", ParseIntPipe) version: number,
|
@Param("version", ParseIntPipe) version: number,
|
||||||
@CurrentUser() user: any,
|
@CurrentUser() user: AuthUser,
|
||||||
@Body() restoreDto: RestoreVersionDto
|
@Body() restoreDto: RestoreVersionDto
|
||||||
) {
|
) {
|
||||||
return this.knowledgeService.restoreVersion(
|
return this.knowledgeService.restoreVersion(
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ import {
|
|||||||
NotFoundException,
|
NotFoundException,
|
||||||
ConflictException,
|
ConflictException,
|
||||||
} from "@nestjs/common";
|
} from "@nestjs/common";
|
||||||
import { EntryStatus } from "@prisma/client";
|
import { EntryStatus, Prisma } from "@prisma/client";
|
||||||
import slugify from "slugify";
|
import slugify from "slugify";
|
||||||
import { PrismaService } from "../prisma/prisma.service";
|
import { PrismaService } from "../prisma/prisma.service";
|
||||||
import type { CreateEntryDto, UpdateEntryDto, EntryQueryDto } from "./dto";
|
import type { CreateEntryDto, UpdateEntryDto, EntryQueryDto } from "./dto";
|
||||||
@@ -41,7 +41,7 @@ export class KnowledgeService {
|
|||||||
const skip = (page - 1) * limit;
|
const skip = (page - 1) * limit;
|
||||||
|
|
||||||
// Build where clause
|
// Build where clause
|
||||||
const where: any = {
|
const where: Prisma.KnowledgeEntryWhereInput = {
|
||||||
workspaceId,
|
workspaceId,
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -308,7 +308,7 @@ export class KnowledgeService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Build update data object conditionally
|
// Build update data object conditionally
|
||||||
const updateData: any = {
|
const updateData: Prisma.KnowledgeEntryUpdateInput = {
|
||||||
updatedBy: userId,
|
updatedBy: userId,
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -764,7 +764,7 @@ export class KnowledgeService {
|
|||||||
* Sync tags for an entry (create missing tags, update associations)
|
* Sync tags for an entry (create missing tags, update associations)
|
||||||
*/
|
*/
|
||||||
private async syncTags(
|
private async syncTags(
|
||||||
tx: any,
|
tx: Prisma.TransactionClient,
|
||||||
workspaceId: string,
|
workspaceId: string,
|
||||||
entryId: string,
|
entryId: string,
|
||||||
tagNames: string[]
|
tagNames: string[]
|
||||||
|
|||||||
@@ -7,3 +7,5 @@ export { EntryViewer } from "./EntryViewer";
|
|||||||
export { EntryEditor } from "./EntryEditor";
|
export { EntryEditor } from "./EntryEditor";
|
||||||
export { EntryMetadata } from "./EntryMetadata";
|
export { EntryMetadata } from "./EntryMetadata";
|
||||||
export { VersionHistory } from "./VersionHistory";
|
export { VersionHistory } from "./VersionHistory";
|
||||||
|
export { StatsDashboard } from "./StatsDashboard";
|
||||||
|
export { EntryGraphViewer } from "./EntryGraphViewer";
|
||||||
|
|||||||
@@ -230,6 +230,94 @@ export async function fetchBacklinks(slug: string): Promise<{
|
|||||||
}>(`/api/knowledge/entries/${slug}/backlinks`);
|
}>(`/api/knowledge/entries/${slug}/backlinks`);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Fetch knowledge base statistics
|
||||||
|
*/
|
||||||
|
export async function fetchKnowledgeStats(): Promise<{
|
||||||
|
overview: {
|
||||||
|
totalEntries: number;
|
||||||
|
totalTags: number;
|
||||||
|
totalLinks: number;
|
||||||
|
publishedEntries: number;
|
||||||
|
draftEntries: number;
|
||||||
|
archivedEntries: number;
|
||||||
|
};
|
||||||
|
mostConnected: Array<{
|
||||||
|
id: string;
|
||||||
|
slug: string;
|
||||||
|
title: string;
|
||||||
|
incomingLinks: number;
|
||||||
|
outgoingLinks: number;
|
||||||
|
totalConnections: number;
|
||||||
|
}>;
|
||||||
|
recentActivity: Array<{
|
||||||
|
id: string;
|
||||||
|
slug: string;
|
||||||
|
title: string;
|
||||||
|
updatedAt: string;
|
||||||
|
status: string;
|
||||||
|
}>;
|
||||||
|
tagDistribution: Array<{
|
||||||
|
id: string;
|
||||||
|
name: string;
|
||||||
|
slug: string;
|
||||||
|
color: string | null;
|
||||||
|
entryCount: number;
|
||||||
|
}>;
|
||||||
|
}> {
|
||||||
|
return apiGet(`/api/knowledge/stats`);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Fetch entry graph (network of connected entries)
|
||||||
|
*/
|
||||||
|
export async function fetchEntryGraph(
|
||||||
|
slug: string,
|
||||||
|
depth: number = 1
|
||||||
|
): Promise<{
|
||||||
|
centerNode: {
|
||||||
|
id: string;
|
||||||
|
slug: string;
|
||||||
|
title: string;
|
||||||
|
summary: string | null;
|
||||||
|
tags: Array<{
|
||||||
|
id: string;
|
||||||
|
name: string;
|
||||||
|
slug: string;
|
||||||
|
color: string | null;
|
||||||
|
}>;
|
||||||
|
depth: number;
|
||||||
|
};
|
||||||
|
nodes: Array<{
|
||||||
|
id: string;
|
||||||
|
slug: string;
|
||||||
|
title: string;
|
||||||
|
summary: string | null;
|
||||||
|
tags: Array<{
|
||||||
|
id: string;
|
||||||
|
name: string;
|
||||||
|
slug: string;
|
||||||
|
color: string | null;
|
||||||
|
}>;
|
||||||
|
depth: number;
|
||||||
|
}>;
|
||||||
|
edges: Array<{
|
||||||
|
id: string;
|
||||||
|
sourceId: string;
|
||||||
|
targetId: string;
|
||||||
|
linkText: string;
|
||||||
|
}>;
|
||||||
|
stats: {
|
||||||
|
totalNodes: number;
|
||||||
|
totalEdges: number;
|
||||||
|
maxDepth: number;
|
||||||
|
};
|
||||||
|
}> {
|
||||||
|
const params = new URLSearchParams();
|
||||||
|
params.append("depth", depth.toString());
|
||||||
|
return apiGet(`/api/knowledge/entries/${slug}/graph?${params.toString()}`);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Mock entries for development (until backend endpoints are ready)
|
* Mock entries for development (until backend endpoints are ready)
|
||||||
*/
|
*/
|
||||||
|
|||||||
Reference in New Issue
Block a user