update
This commit is contained in:
433
ts/config/base.config.ts
Normal file
433
ts/config/base.config.ts
Normal file
@ -0,0 +1,433 @@
|
||||
/**
|
||||
* Base configuration interface with common properties for all services
|
||||
*/
|
||||
export interface IBaseConfig {
|
||||
/**
|
||||
* Unique identifier for this configuration
|
||||
* Used to track configuration versions and changes
|
||||
*/
|
||||
id?: string;
|
||||
|
||||
/**
|
||||
* Configuration version
|
||||
* Used for migration between different config formats
|
||||
*/
|
||||
version?: string;
|
||||
|
||||
/**
|
||||
* Environment this configuration is intended for
|
||||
* (development, test, production, etc.)
|
||||
*/
|
||||
environment?: 'development' | 'test' | 'staging' | 'production';
|
||||
|
||||
/**
|
||||
* Display name for this configuration
|
||||
*/
|
||||
name?: string;
|
||||
|
||||
/**
|
||||
* Whether this configuration is enabled
|
||||
* Services with disabled configuration shouldn't start
|
||||
*/
|
||||
enabled?: boolean;
|
||||
|
||||
/**
|
||||
* Logging configuration
|
||||
*/
|
||||
logging?: {
|
||||
/**
|
||||
* Minimum log level to output
|
||||
*/
|
||||
level?: 'error' | 'warn' | 'info' | 'debug';
|
||||
|
||||
/**
|
||||
* Whether to include structured data in logs
|
||||
*/
|
||||
structured?: boolean;
|
||||
|
||||
/**
|
||||
* Whether to enable correlation tracking in logs
|
||||
*/
|
||||
correlationTracking?: boolean;
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Base database configuration
|
||||
*/
|
||||
export interface IDatabaseConfig {
|
||||
/**
|
||||
* Database connection string or URL
|
||||
*/
|
||||
connectionString?: string;
|
||||
|
||||
/**
|
||||
* Database host
|
||||
*/
|
||||
host?: string;
|
||||
|
||||
/**
|
||||
* Database port
|
||||
*/
|
||||
port?: number;
|
||||
|
||||
/**
|
||||
* Database name
|
||||
*/
|
||||
database?: string;
|
||||
|
||||
/**
|
||||
* Database username
|
||||
*/
|
||||
username?: string;
|
||||
|
||||
/**
|
||||
* Database password
|
||||
*/
|
||||
password?: string;
|
||||
|
||||
/**
|
||||
* SSL configuration for database connection
|
||||
*/
|
||||
ssl?: boolean | {
|
||||
/**
|
||||
* Whether to reject unauthorized SSL connections
|
||||
*/
|
||||
rejectUnauthorized?: boolean;
|
||||
|
||||
/**
|
||||
* Path to CA certificate file
|
||||
*/
|
||||
ca?: string;
|
||||
|
||||
/**
|
||||
* Path to client certificate file
|
||||
*/
|
||||
cert?: string;
|
||||
|
||||
/**
|
||||
* Path to client key file
|
||||
*/
|
||||
key?: string;
|
||||
};
|
||||
|
||||
/**
|
||||
* Connection pool configuration
|
||||
*/
|
||||
pool?: {
|
||||
/**
|
||||
* Minimum number of connections in pool
|
||||
*/
|
||||
min?: number;
|
||||
|
||||
/**
|
||||
* Maximum number of connections in pool
|
||||
*/
|
||||
max?: number;
|
||||
|
||||
/**
|
||||
* Connection idle timeout in milliseconds
|
||||
*/
|
||||
idleTimeoutMillis?: number;
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Base TLS configuration interface
|
||||
*/
|
||||
export interface ITlsConfig {
|
||||
/**
|
||||
* Whether to enable TLS
|
||||
*/
|
||||
enabled?: boolean;
|
||||
|
||||
/**
|
||||
* The domain name for the certificate
|
||||
*/
|
||||
domain?: string;
|
||||
|
||||
/**
|
||||
* Path to certificate file
|
||||
*/
|
||||
certPath?: string;
|
||||
|
||||
/**
|
||||
* Path to private key file
|
||||
*/
|
||||
keyPath?: string;
|
||||
|
||||
/**
|
||||
* Path to CA certificate file
|
||||
*/
|
||||
caPath?: string;
|
||||
|
||||
/**
|
||||
* Minimum TLS version to support
|
||||
*/
|
||||
minVersion?: 'TLSv1.2' | 'TLSv1.3';
|
||||
|
||||
/**
|
||||
* Whether to auto-renew certificates
|
||||
*/
|
||||
autoRenew?: boolean;
|
||||
|
||||
/**
|
||||
* Whether to reject unauthorized certificates
|
||||
*/
|
||||
rejectUnauthorized?: boolean;
|
||||
}
|
||||
|
||||
/**
|
||||
* Base retry configuration interface
|
||||
*/
|
||||
export interface IRetryConfig {
|
||||
/**
|
||||
* Maximum number of retry attempts
|
||||
*/
|
||||
maxAttempts?: number;
|
||||
|
||||
/**
|
||||
* Base delay between retries in milliseconds
|
||||
*/
|
||||
baseDelay?: number;
|
||||
|
||||
/**
|
||||
* Maximum delay between retries in milliseconds
|
||||
*/
|
||||
maxDelay?: number;
|
||||
|
||||
/**
|
||||
* Backoff factor for exponential backoff
|
||||
*/
|
||||
backoffFactor?: number;
|
||||
|
||||
/**
|
||||
* Specific error codes that should trigger retries
|
||||
*/
|
||||
retryableErrorCodes?: string[];
|
||||
|
||||
/**
|
||||
* Whether to add jitter to retry delays
|
||||
*/
|
||||
useJitter?: boolean;
|
||||
}
|
||||
|
||||
/**
|
||||
* Base rate limiting configuration interface
|
||||
*/
|
||||
export interface IRateLimitConfig {
|
||||
/**
|
||||
* Whether rate limiting is enabled
|
||||
*/
|
||||
enabled?: boolean;
|
||||
|
||||
/**
|
||||
* Maximum number of operations per period
|
||||
*/
|
||||
maxPerPeriod?: number;
|
||||
|
||||
/**
|
||||
* Time period in milliseconds
|
||||
*/
|
||||
periodMs?: number;
|
||||
|
||||
/**
|
||||
* Whether to apply per key (e.g., domain, user, etc.)
|
||||
*/
|
||||
perKey?: boolean;
|
||||
|
||||
/**
|
||||
* Number of burst tokens allowed
|
||||
*/
|
||||
burstTokens?: number;
|
||||
}
|
||||
|
||||
/**
|
||||
* Basic HTTP server configuration
|
||||
*/
|
||||
export interface IHttpServerConfig {
|
||||
/**
|
||||
* Whether the HTTP server is enabled
|
||||
*/
|
||||
enabled?: boolean;
|
||||
|
||||
/**
|
||||
* Host to bind to
|
||||
*/
|
||||
host?: string;
|
||||
|
||||
/**
|
||||
* Port to listen on
|
||||
*/
|
||||
port?: number;
|
||||
|
||||
/**
|
||||
* Path prefix for all routes
|
||||
*/
|
||||
basePath?: string;
|
||||
|
||||
/**
|
||||
* CORS configuration
|
||||
*/
|
||||
cors?: boolean | {
|
||||
/**
|
||||
* Allowed origins
|
||||
*/
|
||||
origins?: string[];
|
||||
|
||||
/**
|
||||
* Allowed methods
|
||||
*/
|
||||
methods?: string[];
|
||||
|
||||
/**
|
||||
* Allowed headers
|
||||
*/
|
||||
headers?: string[];
|
||||
|
||||
/**
|
||||
* Whether to allow credentials
|
||||
*/
|
||||
credentials?: boolean;
|
||||
};
|
||||
|
||||
/**
|
||||
* TLS configuration
|
||||
*/
|
||||
tls?: ITlsConfig;
|
||||
|
||||
/**
|
||||
* Maximum request body size in bytes
|
||||
*/
|
||||
maxBodySize?: number;
|
||||
|
||||
/**
|
||||
* Request timeout in milliseconds
|
||||
*/
|
||||
timeout?: number;
|
||||
}
|
||||
|
||||
/**
|
||||
* Basic queue configuration
|
||||
*/
|
||||
export interface IQueueConfig {
|
||||
/**
|
||||
* Type of storage for the queue
|
||||
*/
|
||||
storageType?: 'memory' | 'disk' | 'redis';
|
||||
|
||||
/**
|
||||
* Path for persistent storage
|
||||
*/
|
||||
persistentPath?: string;
|
||||
|
||||
/**
|
||||
* Redis configuration for queue
|
||||
*/
|
||||
redis?: {
|
||||
/**
|
||||
* Redis host
|
||||
*/
|
||||
host?: string;
|
||||
|
||||
/**
|
||||
* Redis port
|
||||
*/
|
||||
port?: number;
|
||||
|
||||
/**
|
||||
* Redis password
|
||||
*/
|
||||
password?: string;
|
||||
|
||||
/**
|
||||
* Redis database number
|
||||
*/
|
||||
db?: number;
|
||||
};
|
||||
|
||||
/**
|
||||
* Maximum size of the queue
|
||||
*/
|
||||
maxSize?: number;
|
||||
|
||||
/**
|
||||
* Maximum number of retry attempts
|
||||
*/
|
||||
maxRetries?: number;
|
||||
|
||||
/**
|
||||
* Base delay between retries in milliseconds
|
||||
*/
|
||||
baseRetryDelay?: number;
|
||||
|
||||
/**
|
||||
* Maximum delay between retries in milliseconds
|
||||
*/
|
||||
maxRetryDelay?: number;
|
||||
|
||||
/**
|
||||
* Check interval for processing in milliseconds
|
||||
*/
|
||||
checkInterval?: number;
|
||||
|
||||
/**
|
||||
* Maximum number of parallel processes
|
||||
*/
|
||||
maxParallelProcessing?: number;
|
||||
}
|
||||
|
||||
/**
|
||||
* Basic monitoring configuration
|
||||
*/
|
||||
export interface IMonitoringConfig {
|
||||
/**
|
||||
* Whether monitoring is enabled
|
||||
*/
|
||||
enabled?: boolean;
|
||||
|
||||
/**
|
||||
* Metrics collection interval in milliseconds
|
||||
*/
|
||||
metricsInterval?: number;
|
||||
|
||||
/**
|
||||
* Whether to expose Prometheus metrics
|
||||
*/
|
||||
exposePrometheus?: boolean;
|
||||
|
||||
/**
|
||||
* Port for Prometheus metrics
|
||||
*/
|
||||
prometheusPort?: number;
|
||||
|
||||
/**
|
||||
* Whether to collect detailed metrics
|
||||
*/
|
||||
detailedMetrics?: boolean;
|
||||
|
||||
/**
|
||||
* Alert thresholds
|
||||
*/
|
||||
alertThresholds?: Record<string, number>;
|
||||
|
||||
/**
|
||||
* Notification configuration
|
||||
*/
|
||||
notifications?: {
|
||||
/**
|
||||
* Whether to send notifications
|
||||
*/
|
||||
enabled?: boolean;
|
||||
|
||||
/**
|
||||
* Email address to send notifications to
|
||||
*/
|
||||
email?: string;
|
||||
|
||||
/**
|
||||
* Webhook URL to send notifications to
|
||||
*/
|
||||
webhook?: string;
|
||||
};
|
||||
}
|
266
ts/config/email.config.ts
Normal file
266
ts/config/email.config.ts
Normal file
@ -0,0 +1,266 @@
|
||||
import type { IBaseConfig, ITlsConfig, IQueueConfig, IRateLimitConfig, IMonitoringConfig } from './base.config.js';
|
||||
|
||||
/**
|
||||
* Email service configuration
|
||||
*/
|
||||
export interface IEmailConfig extends IBaseConfig {
|
||||
/**
|
||||
* Whether to use MTA for sending emails
|
||||
*/
|
||||
useMta?: boolean;
|
||||
|
||||
/**
|
||||
* MTA configuration
|
||||
*/
|
||||
mtaConfig?: IMtaConfig;
|
||||
|
||||
/**
|
||||
* Template configuration
|
||||
*/
|
||||
templateConfig?: {
|
||||
/**
|
||||
* Default sender email address
|
||||
*/
|
||||
from?: string;
|
||||
|
||||
/**
|
||||
* Default reply-to email address
|
||||
*/
|
||||
replyTo?: string;
|
||||
|
||||
/**
|
||||
* Default footer HTML
|
||||
*/
|
||||
footerHtml?: string;
|
||||
|
||||
/**
|
||||
* Default footer text
|
||||
*/
|
||||
footerText?: string;
|
||||
};
|
||||
|
||||
/**
|
||||
* Whether to load templates from directory
|
||||
*/
|
||||
loadTemplatesFromDir?: boolean;
|
||||
|
||||
/**
|
||||
* Directory path for email templates
|
||||
*/
|
||||
templatesDir?: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* MTA configuration
|
||||
*/
|
||||
export interface IMtaConfig {
|
||||
/**
|
||||
* SMTP server configuration
|
||||
*/
|
||||
smtp?: {
|
||||
/**
|
||||
* Whether to enable the SMTP server
|
||||
*/
|
||||
enabled?: boolean;
|
||||
|
||||
/**
|
||||
* Port to listen on
|
||||
*/
|
||||
port?: number;
|
||||
|
||||
/**
|
||||
* SMTP server hostname
|
||||
*/
|
||||
hostname?: string;
|
||||
|
||||
/**
|
||||
* Maximum allowed email size in bytes
|
||||
*/
|
||||
maxSize?: number;
|
||||
};
|
||||
|
||||
/**
|
||||
* TLS configuration
|
||||
*/
|
||||
tls?: ITlsConfig;
|
||||
|
||||
/**
|
||||
* Outbound email configuration
|
||||
*/
|
||||
outbound?: {
|
||||
/**
|
||||
* Maximum concurrent sending jobs
|
||||
*/
|
||||
concurrency?: number;
|
||||
|
||||
/**
|
||||
* Retry configuration
|
||||
*/
|
||||
retries?: {
|
||||
/**
|
||||
* Maximum number of retries per message
|
||||
*/
|
||||
max?: number;
|
||||
|
||||
/**
|
||||
* Initial delay between retries (milliseconds)
|
||||
*/
|
||||
delay?: number;
|
||||
|
||||
/**
|
||||
* Whether to use exponential backoff for retries
|
||||
*/
|
||||
useBackoff?: boolean;
|
||||
};
|
||||
|
||||
/**
|
||||
* Rate limiting configuration
|
||||
*/
|
||||
rateLimit?: IRateLimitConfig;
|
||||
|
||||
/**
|
||||
* IP warmup configuration
|
||||
*/
|
||||
warmup?: {
|
||||
/**
|
||||
* Whether IP warmup is enabled
|
||||
*/
|
||||
enabled?: boolean;
|
||||
|
||||
/**
|
||||
* IP addresses to warm up
|
||||
*/
|
||||
ipAddresses?: string[];
|
||||
|
||||
/**
|
||||
* Target domains to warm up
|
||||
*/
|
||||
targetDomains?: string[];
|
||||
|
||||
/**
|
||||
* Allocation policy to use
|
||||
*/
|
||||
allocationPolicy?: string;
|
||||
|
||||
/**
|
||||
* Fallback percentage for ESP routing during warmup
|
||||
*/
|
||||
fallbackPercentage?: number;
|
||||
};
|
||||
|
||||
/**
|
||||
* Reputation monitoring configuration
|
||||
*/
|
||||
reputation?: IMonitoringConfig & {
|
||||
/**
|
||||
* Alert thresholds
|
||||
*/
|
||||
alertThresholds?: {
|
||||
/**
|
||||
* Minimum acceptable reputation score
|
||||
*/
|
||||
minReputationScore?: number;
|
||||
|
||||
/**
|
||||
* Maximum acceptable complaint rate
|
||||
*/
|
||||
maxComplaintRate?: number;
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
/**
|
||||
* Security settings
|
||||
*/
|
||||
security?: {
|
||||
/**
|
||||
* Whether to use DKIM signing
|
||||
*/
|
||||
useDkim?: boolean;
|
||||
|
||||
/**
|
||||
* Whether to verify inbound DKIM signatures
|
||||
*/
|
||||
verifyDkim?: boolean;
|
||||
|
||||
/**
|
||||
* Whether to verify SPF on inbound
|
||||
*/
|
||||
verifySpf?: boolean;
|
||||
|
||||
/**
|
||||
* Whether to verify DMARC on inbound
|
||||
*/
|
||||
verifyDmarc?: boolean;
|
||||
|
||||
/**
|
||||
* Whether to enforce DMARC policy
|
||||
*/
|
||||
enforceDmarc?: boolean;
|
||||
|
||||
/**
|
||||
* Whether to use TLS for outbound when available
|
||||
*/
|
||||
useTls?: boolean;
|
||||
|
||||
/**
|
||||
* Whether to require valid certificates
|
||||
*/
|
||||
requireValidCerts?: boolean;
|
||||
|
||||
/**
|
||||
* Log level for email security events
|
||||
*/
|
||||
securityLogLevel?: 'info' | 'warn' | 'error';
|
||||
|
||||
/**
|
||||
* Whether to check IP reputation for inbound emails
|
||||
*/
|
||||
checkIPReputation?: boolean;
|
||||
|
||||
/**
|
||||
* Whether to scan content for malicious payloads
|
||||
*/
|
||||
scanContent?: boolean;
|
||||
|
||||
/**
|
||||
* Action to take when malicious content is detected
|
||||
*/
|
||||
maliciousContentAction?: 'tag' | 'quarantine' | 'reject';
|
||||
|
||||
/**
|
||||
* Minimum threat score to trigger action
|
||||
*/
|
||||
threatScoreThreshold?: number;
|
||||
|
||||
/**
|
||||
* Whether to reject connections from high-risk IPs
|
||||
*/
|
||||
rejectHighRiskIPs?: boolean;
|
||||
};
|
||||
|
||||
/**
|
||||
* Domains configuration
|
||||
*/
|
||||
domains?: {
|
||||
/**
|
||||
* List of domains that this MTA will handle as local
|
||||
*/
|
||||
local?: string[];
|
||||
|
||||
/**
|
||||
* Whether to auto-create DNS records
|
||||
*/
|
||||
autoCreateDnsRecords?: boolean;
|
||||
|
||||
/**
|
||||
* DKIM selector to use
|
||||
*/
|
||||
dkimSelector?: string;
|
||||
};
|
||||
|
||||
/**
|
||||
* Queue configuration
|
||||
*/
|
||||
queue?: IQueueConfig;
|
||||
}
|
100
ts/config/index.ts
Normal file
100
ts/config/index.ts
Normal file
@ -0,0 +1,100 @@
|
||||
// Export configuration interfaces
|
||||
export * from './base.config.js';
|
||||
export * from './email.config.js';
|
||||
export * from './sms.config.js';
|
||||
export * from './platform.config.js';
|
||||
|
||||
// Export validation tools
|
||||
export * from './validator.js';
|
||||
export * from './schemas.js';
|
||||
|
||||
// Re-export commonly used types
|
||||
import type { IPlatformConfig } from './platform.config.js';
|
||||
import type { IEmailConfig, IMtaConfig } from './email.config.js';
|
||||
import type { ISmsConfig } from './sms.config.js';
|
||||
import type {
|
||||
IBaseConfig,
|
||||
ITlsConfig,
|
||||
IHttpServerConfig,
|
||||
IRateLimitConfig,
|
||||
IQueueConfig
|
||||
} from './base.config.js';
|
||||
|
||||
// Default platform configuration
|
||||
export const defaultConfig: IPlatformConfig = {
|
||||
id: 'platform-service-config',
|
||||
version: '1.0.0',
|
||||
environment: 'production',
|
||||
name: 'PlatformService',
|
||||
enabled: true,
|
||||
logging: {
|
||||
level: 'info',
|
||||
structured: true,
|
||||
correlationTracking: true
|
||||
},
|
||||
server: {
|
||||
enabled: true,
|
||||
host: '0.0.0.0',
|
||||
port: 3000,
|
||||
cors: true
|
||||
},
|
||||
email: {
|
||||
useMta: true,
|
||||
mtaConfig: {
|
||||
smtp: {
|
||||
enabled: true,
|
||||
port: 25,
|
||||
hostname: 'mta.lossless.one',
|
||||
maxSize: 10 * 1024 * 1024 // 10MB
|
||||
},
|
||||
tls: {
|
||||
domain: 'mta.lossless.one',
|
||||
autoRenew: true
|
||||
},
|
||||
security: {
|
||||
useDkim: true,
|
||||
verifyDkim: true,
|
||||
verifySpf: true,
|
||||
verifyDmarc: true,
|
||||
enforceDmarc: true,
|
||||
useTls: true,
|
||||
requireValidCerts: false,
|
||||
securityLogLevel: 'warn',
|
||||
checkIPReputation: true,
|
||||
scanContent: true,
|
||||
maliciousContentAction: 'tag',
|
||||
threatScoreThreshold: 50,
|
||||
rejectHighRiskIPs: false
|
||||
},
|
||||
domains: {
|
||||
local: ['lossless.one'],
|
||||
autoCreateDnsRecords: true,
|
||||
dkimSelector: 'mta'
|
||||
}
|
||||
},
|
||||
templateConfig: {
|
||||
from: 'no-reply@lossless.one',
|
||||
replyTo: 'support@lossless.one'
|
||||
},
|
||||
loadTemplatesFromDir: true
|
||||
},
|
||||
paths: {
|
||||
dataDir: 'data',
|
||||
logsDir: 'logs',
|
||||
tempDir: 'temp',
|
||||
emailTemplatesDir: 'templates/email'
|
||||
}
|
||||
};
|
||||
|
||||
// Export main types for convenience
|
||||
export type {
|
||||
IPlatformConfig,
|
||||
IEmailConfig,
|
||||
IMtaConfig,
|
||||
ISmsConfig,
|
||||
IBaseConfig,
|
||||
ITlsConfig,
|
||||
IHttpServerConfig,
|
||||
IRateLimitConfig,
|
||||
IQueueConfig
|
||||
};
|
54
ts/config/platform.config.ts
Normal file
54
ts/config/platform.config.ts
Normal file
@ -0,0 +1,54 @@
|
||||
import type { IBaseConfig, IHttpServerConfig, IDatabaseConfig } from './base.config.js';
|
||||
import type { IEmailConfig } from './email.config.js';
|
||||
import type { ISmsConfig } from './sms.config.js';
|
||||
|
||||
/**
|
||||
* Platform service configuration
|
||||
* Root configuration that includes all service configurations
|
||||
*/
|
||||
export interface IPlatformConfig extends IBaseConfig {
|
||||
/**
|
||||
* HTTP server configuration
|
||||
*/
|
||||
server?: IHttpServerConfig;
|
||||
|
||||
/**
|
||||
* Database configuration
|
||||
*/
|
||||
database?: IDatabaseConfig;
|
||||
|
||||
/**
|
||||
* Email service configuration
|
||||
*/
|
||||
email?: IEmailConfig;
|
||||
|
||||
/**
|
||||
* SMS service configuration
|
||||
*/
|
||||
sms?: ISmsConfig;
|
||||
|
||||
/**
|
||||
* Path configuration
|
||||
*/
|
||||
paths?: {
|
||||
/**
|
||||
* Data directory path
|
||||
*/
|
||||
dataDir?: string;
|
||||
|
||||
/**
|
||||
* Logs directory path
|
||||
*/
|
||||
logsDir?: string;
|
||||
|
||||
/**
|
||||
* Temporary directory path
|
||||
*/
|
||||
tempDir?: string;
|
||||
|
||||
/**
|
||||
* Email templates directory path
|
||||
*/
|
||||
emailTemplatesDir?: string;
|
||||
};
|
||||
}
|
770
ts/config/schemas.ts
Normal file
770
ts/config/schemas.ts
Normal file
@ -0,0 +1,770 @@
|
||||
import type { ValidationSchema } from './validator.js';
|
||||
|
||||
/**
|
||||
* Base TLS configuration schema
|
||||
*/
|
||||
export const tlsConfigSchema: ValidationSchema = {
|
||||
enabled: {
|
||||
type: 'boolean',
|
||||
required: false,
|
||||
default: false
|
||||
},
|
||||
domain: {
|
||||
type: 'string',
|
||||
required: false
|
||||
},
|
||||
certPath: {
|
||||
type: 'string',
|
||||
required: false
|
||||
},
|
||||
keyPath: {
|
||||
type: 'string',
|
||||
required: false
|
||||
},
|
||||
caPath: {
|
||||
type: 'string',
|
||||
required: false
|
||||
},
|
||||
minVersion: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
enum: ['TLSv1.2', 'TLSv1.3'],
|
||||
default: 'TLSv1.2'
|
||||
},
|
||||
autoRenew: {
|
||||
type: 'boolean',
|
||||
required: false,
|
||||
default: false
|
||||
},
|
||||
rejectUnauthorized: {
|
||||
type: 'boolean',
|
||||
required: false,
|
||||
default: true
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* HTTP server configuration schema
|
||||
*/
|
||||
export const httpServerSchema: ValidationSchema = {
|
||||
enabled: {
|
||||
type: 'boolean',
|
||||
required: false,
|
||||
default: true
|
||||
},
|
||||
host: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
default: '0.0.0.0'
|
||||
},
|
||||
port: {
|
||||
type: 'number',
|
||||
required: false,
|
||||
default: 3000,
|
||||
min: 1,
|
||||
max: 65535
|
||||
},
|
||||
basePath: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
default: ''
|
||||
},
|
||||
cors: {
|
||||
type: 'boolean',
|
||||
required: false,
|
||||
default: true
|
||||
},
|
||||
tls: {
|
||||
type: 'object',
|
||||
required: false,
|
||||
schema: tlsConfigSchema
|
||||
},
|
||||
maxBodySize: {
|
||||
type: 'number',
|
||||
required: false,
|
||||
default: 1024 * 1024 // 1MB
|
||||
},
|
||||
timeout: {
|
||||
type: 'number',
|
||||
required: false,
|
||||
default: 30000 // 30 seconds
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Rate limit configuration schema
|
||||
*/
|
||||
export const rateLimitSchema: ValidationSchema = {
|
||||
enabled: {
|
||||
type: 'boolean',
|
||||
required: false,
|
||||
default: true
|
||||
},
|
||||
maxPerPeriod: {
|
||||
type: 'number',
|
||||
required: false,
|
||||
default: 100,
|
||||
min: 1
|
||||
},
|
||||
periodMs: {
|
||||
type: 'number',
|
||||
required: false,
|
||||
default: 60000, // 1 minute
|
||||
min: 1000
|
||||
},
|
||||
perKey: {
|
||||
type: 'boolean',
|
||||
required: false,
|
||||
default: true
|
||||
},
|
||||
burstTokens: {
|
||||
type: 'number',
|
||||
required: false,
|
||||
default: 5,
|
||||
min: 0
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Queue configuration schema
|
||||
*/
|
||||
export const queueSchema: ValidationSchema = {
|
||||
storageType: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
enum: ['memory', 'disk', 'redis'],
|
||||
default: 'memory'
|
||||
},
|
||||
persistentPath: {
|
||||
type: 'string',
|
||||
required: false
|
||||
},
|
||||
redis: {
|
||||
type: 'object',
|
||||
required: false,
|
||||
schema: {
|
||||
host: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
default: 'localhost'
|
||||
},
|
||||
port: {
|
||||
type: 'number',
|
||||
required: false,
|
||||
default: 6379,
|
||||
min: 1,
|
||||
max: 65535
|
||||
},
|
||||
password: {
|
||||
type: 'string',
|
||||
required: false
|
||||
},
|
||||
db: {
|
||||
type: 'number',
|
||||
required: false,
|
||||
default: 0,
|
||||
min: 0
|
||||
}
|
||||
}
|
||||
},
|
||||
maxSize: {
|
||||
type: 'number',
|
||||
required: false,
|
||||
default: 10000,
|
||||
min: 1
|
||||
},
|
||||
maxRetries: {
|
||||
type: 'number',
|
||||
required: false,
|
||||
default: 3,
|
||||
min: 0
|
||||
},
|
||||
baseRetryDelay: {
|
||||
type: 'number',
|
||||
required: false,
|
||||
default: 1000, // 1 second
|
||||
min: 1
|
||||
},
|
||||
maxRetryDelay: {
|
||||
type: 'number',
|
||||
required: false,
|
||||
default: 60000, // 1 minute
|
||||
min: 1
|
||||
},
|
||||
checkInterval: {
|
||||
type: 'number',
|
||||
required: false,
|
||||
default: 1000, // 1 second
|
||||
min: 100
|
||||
},
|
||||
maxParallelProcessing: {
|
||||
type: 'number',
|
||||
required: false,
|
||||
default: 5,
|
||||
min: 1
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* SMS service configuration schema
|
||||
*/
|
||||
export const smsConfigSchema: ValidationSchema = {
|
||||
apiGatewayApiToken: {
|
||||
type: 'string',
|
||||
required: true
|
||||
},
|
||||
defaultSender: {
|
||||
type: 'string',
|
||||
required: false
|
||||
},
|
||||
rateLimit: {
|
||||
type: 'object',
|
||||
required: false,
|
||||
schema: {
|
||||
...rateLimitSchema,
|
||||
maxPerRecipientPerDay: {
|
||||
type: 'number',
|
||||
required: false,
|
||||
default: 10,
|
||||
min: 1
|
||||
}
|
||||
}
|
||||
},
|
||||
provider: {
|
||||
type: 'object',
|
||||
required: false,
|
||||
schema: {
|
||||
type: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
enum: ['gateway', 'twilio', 'other'],
|
||||
default: 'gateway'
|
||||
},
|
||||
config: {
|
||||
type: 'object',
|
||||
required: false
|
||||
},
|
||||
fallback: {
|
||||
type: 'object',
|
||||
required: false,
|
||||
schema: {
|
||||
enabled: {
|
||||
type: 'boolean',
|
||||
required: false,
|
||||
default: false
|
||||
},
|
||||
type: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
enum: ['gateway', 'twilio', 'other']
|
||||
},
|
||||
config: {
|
||||
type: 'object',
|
||||
required: false
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
verification: {
|
||||
type: 'object',
|
||||
required: false,
|
||||
schema: {
|
||||
codeLength: {
|
||||
type: 'number',
|
||||
required: false,
|
||||
default: 6,
|
||||
min: 4,
|
||||
max: 10
|
||||
},
|
||||
expirationSeconds: {
|
||||
type: 'number',
|
||||
required: false,
|
||||
default: 300, // 5 minutes
|
||||
min: 60
|
||||
},
|
||||
maxAttempts: {
|
||||
type: 'number',
|
||||
required: false,
|
||||
default: 3,
|
||||
min: 1
|
||||
},
|
||||
cooldownSeconds: {
|
||||
type: 'number',
|
||||
required: false,
|
||||
default: 60, // 1 minute
|
||||
min: 0
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* MTA configuration schema
|
||||
*/
|
||||
export const mtaConfigSchema: ValidationSchema = {
|
||||
smtp: {
|
||||
type: 'object',
|
||||
required: false,
|
||||
schema: {
|
||||
enabled: {
|
||||
type: 'boolean',
|
||||
required: false,
|
||||
default: true
|
||||
},
|
||||
port: {
|
||||
type: 'number',
|
||||
required: false,
|
||||
default: 25,
|
||||
min: 1,
|
||||
max: 65535
|
||||
},
|
||||
hostname: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
default: 'mta.lossless.one'
|
||||
},
|
||||
maxSize: {
|
||||
type: 'number',
|
||||
required: false,
|
||||
default: 10 * 1024 * 1024, // 10MB
|
||||
min: 1024
|
||||
}
|
||||
}
|
||||
},
|
||||
tls: {
|
||||
type: 'object',
|
||||
required: false,
|
||||
schema: tlsConfigSchema
|
||||
},
|
||||
outbound: {
|
||||
type: 'object',
|
||||
required: false,
|
||||
schema: {
|
||||
concurrency: {
|
||||
type: 'number',
|
||||
required: false,
|
||||
default: 5,
|
||||
min: 1
|
||||
},
|
||||
retries: {
|
||||
type: 'object',
|
||||
required: false,
|
||||
schema: {
|
||||
max: {
|
||||
type: 'number',
|
||||
required: false,
|
||||
default: 3,
|
||||
min: 0
|
||||
},
|
||||
delay: {
|
||||
type: 'number',
|
||||
required: false,
|
||||
default: 300000, // 5 minutes
|
||||
min: 1000
|
||||
},
|
||||
useBackoff: {
|
||||
type: 'boolean',
|
||||
required: false,
|
||||
default: true
|
||||
}
|
||||
}
|
||||
},
|
||||
rateLimit: {
|
||||
type: 'object',
|
||||
required: false,
|
||||
schema: rateLimitSchema
|
||||
},
|
||||
warmup: {
|
||||
type: 'object',
|
||||
required: false,
|
||||
schema: {
|
||||
enabled: {
|
||||
type: 'boolean',
|
||||
required: false,
|
||||
default: false
|
||||
},
|
||||
ipAddresses: {
|
||||
type: 'array',
|
||||
required: false,
|
||||
items: {
|
||||
type: 'string'
|
||||
}
|
||||
},
|
||||
targetDomains: {
|
||||
type: 'array',
|
||||
required: false,
|
||||
items: {
|
||||
type: 'string'
|
||||
}
|
||||
},
|
||||
allocationPolicy: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
default: 'balanced'
|
||||
},
|
||||
fallbackPercentage: {
|
||||
type: 'number',
|
||||
required: false,
|
||||
default: 50,
|
||||
min: 0,
|
||||
max: 100
|
||||
}
|
||||
}
|
||||
},
|
||||
reputation: {
|
||||
type: 'object',
|
||||
required: false,
|
||||
schema: {
|
||||
enabled: {
|
||||
type: 'boolean',
|
||||
required: false,
|
||||
default: false
|
||||
},
|
||||
updateFrequency: {
|
||||
type: 'number',
|
||||
required: false,
|
||||
default: 24 * 60 * 60 * 1000, // 1 day
|
||||
min: 60000
|
||||
},
|
||||
alertThresholds: {
|
||||
type: 'object',
|
||||
required: false,
|
||||
schema: {
|
||||
minReputationScore: {
|
||||
type: 'number',
|
||||
required: false,
|
||||
default: 70,
|
||||
min: 0,
|
||||
max: 100
|
||||
},
|
||||
maxComplaintRate: {
|
||||
type: 'number',
|
||||
required: false,
|
||||
default: 0.1, // 0.1%
|
||||
min: 0,
|
||||
max: 100
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
security: {
|
||||
type: 'object',
|
||||
required: false,
|
||||
schema: {
|
||||
useDkim: {
|
||||
type: 'boolean',
|
||||
required: false,
|
||||
default: true
|
||||
},
|
||||
verifyDkim: {
|
||||
type: 'boolean',
|
||||
required: false,
|
||||
default: true
|
||||
},
|
||||
verifySpf: {
|
||||
type: 'boolean',
|
||||
required: false,
|
||||
default: true
|
||||
},
|
||||
verifyDmarc: {
|
||||
type: 'boolean',
|
||||
required: false,
|
||||
default: true
|
||||
},
|
||||
enforceDmarc: {
|
||||
type: 'boolean',
|
||||
required: false,
|
||||
default: true
|
||||
},
|
||||
useTls: {
|
||||
type: 'boolean',
|
||||
required: false,
|
||||
default: true
|
||||
},
|
||||
requireValidCerts: {
|
||||
type: 'boolean',
|
||||
required: false,
|
||||
default: false
|
||||
},
|
||||
securityLogLevel: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
enum: ['info', 'warn', 'error'],
|
||||
default: 'warn'
|
||||
},
|
||||
checkIPReputation: {
|
||||
type: 'boolean',
|
||||
required: false,
|
||||
default: true
|
||||
},
|
||||
scanContent: {
|
||||
type: 'boolean',
|
||||
required: false,
|
||||
default: true
|
||||
},
|
||||
maliciousContentAction: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
enum: ['tag', 'quarantine', 'reject'],
|
||||
default: 'tag'
|
||||
},
|
||||
threatScoreThreshold: {
|
||||
type: 'number',
|
||||
required: false,
|
||||
default: 50,
|
||||
min: 0,
|
||||
max: 100
|
||||
},
|
||||
rejectHighRiskIPs: {
|
||||
type: 'boolean',
|
||||
required: false,
|
||||
default: false
|
||||
}
|
||||
}
|
||||
},
|
||||
domains: {
|
||||
type: 'object',
|
||||
required: false,
|
||||
schema: {
|
||||
local: {
|
||||
type: 'array',
|
||||
required: false,
|
||||
items: {
|
||||
type: 'string'
|
||||
},
|
||||
default: ['lossless.one']
|
||||
},
|
||||
autoCreateDnsRecords: {
|
||||
type: 'boolean',
|
||||
required: false,
|
||||
default: true
|
||||
},
|
||||
dkimSelector: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
default: 'mta'
|
||||
}
|
||||
}
|
||||
},
|
||||
queue: {
|
||||
type: 'object',
|
||||
required: false,
|
||||
schema: queueSchema
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Email service configuration schema
|
||||
*/
|
||||
export const emailConfigSchema: ValidationSchema = {
|
||||
useMta: {
|
||||
type: 'boolean',
|
||||
required: false,
|
||||
default: true
|
||||
},
|
||||
mtaConfig: {
|
||||
type: 'object',
|
||||
required: false,
|
||||
schema: mtaConfigSchema
|
||||
},
|
||||
templateConfig: {
|
||||
type: 'object',
|
||||
required: false,
|
||||
schema: {
|
||||
from: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
default: 'no-reply@lossless.one'
|
||||
},
|
||||
replyTo: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
default: 'support@lossless.one'
|
||||
},
|
||||
footerHtml: {
|
||||
type: 'string',
|
||||
required: false
|
||||
},
|
||||
footerText: {
|
||||
type: 'string',
|
||||
required: false
|
||||
}
|
||||
}
|
||||
},
|
||||
loadTemplatesFromDir: {
|
||||
type: 'boolean',
|
||||
required: false,
|
||||
default: true
|
||||
},
|
||||
templatesDir: {
|
||||
type: 'string',
|
||||
required: false
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Database configuration schema
|
||||
*/
|
||||
export const databaseConfigSchema: ValidationSchema = {
|
||||
connectionString: {
|
||||
type: 'string',
|
||||
required: false
|
||||
},
|
||||
host: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
default: 'localhost'
|
||||
},
|
||||
port: {
|
||||
type: 'number',
|
||||
required: false,
|
||||
default: 5432,
|
||||
min: 1,
|
||||
max: 65535
|
||||
},
|
||||
database: {
|
||||
type: 'string',
|
||||
required: false
|
||||
},
|
||||
username: {
|
||||
type: 'string',
|
||||
required: false
|
||||
},
|
||||
password: {
|
||||
type: 'string',
|
||||
required: false
|
||||
},
|
||||
ssl: {
|
||||
type: 'boolean',
|
||||
required: false,
|
||||
default: false
|
||||
},
|
||||
pool: {
|
||||
type: 'object',
|
||||
required: false,
|
||||
schema: {
|
||||
min: {
|
||||
type: 'number',
|
||||
required: false,
|
||||
default: 2,
|
||||
min: 1
|
||||
},
|
||||
max: {
|
||||
type: 'number',
|
||||
required: false,
|
||||
default: 10,
|
||||
min: 1
|
||||
},
|
||||
idleTimeoutMillis: {
|
||||
type: 'number',
|
||||
required: false,
|
||||
default: 30000,
|
||||
min: 1000
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Platform service configuration schema
|
||||
*/
|
||||
export const platformConfigSchema: ValidationSchema = {
|
||||
id: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
default: 'platform-service-config'
|
||||
},
|
||||
version: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
default: '1.0.0'
|
||||
},
|
||||
environment: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
enum: ['development', 'test', 'staging', 'production'],
|
||||
default: 'production'
|
||||
},
|
||||
name: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
default: 'PlatformService'
|
||||
},
|
||||
enabled: {
|
||||
type: 'boolean',
|
||||
required: false,
|
||||
default: true
|
||||
},
|
||||
logging: {
|
||||
type: 'object',
|
||||
required: false,
|
||||
schema: {
|
||||
level: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
enum: ['error', 'warn', 'info', 'debug'],
|
||||
default: 'info'
|
||||
},
|
||||
structured: {
|
||||
type: 'boolean',
|
||||
required: false,
|
||||
default: true
|
||||
},
|
||||
correlationTracking: {
|
||||
type: 'boolean',
|
||||
required: false,
|
||||
default: true
|
||||
}
|
||||
}
|
||||
},
|
||||
server: {
|
||||
type: 'object',
|
||||
required: false,
|
||||
schema: httpServerSchema
|
||||
},
|
||||
database: {
|
||||
type: 'object',
|
||||
required: false,
|
||||
schema: databaseConfigSchema
|
||||
},
|
||||
email: {
|
||||
type: 'object',
|
||||
required: false,
|
||||
schema: emailConfigSchema
|
||||
},
|
||||
sms: {
|
||||
type: 'object',
|
||||
required: false,
|
||||
schema: smsConfigSchema
|
||||
},
|
||||
paths: {
|
||||
type: 'object',
|
||||
required: false,
|
||||
schema: {
|
||||
dataDir: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
default: 'data'
|
||||
},
|
||||
logsDir: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
default: 'logs'
|
||||
},
|
||||
tempDir: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
default: 'temp'
|
||||
},
|
||||
emailTemplatesDir: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
default: 'templates/email'
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
86
ts/config/sms.config.ts
Normal file
86
ts/config/sms.config.ts
Normal file
@ -0,0 +1,86 @@
|
||||
import type { IBaseConfig, IRateLimitConfig } from './base.config.js';
|
||||
|
||||
/**
|
||||
* SMS service configuration
|
||||
*/
|
||||
export interface ISmsConfig extends IBaseConfig {
|
||||
/**
|
||||
* API token for the gateway service
|
||||
*/
|
||||
apiGatewayApiToken: string;
|
||||
|
||||
/**
|
||||
* Default sender ID or phone number
|
||||
*/
|
||||
defaultSender?: string;
|
||||
|
||||
/**
|
||||
* SMS rate limiting
|
||||
*/
|
||||
rateLimit?: IRateLimitConfig & {
|
||||
/**
|
||||
* Maximum messages per recipient per day
|
||||
*/
|
||||
maxPerRecipientPerDay?: number;
|
||||
};
|
||||
|
||||
/**
|
||||
* SMS provider configuration
|
||||
*/
|
||||
provider?: {
|
||||
/**
|
||||
* Provider type
|
||||
*/
|
||||
type?: 'gateway' | 'twilio' | 'other';
|
||||
|
||||
/**
|
||||
* Provider-specific configuration
|
||||
*/
|
||||
config?: Record<string, any>;
|
||||
|
||||
/**
|
||||
* Fallback provider configuration
|
||||
*/
|
||||
fallback?: {
|
||||
/**
|
||||
* Whether to use fallback provider
|
||||
*/
|
||||
enabled?: boolean;
|
||||
|
||||
/**
|
||||
* Provider type
|
||||
*/
|
||||
type?: 'gateway' | 'twilio' | 'other';
|
||||
|
||||
/**
|
||||
* Provider-specific configuration
|
||||
*/
|
||||
config?: Record<string, any>;
|
||||
};
|
||||
};
|
||||
|
||||
/**
|
||||
* Verification code settings
|
||||
*/
|
||||
verification?: {
|
||||
/**
|
||||
* Code length
|
||||
*/
|
||||
codeLength?: number;
|
||||
|
||||
/**
|
||||
* Code expiration time in seconds
|
||||
*/
|
||||
expirationSeconds?: number;
|
||||
|
||||
/**
|
||||
* Maximum number of attempts
|
||||
*/
|
||||
maxAttempts?: number;
|
||||
|
||||
/**
|
||||
* Cooldown period in seconds
|
||||
*/
|
||||
cooldownSeconds?: number;
|
||||
};
|
||||
}
|
326
ts/config/validator.ts
Normal file
326
ts/config/validator.ts
Normal file
@ -0,0 +1,326 @@
|
||||
import * as plugins from '../plugins.js';
|
||||
import { ValidationError } from '../errors/base.errors.js';
|
||||
import type { IBaseConfig } from './base.config.js';
|
||||
|
||||
/**
|
||||
* Validation result
|
||||
*/
|
||||
export interface IValidationResult {
|
||||
/**
|
||||
* Whether the validation passed
|
||||
*/
|
||||
valid: boolean;
|
||||
|
||||
/**
|
||||
* Validation errors if any
|
||||
*/
|
||||
errors?: string[];
|
||||
|
||||
/**
|
||||
* Validated configuration (may include defaults)
|
||||
*/
|
||||
config?: any;
|
||||
}
|
||||
|
||||
/**
|
||||
* Validation schema types
|
||||
*/
|
||||
export type ValidationSchema = Record<string, {
|
||||
/**
|
||||
* Type of the value
|
||||
*/
|
||||
type: 'string' | 'number' | 'boolean' | 'object' | 'array';
|
||||
|
||||
/**
|
||||
* Whether the field is required
|
||||
*/
|
||||
required?: boolean;
|
||||
|
||||
/**
|
||||
* Default value if not specified
|
||||
*/
|
||||
default?: any;
|
||||
|
||||
/**
|
||||
* Minimum value (for numbers)
|
||||
*/
|
||||
min?: number;
|
||||
|
||||
/**
|
||||
* Maximum value (for numbers)
|
||||
*/
|
||||
max?: number;
|
||||
|
||||
/**
|
||||
* Minimum length (for strings or arrays)
|
||||
*/
|
||||
minLength?: number;
|
||||
|
||||
/**
|
||||
* Maximum length (for strings or arrays)
|
||||
*/
|
||||
maxLength?: number;
|
||||
|
||||
/**
|
||||
* Pattern to match (for strings)
|
||||
*/
|
||||
pattern?: RegExp;
|
||||
|
||||
/**
|
||||
* Allowed values (for strings, numbers)
|
||||
*/
|
||||
enum?: any[];
|
||||
|
||||
/**
|
||||
* Nested schema (for objects)
|
||||
*/
|
||||
schema?: ValidationSchema;
|
||||
|
||||
/**
|
||||
* Item schema (for arrays)
|
||||
*/
|
||||
items?: {
|
||||
type: 'string' | 'number' | 'boolean' | 'object';
|
||||
schema?: ValidationSchema;
|
||||
};
|
||||
|
||||
/**
|
||||
* Custom validation function
|
||||
*/
|
||||
validate?: (value: any) => boolean | string;
|
||||
}>;
|
||||
|
||||
/**
|
||||
* Configuration validator
|
||||
* Validates configuration objects against schemas and provides default values
|
||||
*/
|
||||
export class ConfigValidator {
|
||||
/**
|
||||
* Basic schema for IBaseConfig
|
||||
*/
|
||||
private static baseConfigSchema: ValidationSchema = {
|
||||
id: {
|
||||
type: 'string',
|
||||
required: false
|
||||
},
|
||||
version: {
|
||||
type: 'string',
|
||||
required: false
|
||||
},
|
||||
environment: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
enum: ['development', 'test', 'staging', 'production'],
|
||||
default: 'production'
|
||||
},
|
||||
name: {
|
||||
type: 'string',
|
||||
required: false
|
||||
},
|
||||
enabled: {
|
||||
type: 'boolean',
|
||||
required: false,
|
||||
default: true
|
||||
},
|
||||
logging: {
|
||||
type: 'object',
|
||||
required: false,
|
||||
schema: {
|
||||
level: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
enum: ['error', 'warn', 'info', 'debug'],
|
||||
default: 'info'
|
||||
},
|
||||
structured: {
|
||||
type: 'boolean',
|
||||
required: false,
|
||||
default: true
|
||||
},
|
||||
correlationTracking: {
|
||||
type: 'boolean',
|
||||
required: false,
|
||||
default: true
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Validate a configuration object against a schema
|
||||
*
|
||||
* @param config Configuration object to validate
|
||||
* @param schema Validation schema
|
||||
* @returns Validation result
|
||||
*/
|
||||
public static validate<T>(config: T, schema: ValidationSchema): IValidationResult {
|
||||
const errors: string[] = [];
|
||||
const validatedConfig = { ...config };
|
||||
|
||||
// Validate each field against the schema
|
||||
for (const [key, rules] of Object.entries(schema)) {
|
||||
const value = config[key];
|
||||
|
||||
// Check if required
|
||||
if (rules.required && (value === undefined || value === null)) {
|
||||
errors.push(`${key} is required`);
|
||||
continue;
|
||||
}
|
||||
|
||||
// If not present and not required, apply default if available
|
||||
if ((value === undefined || value === null)) {
|
||||
if (rules.default !== undefined) {
|
||||
validatedConfig[key] = rules.default;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
// Type validation
|
||||
if (value !== undefined && value !== null) {
|
||||
const valueType = Array.isArray(value) ? 'array' : typeof value;
|
||||
if (valueType !== rules.type) {
|
||||
errors.push(`${key} must be of type ${rules.type}, got ${valueType}`);
|
||||
continue;
|
||||
}
|
||||
|
||||
// Type-specific validations
|
||||
switch (rules.type) {
|
||||
case 'number':
|
||||
if (rules.min !== undefined && value < rules.min) {
|
||||
errors.push(`${key} must be at least ${rules.min}`);
|
||||
}
|
||||
if (rules.max !== undefined && value > rules.max) {
|
||||
errors.push(`${key} must be at most ${rules.max}`);
|
||||
}
|
||||
break;
|
||||
|
||||
case 'string':
|
||||
if (rules.minLength !== undefined && value.length < rules.minLength) {
|
||||
errors.push(`${key} must be at least ${rules.minLength} characters`);
|
||||
}
|
||||
if (rules.maxLength !== undefined && value.length > rules.maxLength) {
|
||||
errors.push(`${key} must be at most ${rules.maxLength} characters`);
|
||||
}
|
||||
if (rules.pattern && !rules.pattern.test(value)) {
|
||||
errors.push(`${key} must match pattern ${rules.pattern}`);
|
||||
}
|
||||
break;
|
||||
|
||||
case 'array':
|
||||
if (rules.minLength !== undefined && value.length < rules.minLength) {
|
||||
errors.push(`${key} must have at least ${rules.minLength} items`);
|
||||
}
|
||||
if (rules.maxLength !== undefined && value.length > rules.maxLength) {
|
||||
errors.push(`${key} must have at most ${rules.maxLength} items`);
|
||||
}
|
||||
if (rules.items && value.length > 0) {
|
||||
for (let i = 0; i < value.length; i++) {
|
||||
const itemType = Array.isArray(value[i]) ? 'array' : typeof value[i];
|
||||
if (itemType !== rules.items.type) {
|
||||
errors.push(`${key}[${i}] must be of type ${rules.items.type}, got ${itemType}`);
|
||||
} else if (rules.items.schema && itemType === 'object') {
|
||||
const itemResult = this.validate(value[i], rules.items.schema);
|
||||
if (!itemResult.valid) {
|
||||
errors.push(...itemResult.errors.map(err => `${key}[${i}].${err}`));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
case 'object':
|
||||
if (rules.schema) {
|
||||
const nestedResult = this.validate(value, rules.schema);
|
||||
if (!nestedResult.valid) {
|
||||
errors.push(...nestedResult.errors.map(err => `${key}.${err}`));
|
||||
}
|
||||
validatedConfig[key] = nestedResult.config;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
// Enum validation
|
||||
if (rules.enum && !rules.enum.includes(value)) {
|
||||
errors.push(`${key} must be one of [${rules.enum.join(', ')}]`);
|
||||
}
|
||||
|
||||
// Custom validation
|
||||
if (rules.validate) {
|
||||
const result = rules.validate(value);
|
||||
if (result !== true) {
|
||||
errors.push(typeof result === 'string' ? result : `${key} failed custom validation`);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
valid: errors.length === 0,
|
||||
errors: errors.length > 0 ? errors : undefined,
|
||||
config: validatedConfig
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Validate base configuration
|
||||
*
|
||||
* @param config Base configuration
|
||||
* @returns Validation result for base configuration
|
||||
*/
|
||||
public static validateBaseConfig(config: IBaseConfig): IValidationResult {
|
||||
return this.validate(config, this.baseConfigSchema);
|
||||
}
|
||||
|
||||
/**
|
||||
* Apply defaults to a configuration object based on a schema
|
||||
*
|
||||
* @param config Configuration object to apply defaults to
|
||||
* @param schema Validation schema with defaults
|
||||
* @returns Configuration with defaults applied
|
||||
*/
|
||||
public static applyDefaults<T>(config: T, schema: ValidationSchema): T {
|
||||
const result = { ...config };
|
||||
|
||||
for (const [key, rules] of Object.entries(schema)) {
|
||||
if (result[key] === undefined && rules.default !== undefined) {
|
||||
result[key] = rules.default;
|
||||
}
|
||||
|
||||
// Apply defaults to nested objects
|
||||
if (result[key] && rules.type === 'object' && rules.schema) {
|
||||
result[key] = this.applyDefaults(result[key], rules.schema);
|
||||
}
|
||||
|
||||
// Apply defaults to array items
|
||||
if (result[key] && rules.type === 'array' && rules.items && rules.items.schema) {
|
||||
result[key] = result[key].map(item =>
|
||||
typeof item === 'object' ? this.applyDefaults(item, rules.items.schema) : item
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Throw a validation error if the configuration is invalid
|
||||
*
|
||||
* @param config Configuration to validate
|
||||
* @param schema Validation schema
|
||||
* @returns Validated configuration with defaults
|
||||
* @throws ValidationError if validation fails
|
||||
*/
|
||||
public static validateOrThrow<T>(config: T, schema: ValidationSchema): T {
|
||||
const result = this.validate(config, schema);
|
||||
|
||||
if (!result.valid) {
|
||||
throw new ValidationError(
|
||||
`Configuration validation failed: ${result.errors.join(', ')}`,
|
||||
'CONFIG_VALIDATION_ERROR',
|
||||
{ data: { errors: result.errors } }
|
||||
);
|
||||
}
|
||||
|
||||
return result.config;
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user