import { Controller, Get, Post, Put, Delete, Body, Param, Query, UseGuards, ParseIntPipe, DefaultValuePipe, } from "@nestjs/common"; import type { AuthUser } from "@mosaic/shared"; import { EntryStatus } from "@prisma/client"; import { KnowledgeService } from "./knowledge.service"; import { CreateEntryDto, UpdateEntryDto, EntryQueryDto, RestoreVersionDto } from "./dto"; import { AuthGuard } from "../auth/guards/auth.guard"; import { WorkspaceGuard, PermissionGuard } from "../common/guards"; import { Workspace, Permission, RequirePermission } from "../common/decorators"; import { CurrentUser } from "../auth/decorators/current-user.decorator"; import { LinkSyncService } from "./services/link-sync.service"; import { KnowledgeCacheService } from "./services/cache.service"; /** * Controller for knowledge entry endpoints * All endpoints require authentication and workspace context * Uses the new guard-based permission system */ @Controller("knowledge/entries") @UseGuards(AuthGuard, WorkspaceGuard, PermissionGuard) export class KnowledgeController { constructor( private readonly knowledgeService: KnowledgeService, private readonly linkSync: LinkSyncService ) {} /** * GET /api/knowledge/entries * List all entries in the workspace with pagination and filtering * Requires: Any workspace member */ @Get() @RequirePermission(Permission.WORKSPACE_ANY) async findAll( @Workspace() workspaceId: string, @Query() query: EntryQueryDto ) { return this.knowledgeService.findAll(workspaceId, query); } /** * GET /api/knowledge/entries/:slug * Get a single entry by slug * Requires: Any workspace member */ @Get(":slug") @RequirePermission(Permission.WORKSPACE_ANY) async findOne( @Workspace() workspaceId: string, @Param("slug") slug: string ) { return this.knowledgeService.findOne(workspaceId, slug); } /** * POST /api/knowledge/entries * Create a new knowledge entry * Requires: MEMBER role or higher */ @Post() @RequirePermission(Permission.WORKSPACE_MEMBER) async create( @Workspace() workspaceId: string, @CurrentUser() user: AuthUser, @Body() createDto: CreateEntryDto ) { return this.knowledgeService.create(workspaceId, user.id, createDto); } /** * PUT /api/knowledge/entries/:slug * Update an existing entry * Requires: MEMBER role or higher */ @Put(":slug") @RequirePermission(Permission.WORKSPACE_MEMBER) async update( @Workspace() workspaceId: string, @Param("slug") slug: string, @CurrentUser() user: AuthUser, @Body() updateDto: UpdateEntryDto ) { return this.knowledgeService.update(workspaceId, slug, user.id, updateDto); } /** * DELETE /api/knowledge/entries/:slug * Soft delete an entry (sets status to ARCHIVED) * Requires: ADMIN role or higher */ @Delete(":slug") @RequirePermission(Permission.WORKSPACE_ADMIN) async remove( @Workspace() workspaceId: string, @Param("slug") slug: string, @CurrentUser() user: AuthUser ) { await this.knowledgeService.remove(workspaceId, slug, user.id); return { message: "Entry archived successfully" }; } /** * GET /api/knowledge/entries/:slug/backlinks * Get all backlinks for an entry * Requires: Any workspace member */ @Get(":slug/backlinks") @RequirePermission(Permission.WORKSPACE_ANY) async getBacklinks( @Workspace() workspaceId: string, @Param("slug") slug: string ) { // First find the entry to get its ID const entry = await this.knowledgeService.findOne(workspaceId, slug); // Get backlinks const backlinks = await this.linkSync.getBacklinks(entry.id); return { entry: { id: entry.id, slug: entry.slug, title: entry.title, }, backlinks, count: backlinks.length, }; } /** * GET /api/knowledge/entries/:slug/versions * List all versions for an entry with pagination * Requires: Any workspace member */ @Get(":slug/versions") @RequirePermission(Permission.WORKSPACE_ANY) async getVersions( @Workspace() workspaceId: string, @Param("slug") slug: string, @Query("page", new DefaultValuePipe(1), ParseIntPipe) page: number, @Query("limit", new DefaultValuePipe(20), ParseIntPipe) limit: number ) { return this.knowledgeService.findVersions(workspaceId, slug, page, limit); } /** * GET /api/knowledge/entries/:slug/versions/:version * Get a specific version of an entry * Requires: Any workspace member */ @Get(":slug/versions/:version") @RequirePermission(Permission.WORKSPACE_ANY) async getVersion( @Workspace() workspaceId: string, @Param("slug") slug: string, @Param("version", ParseIntPipe) version: number ) { return this.knowledgeService.findVersion(workspaceId, slug, version); } /** * POST /api/knowledge/entries/:slug/restore/:version * Restore a previous version of an entry * Requires: MEMBER role or higher */ @Post(":slug/restore/:version") @RequirePermission(Permission.WORKSPACE_MEMBER) async restoreVersion( @Workspace() workspaceId: string, @Param("slug") slug: string, @Param("version", ParseIntPipe) version: number, @CurrentUser() user: AuthUser, @Body() restoreDto: RestoreVersionDto ) { return this.knowledgeService.restoreVersion( workspaceId, slug, version, user.id, restoreDto.changeNote ); } } /** * Controller for knowledge embeddings endpoints */ @Controller("knowledge/embeddings") @UseGuards(AuthGuard, WorkspaceGuard, PermissionGuard) export class KnowledgeEmbeddingsController { constructor(private readonly knowledgeService: KnowledgeService) {} /** * POST /api/knowledge/embeddings/batch * Batch generate embeddings for all entries in the workspace * Useful for populating embeddings for existing entries * Requires: ADMIN role or higher */ @Post("batch") @RequirePermission(Permission.WORKSPACE_ADMIN) async batchGenerate( @Workspace() workspaceId: string, @Body() body: { status?: string } ) { const status = body.status as EntryStatus | undefined; const result = await this.knowledgeService.batchGenerateEmbeddings( workspaceId, status ); return { message: `Generated ${result.success} embeddings out of ${result.total} entries`, ...result, }; } } /** * Controller for knowledge cache endpoints */ @Controller("knowledge/cache") @UseGuards(AuthGuard, WorkspaceGuard, PermissionGuard) export class KnowledgeCacheController { constructor(private readonly cache: KnowledgeCacheService) {} /** * GET /api/knowledge/cache/stats * Get cache statistics (hits, misses, hit rate, etc.) * Requires: Any workspace member */ @Get("stats") @RequirePermission(Permission.WORKSPACE_ANY) async getStats() { return { enabled: this.cache.isEnabled(), stats: this.cache.getStats(), }; } /** * POST /api/knowledge/cache/clear * Clear all caches for the workspace * Requires: ADMIN role or higher */ @Post("clear") @RequirePermission(Permission.WORKSPACE_ADMIN) async clearCache(@Workspace() workspaceId: string) { await this.cache.clearWorkspaceCache(workspaceId); return { message: "Cache cleared successfully" }; } /** * POST /api/knowledge/cache/stats/reset * Reset cache statistics * Requires: ADMIN role or higher */ @Post("stats/reset") @RequirePermission(Permission.WORKSPACE_ADMIN) async resetStats() { this.cache.resetStats(); return { message: "Cache statistics reset successfully" }; } }