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

@@ -3,7 +3,12 @@
* Handles knowledge entry-related API requests
*/
import type { KnowledgeEntryWithTags, KnowledgeTag } from "@mosaic/shared";
import type {
KnowledgeEntryWithTags,
KnowledgeTag,
KnowledgeEntryVersionWithAuthor,
PaginatedResponse,
} from "@mosaic/shared";
import { EntryStatus, Visibility } from "@mosaic/shared";
import { apiGet, apiPost, apiPatch, apiDelete, type ApiResponse } from "./client";
@@ -44,6 +49,11 @@ export interface UpdateEntryData {
status?: EntryStatus;
visibility?: Visibility;
tags?: string[];
changeNote?: string;
}
export interface RestoreVersionData {
changeNote?: string;
}
/**
@@ -128,6 +138,49 @@ export async function fetchTags(): Promise<KnowledgeTag[]> {
return response.data;
}
/**
* Fetch version history for an entry
*/
export async function fetchVersions(
slug: string,
page: number = 1,
limit: number = 20
): Promise<PaginatedResponse<KnowledgeEntryVersionWithAuthor>> {
const params = new URLSearchParams();
params.append("page", page.toString());
params.append("limit", limit.toString());
return apiGet<PaginatedResponse<KnowledgeEntryVersionWithAuthor>>(
`/api/knowledge/entries/${slug}/versions?${params.toString()}`
);
}
/**
* Fetch a specific version of an entry
*/
export async function fetchVersion(
slug: string,
version: number
): Promise<KnowledgeEntryVersionWithAuthor> {
return apiGet<KnowledgeEntryVersionWithAuthor>(
`/api/knowledge/entries/${slug}/versions/${version}`
);
}
/**
* Restore a previous version of an entry
*/
export async function restoreVersion(
slug: string,
version: number,
data?: RestoreVersionData
): Promise<KnowledgeEntryWithTags> {
return apiPost<KnowledgeEntryWithTags>(
`/api/knowledge/entries/${slug}/restore/${version}`,
data || {}
);
}
/**
* Mock entries for development (until backend endpoints are ready)
*/