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
This commit is contained in:
32
packages/monitoring/package.json
Normal file
32
packages/monitoring/package.json
Normal file
@@ -0,0 +1,32 @@
|
||||
{
|
||||
"name": "@the-order/monitoring",
|
||||
"version": "0.1.0",
|
||||
"private": true,
|
||||
"description": "Monitoring and observability for The Order",
|
||||
"main": "./src/index.ts",
|
||||
"types": "./src/index.ts",
|
||||
"scripts": {
|
||||
"build": "tsc",
|
||||
"dev": "tsc --watch",
|
||||
"lint": "eslint src --ext .ts",
|
||||
"type-check": "tsc --noEmit"
|
||||
},
|
||||
"dependencies": {
|
||||
"@opentelemetry/api": "^1.8.0",
|
||||
"@opentelemetry/sdk-node": "^0.51.0",
|
||||
"@opentelemetry/instrumentation": "^0.51.0",
|
||||
"@opentelemetry/instrumentation-fastify": "^0.36.0",
|
||||
"@opentelemetry/instrumentation-http": "^0.51.0",
|
||||
"@opentelemetry/exporter-prometheus": "^0.51.0",
|
||||
"@opentelemetry/auto-instrumentations-node": "^0.51.0",
|
||||
"@opentelemetry/exporter-trace-otlp-http": "^0.51.0",
|
||||
"@opentelemetry/resources": "^1.25.0",
|
||||
"@opentelemetry/semantic-conventions": "^1.25.0",
|
||||
"prom-client": "^15.1.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/node": "^20.10.6",
|
||||
"typescript": "^5.3.3"
|
||||
}
|
||||
}
|
||||
|
||||
238
packages/monitoring/src/business-metrics.ts
Normal file
238
packages/monitoring/src/business-metrics.ts
Normal file
@@ -0,0 +1,238 @@
|
||||
/**
|
||||
* Business metrics for The Order
|
||||
* Tracks business KPIs, credential issuance, payments, documents, and more
|
||||
*/
|
||||
|
||||
import { Counter, Histogram, Gauge, register } from 'prom-client';
|
||||
|
||||
// Credential metrics
|
||||
export const credentialIssued = new Counter({
|
||||
name: 'credential_issued_total',
|
||||
help: 'Total number of credentials issued',
|
||||
labelNames: ['credential_type', 'issuer', 'status'],
|
||||
registers: [register],
|
||||
});
|
||||
|
||||
export const credentialIssuanceDuration = new Histogram({
|
||||
name: 'credential_issuance_duration_seconds',
|
||||
help: 'Time to issue a credential',
|
||||
labelNames: ['credential_type'],
|
||||
buckets: [0.1, 0.5, 1, 2, 5, 10],
|
||||
registers: [register],
|
||||
});
|
||||
|
||||
export const credentialVerified = new Counter({
|
||||
name: 'credential_verified_total',
|
||||
help: 'Total number of credentials verified',
|
||||
labelNames: ['credential_type', 'result'],
|
||||
registers: [register],
|
||||
});
|
||||
|
||||
export const credentialRevoked = new Counter({
|
||||
name: 'credential_revoked_total',
|
||||
help: 'Total number of credentials revoked',
|
||||
labelNames: ['credential_type', 'reason'],
|
||||
registers: [register],
|
||||
});
|
||||
|
||||
export const credentialExpired = new Counter({
|
||||
name: 'credential_expired_total',
|
||||
help: 'Total number of credentials expired',
|
||||
labelNames: ['credential_type'],
|
||||
registers: [register],
|
||||
});
|
||||
|
||||
export const credentialsActive = new Gauge({
|
||||
name: 'credentials_active',
|
||||
help: 'Number of active credentials',
|
||||
labelNames: ['credential_type'],
|
||||
registers: [register],
|
||||
});
|
||||
|
||||
// Document metrics
|
||||
export const documentsIngested = new Counter({
|
||||
name: 'documents_ingested_total',
|
||||
help: 'Total number of documents ingested',
|
||||
labelNames: ['type', 'status', 'classification'],
|
||||
registers: [register],
|
||||
});
|
||||
|
||||
export const documentProcessingDuration = new Histogram({
|
||||
name: 'document_processing_duration_seconds',
|
||||
help: 'Time to process a document',
|
||||
labelNames: ['type', 'classification'],
|
||||
buckets: [1, 5, 10, 30, 60, 120, 300],
|
||||
registers: [register],
|
||||
});
|
||||
|
||||
export const documentsProcessed = new Counter({
|
||||
name: 'documents_processed_total',
|
||||
help: 'Total number of documents processed',
|
||||
labelNames: ['status', 'classification'],
|
||||
registers: [register],
|
||||
});
|
||||
|
||||
export const documentsApproved = new Counter({
|
||||
name: 'documents_approved_total',
|
||||
help: 'Total number of documents approved',
|
||||
labelNames: ['type'],
|
||||
registers: [register],
|
||||
});
|
||||
|
||||
// Payment metrics
|
||||
export const paymentsProcessed = new Counter({
|
||||
name: 'payments_processed_total',
|
||||
help: 'Total number of payments processed',
|
||||
labelNames: ['status', 'currency', 'payment_method'],
|
||||
registers: [register],
|
||||
});
|
||||
|
||||
export const paymentAmount = new Histogram({
|
||||
name: 'payment_amount',
|
||||
help: 'Payment amounts',
|
||||
labelNames: ['currency'],
|
||||
buckets: [10, 50, 100, 500, 1000, 5000, 10000, 50000, 100000],
|
||||
registers: [register],
|
||||
});
|
||||
|
||||
export const paymentProcessingDuration = new Histogram({
|
||||
name: 'payment_processing_duration_seconds',
|
||||
help: 'Time to process a payment',
|
||||
labelNames: ['payment_method'],
|
||||
buckets: [0.1, 0.5, 1, 2, 5, 10],
|
||||
registers: [register],
|
||||
});
|
||||
|
||||
export const paymentsFailed = new Counter({
|
||||
name: 'payments_failed_total',
|
||||
help: 'Total number of failed payments',
|
||||
labelNames: ['currency', 'reason'],
|
||||
registers: [register],
|
||||
});
|
||||
|
||||
// Deal metrics
|
||||
export const dealsCreated = new Counter({
|
||||
name: 'deals_created_total',
|
||||
help: 'Total number of deals created',
|
||||
labelNames: ['status'],
|
||||
registers: [register],
|
||||
});
|
||||
|
||||
export const dealsActive = new Gauge({
|
||||
name: 'deals_active',
|
||||
help: 'Number of active deals',
|
||||
registers: [register],
|
||||
});
|
||||
|
||||
export const dealDocumentsUploaded = new Counter({
|
||||
name: 'deal_documents_uploaded_total',
|
||||
help: 'Total number of documents uploaded to deals',
|
||||
labelNames: ['deal_id'],
|
||||
registers: [register],
|
||||
});
|
||||
|
||||
// User metrics
|
||||
export const usersRegistered = new Counter({
|
||||
name: 'users_registered_total',
|
||||
help: 'Total number of users registered',
|
||||
labelNames: ['role'],
|
||||
registers: [register],
|
||||
});
|
||||
|
||||
export const usersActive = new Gauge({
|
||||
name: 'users_active',
|
||||
help: 'Number of active users',
|
||||
labelNames: ['role'],
|
||||
registers: [register],
|
||||
});
|
||||
|
||||
// Compliance metrics
|
||||
export const complianceChecksPerformed = new Counter({
|
||||
name: 'compliance_checks_performed_total',
|
||||
help: 'Total number of compliance checks performed',
|
||||
labelNames: ['check_type', 'result'],
|
||||
registers: [register],
|
||||
});
|
||||
|
||||
export const complianceCheckDuration = new Histogram({
|
||||
name: 'compliance_check_duration_seconds',
|
||||
help: 'Time to perform a compliance check',
|
||||
labelNames: ['check_type'],
|
||||
buckets: [0.1, 0.5, 1, 2, 5, 10],
|
||||
registers: [register],
|
||||
});
|
||||
|
||||
// Event metrics
|
||||
export const eventsPublished = new Counter({
|
||||
name: 'events_published_total',
|
||||
help: 'Total number of events published',
|
||||
labelNames: ['event_type'],
|
||||
registers: [register],
|
||||
});
|
||||
|
||||
export const eventsProcessed = new Counter({
|
||||
name: 'events_processed_total',
|
||||
help: 'Total number of events processed',
|
||||
labelNames: ['event_type', 'status'],
|
||||
registers: [register],
|
||||
});
|
||||
|
||||
// Job queue metrics
|
||||
export const jobsQueued = new Counter({
|
||||
name: 'jobs_queued_total',
|
||||
help: 'Total number of jobs queued',
|
||||
labelNames: ['job_type'],
|
||||
registers: [register],
|
||||
});
|
||||
|
||||
export const jobsProcessed = new Counter({
|
||||
name: 'jobs_processed_total',
|
||||
help: 'Total number of jobs processed',
|
||||
labelNames: ['job_type', 'status'],
|
||||
registers: [register],
|
||||
});
|
||||
|
||||
export const jobProcessingDuration = new Histogram({
|
||||
name: 'job_processing_duration_seconds',
|
||||
help: 'Time to process a job',
|
||||
labelNames: ['job_type'],
|
||||
buckets: [1, 5, 10, 30, 60, 120, 300],
|
||||
registers: [register],
|
||||
});
|
||||
|
||||
export const jobsActive = new Gauge({
|
||||
name: 'jobs_active',
|
||||
help: 'Number of active jobs',
|
||||
labelNames: ['job_type'],
|
||||
registers: [register],
|
||||
});
|
||||
|
||||
// Cache metrics
|
||||
export const cacheHits = new Counter({
|
||||
name: 'cache_hits_total',
|
||||
help: 'Total number of cache hits',
|
||||
labelNames: ['cache_key'],
|
||||
registers: [register],
|
||||
});
|
||||
|
||||
export const cacheMisses = new Counter({
|
||||
name: 'cache_misses_total',
|
||||
help: 'Total number of cache misses',
|
||||
labelNames: ['cache_key'],
|
||||
registers: [register],
|
||||
});
|
||||
|
||||
export const cacheOperations = new Counter({
|
||||
name: 'cache_operations_total',
|
||||
help: 'Total number of cache operations',
|
||||
labelNames: ['operation'],
|
||||
registers: [register],
|
||||
});
|
||||
|
||||
/**
|
||||
* Get all business metrics in Prometheus format
|
||||
*/
|
||||
export async function getBusinessMetrics(): Promise<string> {
|
||||
return register.metrics();
|
||||
}
|
||||
|
||||
8
packages/monitoring/src/index.ts
Normal file
8
packages/monitoring/src/index.ts
Normal file
@@ -0,0 +1,8 @@
|
||||
/**
|
||||
* Monitoring and observability
|
||||
*/
|
||||
|
||||
export * from './otel';
|
||||
export * from './metrics';
|
||||
export * from './business-metrics';
|
||||
|
||||
65
packages/monitoring/src/metrics.ts
Normal file
65
packages/monitoring/src/metrics.ts
Normal file
@@ -0,0 +1,65 @@
|
||||
/**
|
||||
* Prometheus metrics
|
||||
*/
|
||||
|
||||
import { Registry, Counter, Histogram, Gauge } from 'prom-client';
|
||||
|
||||
export const register = new Registry();
|
||||
|
||||
// HTTP request metrics
|
||||
export const httpRequestDuration = new Histogram({
|
||||
name: 'http_request_duration_seconds',
|
||||
help: 'Duration of HTTP requests in seconds',
|
||||
labelNames: ['method', 'route', 'status_code'],
|
||||
registers: [register],
|
||||
});
|
||||
|
||||
export const httpRequestTotal = new Counter({
|
||||
name: 'http_requests_total',
|
||||
help: 'Total number of HTTP requests',
|
||||
labelNames: ['method', 'route', 'status_code'],
|
||||
registers: [register],
|
||||
});
|
||||
|
||||
// Database metrics
|
||||
export const dbQueryDuration = new Histogram({
|
||||
name: 'db_query_duration_seconds',
|
||||
help: 'Duration of database queries in seconds',
|
||||
labelNames: ['query', 'status'],
|
||||
registers: [register],
|
||||
});
|
||||
|
||||
export const dbConnectionsActive = new Gauge({
|
||||
name: 'db_connections_active',
|
||||
help: 'Number of active database connections',
|
||||
registers: [register],
|
||||
});
|
||||
|
||||
// Business metrics
|
||||
export const documentsProcessed = new Counter({
|
||||
name: 'documents_processed_total',
|
||||
help: 'Total number of documents processed',
|
||||
labelNames: ['status', 'classification'],
|
||||
registers: [register],
|
||||
});
|
||||
|
||||
export const paymentsProcessed = new Counter({
|
||||
name: 'payments_processed_total',
|
||||
help: 'Total number of payments processed',
|
||||
labelNames: ['status', 'currency'],
|
||||
registers: [register],
|
||||
});
|
||||
|
||||
export const vcIssued = new Counter({
|
||||
name: 'verifiable_credentials_issued_total',
|
||||
help: 'Total number of verifiable credentials issued',
|
||||
registers: [register],
|
||||
});
|
||||
|
||||
/**
|
||||
* Get metrics in Prometheus format
|
||||
*/
|
||||
export async function getMetrics(): Promise<string> {
|
||||
return register.metrics();
|
||||
}
|
||||
|
||||
59
packages/monitoring/src/otel.ts
Normal file
59
packages/monitoring/src/otel.ts
Normal file
@@ -0,0 +1,59 @@
|
||||
/**
|
||||
* OpenTelemetry setup
|
||||
*/
|
||||
|
||||
import { NodeSDK } from '@opentelemetry/sdk-node';
|
||||
import { getNodeAutoInstrumentations } from '@opentelemetry/auto-instrumentations-node';
|
||||
import { OTLPTraceExporter } from '@opentelemetry/exporter-trace-otlp-http';
|
||||
import { Resource } from '@opentelemetry/resources';
|
||||
import { SEMRESATTRS_SERVICE_NAME, SEMRESATTRS_SERVICE_VERSION } from '@opentelemetry/semantic-conventions';
|
||||
import { getEnv } from '@the-order/shared';
|
||||
|
||||
let sdk: NodeSDK | null = null;
|
||||
|
||||
/**
|
||||
* Initialize OpenTelemetry
|
||||
*/
|
||||
export function initializeOpenTelemetry(serviceName: string): void {
|
||||
if (sdk) {
|
||||
return; // Already initialized
|
||||
}
|
||||
|
||||
const env = getEnv();
|
||||
|
||||
const resource = new Resource({
|
||||
[SEMRESATTRS_SERVICE_NAME]: serviceName,
|
||||
[SEMRESATTRS_SERVICE_VERSION]: '0.1.0',
|
||||
});
|
||||
|
||||
const traceExporter = env.OTEL_EXPORTER_OTLP_ENDPOINT
|
||||
? new OTLPTraceExporter({
|
||||
url: `${env.OTEL_EXPORTER_OTLP_ENDPOINT}/v1/traces`,
|
||||
})
|
||||
: undefined;
|
||||
|
||||
sdk = new NodeSDK({
|
||||
resource,
|
||||
traceExporter,
|
||||
instrumentations: [
|
||||
getNodeAutoInstrumentations({
|
||||
'@opentelemetry/instrumentation-fs': {
|
||||
enabled: false,
|
||||
},
|
||||
}),
|
||||
],
|
||||
});
|
||||
|
||||
sdk.start();
|
||||
}
|
||||
|
||||
/**
|
||||
* Shutdown OpenTelemetry
|
||||
*/
|
||||
export async function shutdownOpenTelemetry(): Promise<void> {
|
||||
if (sdk) {
|
||||
await sdk.shutdown();
|
||||
sdk = null;
|
||||
}
|
||||
}
|
||||
|
||||
14
packages/monitoring/tsconfig.json
Normal file
14
packages/monitoring/tsconfig.json
Normal file
@@ -0,0 +1,14 @@
|
||||
{
|
||||
"extends": "../../tsconfig.base.json",
|
||||
"compilerOptions": {
|
||||
"outDir": "./dist",
|
||||
"rootDir": "./src",
|
||||
"composite": true
|
||||
},
|
||||
"include": ["src/**/*"],
|
||||
"exclude": ["node_modules", "dist", "**/*.test.ts", "**/*.spec.ts"],
|
||||
"references": [
|
||||
{ "path": "../shared" }
|
||||
]
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user