
- 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.
197 lines
5.3 KiB
JavaScript
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;
|