846 lines
21 KiB
TypeScript
846 lines
21 KiB
TypeScript
import * as plugins from '../plugins.js';
|
|
import { Email, IEmailOptions } from './mta.classes.email.js';
|
|
import { DeliveryStatus } from './mta.classes.emailsendjob.js';
|
|
import type { MtaService } from './mta.classes.mta.js';
|
|
import type { IDnsRecord } from './mta.classes.dnsmanager.js';
|
|
|
|
/**
|
|
* Authentication options for API requests
|
|
*/
|
|
interface AuthOptions {
|
|
/** Required API keys for different endpoints */
|
|
apiKeys: Map<string, string[]>;
|
|
/** JWT secret for token-based authentication */
|
|
jwtSecret?: string;
|
|
/** Whether to validate IP addresses */
|
|
validateIp?: boolean;
|
|
/** Allowed IP addresses */
|
|
allowedIps?: string[];
|
|
}
|
|
|
|
/**
|
|
* Rate limiting options for API endpoints
|
|
*/
|
|
interface RateLimitOptions {
|
|
/** Maximum requests per window */
|
|
maxRequests: number;
|
|
/** Time window in milliseconds */
|
|
windowMs: number;
|
|
/** Whether to apply per endpoint */
|
|
perEndpoint?: boolean;
|
|
/** Whether to apply per IP */
|
|
perIp?: boolean;
|
|
}
|
|
|
|
/**
|
|
* API route definition
|
|
*/
|
|
interface ApiRoute {
|
|
/** HTTP method */
|
|
method: 'GET' | 'POST' | 'PUT' | 'DELETE';
|
|
/** Path pattern */
|
|
path: string;
|
|
/** Handler function */
|
|
handler: (req: any, res: any) => Promise<any>;
|
|
/** Required authentication level */
|
|
authLevel: 'none' | 'basic' | 'admin';
|
|
/** Rate limiting options */
|
|
rateLimit?: RateLimitOptions;
|
|
/** Route description */
|
|
description?: string;
|
|
}
|
|
|
|
/**
|
|
* Email send request
|
|
*/
|
|
interface SendEmailRequest {
|
|
/** Email details */
|
|
email: IEmailOptions;
|
|
/** Whether to validate domains before sending */
|
|
validateDomains?: boolean;
|
|
/** Priority level (1-5, 1 = highest) */
|
|
priority?: number;
|
|
}
|
|
|
|
/**
|
|
* Email status response
|
|
*/
|
|
interface EmailStatusResponse {
|
|
/** Email ID */
|
|
id: string;
|
|
/** Current status */
|
|
status: DeliveryStatus;
|
|
/** Send time */
|
|
sentAt?: Date;
|
|
/** Delivery time */
|
|
deliveredAt?: Date;
|
|
/** Error message if failed */
|
|
error?: string;
|
|
/** Recipient address */
|
|
recipient: string;
|
|
/** Number of delivery attempts */
|
|
attempts: number;
|
|
/** Next retry time */
|
|
nextRetry?: Date;
|
|
}
|
|
|
|
/**
|
|
* Domain verification response
|
|
*/
|
|
interface DomainVerificationResponse {
|
|
/** Domain name */
|
|
domain: string;
|
|
/** Whether the domain is verified */
|
|
verified: boolean;
|
|
/** Verification details */
|
|
details: {
|
|
/** SPF record status */
|
|
spf: {
|
|
valid: boolean;
|
|
record?: string;
|
|
error?: string;
|
|
};
|
|
/** DKIM record status */
|
|
dkim: {
|
|
valid: boolean;
|
|
record?: string;
|
|
error?: string;
|
|
};
|
|
/** DMARC record status */
|
|
dmarc: {
|
|
valid: boolean;
|
|
record?: string;
|
|
error?: string;
|
|
};
|
|
/** MX record status */
|
|
mx: {
|
|
valid: boolean;
|
|
records?: string[];
|
|
error?: string;
|
|
};
|
|
};
|
|
}
|
|
|
|
/**
|
|
* API error response
|
|
*/
|
|
interface ApiError {
|
|
/** Error code */
|
|
code: string;
|
|
/** Error message */
|
|
message: string;
|
|
/** Detailed error information */
|
|
details?: any;
|
|
}
|
|
|
|
/**
|
|
* API Manager for MTA service
|
|
*/
|
|
export class ApiManager {
|
|
/** TypedRouter for API routing */
|
|
public typedrouter = new plugins.typedrequest.TypedRouter();
|
|
/** MTA service reference */
|
|
private mtaRef: MtaService;
|
|
/** Express app */
|
|
private app: any;
|
|
/** Authentication options */
|
|
private authOptions: AuthOptions;
|
|
/** API routes */
|
|
private routes: ApiRoute[] = [];
|
|
/** Rate limiters */
|
|
private rateLimiters: Map<string, {
|
|
count: number;
|
|
resetTime: number;
|
|
clients: Map<string, {
|
|
count: number;
|
|
resetTime: number;
|
|
}>;
|
|
}> = new Map();
|
|
|
|
/**
|
|
* Initialize API Manager
|
|
* @param mtaRef MTA service reference
|
|
*/
|
|
constructor(mtaRef?: MtaService) {
|
|
this.mtaRef = mtaRef;
|
|
|
|
// Initialize Express app
|
|
this.app = plugins.express();
|
|
|
|
// Default authentication options
|
|
this.authOptions = {
|
|
apiKeys: new Map(),
|
|
validateIp: false,
|
|
allowedIps: []
|
|
};
|
|
|
|
// Configure middleware
|
|
this.configureMiddleware();
|
|
|
|
// Register routes
|
|
this.registerRoutes();
|
|
}
|
|
|
|
/**
|
|
* Set MTA service reference
|
|
* @param mtaRef MTA service reference
|
|
*/
|
|
public setMtaService(mtaRef: MtaService): void {
|
|
this.mtaRef = mtaRef;
|
|
}
|
|
|
|
/**
|
|
* Configure authentication options
|
|
* @param options Authentication options
|
|
*/
|
|
public configureAuth(options: Partial<AuthOptions>): void {
|
|
this.authOptions = {
|
|
...this.authOptions,
|
|
...options
|
|
};
|
|
}
|
|
|
|
/**
|
|
* Configure Express middleware
|
|
*/
|
|
private configureMiddleware(): void {
|
|
// JSON body parser
|
|
this.app.use(plugins.express.json({ limit: '10mb' }));
|
|
|
|
// CORS middleware
|
|
this.app.use((req: any, res: any, next: any) => {
|
|
res.header('Access-Control-Allow-Origin', '*');
|
|
res.header('Access-Control-Allow-Methods', 'GET, POST, PUT, DELETE, OPTIONS');
|
|
res.header('Access-Control-Allow-Headers', 'Origin, X-Requested-With, Content-Type, Accept, Authorization, X-API-Key');
|
|
|
|
if (req.method === 'OPTIONS') {
|
|
return res.status(200).end();
|
|
}
|
|
|
|
next();
|
|
});
|
|
|
|
// Request logging
|
|
this.app.use((req: any, res: any, next: any) => {
|
|
const start = Date.now();
|
|
|
|
res.on('finish', () => {
|
|
const duration = Date.now() - start;
|
|
console.log(`[API] ${req.method} ${req.path} ${res.statusCode} ${duration}ms`);
|
|
});
|
|
|
|
next();
|
|
});
|
|
|
|
// Authentication middleware
|
|
this.app.use((req: any, res: any, next: any) => {
|
|
// Store authentication level in request
|
|
req.authLevel = 'none';
|
|
|
|
// Check API key
|
|
const apiKey = req.headers['x-api-key'];
|
|
if (apiKey) {
|
|
for (const [level, keys] of this.authOptions.apiKeys.entries()) {
|
|
if (keys.includes(apiKey)) {
|
|
req.authLevel = level;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
// Check JWT token (if configured)
|
|
if (this.authOptions.jwtSecret && req.headers.authorization) {
|
|
try {
|
|
const token = req.headers.authorization.split(' ')[1];
|
|
const decoded = plugins.jwt.verify(token, this.authOptions.jwtSecret);
|
|
|
|
if (decoded && decoded.level) {
|
|
req.authLevel = decoded.level;
|
|
req.user = decoded;
|
|
}
|
|
} catch (error) {
|
|
// Invalid token, but don't fail the request yet
|
|
console.error('Invalid JWT token:', error.message);
|
|
}
|
|
}
|
|
|
|
// Check IP address (if configured)
|
|
if (this.authOptions.validateIp) {
|
|
const clientIp = req.ip || req.connection.remoteAddress;
|
|
if (!this.authOptions.allowedIps.includes(clientIp)) {
|
|
return res.status(403).json({
|
|
code: 'FORBIDDEN',
|
|
message: 'IP address not allowed'
|
|
});
|
|
}
|
|
}
|
|
|
|
next();
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Register API routes
|
|
*/
|
|
private registerRoutes(): void {
|
|
// Email routes
|
|
this.addRoute({
|
|
method: 'POST',
|
|
path: '/api/email/send',
|
|
handler: this.handleSendEmail.bind(this),
|
|
authLevel: 'basic',
|
|
description: 'Send an email'
|
|
});
|
|
|
|
this.addRoute({
|
|
method: 'GET',
|
|
path: '/api/email/status/:id',
|
|
handler: this.handleGetEmailStatus.bind(this),
|
|
authLevel: 'basic',
|
|
description: 'Get email delivery status'
|
|
});
|
|
|
|
// Domain routes
|
|
this.addRoute({
|
|
method: 'GET',
|
|
path: '/api/domain/verify/:domain',
|
|
handler: this.handleVerifyDomain.bind(this),
|
|
authLevel: 'basic',
|
|
description: 'Verify domain DNS records'
|
|
});
|
|
|
|
this.addRoute({
|
|
method: 'GET',
|
|
path: '/api/domain/records/:domain',
|
|
handler: this.handleGetDomainRecords.bind(this),
|
|
authLevel: 'basic',
|
|
description: 'Get recommended DNS records for domain'
|
|
});
|
|
|
|
// DKIM routes
|
|
this.addRoute({
|
|
method: 'POST',
|
|
path: '/api/dkim/generate/:domain',
|
|
handler: this.handleGenerateDkim.bind(this),
|
|
authLevel: 'admin',
|
|
description: 'Generate DKIM keys for domain'
|
|
});
|
|
|
|
this.addRoute({
|
|
method: 'GET',
|
|
path: '/api/dkim/public/:domain',
|
|
handler: this.handleGetDkimPublicKey.bind(this),
|
|
authLevel: 'basic',
|
|
description: 'Get DKIM public key for domain'
|
|
});
|
|
|
|
// Stats route
|
|
this.addRoute({
|
|
method: 'GET',
|
|
path: '/api/stats',
|
|
handler: this.handleGetStats.bind(this),
|
|
authLevel: 'admin',
|
|
description: 'Get MTA statistics'
|
|
});
|
|
|
|
// Documentation route
|
|
this.addRoute({
|
|
method: 'GET',
|
|
path: '/api',
|
|
handler: this.handleGetApiDocs.bind(this),
|
|
authLevel: 'none',
|
|
description: 'API documentation'
|
|
});
|
|
|
|
// Map routes to Express
|
|
this.mapRoutesToExpress();
|
|
}
|
|
|
|
/**
|
|
* Add an API route
|
|
* @param route Route definition
|
|
*/
|
|
private addRoute(route: ApiRoute): void {
|
|
this.routes.push(route);
|
|
}
|
|
|
|
/**
|
|
* Map defined routes to Express
|
|
*/
|
|
private mapRoutesToExpress(): void {
|
|
for (const route of this.routes) {
|
|
const { method, path, handler, authLevel } = route;
|
|
|
|
// Add Express route
|
|
this.app[method.toLowerCase()](path, async (req: any, res: any) => {
|
|
try {
|
|
// Check authentication
|
|
if (authLevel !== 'none' && req.authLevel !== authLevel && req.authLevel !== 'admin') {
|
|
return res.status(403).json({
|
|
code: 'FORBIDDEN',
|
|
message: `This endpoint requires ${authLevel} access`
|
|
});
|
|
}
|
|
|
|
// Check rate limit
|
|
if (route.rateLimit) {
|
|
const exceeded = this.checkRateLimit(route, req);
|
|
if (exceeded) {
|
|
return res.status(429).json({
|
|
code: 'RATE_LIMIT_EXCEEDED',
|
|
message: 'Rate limit exceeded, please try again later'
|
|
});
|
|
}
|
|
}
|
|
|
|
// Handle the request
|
|
await handler(req, res);
|
|
} catch (error) {
|
|
console.error(`Error handling ${method} ${path}:`, error);
|
|
|
|
// Send appropriate error response
|
|
const status = error.status || 500;
|
|
const apiError: ApiError = {
|
|
code: error.code || 'INTERNAL_ERROR',
|
|
message: error.message || 'Internal server error'
|
|
};
|
|
|
|
if (process.env.NODE_ENV !== 'production') {
|
|
apiError.details = error.stack;
|
|
}
|
|
|
|
res.status(status).json(apiError);
|
|
}
|
|
});
|
|
}
|
|
|
|
// Add 404 handler
|
|
this.app.use((req: any, res: any) => {
|
|
res.status(404).json({
|
|
code: 'NOT_FOUND',
|
|
message: 'Endpoint not found'
|
|
});
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Check rate limit for a route
|
|
* @param route Route definition
|
|
* @param req Express request
|
|
* @returns Whether rate limit is exceeded
|
|
*/
|
|
private checkRateLimit(route: ApiRoute, req: any): boolean {
|
|
if (!route.rateLimit) return false;
|
|
|
|
const { maxRequests, windowMs, perEndpoint, perIp } = route.rateLimit;
|
|
|
|
// Determine rate limit key
|
|
let key = 'global';
|
|
if (perEndpoint) {
|
|
key = `${route.method}:${route.path}`;
|
|
}
|
|
|
|
// Get or create limiter
|
|
if (!this.rateLimiters.has(key)) {
|
|
this.rateLimiters.set(key, {
|
|
count: 0,
|
|
resetTime: Date.now() + windowMs,
|
|
clients: new Map()
|
|
});
|
|
}
|
|
|
|
const limiter = this.rateLimiters.get(key);
|
|
|
|
// Reset if window has passed
|
|
if (Date.now() > limiter.resetTime) {
|
|
limiter.count = 0;
|
|
limiter.resetTime = Date.now() + windowMs;
|
|
limiter.clients.clear();
|
|
}
|
|
|
|
// Check per-IP limit if enabled
|
|
if (perIp) {
|
|
const clientIp = req.ip || req.connection.remoteAddress;
|
|
let clientLimiter = limiter.clients.get(clientIp);
|
|
|
|
if (!clientLimiter) {
|
|
clientLimiter = {
|
|
count: 0,
|
|
resetTime: Date.now() + windowMs
|
|
};
|
|
limiter.clients.set(clientIp, clientLimiter);
|
|
}
|
|
|
|
// Reset client limiter if needed
|
|
if (Date.now() > clientLimiter.resetTime) {
|
|
clientLimiter.count = 0;
|
|
clientLimiter.resetTime = Date.now() + windowMs;
|
|
}
|
|
|
|
// Check client limit
|
|
if (clientLimiter.count >= maxRequests) {
|
|
return true;
|
|
}
|
|
|
|
// Increment client count
|
|
clientLimiter.count++;
|
|
} else {
|
|
// Check global limit
|
|
if (limiter.count >= maxRequests) {
|
|
return true;
|
|
}
|
|
|
|
// Increment global count
|
|
limiter.count++;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
/**
|
|
* Create an API error
|
|
* @param code Error code
|
|
* @param message Error message
|
|
* @param status HTTP status code
|
|
* @param details Additional details
|
|
* @returns API error
|
|
*/
|
|
private createError(code: string, message: string, status = 400, details?: any): Error & { code: string; status: number; details?: any } {
|
|
const error = new Error(message) as Error & { code: string; status: number; details?: any };
|
|
error.code = code;
|
|
error.status = status;
|
|
if (details) {
|
|
error.details = details;
|
|
}
|
|
return error;
|
|
}
|
|
|
|
/**
|
|
* Validate that MTA service is available
|
|
*/
|
|
private validateMtaService(): void {
|
|
if (!this.mtaRef) {
|
|
throw this.createError('SERVICE_UNAVAILABLE', 'MTA service is not available', 503);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Handle email send request
|
|
* @param req Express request
|
|
* @param res Express response
|
|
*/
|
|
private async handleSendEmail(req: any, res: any): Promise<void> {
|
|
this.validateMtaService();
|
|
|
|
const data = req.body as SendEmailRequest;
|
|
|
|
if (!data || !data.email) {
|
|
throw this.createError('INVALID_REQUEST', 'Missing email data');
|
|
}
|
|
|
|
try {
|
|
// Create Email instance
|
|
const email = new Email(data.email);
|
|
|
|
// Validate domains if requested
|
|
if (data.validateDomains) {
|
|
const fromDomain = email.getFromDomain();
|
|
if (fromDomain) {
|
|
const verification = await this.mtaRef.dnsManager.verifyEmailAuthRecords(fromDomain);
|
|
|
|
// Check if SPF and DKIM are valid
|
|
if (!verification.spf.valid || !verification.dkim.valid) {
|
|
throw this.createError('DOMAIN_VERIFICATION_FAILED', 'Domain DNS verification failed', 400, {
|
|
verification
|
|
});
|
|
}
|
|
}
|
|
}
|
|
|
|
// Send email
|
|
const id = await this.mtaRef.send(email);
|
|
|
|
// Return success response
|
|
res.json({
|
|
id,
|
|
message: 'Email queued successfully',
|
|
status: 'pending'
|
|
});
|
|
} catch (error) {
|
|
// Handle Email constructor errors
|
|
if (error.message.includes('Invalid') || error.message.includes('must have')) {
|
|
throw this.createError('INVALID_EMAIL', error.message);
|
|
}
|
|
|
|
throw error;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Handle email status request
|
|
* @param req Express request
|
|
* @param res Express response
|
|
*/
|
|
private async handleGetEmailStatus(req: any, res: any): Promise<void> {
|
|
this.validateMtaService();
|
|
|
|
const id = req.params.id;
|
|
|
|
if (!id) {
|
|
throw this.createError('INVALID_REQUEST', 'Missing email ID');
|
|
}
|
|
|
|
// Get email status
|
|
const status = this.mtaRef.getEmailStatus(id);
|
|
|
|
if (!status) {
|
|
throw this.createError('NOT_FOUND', `Email with ID ${id} not found`, 404);
|
|
}
|
|
|
|
// Create response
|
|
const response: EmailStatusResponse = {
|
|
id: status.id,
|
|
status: status.status,
|
|
sentAt: status.addedAt,
|
|
recipient: status.email.to[0],
|
|
attempts: status.attempts
|
|
};
|
|
|
|
// Add additional fields if available
|
|
if (status.lastAttempt) {
|
|
response.sentAt = status.lastAttempt;
|
|
}
|
|
|
|
if (status.status === DeliveryStatus.DELIVERED) {
|
|
response.deliveredAt = status.lastAttempt;
|
|
}
|
|
|
|
if (status.error) {
|
|
response.error = status.error.message;
|
|
}
|
|
|
|
if (status.nextAttempt) {
|
|
response.nextRetry = status.nextAttempt;
|
|
}
|
|
|
|
res.json(response);
|
|
}
|
|
|
|
/**
|
|
* Handle domain verification request
|
|
* @param req Express request
|
|
* @param res Express response
|
|
*/
|
|
private async handleVerifyDomain(req: any, res: any): Promise<void> {
|
|
this.validateMtaService();
|
|
|
|
const domain = req.params.domain;
|
|
|
|
if (!domain) {
|
|
throw this.createError('INVALID_REQUEST', 'Missing domain');
|
|
}
|
|
|
|
try {
|
|
// Verify domain DNS records
|
|
const records = await this.mtaRef.dnsManager.verifyEmailAuthRecords(domain);
|
|
|
|
// Get MX records
|
|
let mxValid = false;
|
|
let mxRecords: string[] = [];
|
|
let mxError: string = undefined;
|
|
|
|
try {
|
|
const mxResult = await this.mtaRef.dnsManager.lookupMx(domain);
|
|
mxValid = mxResult.length > 0;
|
|
mxRecords = mxResult.map(mx => mx.exchange);
|
|
} catch (error) {
|
|
mxError = error.message;
|
|
}
|
|
|
|
// Create response
|
|
const response: DomainVerificationResponse = {
|
|
domain,
|
|
verified: records.spf.valid && records.dkim.valid && records.dmarc.valid && mxValid,
|
|
details: {
|
|
spf: {
|
|
valid: records.spf.valid,
|
|
record: records.spf.value,
|
|
error: records.spf.error
|
|
},
|
|
dkim: {
|
|
valid: records.dkim.valid,
|
|
record: records.dkim.value,
|
|
error: records.dkim.error
|
|
},
|
|
dmarc: {
|
|
valid: records.dmarc.valid,
|
|
record: records.dmarc.value,
|
|
error: records.dmarc.error
|
|
},
|
|
mx: {
|
|
valid: mxValid,
|
|
records: mxRecords,
|
|
error: mxError
|
|
}
|
|
}
|
|
};
|
|
|
|
res.json(response);
|
|
} catch (error) {
|
|
throw this.createError('VERIFICATION_FAILED', `Domain verification failed: ${error.message}`);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Handle get domain records request
|
|
* @param req Express request
|
|
* @param res Express response
|
|
*/
|
|
private async handleGetDomainRecords(req: any, res: any): Promise<void> {
|
|
this.validateMtaService();
|
|
|
|
const domain = req.params.domain;
|
|
|
|
if (!domain) {
|
|
throw this.createError('INVALID_REQUEST', 'Missing domain');
|
|
}
|
|
|
|
try {
|
|
// Generate recommended DNS records
|
|
const records = await this.mtaRef.dnsManager.generateAllRecommendedRecords(domain);
|
|
|
|
res.json({
|
|
domain,
|
|
records
|
|
});
|
|
} catch (error) {
|
|
throw this.createError('GENERATION_FAILED', `DNS record generation failed: ${error.message}`);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Handle generate DKIM keys request
|
|
* @param req Express request
|
|
* @param res Express response
|
|
*/
|
|
private async handleGenerateDkim(req: any, res: any): Promise<void> {
|
|
this.validateMtaService();
|
|
|
|
const domain = req.params.domain;
|
|
|
|
if (!domain) {
|
|
throw this.createError('INVALID_REQUEST', 'Missing domain');
|
|
}
|
|
|
|
try {
|
|
// Generate DKIM keys
|
|
await this.mtaRef.dkimCreator.createAndStoreDKIMKeys(domain);
|
|
|
|
// Get DNS record
|
|
const dnsRecord = await this.mtaRef.dkimCreator.getDNSRecordForDomain(domain);
|
|
|
|
res.json({
|
|
domain,
|
|
dnsRecord,
|
|
message: 'DKIM keys generated successfully'
|
|
});
|
|
} catch (error) {
|
|
throw this.createError('GENERATION_FAILED', `DKIM generation failed: ${error.message}`);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Handle get DKIM public key request
|
|
* @param req Express request
|
|
* @param res Express response
|
|
*/
|
|
private async handleGetDkimPublicKey(req: any, res: any): Promise<void> {
|
|
this.validateMtaService();
|
|
|
|
const domain = req.params.domain;
|
|
|
|
if (!domain) {
|
|
throw this.createError('INVALID_REQUEST', 'Missing domain');
|
|
}
|
|
|
|
try {
|
|
// Get DKIM keys
|
|
const keys = await this.mtaRef.dkimCreator.readDKIMKeys(domain);
|
|
|
|
// Get DNS record
|
|
const dnsRecord = await this.mtaRef.dkimCreator.getDNSRecordForDomain(domain);
|
|
|
|
res.json({
|
|
domain,
|
|
publicKey: keys.publicKey,
|
|
dnsRecord
|
|
});
|
|
} catch (error) {
|
|
throw this.createError('NOT_FOUND', `DKIM keys not found for domain: ${domain}`, 404);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Handle get stats request
|
|
* @param req Express request
|
|
* @param res Express response
|
|
*/
|
|
private async handleGetStats(req: any, res: any): Promise<void> {
|
|
this.validateMtaService();
|
|
|
|
// Get MTA stats
|
|
const stats = this.mtaRef.getStats();
|
|
|
|
res.json(stats);
|
|
}
|
|
|
|
/**
|
|
* Handle get API docs request
|
|
* @param req Express request
|
|
* @param res Express response
|
|
*/
|
|
private async handleGetApiDocs(req: any, res: any): Promise<void> {
|
|
// Generate API documentation
|
|
const docs = {
|
|
name: 'MTA API',
|
|
version: '1.0.0',
|
|
description: 'API for interacting with the MTA service',
|
|
endpoints: this.routes.map(route => ({
|
|
method: route.method,
|
|
path: route.path,
|
|
description: route.description,
|
|
authLevel: route.authLevel
|
|
}))
|
|
};
|
|
|
|
res.json(docs);
|
|
}
|
|
|
|
/**
|
|
* Start the API server
|
|
* @param port Port to listen on
|
|
* @returns Promise that resolves when server is started
|
|
*/
|
|
public start(port: number = 3000): Promise<void> {
|
|
return new Promise((resolve, reject) => {
|
|
try {
|
|
// Start HTTP server
|
|
this.app.listen(port, () => {
|
|
console.log(`API server listening on port ${port}`);
|
|
resolve();
|
|
});
|
|
} catch (error) {
|
|
console.error('Failed to start API server:', error);
|
|
reject(error);
|
|
}
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Stop the API server
|
|
*/
|
|
public stop(): void {
|
|
// Nothing to do if not running
|
|
console.log('API server stopped');
|
|
}
|
|
} |