const express = require('express'); const http = require('http'); const path = require('path'); const cors = require('cors'); const ioModule = require('./io'); const DataService = require('./services/data'); const { jsonStringifyUnicode } = require('./services/unicode-json'); const app = express(); const server = http.createServer(app); // Initialize Socket.IO const io = ioModule.init(server); app.use(express.json()); app.use(cors()); app.use((req, res, next) => { const origJson = res.json.bind(res); res.json = (data) => { if (data === undefined) return origJson(data); const body = jsonStringifyUnicode(data); res.set('Content-Type', 'application/json; charset=utf-8'); return res.send(body); }; next(); }); // Mount API Routes app.use('/api/public', require('./routes/public')); app.use('/api/files', require('./routes/files')); app.use('/api/assets', require('./routes/assets')); app.use('/api', require('./routes/api')); // Serve Audio Files Statically const AUDIO_DIR = path.join(__dirname, '../Audio'); app.use('/audio', express.static(AUDIO_DIR)); const ASSETS_DIR = path.join(__dirname, '../Assets'); app.use('/assets', express.static(ASSETS_DIR)); // Serve Lua Scripts const ROOT_DIR = path.join(__dirname, '..'); app.get('/scripts/:name', (req, res) => { const name = req.params.name; if (!name.match(/^[a-zA-Z0-9_]+\.lua$/)) return res.status(400).send('Invalid script name'); const allowed = ['gate.lua', 'ticketmachine.lua', 'startup.lua', 'install_gate.lua', 'install_machine.lua', 'update_gate.lua', 'update_machine.lua', 'server.lua', 'installer.lua', 'installer_bi.lua']; if (!allowed.includes(name)) return res.status(404).send('Script not found'); res.sendFile(path.join(ROOT_DIR, name)); }); // Serve Static Files const WEB_DIR = path.join(__dirname, '../web'); // Path Routing for single domain app.get('/', (req, res, next) => { const host = req.hostname; console.log(`[Router] Accessing / with host: ${host}`); if (host === 'ticket.fse-media.group' || !host.includes('fse-media.group')) { return res.sendFile(path.join(WEB_DIR, 'home.html')); } next(); }); app.get('/order', (req, res) => res.sendFile(path.join(WEB_DIR, 'ticket-order.html'))); app.get('/search', (req, res) => res.sendFile(path.join(WEB_DIR, 'ticket-search.html'))); app.get('/ic-card/search', (req, res) => res.sendFile(path.join(WEB_DIR, 'ic-card-search.html'))); app.get('/ic-card/order', (req, res) => res.sendFile(path.join(WEB_DIR, 'ic-card-order.html'))); app.get('/ic/:card_id', (req, res) => res.sendFile(path.join(WEB_DIR, 'ic-card-detail.html'))); app.get('/token', (req, res) => res.sendFile(path.join(WEB_DIR, 'token.html'))); app.get('/admin', (req, res) => res.sendFile(path.join(WEB_DIR, 'index.html'))); app.get('/admin/ic-card', (req, res) => res.redirect('/admin?view=iccards')); app.get('/route', (req, res) => res.sendFile(path.join(WEB_DIR, 'ticket-route.html'))); app.use('/', express.static(WEB_DIR)); // Fallback for static routes (ticket-search, ticket-order) - Legacy support app.get('/ticket-search/:id', (req, res) => { res.sendFile(path.join(WEB_DIR, 'ticket-search.html')); }); app.get('/ticket-search', (req, res) => { res.sendFile(path.join(WEB_DIR, 'ticket-search.html')); }); app.get('/ticket-order', (req, res) => { res.sendFile(path.join(WEB_DIR, 'ticket-order.html')); }); app.get('/ic-card-admin', (req, res) => { res.redirect('/admin?view=iccards'); }); app.get('/ic-card-search', (req, res) => { res.sendFile(path.join(WEB_DIR, 'ic-card-search.html')); }); app.get('/ic-card-order', (req, res) => { res.sendFile(path.join(WEB_DIR, 'ic-card-order.html')); }); // Ticket Board // Handles ticket.fse-media.group/detail/TicketID app.get('/detail/:ticket_id', (req, res, next) => { const id = req.params.ticket_id; // If it's a known static file extension, let express.static handle it if (id.includes('.')) return next(); return res.sendFile(path.join(WEB_DIR, 'ticket-board.html')); }); // Start Server const PORT = process.env.PORT || 23333; const HOST = process.env.HOST || '0.0.0.0'; (async () => { await DataService.init(); // Start periodic export // setInterval(DataService.saveExport, 10000); // Global Error Handler app.use((err, req, res, next) => { console.error(err.stack); try { DataService.appendLog({ ts: new Date().toISOString(), ip: (req.headers['x-forwarded-for'] || '').toString().split(',')[0].trim() || req.ip || req.connection?.remoteAddress || '', ua: String(req.headers['user-agent'] || ''), method: req.method, path: req.originalUrl || req.path || '', category: 'system', level: 'error', type: 'unhandled_error', detail: { error: err?.message || String(err) } }); } catch (e) {} res.status(500).json({ ok: false, error: 'Internal Server Error' }); }); server.listen(PORT, HOST, () => { console.log(`ftc admin server running at http://${HOST}:${PORT}/`); }); })();