feat: comprehensive project structure improvements and Cloud for Sovereignty landing zone
- Add Cloud for Sovereignty landing zone architecture and deployment - Implement complete legal document management system - Reorganize documentation with improved navigation - Add infrastructure improvements (Dockerfiles, K8s, monitoring) - Add operational improvements (graceful shutdown, rate limiting, caching) - Create comprehensive project structure documentation - Add Azure deployment automation scripts - Improve repository navigation and organization
This commit is contained in:
268
packages/database/src/document-versions.ts
Normal file
268
packages/database/src/document-versions.ts
Normal file
@@ -0,0 +1,268 @@
|
||||
/**
|
||||
* Document Version Management
|
||||
* Handles document versioning, revision history, and version control
|
||||
*/
|
||||
|
||||
import { query } from './client';
|
||||
import { z } from 'zod';
|
||||
|
||||
export const DocumentVersionSchema = z.object({
|
||||
id: z.string().uuid(),
|
||||
document_id: z.string().uuid(),
|
||||
version_number: z.number().int().positive(),
|
||||
version_label: z.string().optional(),
|
||||
content: z.string().optional(),
|
||||
file_url: z.string().url().optional(),
|
||||
file_hash: z.string().optional(),
|
||||
file_size: z.number().int().nonnegative().optional(),
|
||||
mime_type: z.string().optional(),
|
||||
change_summary: z.string().optional(),
|
||||
change_type: z.enum(['created', 'modified', 'restored', 'merged']),
|
||||
created_by: z.string().uuid().optional(),
|
||||
created_at: z.date(),
|
||||
});
|
||||
|
||||
export type DocumentVersion = z.infer<typeof DocumentVersionSchema>;
|
||||
|
||||
export interface CreateDocumentVersionInput {
|
||||
document_id: string;
|
||||
version_number?: number;
|
||||
version_label?: string;
|
||||
content?: string;
|
||||
file_url?: string;
|
||||
file_hash?: string;
|
||||
file_size?: number;
|
||||
mime_type?: string;
|
||||
change_summary?: string;
|
||||
change_type?: 'created' | 'modified' | 'restored' | 'merged';
|
||||
created_by?: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new document version
|
||||
*/
|
||||
export async function createDocumentVersion(
|
||||
input: CreateDocumentVersionInput
|
||||
): Promise<DocumentVersion> {
|
||||
// Get next version number if not provided
|
||||
let version_number = input.version_number;
|
||||
if (!version_number) {
|
||||
const maxVersion = await query<{ max_version: number | null }>(
|
||||
`SELECT MAX(version_number) as max_version
|
||||
FROM document_versions
|
||||
WHERE document_id = $1`,
|
||||
[input.document_id]
|
||||
);
|
||||
version_number = (maxVersion.rows[0]?.max_version || 0) + 1;
|
||||
}
|
||||
|
||||
const result = await query<DocumentVersion>(
|
||||
`INSERT INTO document_versions
|
||||
(document_id, version_number, version_label, content, file_url, file_hash,
|
||||
file_size, mime_type, change_summary, change_type, created_by)
|
||||
VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11)
|
||||
RETURNING *`,
|
||||
[
|
||||
input.document_id,
|
||||
version_number,
|
||||
input.version_label || null,
|
||||
input.content || null,
|
||||
input.file_url || null,
|
||||
input.file_hash || null,
|
||||
input.file_size || null,
|
||||
input.mime_type || null,
|
||||
input.change_summary || null,
|
||||
input.change_type || 'modified',
|
||||
input.created_by || null,
|
||||
]
|
||||
);
|
||||
|
||||
const version = result.rows[0]!;
|
||||
|
||||
// Update document's current version
|
||||
await query(
|
||||
`UPDATE documents
|
||||
SET current_version = $1, latest_version_id = $2, updated_at = NOW()
|
||||
WHERE id = $3`,
|
||||
[version_number, version.id, input.document_id]
|
||||
);
|
||||
|
||||
return version;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get document version by ID
|
||||
*/
|
||||
export async function getDocumentVersion(id: string): Promise<DocumentVersion | null> {
|
||||
const result = await query<DocumentVersion>(
|
||||
`SELECT * FROM document_versions WHERE id = $1`,
|
||||
[id]
|
||||
);
|
||||
return result.rows[0] || null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all versions for a document
|
||||
*/
|
||||
export async function getDocumentVersions(
|
||||
document_id: string,
|
||||
limit = 100,
|
||||
offset = 0
|
||||
): Promise<DocumentVersion[]> {
|
||||
const result = await query<DocumentVersion>(
|
||||
`SELECT * FROM document_versions
|
||||
WHERE document_id = $1
|
||||
ORDER BY version_number DESC
|
||||
LIMIT $2 OFFSET $3`,
|
||||
[document_id, limit, offset]
|
||||
);
|
||||
return result.rows;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get specific version by number
|
||||
*/
|
||||
export async function getDocumentVersionByNumber(
|
||||
document_id: string,
|
||||
version_number: number
|
||||
): Promise<DocumentVersion | null> {
|
||||
const result = await query<DocumentVersion>(
|
||||
`SELECT * FROM document_versions
|
||||
WHERE document_id = $1 AND version_number = $2`,
|
||||
[document_id, version_number]
|
||||
);
|
||||
return result.rows[0] || null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get latest version of a document
|
||||
*/
|
||||
export async function getLatestDocumentVersion(
|
||||
document_id: string
|
||||
): Promise<DocumentVersion | null> {
|
||||
const result = await query<DocumentVersion>(
|
||||
`SELECT * FROM document_versions
|
||||
WHERE document_id = $1
|
||||
ORDER BY version_number DESC
|
||||
LIMIT 1`,
|
||||
[document_id]
|
||||
);
|
||||
return result.rows[0] || null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Compare two document versions
|
||||
*/
|
||||
export interface VersionComparison {
|
||||
version1: DocumentVersion;
|
||||
version2: DocumentVersion;
|
||||
differences: {
|
||||
field: string;
|
||||
old_value: unknown;
|
||||
new_value: unknown;
|
||||
}[];
|
||||
}
|
||||
|
||||
export async function compareDocumentVersions(
|
||||
version1_id: string,
|
||||
version2_id: string
|
||||
): Promise<VersionComparison | null> {
|
||||
const v1 = await getDocumentVersion(version1_id);
|
||||
const v2 = await getDocumentVersion(version2_id);
|
||||
|
||||
if (!v1 || !v2) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const differences: VersionComparison['differences'] = [];
|
||||
|
||||
if (v1.content !== v2.content) {
|
||||
differences.push({
|
||||
field: 'content',
|
||||
old_value: v1.content,
|
||||
new_value: v2.content,
|
||||
});
|
||||
}
|
||||
|
||||
if (v1.file_url !== v2.file_url) {
|
||||
differences.push({
|
||||
field: 'file_url',
|
||||
old_value: v1.file_url,
|
||||
new_value: v2.file_url,
|
||||
});
|
||||
}
|
||||
|
||||
if (v1.file_hash !== v2.file_hash) {
|
||||
differences.push({
|
||||
field: 'file_hash',
|
||||
old_value: v1.file_hash,
|
||||
new_value: v2.file_hash,
|
||||
});
|
||||
}
|
||||
|
||||
if (v1.change_summary !== v2.change_summary) {
|
||||
differences.push({
|
||||
field: 'change_summary',
|
||||
old_value: v1.change_summary,
|
||||
new_value: v2.change_summary,
|
||||
});
|
||||
}
|
||||
|
||||
return {
|
||||
version1: v1,
|
||||
version2: v2,
|
||||
differences,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Restore a document to a previous version
|
||||
*/
|
||||
export async function restoreDocumentVersion(
|
||||
document_id: string,
|
||||
version_id: string,
|
||||
restored_by: string,
|
||||
change_summary?: string
|
||||
): Promise<DocumentVersion> {
|
||||
const targetVersion = await getDocumentVersion(version_id);
|
||||
if (!targetVersion || targetVersion.document_id !== document_id) {
|
||||
throw new Error('Version not found or does not belong to document');
|
||||
}
|
||||
|
||||
// Create new version from the restored one
|
||||
return createDocumentVersion({
|
||||
document_id,
|
||||
version_label: `Restored from v${targetVersion.version_number}`,
|
||||
content: targetVersion.content,
|
||||
file_url: targetVersion.file_url,
|
||||
file_hash: targetVersion.file_hash,
|
||||
file_size: targetVersion.file_size,
|
||||
mime_type: targetVersion.mime_type,
|
||||
change_summary: change_summary || `Restored from version ${targetVersion.version_number}`,
|
||||
change_type: 'restored',
|
||||
created_by: restored_by,
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Get version history with change summaries
|
||||
*/
|
||||
export interface VersionHistoryEntry extends DocumentVersion {
|
||||
created_by_name?: string;
|
||||
created_by_email?: string;
|
||||
}
|
||||
|
||||
export async function getDocumentVersionHistory(
|
||||
document_id: string
|
||||
): Promise<VersionHistoryEntry[]> {
|
||||
const result = await query<VersionHistoryEntry>(
|
||||
`SELECT dv.*, u.name as created_by_name, u.email as created_by_email
|
||||
FROM document_versions dv
|
||||
LEFT JOIN users u ON dv.created_by = u.id
|
||||
WHERE dv.document_id = $1
|
||||
ORDER BY dv.version_number DESC`,
|
||||
[document_id]
|
||||
);
|
||||
return result.rows;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user