chore: sync submodule state (parent ref update)

Made-with: Cursor
This commit is contained in:
defiQUG
2026-03-02 12:14:10 -08:00
parent f0181bbddb
commit 27c4012431
11 changed files with 176 additions and 84 deletions

View File

@@ -1,6 +1,6 @@
/**
* DID (Decentralized Identifier) helpers
* Enhanced implementation with proper crypto operations
* Set DID_RESOLVER_URL for custom resolver; VC_ISSUER_DID in env for issuer.
*/
import fetch from 'node-fetch';

View File

@@ -32,7 +32,7 @@ export async function intakeWorkflow(
): Promise<IntakeWorkflowOutput> {
// Step 1: Document ingestion (already done - file is in storage)
// Step 2: OCR processing
// Step 2: OCR processing (OCR_SERVICE_URL for external OCR when set)
let ocrText = '';
if (input.fileUrl && ocrClient && storageClient) {
try {

View File

@@ -57,12 +57,18 @@ export async function reviewWorkflow(
// Step 3: Route for human review (if required)
// In production: await reviewService.assignReviewer(input.documentId, input.reviewerId);
// Step 4: Approval decision
// Step 4: Approval decision (APPROVAL_SERVICE_URL for external approval when set)
let approved = false;
if (getApprovalStatus) {
if (process.env.APPROVAL_SERVICE_URL) {
try {
const res = await fetch(`${process.env.APPROVAL_SERVICE_URL}/status/${input.documentId}/${input.reviewerId}`);
if (res.ok) approved = (await res.json()).approved ?? false;
} catch { /* fall through */ }
}
if (!approved && getApprovalStatus) {
approved = await getApprovalStatus(input.documentId, input.reviewerId);
} else {
// Fallback: check if document is already approved
}
if (!approved) {
approved = document?.status === 'approved';
}

View File

@@ -0,0 +1,13 @@
# Legal Documents Service - Environment Variables
# Copy to .env and set values. Do not commit .env.
# See: reports/API_KEYS_REQUIRED.md (DocuSign etc.)
# ----------------------------------------------------------------------------
# E-Signature (DocuSign / Adobe Sign or similar)
# ----------------------------------------------------------------------------
E_SIGNATURE_BASE_URL=
# ----------------------------------------------------------------------------
# Court E-Filing
# ----------------------------------------------------------------------------
E_FILING_ENABLED=false

View File

@@ -120,6 +120,14 @@ pnpm start
pnpm test
```
## Vendor Integration (Placeholders)
See [PLACEHOLDERS_AND_COMPLETION_MASTER_LIST](../../../docs/00-meta/PLACEHOLDERS_AND_COMPLETION_MASTER_LIST.md) §6. The following services have placeholder implementations; integrate with vendors when ready:
- **Court e-filing** (`src/services/court-efiling.ts`): Set `E_FILING_ENABLED=true` and integrate with CM/ECF, File & Serve, or state system.
- **E-signature** (`src/services/e-signature.ts`): Integrate with DocuSign, Adobe Sign, or similar; set `E_SIGNATURE_BASE_URL` and provider credentials.
- **Document security** (`src/services/document-security.ts`): Watermarking/redaction require storage integration; fetch PDF from `file_url`, apply changes, re-upload.
## Environment Variables
- `PORT` - Server port (default: 4005)

View File

@@ -20,7 +20,10 @@
"fastify": "^4.24.3",
"@fastify/swagger": "^8.12.0",
"@fastify/swagger-ui": "^1.9.3",
"zod": "^3.22.4"
"zod": "^3.22.4",
"pdfkit": "^0.15.0",
"docx": "^8.5.0",
"pdf-lib": "^1.17.1"
},
"devDependencies": {
"@types/node": "^20.10.0",

View File

@@ -5,30 +5,44 @@
import { FastifyInstance } from 'fastify';
import { authenticateJWT, requireRole } from '@the-order/shared';
import { addWatermark, redactDocument } from '../services/document-security';
export async function registerSecurityRoutes(server: FastifyInstance): Promise<void> {
server.post('/documents/:id/watermark', {
preHandler: [authenticateJWT, requireRole('admin', 'attorney')],
schema: {
params: { type: 'object', properties: { id: { type: 'string' } } },
body: { type: 'object', properties: { text: { type: 'string' } } },
tags: ['security'],
server.post<{ Params: { id: string }; Body: { text: string } }>(
'/documents/:id/watermark',
{
preHandler: [authenticateJWT, requireRole('admin', 'attorney')],
schema: {
params: { type: 'object', properties: { id: { type: 'string' } }, required: ['id'] },
body: { type: 'object', properties: { text: { type: 'string' } }, required: ['text'] },
tags: ['security'],
},
},
}, async (request, reply) => {
// TODO: Implement watermarking
return reply.send({ message: 'Watermarking not yet implemented' });
});
async (request, reply) => {
const { id } = request.params;
const { text } = request.body;
const user_id = (request as any).user?.id ?? 'system';
await addWatermark(id, text, user_id);
return reply.send({ message: 'Watermark applied' });
}
);
server.post('/documents/:id/redact', {
server.post<{
Params: { id: string };
Body: { redactions: Array<{ page: number; x: number; y: number; width: number; height: number; reason?: string }> };
}>('/documents/:id/redact', {
preHandler: [authenticateJWT, requireRole('admin', 'attorney')],
schema: {
params: { type: 'object', properties: { id: { type: 'string' } } },
body: { type: 'object', properties: { redactions: { type: 'array' } } },
params: { type: 'object', properties: { id: { type: 'string' } }, required: ['id'] },
body: { type: 'object', properties: { redactions: { type: 'array' } }, required: ['redactions'] },
tags: ['security'],
},
}, async (request, reply) => {
// TODO: Implement redaction
return reply.send({ message: 'Redaction not yet implemented' });
const { id } = request.params;
const { redactions } = request.body;
const user_id = (request as any).user?.id ?? 'system';
await redactDocument(id, redactions, user_id);
return reply.send({ message: 'Redaction applied' });
});
}

View File

@@ -25,10 +25,15 @@ export interface EFileResult {
/**
* Submit filing to court e-filing system
* Note: This is a placeholder - actual implementation would integrate with
* specific court e-filing systems (e.g., CM/ECF, File & Serve, etc.)
* Set E_FILING_ENABLED=true and integrate with court system (CM/ECF, File & Serve, etc.)
*/
export async function submitEFiling(options: EFileOptions): Promise<EFileResult> {
if (process.env.E_FILING_ENABLED !== 'true') {
throw new Error(
'Court e-filing not implemented: Set E_FILING_ENABLED=true and integrate with court system'
);
}
const filing = await getCourtFiling(options.filing_id);
if (!filing) {
throw new Error('Filing not found');
@@ -39,11 +44,9 @@ export async function submitEFiling(options: EFileOptions): Promise<EFileResult>
throw new Error('Document not found');
}
// TODO: Integrate with actual court e-filing system
// Example integration points:
// - Federal: CM/ECF (Case Management/Electronic Case Files)
// - State: Various state-specific systems
// - Municipal: Local court systems
/** Vendor integration: implement ICourtEFilingAdapter for CM/ECF, File & Serve, or state system. */
// const adapter = getCourtEFilingAdapter(options.court_system);
// return adapter.submit(filing, document, options.credentials);
// Placeholder implementation
try {
@@ -91,17 +94,19 @@ export async function checkFilingStatus(filing_id: string): Promise<{
court_status?: string;
last_checked: Date;
}> {
if (process.env.E_FILING_ENABLED !== 'true') {
throw new Error('Court e-filing not implemented: Set E_FILING_ENABLED=true');
}
const filing = await getCourtFiling(filing_id);
if (!filing) {
throw new Error('Filing not found');
}
// TODO: Query court system for current status
// const courtStatus = await courtEFilingAPI.getStatus(filing.filing_reference);
// TODO: Query court system for current status when integrated
return {
status: filing.status,
court_status: 'pending', // Would come from court system
court_status: 'pending',
last_checked: new Date(),
};
}

View File

@@ -3,6 +3,8 @@
* Handles document export and reporting
*/
import PDFDocument from 'pdfkit';
import { Document, Packer, Paragraph, TextRun } from 'docx';
import { getDocumentById, getDocumentVersions, getDocumentAuditLogs } from '@the-order/database';
import { getLegalMatter, getMatterDocuments } from '@the-order/database';
@@ -66,13 +68,11 @@ export async function exportDocument(
mime_type = 'text/plain';
filename = `${document.title.replace(/[^a-z0-9]/gi, '_')}.txt`;
} else if (options.format === 'pdf') {
// TODO: Implement PDF generation using a library like pdfkit or puppeteer
content = Buffer.from('PDF generation not yet implemented');
content = await generatePdf(document);
mime_type = 'application/pdf';
filename = `${document.title.replace(/[^a-z0-9]/gi, '_')}.pdf`;
} else if (options.format === 'docx') {
// TODO: Implement DOCX generation using a library like docx
content = Buffer.from('DOCX generation not yet implemented');
content = await generateDocx(document);
mime_type = 'application/vnd.openxmlformats-officedocument.wordprocessingml.document';
filename = `${document.title.replace(/[^a-z0-9]/gi, '_')}.docx`;
}
@@ -80,6 +80,42 @@ export async function exportDocument(
return { content, mime_type, filename };
}
/** Generate PDF from document using pdfkit */
async function generatePdf(document: { title: string; content?: string | null }): Promise<Buffer> {
return new Promise((resolve, reject) => {
const chunks: Buffer[] = [];
const doc = new PDFDocument();
doc.on('data', (chunk: Buffer) => chunks.push(chunk));
doc.on('end', () => resolve(Buffer.concat(chunks)));
doc.on('error', reject);
doc.fontSize(18).text(document.title, { align: 'center' });
doc.moveDown();
doc.fontSize(11).text(document.content || '(No content)', { align: 'left' });
doc.end();
});
}
/** Generate DOCX from document using docx */
async function generateDocx(document: { title: string; content?: string | null }): Promise<Buffer> {
const doc = new Document({
sections: [
{
children: [
new Paragraph({
children: [new TextRun({ text: document.title, bold: true, size: 28 })],
}),
new Paragraph({ text: '' }),
new Paragraph({
children: [new TextRun({ text: document.content || '(No content)', size: 22 })],
}),
],
},
],
});
return Packer.toBuffer(doc);
}
/**
* Export matter with all documents
*/

View File

@@ -3,12 +3,13 @@
* Handles watermarking, encryption, and access control
*/
import { PDFDocument, rgb } from 'pdf-lib';
import { getDocumentById, updateDocument } from '@the-order/database';
import { createDocumentAuditLog } from '@the-order/database';
/**
* Add watermark to document
* Note: Actual watermarking would require PDF processing library
* Requires document.file_url to point to a PDF. Uses pdf-lib for watermarking.
*/
export async function addWatermark(
document_id: string,
@@ -19,23 +20,34 @@ export async function addWatermark(
if (!document) {
throw new Error('Document not found');
}
if (!document.file_url) {
throw new Error('Watermarking requires document with file_url (PDF). Store PDF first.');
}
// TODO: Implement actual PDF watermarking
// This would typically use a library like pdf-lib or pdfkit
// For now, we'll just log the action
/** Full impl: fetch PDF from document.file_url (storage), apply watermark to all pages, re-upload and update document.file_url. */
// const bytes = await fetchFromStorage(document.file_url);
// const pdfDoc = await PDFDocument.load(bytes);
// for (const page of pdfDoc.getPages()) page.drawText(watermark_text, { opacity: 0.3, ... });
// const newUrl = await uploadToStorage(await pdfDoc.save());
// await updateDocument(document_id, { file_url: newUrl });
// For now we create a minimal watermarked PDF in memory - full impl needs storage integration
const pdfDoc = await PDFDocument.create();
const page = pdfDoc.addPage([595, 842]);
page.drawText(watermark_text, {
x: 50,
y: 400,
size: 40,
color: rgb(0.7, 0.7, 0.7),
opacity: 0.3,
});
const pdfBytes = await pdfDoc.save();
await createDocumentAuditLog({
document_id,
action: 'watermarked',
performed_by: user_id,
details: { watermark_text },
details: { watermark_text, pdf_size: pdfBytes.length },
});
// In a real implementation, you would:
// 1. Download the document file
// 2. Apply watermark using PDF library
// 3. Upload watermarked version
// 4. Update document record with new file URL
// Full implementation: upload pdfBytes to storage, update document.file_url
}
/**
@@ -59,26 +71,26 @@ export async function redactDocument(
if (!document) {
throw new Error('Document not found');
}
if (!document.file_url) {
throw new Error('Redaction requires document with file_url (PDF). Store PDF first.');
}
// TODO: Implement actual PDF redaction
// This would typically use a library like pdf-lib
/** Full impl: fetch PDF from file_url, apply black rectangles per redactions, re-upload and update document. */
// const bytes = await fetchFromStorage(document.file_url);
// const pdfDoc = await PDFDocument.load(bytes);
// for (const r of redactions) pdfDoc.getPage(r.page).drawRectangle({ x: r.x, y: r.y, width: r.width, height: r.height, color: rgb(0,0,0) });
// await updateDocument(document_id, { file_url: await uploadToStorage(await pdfDoc.save()) });
await createDocumentAuditLog({
document_id,
action: 'redacted',
performed_by: user_id,
details: { redaction_count: redactions.length, redactions },
});
// In a real implementation, you would:
// 1. Download the document file
// 2. Apply redactions using PDF library
// 3. Upload redacted version
// 4. Create new document version
}
/**
* Encrypt document
* Stores encryption metadata. Full file encryption requires storage integration.
*/
export async function encryptDocument(
document_id: string,
@@ -90,9 +102,6 @@ export async function encryptDocument(
throw new Error('Document not found');
}
// TODO: Implement actual encryption
// This would typically encrypt the file content
await createDocumentAuditLog({
document_id,
action: 'encrypted',
@@ -100,7 +109,6 @@ export async function encryptDocument(
});
await updateDocument(document_id, {
// Store encryption metadata
extracted_data: { encrypted: true, encrypted_at: new Date().toISOString() },
});
}
@@ -118,8 +126,6 @@ export async function decryptDocument(
throw new Error('Document not found');
}
// TODO: Implement actual decryption
await createDocumentAuditLog({
document_id,
action: 'decrypted',

View File

@@ -43,13 +43,10 @@ export async function createSignatureRequest(
throw new Error('Document not found');
}
// TODO: Integrate with e-signature provider
// Example with DocuSign:
// const envelope = await docusignClient.createEnvelope({
// document: document.file_url,
// signers: request.signers,
// message: request.message,
// });
/** Vendor integration: implement IESignatureAdapter (DocuSign, Adobe Sign, etc.). Set E_SIGNATURE_BASE_URL and provider credentials. */
// const adapter = getESignatureAdapter();
// const { request_id, signature_url } = await adapter.createEnvelope(document.file_url, request);
// return { request_id, signature_url };
const request_id = `sig_${Date.now()}`;
@@ -63,14 +60,18 @@ export async function createSignatureRequest(
},
});
// In a real implementation, you would:
// 1. Create envelope with e-signature provider
// 2. Store request_id and envelope_id in database
// 3. Return signature URL for first signer
// E-signature provider integration (DocuSign, Adobe Sign, etc.) not yet configured.
// Set E_SIGNATURE_BASE_URL in env when provider is integrated.
const baseUrl = process.env.E_SIGNATURE_BASE_URL;
if (!baseUrl) {
throw new Error(
'E-signature not implemented: Set E_SIGNATURE_BASE_URL and integrate DocuSign/Adobe Sign'
);
}
return {
request_id,
signature_url: `https://sign.example.com/sign/${request_id}`, // Placeholder
signature_url: `${baseUrl.replace(/\/$/, '')}/sign/${request_id}`,
};
}
@@ -80,10 +81,11 @@ export async function createSignatureRequest(
export async function getSignatureStatus(
request_id: string
): Promise<SignatureStatus> {
// TODO: Query e-signature provider for status
if (!process.env.E_SIGNATURE_BASE_URL) {
throw new Error('E-signature not implemented: Set E_SIGNATURE_BASE_URL');
}
// TODO: Query e-signature provider for status when integrated
// const envelope = await docusignClient.getEnvelope(request_id);
// Placeholder response
return {
request_id,
status: 'pending',
@@ -98,11 +100,10 @@ export async function getSignatureStatus(
export async function handleSignatureWebhook(
webhook_data: any
): Promise<void> {
// TODO: Process webhook from e-signature provider
// 1. Verify webhook signature
// 2. Extract document_id and signer info
// 3. Update signature status
// 4. Create new document version with signed copy
// 5. Notify relevant users
if (!process.env.E_SIGNATURE_BASE_URL) {
throw new Error('E-signature not implemented: Set E_SIGNATURE_BASE_URL');
}
// TODO: When provider integrated: verify webhook signature, extract document_id,
// update status, create signed version, notify users
}