formies/server.js
Mohamad.Elsena a3236ae9d5 Refactor environment configuration for PostgreSQL and enhance application structure
- Updated `.env` and `.env.test` files to include PostgreSQL connection settings and Redis configuration.
- Migrated database from SQLite to PostgreSQL, updating relevant queries and connection logic.
- Enhanced error handling and logging throughout the application.
- Added new test utilities for PostgreSQL integration and updated user model methods.
- Introduced new routes for user authentication and form management, ensuring compatibility with the new database structure.
- Created login and registration views in EJS for user interaction.
2025-05-28 16:16:33 +02:00

197 lines
5.3 KiB
JavaScript

require("dotenv").config();
const express = require("express");
const path = require("path");
const fs = require("fs"); // Added for fs operations
const pool = require("./src/config/database"); // Changed to pg pool
const helmet = require("helmet");
const session = require("express-session");
const passport = require("./src/config/passport");
const logger = require("./config/logger"); // Corrected logger path back to original
const errorHandler = require("./middleware/errorHandler");
const { connectRedis, closeRedis } = require("./src/config/redis");
// Import routes
const publicRoutes = require("./src/routes/public");
const authRoutes = require("./src/routes/auth");
const dashboardRoutes = require("./src/routes/dashboard");
const apiV1Routes = require("./src/routes/api_v1");
const app = express();
const PORT = process.env.PORT || 3000;
// Function to initialize the database with PostgreSQL
async function initializeDatabase() {
try {
// Check if a key table exists (e.g., users) to see if DB is initialized
await pool.query("SELECT 1 FROM users LIMIT 1");
logger.info("Database tables appear to exist. Skipping initialization.");
} catch (tableCheckError) {
// Specific error code for undefined_table in PostgreSQL is '42P01'
if (tableCheckError.code === "42P01") {
logger.info(
"Users table not found, attempting to initialize database..."
);
try {
const initSql = fs.readFileSync(
path.resolve(__dirname, "init.sql"),
"utf8"
);
// Execute the entire init.sql script.
// pg library can usually handle multi-statement queries if separated by semicolons.
await pool.query(initSql);
logger.info("Database initialized successfully from init.sql.");
} catch (initError) {
logger.error("Failed to initialize database with init.sql:", initError);
process.exit(1); // Exit if DB initialization fails
}
} else {
// Another error occurred during the table check
logger.error("Error checking for users table:", tableCheckError);
process.exit(1); // Exit on other DB errors during startup
}
}
}
// Initialize Redis connection and Database
async function initializeApp() {
// Initialize Redis first, but don't block on failure
connectRedis().catch(() => {
logger.warn(
"Redis connection failed, continuing with in-memory rate limiting"
);
});
try {
await initializeDatabase(); // Initialize PostgreSQL database
} catch (error) {
logger.error("Failed to initialize database:", error);
process.exit(1); // Exit if DB initialization fails
}
// Middleware
app.use(
helmet({
contentSecurityPolicy: {
directives: {
defaultSrc: ["'self'"],
styleSrc: ["'self'", "'unsafe-inline'"],
scriptSrc: ["'self'"],
imgSrc: ["'self'", "data:", "https:"],
},
},
})
);
app.use(express.json({ limit: "10mb" }));
app.use(express.urlencoded({ extended: true, limit: "10mb" }));
// Session configuration (for development only, use Redis in production)
app.use(
session({
secret:
process.env.SESSION_SECRET || "fallback-secret-change-in-production",
resave: false,
saveUninitialized: false,
cookie: {
secure: process.env.NODE_ENV === "production",
httpOnly: true,
maxAge: 24 * 60 * 60 * 1000, // 24 hours
},
})
);
// Initialize Passport
app.use(passport.initialize());
app.use(passport.session());
// Set view engine
app.set("view engine", "ejs");
// API Routes
app.use("/api/auth", authRoutes);
// API V1 Routes
app.use("/api/v1", apiV1Routes);
// User Dashboard Routes
app.use("/dashboard", dashboardRoutes);
// Existing routes (maintaining backward compatibility)
app.use("/", publicRoutes);
// Health check endpoint
app.get("/health", (req, res) => {
res.json({
status: "healthy",
timestamp: new Date().toISOString(),
version: "1.0.0",
});
});
// Global error handler - should be the last middleware
app.use(errorHandler);
// 404 handler
app.use((req, res) => {
logger.warn(
`404 - Endpoint not found: ${req.originalUrl} - Method: ${req.method} - IP: ${req.ip}`
);
res.status(404).json({
error: {
message: "Endpoint not found",
code: "NOT_FOUND",
},
});
});
// Start server
app.listen(PORT, () => {
logger.info(`Server running on http://localhost:${PORT}`);
// Environment checks
if (!process.env.JWT_SECRET) {
logger.warn(
"WARNING: JWT_SECRET not set. Authentication will not work properly."
);
}
if (process.env.NTFY_ENABLED === "true" && process.env.NTFY_TOPIC_URL) {
logger.info(
`Ntfy notifications enabled for topic: ${process.env.NTFY_TOPIC_URL}`
);
} else {
logger.info("Ntfy notifications disabled or topic not configured.");
}
// Start cleanup of expired sessions every hour
setInterval(
() => {
const jwtService = require("./src/services/jwtService");
jwtService.cleanupExpiredSessions();
},
60 * 60 * 1000
);
});
// Graceful shutdown
process.on("SIGINT", async () => {
logger.info("Received SIGINT, shutting down gracefully...");
await closeRedis();
process.exit(0);
});
process.on("SIGTERM", async () => {
logger.info("Received SIGTERM, shutting down gracefully...");
await closeRedis();
process.exit(0);
});
}
// Initialize the application
initializeApp().catch((error) => {
logger.error("Failed to initialize application:", error);
process.exit(1);
});
module.exports = app;