import express from 'express' import http from 'http' import { WebSocketServer } from 'ws' import path from 'path' import fs from 'fs' import { fileURLToPath } from 'url' const __dirname = path.dirname(fileURLToPath(import.meta.url)) const app = express() const server = http.createServer(app) const PORT = process.env.PORT || 3001 const MAPS_DIR = path.join(__dirname, '..', 'maps') const DOORS_FILE = path.join(__dirname, '..', 'src', 'data', 'door-positions.json') const BASEMAP_FILE = path.join(__dirname, '..', 'src', 'data', 'basemap.json') const SIZES_FILE = path.join(__dirname, '..', 'src', 'data', 'room-sizes.json') // ── WebSocket (admin collab) ──────────────────────────────────────────────── const wss = new WebSocketServer({ server, path: '/ws/admin' }) const adminClients = new Set() function readJSON(file, fallback) { try { return JSON.parse(fs.readFileSync(file, 'utf8')) } catch { return fallback } } function broadcast(sender, msg) { const str = JSON.stringify(msg) for (const client of adminClients) { if (client !== sender && client.readyState === 1) client.send(str) } } wss.on('connection', (ws) => { adminClients.add(ws) ws.send(JSON.stringify({ type: 'init', doors: readJSON(DOORS_FILE, {}), basemap: readJSON(BASEMAP_FILE, []), sizes: readJSON(SIZES_FILE, {}), })) ws.on('message', (raw) => { try { const msg = JSON.parse(raw) if (msg.type === 'doors-save') { fs.writeFileSync(DOORS_FILE, JSON.stringify(msg.data, null, 2)) broadcast(ws, { type: 'doors-update', data: msg.data }) } if (msg.type === 'basemap-save') { fs.writeFileSync(BASEMAP_FILE, JSON.stringify(msg.data, null, 2)) broadcast(ws, { type: 'basemap-update', data: msg.data }) } if (msg.type === 'sizes-save') { fs.writeFileSync(SIZES_FILE, JSON.stringify(msg.data, null, 2)) broadcast(ws, { type: 'sizes-update', data: msg.data }) } if (msg.type === 'basemap-position-save') { const current = readJSON(BASEMAP_FILE, []) const next = current.map(entry => entry.id === msg.id ? { ...entry, position: msg.position } : entry) fs.writeFileSync(BASEMAP_FILE, JSON.stringify(next, null, 2)) broadcast(ws, { type: 'basemap-update', data: next }) } } catch {} }) ws.on('close', () => adminClients.delete(ws)) }) // ── REST ──────────────────────────────────────────────────────────────────── function formatName(f) { return f.replace(/_/g,' ').replace(/\b\w/g,c=>c.toUpperCase()) .replace(/\b1f\b/gi,'1F').replace(/\b2f\b/gi,'2F') .replace(/\bB1f\b/gi,'B1F').replace(/\bB2f\b/gi,'B2F') .replace(/\bRt\b/g,'Route').replace(/\bPokecenter\b/g,'Pokémon Center') } function scanRooms(dir, rooms = []) { for (const entry of fs.readdirSync(dir, { withFileTypes: true })) { const full = path.join(dir, entry.name) if (entry.isDirectory()) scanRooms(full, rooms) else if (entry.name.toLowerCase().endsWith('.png')) { const base = path.basename(entry.name, path.extname(entry.name)).toLowerCase() const rel = path.relative(MAPS_DIR, full).replace(/\\/g, '/') const cat = rel.split('/').slice(-2,-1)[0] ?? 'other' rooms.push({ id: base, name: formatName(base), image: `/maps/${rel}`, category: cat }) } } return rooms } app.get('/api/rooms', (_, res) => { try { res.json(scanRooms(MAPS_DIR)) } catch(e) { res.status(500).json({error:e.message}) } }) app.get('/api/doors', (_, res) => res.json(readJSON(DOORS_FILE, {}))) app.get('/api/basemap', (_, res) => res.json(readJSON(BASEMAP_FILE, []))) app.get('/api/room-sizes', (_, res) => res.json(readJSON(SIZES_FILE, {}))) app.post('/api/doors', express.json(), (req, res) => { try { fs.writeFileSync(DOORS_FILE, JSON.stringify(req.body,null,2)); res.json({ok:true}) } catch(e){res.status(500).json({error:e.message})} }) app.post('/api/basemap', express.json(), (req, res) => { try { fs.writeFileSync(BASEMAP_FILE, JSON.stringify(req.body,null,2)); res.json({ok:true}) } catch(e){res.status(500).json({error:e.message})} }) app.post('/api/room-sizes', express.json(), (req, res) => { try { fs.writeFileSync(SIZES_FILE, JSON.stringify(req.body,null,2)); res.json({ok:true}) } catch(e){res.status(500).json({error:e.message})} }) app.use('/maps', express.static(MAPS_DIR)) app.use(express.static(path.join(__dirname, '..', 'dist'))) app.get('/{*path}', (_, res) => res.sendFile(path.join(__dirname, '..', 'dist', 'index.html'))) server.listen(PORT, () => console.log(`Pokemon Doors running on http://localhost:${PORT}`))