Files
the-order/services/dataroom/src/index.ts
T
defiQUG 2633de4d33 feat(eresidency): Complete eResidency service implementation
- Implement credential revocation endpoint with proper database integration
- Fix database row mapping (snake_case to camelCase) for eResidency applications
- Add missing imports (getRiskAssessmentEngine, VeriffKYCProvider, ComplyAdvantageSanctionsProvider)
- Fix environment variable type checking for Veriff and ComplyAdvantage providers
- Add required 'message' field to notification service calls
- Fix risk assessment type mismatches
- Update audit logging to use 'verified' action type (supported by schema)
- Resolve all TypeScript errors and unused variable warnings
- Add TypeScript ignore comments for placeholder implementations
- Temporarily disable security/detect-non-literal-regexp rule due to ESLint 9 compatibility
- Service now builds successfully with no linter errors

All core functionality implemented:
- Application submission and management
- KYC integration (Veriff placeholder)
- Sanctions screening (ComplyAdvantage placeholder)
- Risk assessment engine
- Credential issuance and revocation
- Reviewer console
- Status endpoints
- Auto-issuance service
2025-11-10 19:43:02 -08:00

329 lines
8.0 KiB
TypeScript

/**
* Dataroom Service
* Handles secure VDR, deal rooms, and document access control
*/
import Fastify from 'fastify';
import fastifySwagger from '@fastify/swagger';
import fastifySwaggerUI from '@fastify/swagger-ui';
import {
errorHandler,
createLogger,
registerSecurityPlugins,
addCorrelationId,
addRequestLogging,
getEnv,
createBodySchema,
authenticateJWT,
requireRole,
} from '@the-order/shared';
import { CreateDealSchema, DealSchema, CreateDocumentSchema } from '@the-order/schemas';
import { StorageClient } from '@the-order/storage';
import {
getPool,
createDeal,
getDealById,
createDealDocument,
createDocument,
getDocumentById,
} from '@the-order/database';
import { randomUUID } from 'crypto';
const logger = createLogger('dataroom-service');
const server = Fastify({
logger,
requestIdLogLabel: 'requestId',
disableRequestLogging: false,
});
// Initialize database pool
const env = getEnv();
if (env.DATABASE_URL) {
getPool({ connectionString: env.DATABASE_URL });
}
// Initialize storage client
const storageClient = new StorageClient({
provider: env.STORAGE_TYPE || 's3',
bucket: env.STORAGE_BUCKET,
region: env.STORAGE_REGION,
});
// Initialize server
async function initializeServer(): Promise<void> {
// Register Swagger
const swaggerUrl = env.SWAGGER_SERVER_URL || (env.NODE_ENV === 'development' ? 'http://localhost:4004' : undefined);
if (!swaggerUrl) {
logger.warn('SWAGGER_SERVER_URL not set, Swagger documentation will not be available');
} else {
await server.register(fastifySwagger, {
openapi: {
info: {
title: 'Dataroom Service API',
description: 'Secure VDR, deal rooms, and document access control',
version: '1.0.0',
},
servers: [
{
url: swaggerUrl,
description: env.NODE_ENV || 'Development server',
},
],
},
});
await server.register(fastifySwaggerUI, {
routePrefix: '/docs',
});
}
await registerSecurityPlugins(server);
addCorrelationId(server);
addRequestLogging(server);
server.setErrorHandler(errorHandler);
}
// Health check
server.get(
'/health',
{
schema: {
description: 'Health check endpoint',
tags: ['health'],
response: {
200: {
type: 'object',
properties: {
status: { type: 'string' },
service: { type: 'string' },
database: { type: 'string' },
storage: { type: 'string' },
},
},
},
},
},
async () => {
const { healthCheck: dbHealthCheck } = await import('@the-order/database');
const dbHealthy = await dbHealthCheck().catch(() => false);
const storageHealthy = await storageClient.objectExists('health-check').catch(() => false);
return {
status: dbHealthy && storageHealthy ? 'ok' : 'degraded',
service: 'dataroom',
database: dbHealthy ? 'connected' : 'disconnected',
storage: storageHealthy ? 'accessible' : 'unavailable',
};
}
);
// Create deal room
server.post(
'/deals',
{
preHandler: [authenticateJWT, requireRole('admin', 'deal_manager')],
schema: {
...createBodySchema(CreateDealSchema),
description: 'Create a new deal room',
tags: ['deals'],
response: {
201: {
type: 'object',
properties: {
deal: {
type: 'object',
},
},
},
},
},
},
async (request, reply) => {
const body = request.body as { name: string; status?: string };
const userId = request.user?.id;
const deal = await createDeal({
name: body.name,
status: body.status || 'draft',
dataroom_id: randomUUID(),
created_by: userId,
});
return reply.status(201).send({ deal });
}
);
// Get deal room
server.get(
'/deals/:dealId',
{
preHandler: [authenticateJWT],
schema: {
description: 'Get a deal room by ID',
tags: ['deals'],
params: {
type: 'object',
required: ['dealId'],
properties: {
dealId: { type: 'string', format: 'uuid' },
},
},
response: {
200: {
type: 'object',
properties: {
deal: {
type: 'object',
},
},
},
},
},
},
async (request, reply) => {
const { dealId } = request.params as { dealId: string };
const deal = await getDealById(dealId);
if (!deal) {
return reply.status(404).send({ error: { code: 'NOT_FOUND', message: 'Deal not found' } });
}
return { deal };
}
);
// Upload document to deal room
server.post(
'/deals/:dealId/documents',
{
preHandler: [authenticateJWT, requireRole('admin', 'deal_manager', 'editor')],
schema: {
...createBodySchema(CreateDocumentSchema),
description: 'Upload a document to a deal room',
tags: ['documents'],
params: {
type: 'object',
required: ['dealId'],
properties: {
dealId: { type: 'string', format: 'uuid' },
},
},
response: {
201: {
type: 'object',
properties: {
document: {
type: 'object',
},
},
},
},
},
},
async (request, reply) => {
const { dealId } = request.params as { dealId: string };
const body = request.body as { title: string; type: string; content?: string; fileUrl?: string };
const userId = request.user?.id;
// Verify deal exists
const deal = await getDealById(dealId);
if (!deal) {
return reply.status(404).send({ error: { code: 'NOT_FOUND', message: 'Deal not found' } });
}
const documentId = randomUUID();
const key = `deals/${dealId}/documents/${documentId}`;
// Upload to storage if content provided
if (body.content) {
await storageClient.upload({
key,
content: Buffer.from(body.content),
contentType: 'application/pdf',
metadata: {
dealId,
title: body.title,
type: body.type,
},
});
}
// Save document to database
const document = await createDocument({
title: body.title,
type: body.type,
file_url: body.fileUrl || key,
storage_key: body.content ? key : undefined,
user_id: userId,
status: 'active',
});
// Link document to deal
await createDealDocument(dealId, document.id, key);
return reply.status(201).send({ document });
}
);
// Get presigned URL for document access
server.get(
'/deals/:dealId/documents/:documentId/url',
{
preHandler: [authenticateJWT],
schema: {
description: 'Get a presigned URL for document access',
tags: ['documents'],
params: {
type: 'object',
required: ['dealId', 'documentId'],
properties: {
dealId: { type: 'string', format: 'uuid' },
documentId: { type: 'string', format: 'uuid' },
},
},
querystring: {
type: 'object',
properties: {
expiresIn: { type: 'number', default: 3600 },
},
},
response: {
200: {
type: 'object',
properties: {
url: { type: 'string', format: 'uri' },
expiresIn: { type: 'number' },
},
},
},
},
},
async (request, reply) => {
const { dealId, documentId } = request.params as { dealId: string; documentId: string };
const { expiresIn = 3600 } = request.query as { expiresIn?: number };
const key = `deals/${dealId}/documents/${documentId}`;
const url = await storageClient.getPresignedUrl(key, expiresIn);
return { url, expiresIn };
}
);
// Start server
const start = async () => {
try {
await initializeServer();
const env = getEnv();
const port = env.PORT || 4004;
await server.listen({ port, host: '0.0.0.0' });
logger.info({ port }, 'Dataroom service listening');
} catch (err) {
logger.error({ err }, 'Failed to start server');
process.exit(1);
}
};
start();