diff --git a/apps/api/src/knowledge/knowledge.controller.ts b/apps/api/src/knowledge/knowledge.controller.ts index 3ad6e8c..1f8837e 100644 --- a/apps/api/src/knowledge/knowledge.controller.ts +++ b/apps/api/src/knowledge/knowledge.controller.ts @@ -11,6 +11,7 @@ import { ParseIntPipe, DefaultValuePipe, } from "@nestjs/common"; +import type { AuthUser } from "@mosaic/shared"; import { KnowledgeService } from "./knowledge.service"; import { CreateEntryDto, UpdateEntryDto, EntryQueryDto, RestoreVersionDto } from "./dto"; import { AuthGuard } from "../auth/guards/auth.guard"; @@ -69,7 +70,7 @@ export class KnowledgeController { @RequirePermission(Permission.WORKSPACE_MEMBER) async create( @Workspace() workspaceId: string, - @CurrentUser() user: any, + @CurrentUser() user: AuthUser, @Body() createDto: CreateEntryDto ) { return this.knowledgeService.create(workspaceId, user.id, createDto); @@ -85,7 +86,7 @@ export class KnowledgeController { async update( @Workspace() workspaceId: string, @Param("slug") slug: string, - @CurrentUser() user: any, + @CurrentUser() user: AuthUser, @Body() updateDto: UpdateEntryDto ) { return this.knowledgeService.update(workspaceId, slug, user.id, updateDto); @@ -101,7 +102,7 @@ export class KnowledgeController { async remove( @Workspace() workspaceId: string, @Param("slug") slug: string, - @CurrentUser() user: any + @CurrentUser() user: AuthUser ) { await this.knowledgeService.remove(workspaceId, slug, user.id); return { message: "Entry archived successfully" }; @@ -177,7 +178,7 @@ export class KnowledgeController { @Workspace() workspaceId: string, @Param("slug") slug: string, @Param("version", ParseIntPipe) version: number, - @CurrentUser() user: any, + @CurrentUser() user: AuthUser, @Body() restoreDto: RestoreVersionDto ) { return this.knowledgeService.restoreVersion( diff --git a/apps/api/src/knowledge/knowledge.service.ts b/apps/api/src/knowledge/knowledge.service.ts index 6c65bb3..cdfef40 100644 --- a/apps/api/src/knowledge/knowledge.service.ts +++ b/apps/api/src/knowledge/knowledge.service.ts @@ -3,7 +3,7 @@ import { NotFoundException, ConflictException, } from "@nestjs/common"; -import { EntryStatus } from "@prisma/client"; +import { EntryStatus, Prisma } from "@prisma/client"; import slugify from "slugify"; import { PrismaService } from "../prisma/prisma.service"; import type { CreateEntryDto, UpdateEntryDto, EntryQueryDto } from "./dto"; @@ -41,7 +41,7 @@ export class KnowledgeService { const skip = (page - 1) * limit; // Build where clause - const where: any = { + const where: Prisma.KnowledgeEntryWhereInput = { workspaceId, }; @@ -308,7 +308,7 @@ export class KnowledgeService { } // Build update data object conditionally - const updateData: any = { + const updateData: Prisma.KnowledgeEntryUpdateInput = { updatedBy: userId, }; @@ -764,7 +764,7 @@ export class KnowledgeService { * Sync tags for an entry (create missing tags, update associations) */ private async syncTags( - tx: any, + tx: Prisma.TransactionClient, workspaceId: string, entryId: string, tagNames: string[] diff --git a/apps/web/src/components/knowledge/index.ts b/apps/web/src/components/knowledge/index.ts index 6674ea2..8f0786a 100644 --- a/apps/web/src/components/knowledge/index.ts +++ b/apps/web/src/components/knowledge/index.ts @@ -7,3 +7,5 @@ export { EntryViewer } from "./EntryViewer"; export { EntryEditor } from "./EntryEditor"; export { EntryMetadata } from "./EntryMetadata"; export { VersionHistory } from "./VersionHistory"; +export { StatsDashboard } from "./StatsDashboard"; +export { EntryGraphViewer } from "./EntryGraphViewer"; diff --git a/apps/web/src/lib/api/knowledge.ts b/apps/web/src/lib/api/knowledge.ts index bb4d06f..a5e5522 100644 --- a/apps/web/src/lib/api/knowledge.ts +++ b/apps/web/src/lib/api/knowledge.ts @@ -230,6 +230,94 @@ export async function fetchBacklinks(slug: string): Promise<{ }>(`/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) */