freealberta/map/app/server.js
2025-07-10 10:56:52 -06:00

137 lines
4.3 KiB
JavaScript

const express = require('express');
const helmet = require('helmet');
const cors = require('cors');
const session = require('express-session');
const cookieParser = require('cookie-parser');
const crypto = require('crypto');
// Import configuration and utilities
const config = require('./config');
const logger = require('./utils/logger');
const { getCookieConfig } = require('./utils/helpers');
const { apiLimiter } = require('./middleware/rateLimiter');
// Initialize Express app
const app = express();
// Trust proxy for Cloudflare
app.set('trust proxy', true);
// Cookie parser
app.use(cookieParser());
// Session configuration
app.use(session({
secret: config.session.secret,
resave: false,
saveUninitialized: false,
cookie: getCookieConfig(),
name: 'nocodb-map-session',
genid: (req) => {
// Use a custom session ID generator to avoid conflicts
return crypto.randomBytes(16).toString('hex');
}
}));
// Security middleware
app.use(helmet({
contentSecurityPolicy: {
directives: {
defaultSrc: ["'self'"],
styleSrc: ["'self'", "'unsafe-inline'", "https://unpkg.com"],
scriptSrc: ["'self'", "'unsafe-inline'", "https://unpkg.com", "https://cdn.jsdelivr.net"],
imgSrc: ["'self'", "data:", "https://*.tile.openstreetmap.org", "https://unpkg.com"],
connectSrc: ["'self'"]
}
}
}));
// CORS configuration
app.use(cors({
origin: function(origin, callback) {
// Allow requests with no origin (like mobile apps or curl requests)
if (!origin) return callback(null, true);
const allowedOrigins = config.cors.allowedOrigins;
if (allowedOrigins.includes(origin) || allowedOrigins.includes('*')) {
callback(null, true);
} else {
callback(new Error('Not allowed by CORS'));
}
},
credentials: true,
methods: ['GET', 'POST', 'PUT', 'DELETE', 'OPTIONS'],
allowedHeaders: ['Content-Type', 'Authorization']
}));
// Body parser middleware
app.use(express.json({ limit: '10mb' }));
// Apply rate limiting to API routes
app.use('/api/', apiLimiter);
// Import and setup routes
require('./routes')(app);
// Error handling middleware
app.use((err, req, res, next) => {
logger.error('Unhandled error:', err);
// Don't leak error details in production
const message = config.isProduction ?
'Internal server error' :
err.message || 'Internal server error';
res.status(err.status || 500).json({
success: false,
error: message
});
});
// Start server
const server = app.listen(config.port, () => {
logger.info(`
╔════════════════════════════════════════╗
║ BNKops Map Server ║
╠════════════════════════════════════════╣
║ Status: Running ║
║ Port: ${config.port}
║ Environment: ${config.nodeEnv}
║ Project ID: ${config.nocodb.projectId}
║ Table ID: ${config.nocodb.tableId}
║ Login Sheet: ${config.nocodb.loginSheetId || 'Not Configured'}
║ Time: ${new Date().toISOString()}
╚════════════════════════════════════════╝
`);
});
// Graceful shutdown
process.on('SIGTERM', () => {
logger.info('SIGTERM signal received: closing HTTP server');
server.close(() => {
logger.info('HTTP server closed');
process.exit(0);
});
});
process.on('SIGINT', () => {
logger.info('SIGINT signal received: closing HTTP server');
server.close(() => {
logger.info('HTTP server closed');
process.exit(0);
});
});
// Handle uncaught exceptions
process.on('uncaughtException', (err) => {
logger.error('Uncaught exception:', err);
process.exit(1);
});
// Handle unhandled promise rejections
process.on('unhandledRejection', (reason, promise) => {
logger.error('Unhandled Rejection at:', promise, 'reason:', reason);
process.exit(1);
});
module.exports = app;