Files
dashboard/backend/server.js
T

116 lines
3.8 KiB
JavaScript

const express = require('express');
const cors = require('cors');
const path = require('path');
require('dotenv').config();
require('dotenv').config({ path: path.join(__dirname, '../.env') });
// Initialize database connection (auto-creates schema)
require('./database/db');
// Migration runner
const { runMigrations } = require('./database/runMigrations');
const app = express();
const PORT = process.env.PORT || 5001;
// Middleware
/**
* Origins allowed to call this API.
*
* Production keeps an explicit list. Development also accepts ANY port on
* localhost, because Vite is configured for 3000 but silently falls back when
* that port is taken — 3001, 3002, 3003 and up, depending on what else the
* developer happens to be running. A hardcoded list meant the app loaded fine
* on the fallback port while every request failed CORS, which reads as "the
* data is gone" rather than "the port moved".
*
* The loopback-only test is what keeps this safe: it never reflects a remote
* origin, so `credentials: true` cannot be used by another site.
*/
const ALLOWED_ORIGINS = [
'http://localhost:3000',
'http://localhost:3001',
'http://localhost:3002',
'http://localhost:5173',
'http://localhost:5174',
'http://localhost:5002',
];
const isDevelopment = process.env.NODE_ENV !== 'production';
const LOOPBACK_ORIGIN = /^https?:\/\/(localhost|127\.0\.0\.1|\[::1\])(:\d+)?$/;
app.use(
cors({
origin: (origin, callback) => {
// No Origin header: same-origin, curl, or a server-to-server call.
if (!origin) return callback(null, true);
if (ALLOWED_ORIGINS.includes(origin)) return callback(null, true);
if (isDevelopment && LOOPBACK_ORIGIN.test(origin)) return callback(null, true);
return callback(new Error(`Origin tidak diizinkan oleh CORS: ${origin}`));
},
credentials: true,
})
);
app.use(express.json());
// Request logging middleware
app.use((req, res, next) => {
next();
});
// Routes
const cyclesRouter = require('./routes/cycles');
const mortalityRouter = require('./routes/mortality');
const chickenCountsRouter = require('./routes/chickenCounts');
const apiKeysRouter = require('./routes/apiKeys');
const kandangsRouter = require('./routes/kandangs');
const feedSackColumnConfigRouter = require('./routes/feedSackColumnConfig');
const manualFeedSackRouter = require('./routes/manualFeedSack');
const cycleFeedInitialBalanceRouter = require('./routes/cycleFeedInitialBalance');
const auditLogsRouter = require('./routes/auditLogs');
const aiInsightsRouter = require('./routes/aiInsights');
app.use('/api/cycles', cyclesRouter);
app.use('/api/mortality', mortalityRouter);
app.use('/api/chicken-counting', chickenCountsRouter);
app.use('/api/admin/api-keys', apiKeysRouter);
app.use('/api/kandangs', kandangsRouter);
app.use('/api/feed-sack-column-config', feedSackColumnConfigRouter);
app.use('/api/manual-feed-sack', manualFeedSackRouter);
app.use('/api/cycle-feed-initial-balance', cycleFeedInitialBalanceRouter);
app.use('/api/audit-logs', auditLogsRouter);
app.use('/api/ai-insights', aiInsightsRouter);
// Health check endpoint
app.get('/health', (req, res) => {
res.json({ status: 'ok', timestamp: new Date().toISOString(), database: 'postgresql' });
});
// 404 handler
app.use((req, res) => {
res.status(404).json({ success: false, error: 'Route not found' });
});
// Error handling middleware (MUST be last)
const { errorHandler } = require('./middleware/errorHandler');
app.use(errorHandler);
// Start server with migrations
async function startServer() {
try {
// Run database migrations first
await runMigrations();
// Then start the server
app.listen(PORT, () => {});
} catch (error) {
console.error('❌ Failed to start server due to migration error');
console.error(error);
process.exit(1);
}
}
// Start the server
startServer();