Files
2026-06-21 10:00:13 +08:00

129 lines
4.9 KiB
JavaScript

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}/`);
});
})();