Skip to content

Commit adc1a21

Browse files
committed
update
1 parent 466b81a commit adc1a21

File tree

5 files changed

+1291
-35
lines changed

5 files changed

+1291
-35
lines changed

src/app.js

Lines changed: 225 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,24 +1,233 @@
11
// src/app.js
22
import express from 'express';
3+
import helmet from 'helmet';
4+
import compression from 'compression';
5+
import cors from 'cors';
6+
import path from 'path';
7+
import config from './config/config.js';
8+
import routes from './routes/index.js';
39
import { securityMiddleware } from './middleware/security.js';
4-
import { loggingMiddleware } from './middleware/logging.js';
5-
import apiRoutes from './routes/api.js';
6-
import { register } from './metrics/prometheus.js';
10+
import { requestLogger, errorLogger } from './middleware/logging.js';
11+
import { errorHandler, notFoundHandler } from './middleware/errorHandler.js';
12+
import databaseManager from './config/database.js';
13+
import prometheusMetrics from './metrics/prometheus.js';
14+
import metricCollectors from './metrics/collectors.js';
715

8-
const app = express();
16+
/**
17+
* CloudPanel API Application
18+
* Main application setup and initialization.
19+
* Configures Express server with middleware, routes, and error handling.
20+
*/
21+
class Application {
22+
constructor() {
23+
this.app = express();
24+
this.initialized = false;
25+
}
926

10-
// Apply middleware
11-
app.use(securityMiddleware);
12-
app.use(loggingMiddleware);
13-
app.use(express.json());
27+
/**
28+
* Initializes the application and all its dependencies
29+
* Handles startup in the correct order with proper error handling
30+
*/
31+
async initialize() {
32+
if (this.initialized) {
33+
return;
34+
}
1435

15-
// API routes
16-
app.use('/api/v1', apiRoutes);
36+
try {
37+
// Initialize database first
38+
await databaseManager.initialize();
39+
console.log('Database initialized successfully');
1740

18-
// Metrics endpoint
19-
app.get('/metrics', async (req, res) => {
20-
res.setHeader('Content-Type', register.contentType);
21-
res.send(await register.metrics());
22-
});
41+
// Initialize metrics collection
42+
await prometheusMetrics.initialize();
43+
console.log('Prometheus metrics initialized');
2344

24-
export default app;
45+
// Configure basic middleware
46+
this.configureMiddleware();
47+
console.log('Middleware configured');
48+
49+
// Configure routes
50+
this.configureRoutes();
51+
console.log('Routes configured');
52+
53+
// Configure error handling
54+
this.configureErrorHandling();
55+
console.log('Error handling configured');
56+
57+
// Start metric collectors
58+
metricCollectors.startCollectors();
59+
console.log('Metric collectors started');
60+
61+
this.initialized = true;
62+
console.log('Application initialization completed');
63+
} catch (error) {
64+
console.error('Application initialization failed:', error);
65+
throw error;
66+
}
67+
}
68+
69+
/**
70+
* Configures Express middleware in the correct order
71+
* Sets up security, parsing, and utility middleware
72+
*/
73+
configureMiddleware() {
74+
// Security middleware
75+
this.app.use(helmet());
76+
this.app.use(cors(config.security.cors));
77+
this.app.use(securityMiddleware);
78+
79+
// Request parsing
80+
this.app.use(express.json({ limit: '10mb' }));
81+
this.app.use(express.urlencoded({ extended: true, limit: '10mb' }));
82+
83+
// Compression
84+
this.app.use(compression());
85+
86+
// Request logging
87+
this.app.use(requestLogger);
88+
89+
// Serve static files if configured
90+
if (config.server.serveStatic) {
91+
this.app.use('/static', express.static(path.join(config.paths.root, 'public')));
92+
}
93+
}
94+
95+
/**
96+
* Configures application routes and API endpoints
97+
* Sets up metrics endpoint and API routes
98+
*/
99+
configureRoutes() {
100+
// Health check endpoint
101+
this.app.get('/health', async (req, res) => {
102+
try {
103+
await databaseManager.db.get('SELECT 1');
104+
res.json({ status: 'healthy', timestamp: new Date().toISOString() });
105+
} catch (error) {
106+
res.status(503).json({
107+
status: 'unhealthy',
108+
error: 'Database connection failed',
109+
timestamp: new Date().toISOString()
110+
});
111+
}
112+
});
113+
114+
// Metrics endpoint
115+
this.app.get('/metrics', async (req, res) => {
116+
try {
117+
const metrics = await prometheusMetrics.getMetrics();
118+
res.set('Content-Type', 'text/plain');
119+
res.send(metrics);
120+
} catch (error) {
121+
res.status(500).json({ error: 'Failed to collect metrics' });
122+
}
123+
});
124+
125+
// API routes
126+
this.app.use('/api', routes);
127+
128+
// Catch 404
129+
this.app.use(notFoundHandler);
130+
}
131+
132+
/**
133+
* Configures error handling middleware
134+
* Sets up logging and error response formatting
135+
*/
136+
configureErrorHandling() {
137+
this.app.use(errorLogger);
138+
this.app.use(errorHandler);
139+
140+
// Handle unhandled rejections
141+
process.on('unhandledRejection', (reason, promise) => {
142+
console.error('Unhandled Rejection at:', promise, 'reason:', reason);
143+
});
144+
145+
// Handle uncaught exceptions
146+
process.on('uncaughtException', (error) => {
147+
console.error('Uncaught Exception:', error);
148+
this.shutdown(1);
149+
});
150+
}
151+
152+
/**
153+
* Starts the application server
154+
* @returns {Promise<void>}
155+
*/
156+
async start() {
157+
if (!this.initialized) {
158+
await this.initialize();
159+
}
160+
161+
const { port, host } = config.server;
162+
163+
return new Promise((resolve) => {
164+
this.server = this.app.listen(port, host, () => {
165+
console.log(`Server running at http://${host}:${port}`);
166+
resolve();
167+
});
168+
169+
// Configure server timeouts
170+
this.server.timeout = 30000; // 30 seconds
171+
this.server.keepAliveTimeout = 65000; // 65 seconds
172+
});
173+
}
174+
175+
/**
176+
* Performs graceful shutdown of the application
177+
* @param {number} [code=0] - Exit code
178+
* @returns {Promise<void>}
179+
*/
180+
async shutdown(code = 0) {
181+
console.log('Shutting down application...');
182+
183+
try {
184+
// Stop metric collectors
185+
metricCollectors.stopCollectors();
186+
console.log('Metric collectors stopped');
187+
188+
// Close database connections
189+
await databaseManager.close();
190+
console.log('Database connections closed');
191+
192+
// Close server
193+
if (this.server) {
194+
await new Promise((resolve) => {
195+
this.server.close(resolve);
196+
});
197+
console.log('Server stopped');
198+
}
199+
200+
console.log('Shutdown completed');
201+
process.exit(code);
202+
} catch (error) {
203+
console.error('Error during shutdown:', error);
204+
process.exit(1);
205+
}
206+
}
207+
208+
/**
209+
* Gets Express application instance
210+
* Useful for testing and custom configurations
211+
* @returns {express.Application}
212+
*/
213+
getApp() {
214+
return this.app;
215+
}
216+
}
217+
218+
// Create and export application instance
219+
const application = new Application();
220+
221+
// Handle shutdown signals
222+
process.on('SIGTERM', () => application.shutdown());
223+
process.on('SIGINT', () => application.shutdown());
224+
225+
export default application;
226+
227+
// If this is the main module, start the application
228+
if (import.meta.url === `file://${process.argv[1]}`) {
229+
application.start().catch((error) => {
230+
console.error('Failed to start application:', error);
231+
process.exit(1);
232+
});
233+
}

0 commit comments

Comments
 (0)