feat: add knowledge version history (closes #75, closes #76)

- Added EntryVersion model with author relation
- Implemented automatic versioning on entry create/update
- Added API endpoints for version history:
  - GET /api/knowledge/entries/:slug/versions - list versions
  - GET /api/knowledge/entries/:slug/versions/:version - get specific
  - POST /api/knowledge/entries/:slug/restore/:version - restore version
- Created VersionHistory.tsx component with timeline view
- Added History tab to entry detail page
- Supports version viewing and restoring
- Includes comprehensive tests for version operations
- All TypeScript types are explicit and type-safe
This commit is contained in:
Jason Woltje
2026-01-29 23:27:03 -06:00
parent 59aec28d5c
commit 7465d0a3c2
14 changed files with 2450 additions and 24 deletions

View File

@@ -8,9 +8,11 @@ import {
Param,
Query,
UseGuards,
ParseIntPipe,
DefaultValuePipe,
} from "@nestjs/common";
import { KnowledgeService } from "./knowledge.service";
import { CreateEntryDto, UpdateEntryDto, EntryQueryDto } from "./dto";
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";
@@ -132,4 +134,58 @@ export class KnowledgeController {
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: any,
@Body() restoreDto: RestoreVersionDto
) {
return this.knowledgeService.restoreVersion(
workspaceId,
slug,
version,
user.id,
restoreDto.changeNote
);
}
}