164 lines
7.2 KiB
JavaScript
164 lines
7.2 KiB
JavaScript
// backend/server.js
|
|
// ─────────────────────────────────────────────────────────────────────────────
|
|
// Polyfill global crypto for Node 18 compatibility (required by mongodb driver)
|
|
if (typeof globalThis.crypto === 'undefined') {
|
|
globalThis.crypto = require('crypto');
|
|
}
|
|
|
|
// BackOne Backend API Server
|
|
//
|
|
// Tanggung jawab backend ini adalah READ-ONLY dari MongoDB.
|
|
// Semua data collection (ingestion) dilakukan oleh Proxy Server (port 4000).
|
|
// Backend TIDAK memanggil DPI API secara langsung.
|
|
//
|
|
// Environment Variables:
|
|
// MONGODB_URI - MongoDB connection string
|
|
// BACKEND_PORT - Port server ini (default: 3001)
|
|
// JWT_SECRET - Secret untuk JWT auth
|
|
// ALLOWED_ORIGINS- Comma-separated allowed CORS origins
|
|
// PROXY_URL - URL proxy server (untuk trigger manual refresh)
|
|
// ─────────────────────────────────────────────────────────────────────────────
|
|
|
|
const path = require('path');
|
|
const envFile = process.env.NODE_ENV === 'production' ? '.env.production' : '.env.local';
|
|
require('dotenv').config({ path: path.join(__dirname, '..', envFile) });
|
|
|
|
const express = require('express');
|
|
const cors = require('cors');
|
|
const cookieParser = require('cookie-parser');
|
|
const jwt = require('jsonwebtoken');
|
|
const connectDB = require('./db/mongoose');
|
|
|
|
// ─── Connect to MongoDB (read-only mode) ──────────────────────────────────────
|
|
connectDB();
|
|
|
|
const app = express();
|
|
const PORT = process.env.BACKEND_PORT || 3001;
|
|
|
|
// ─── Middleware ────────────────────────────────────────────────────────────────
|
|
const ALLOWED_ORIGINS = process.env.ALLOWED_ORIGINS
|
|
? process.env.ALLOWED_ORIGINS.split(',')
|
|
: ['http://localhost:3000', 'http://127.0.0.1:3000'];
|
|
|
|
app.use(cors({
|
|
origin: (origin, callback) => {
|
|
if (!origin) return callback(null, true);
|
|
if (ALLOWED_ORIGINS.includes(origin)) {
|
|
callback(null, true);
|
|
} else {
|
|
callback(new Error('Blocked by CORS policy (Unauthorized Origin)'));
|
|
}
|
|
},
|
|
credentials: true
|
|
}));
|
|
app.use(express.json({ limit: '10mb' }));
|
|
app.use(express.urlencoded({ extended: true, limit: '10mb' }));
|
|
app.use(cookieParser());
|
|
|
|
app.use((req, res, next) => {
|
|
if (req.originalUrl && req.originalUrl.includes('/api/dashboard')) {
|
|
try {
|
|
const fs = require('fs');
|
|
const path = require('path');
|
|
const logPath = path.join(__dirname, '../scratch/http_requests.log');
|
|
const logLine = `[${new Date().toISOString()}] ${req.method} ${req.originalUrl} - Query: ${JSON.stringify(req.query)}\n`;
|
|
fs.appendFileSync(logPath, logLine);
|
|
} catch (e) {
|
|
console.error('Logger error:', e.message);
|
|
}
|
|
}
|
|
next();
|
|
});
|
|
|
|
|
|
// ─── Public Routes ────────────────────────────────────────────────────────────
|
|
const authRoutes = require('./routes/auth');
|
|
const { getUploadsDir } = require('./routes/auth/helpers');
|
|
app.use('/api/auth', authRoutes);
|
|
app.use('/api/uploads', express.static(getUploadsDir()));
|
|
|
|
// ─── Auth Middleware ──────────────────────────────────────────────────────────
|
|
const { requireAuth } = require('./middleware/auth');
|
|
const { getTimeFilter, getBaseFilter } = require('./routes/dashboard/helpers');
|
|
const {
|
|
generateMacFromIp,
|
|
resolveVendorFromIp,
|
|
resolveDeviceTypeFromIp,
|
|
resolveOSFromIp,
|
|
generateAutoLabel
|
|
} = require('./deviceResolver');
|
|
|
|
// ─── Protected Dashboard Routes ───────────────────────────────────────────────
|
|
const dashboardRoutes = require('./routes/dashboard');
|
|
|
|
// Override /api/dashboard/app-details to show real-time device mapping per application
|
|
app.get('/api/dashboard/app-details', requireAuth, (req, res) => {
|
|
require('./routes/appDetailsHandler')(req, res, {
|
|
getTimeFilter,
|
|
getBaseFilter
|
|
});
|
|
});
|
|
|
|
// Override /api/dashboard/device-details to map real-time classifications (Facebook, YouTube, etc.)
|
|
app.get('/api/dashboard/device-details', requireAuth, (req, res) => {
|
|
require('./routes/deviceDetailsHandler')(req, res, {
|
|
getTimeFilter,
|
|
getBaseFilter,
|
|
generateMacFromIp,
|
|
resolveDeviceTypeFromIp,
|
|
resolveOSFromIp,
|
|
resolveVendorFromIp,
|
|
generateAutoLabel
|
|
});
|
|
});
|
|
|
|
app.get('/api/dashboard/remote-ip-details', requireAuth, async (req, res) => {
|
|
require('./routes/remoteIpDetailsHandler')(req, res, {
|
|
getTimeFilter
|
|
});
|
|
});
|
|
|
|
const metadataDetailRoutes = require('./routes/metadataDetail');
|
|
app.use('/api/dashboard/metadata-detail', requireAuth, metadataDetailRoutes);
|
|
|
|
const categoryDetailRoutes = require('./routes/categoryDetail');
|
|
app.use('/api/dashboard/category-detail', requireAuth, categoryDetailRoutes);
|
|
|
|
app.use('/api/dashboard', requireAuth, dashboardRoutes);
|
|
|
|
|
|
|
|
|
|
// ─── Health Check ─────────────────────────────────────────────────────────────
|
|
app.get('/api/health', (req, res) => {
|
|
res.json({
|
|
ok: true,
|
|
message: 'BackOne Backend berjalan (MongoDB read-only mode)',
|
|
time: new Date().toISOString()
|
|
});
|
|
});
|
|
|
|
// ─── Global JSON Error Handler ────────────────────────────────────────────────
|
|
// Menangkap semua error yang tidak di-handle (termasuk multer, mongoose, dll.)
|
|
// dan memastikan response selalu JSON, BUKAN HTML default Express.
|
|
// eslint-disable-next-line no-unused-vars
|
|
app.use((err, req, res, next) => {
|
|
console.error('[Global Error Handler]', err.message || err);
|
|
const status = err.status || err.statusCode || 500;
|
|
res.status(status).json({
|
|
error: err.message || 'Internal server error',
|
|
code: err.code || undefined,
|
|
});
|
|
});
|
|
|
|
// ─── Start Server ─────────────────────────────────────────────────────────────
|
|
// Bind to 127.0.0.1 in production to prevent direct external access to port 3001.
|
|
// All external traffic must go through the reverse proxy (Apache/Nginx) at port 80/443.
|
|
const BIND_HOST = process.env.NODE_ENV === 'production' ? '127.0.0.1' : '0.0.0.0';
|
|
app.listen(PORT, BIND_HOST, () => {
|
|
console.log(`\n🚀 BackOne API Server berjalan di http://${BIND_HOST}:${PORT}`);
|
|
console.log(`🔌 API Health : http://${BIND_HOST}:${PORT}/api/health`);
|
|
console.log(`📡 Mode : READ-ONLY dari MongoDB (data dikirim oleh Proxy Server)`);
|
|
console.log(`🔒 Security : Bound to ${BIND_HOST} (internal only in production)\n`);
|
|
});
|