Files
the_order/packages/database/src/document-checkout.ts
defiQUG 6a8582e54d 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
2025-11-13 09:32:55 -08:00

219 lines
5.3 KiB
TypeScript

/**
* 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;
}