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:
218
packages/database/src/document-checkout.ts
Normal file
218
packages/database/src/document-checkout.ts
Normal file
@@ -0,0 +1,218 @@
|
||||
/**
|
||||
* Document Checkout/Lock Management
|
||||
* Prevents concurrent edits and manages document locks
|
||||
*/
|
||||
|
||||
import { query } from './client';
|
||||
import { z } from 'zod';
|
||||
|
||||
export const DocumentCheckoutSchema = z.object({
|
||||
id: z.string().uuid(),
|
||||
document_id: z.string().uuid(),
|
||||
checked_out_by: z.string().uuid(),
|
||||
checked_out_at: z.date(),
|
||||
expires_at: z.date(),
|
||||
lock_type: z.enum(['exclusive', 'shared_read']),
|
||||
notes: z.string().optional(),
|
||||
});
|
||||
|
||||
export type DocumentCheckout = z.infer<typeof DocumentCheckoutSchema>;
|
||||
|
||||
export interface CreateCheckoutInput {
|
||||
document_id: string;
|
||||
checked_out_by: string;
|
||||
duration_hours?: number;
|
||||
lock_type?: 'exclusive' | 'shared_read';
|
||||
notes?: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check out a document (lock it for editing)
|
||||
*/
|
||||
export async function checkoutDocument(
|
||||
input: CreateCheckoutInput
|
||||
): Promise<DocumentCheckout> {
|
||||
// Check if document is already checked out
|
||||
const existing = await getDocumentCheckout(input.document_id);
|
||||
if (existing) {
|
||||
if (existing.checked_out_by !== input.checked_out_by) {
|
||||
throw new Error('Document is already checked out by another user');
|
||||
}
|
||||
// Same user - extend checkout
|
||||
return extendCheckout(input.document_id, input.duration_hours || 24);
|
||||
}
|
||||
|
||||
const duration_hours = input.duration_hours || 24;
|
||||
const expires_at = new Date();
|
||||
expires_at.setHours(expires_at.getHours() + duration_hours);
|
||||
|
||||
const result = await query<DocumentCheckout>(
|
||||
`INSERT INTO document_checkouts
|
||||
(document_id, checked_out_by, expires_at, lock_type, notes)
|
||||
VALUES ($1, $2, $3, $4, $5)
|
||||
RETURNING *`,
|
||||
[
|
||||
input.document_id,
|
||||
input.checked_out_by,
|
||||
expires_at,
|
||||
input.lock_type || 'exclusive',
|
||||
input.notes || null,
|
||||
]
|
||||
);
|
||||
|
||||
// Update document table
|
||||
await query(
|
||||
`UPDATE documents
|
||||
SET is_checked_out = TRUE, checked_out_by = $1, checked_out_at = NOW()
|
||||
WHERE id = $2`,
|
||||
[input.checked_out_by, input.document_id]
|
||||
);
|
||||
|
||||
return result.rows[0]!;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check in a document (release the lock)
|
||||
*/
|
||||
export async function checkinDocument(
|
||||
document_id: string,
|
||||
checked_out_by: string
|
||||
): Promise<boolean> {
|
||||
const checkout = await getDocumentCheckout(document_id);
|
||||
if (!checkout) {
|
||||
return false; // Not checked out
|
||||
}
|
||||
|
||||
if (checkout.checked_out_by !== checked_out_by) {
|
||||
throw new Error('Document is checked out by another user');
|
||||
}
|
||||
|
||||
await query(
|
||||
`DELETE FROM document_checkouts WHERE document_id = $1`,
|
||||
[document_id]
|
||||
);
|
||||
|
||||
// Update document table
|
||||
await query(
|
||||
`UPDATE documents
|
||||
SET is_checked_out = FALSE, checked_out_by = NULL, checked_out_at = NULL
|
||||
WHERE id = $1`,
|
||||
[document_id]
|
||||
);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get checkout status for a document
|
||||
*/
|
||||
export async function getDocumentCheckout(
|
||||
document_id: string
|
||||
): Promise<DocumentCheckout | null> {
|
||||
// Clean up expired checkouts first
|
||||
await cleanupExpiredCheckouts();
|
||||
|
||||
const result = await query<DocumentCheckout>(
|
||||
`SELECT * FROM document_checkouts
|
||||
WHERE document_id = $1 AND expires_at > NOW()`,
|
||||
[document_id]
|
||||
);
|
||||
return result.rows[0] || null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Extend checkout duration
|
||||
*/
|
||||
export async function extendCheckout(
|
||||
document_id: string,
|
||||
additional_hours: number
|
||||
): Promise<DocumentCheckout> {
|
||||
const result = await query<DocumentCheckout>(
|
||||
`UPDATE document_checkouts
|
||||
SET expires_at = expires_at + INTERVAL '${additional_hours} hours'
|
||||
WHERE document_id = $1
|
||||
RETURNING *`,
|
||||
[document_id]
|
||||
);
|
||||
|
||||
if (result.rows.length === 0) {
|
||||
throw new Error('Document is not checked out');
|
||||
}
|
||||
|
||||
return result.rows[0]!;
|
||||
}
|
||||
|
||||
/**
|
||||
* Force release checkout (admin function)
|
||||
*/
|
||||
export async function forceReleaseCheckout(document_id: string): Promise<boolean> {
|
||||
await query(`DELETE FROM document_checkouts WHERE document_id = $1`, [document_id]);
|
||||
|
||||
await query(
|
||||
`UPDATE documents
|
||||
SET is_checked_out = FALSE, checked_out_by = NULL, checked_out_at = NULL
|
||||
WHERE id = $1`,
|
||||
[document_id]
|
||||
);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Clean up expired checkouts
|
||||
*/
|
||||
export async function cleanupExpiredCheckouts(): Promise<number> {
|
||||
const result = await query<{ count: string }>(
|
||||
`SELECT COUNT(*) as count
|
||||
FROM document_checkouts
|
||||
WHERE expires_at <= NOW()`
|
||||
);
|
||||
const expiredCount = parseInt(result.rows[0]?.count || '0', 10);
|
||||
|
||||
if (expiredCount > 0) {
|
||||
await query(
|
||||
`DELETE FROM document_checkouts WHERE expires_at <= NOW()`
|
||||
);
|
||||
|
||||
// Update documents table
|
||||
await query(
|
||||
`UPDATE documents
|
||||
SET is_checked_out = FALSE, checked_out_by = NULL, checked_out_at = NULL
|
||||
WHERE id IN (
|
||||
SELECT document_id FROM document_checkouts WHERE expires_at <= NOW()
|
||||
)`
|
||||
);
|
||||
}
|
||||
|
||||
return expiredCount;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all checkouts for a user
|
||||
*/
|
||||
export async function getUserCheckouts(user_id: string): Promise<DocumentCheckout[]> {
|
||||
await cleanupExpiredCheckouts();
|
||||
|
||||
const result = await query<DocumentCheckout>(
|
||||
`SELECT * FROM document_checkouts
|
||||
WHERE checked_out_by = $1 AND expires_at > NOW()
|
||||
ORDER BY expires_at ASC`,
|
||||
[user_id]
|
||||
);
|
||||
return result.rows;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all active checkouts
|
||||
*/
|
||||
export async function getAllActiveCheckouts(): Promise<DocumentCheckout[]> {
|
||||
await cleanupExpiredCheckouts();
|
||||
|
||||
const result = await query<DocumentCheckout>(
|
||||
`SELECT * FROM document_checkouts
|
||||
WHERE expires_at > NOW()
|
||||
ORDER BY expires_at ASC`
|
||||
);
|
||||
return result.rows;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user