refactor: replace express server setup with platform server integration and streamline proxy endpoints

This commit is contained in:
MayaTheShy
2026-03-26 15:19:53 -04:00
parent 6f462c97e0
commit 9291b063d0

View File

@@ -1,41 +1,27 @@
import express from 'express';
import { WebSocketServer } from 'ws'; import { WebSocketServer } from 'ws';
import cors from 'cors'; import { createRequire } from 'module';
import { createServer } from 'http';
import * as db from './database.js'; import * as db from './database.js';
import { Turtle } from './Turtle.js'; import { Turtle } from './Turtle.js';
import { TaskDispatcher } from './TaskDispatcher.js'; import { TaskDispatcher } from './TaskDispatcher.js';
import { WorldBlockCache } from './WorldBlockCache.js'; import { WorldBlockCache } from './WorldBlockCache.js';
const app = express(); const require = createRequire(import.meta.url);
const PORT = 3001; const {
createPlatformServer,
setupGracefulShutdown,
createProxyEndpoint,
} = require('@cc-platform/server');
app.use(cors()); // ========== Platform Server Setup ==========
app.use(express.json({ limit: '5mb' })); const { app, server, auth, start, port } = createPlatformServer({
serviceName: 'turtle-control',
// ========== API Key Authentication ========== port: 3001,
const API_KEY = process.env.API_KEY || ''; cors: true,
function extractApiKey(req) {
const auth = req.headers.authorization || '';
if (auth.startsWith('Bearer ')) return auth.slice(7);
return req.headers['x-api-key'] || req.query.key || '';
}
function requireAuth(req, res, next) {
if (!API_KEY) return next(); // Auth disabled when no key configured
if (extractApiKey(req) === API_KEY) return next();
return res.status(401).json({ error: 'Unauthorized — invalid or missing API key' });
}
// Protect mutating endpoints when an API key is set
app.use((req, res, next) => {
if (req.method === 'GET' || req.method === 'HEAD' || req.method === 'OPTIONS') {
return next();
}
return requireAuth(req, res, next);
}); });
const { requireAuth } = auth;
const API_KEY = process.env.API_KEY || '';
// Rewrite requests that arrive without /api prefix (from reverse proxy stripping it) // Rewrite requests that arrive without /api prefix (from reverse proxy stripping it)
app.use((req, res, next) => { app.use((req, res, next) => {
if (!req.path.startsWith('/api') && req.path !== '/' && req.path !== '/health') { if (!req.path.startsWith('/api') && req.path !== '/' && req.path !== '/health') {
@@ -215,19 +201,12 @@ function getBlockPosition(turtlePos, facing, direction) {
return pos; return pos;
} }
// Create HTTP server
const server = createServer(app);
// WebSocket server for web clients AND bridge connections // WebSocket server for web clients AND bridge connections
const wss = new WebSocketServer({ server }); const wss = new WebSocketServer({ server });
// Track bridge connections separately // Track bridge connections separately
const bridgeClients = new Set(); const bridgeClients = new Set();
console.log(`🚀 Turtle Control Server starting...`);
console.log(`📡 HTTP Server: http://localhost:${PORT}`);
console.log(`🔌 WebSocket Server: ws://localhost:${PORT}/ws`);
/** /**
* Push a command to the webbridge for a specific turtle. * Push a command to the webbridge for a specific turtle.
* If a bridge is connected via WebSocket, send instantly. * If a bridge is connected via WebSocket, send instantly.
@@ -1880,8 +1859,6 @@ app.post('/api/turtle/:id/refresh-inventory', async (req, res) => {
// ========== Cross-Project Integration API ========== // ========== Cross-Project Integration API ==========
// These endpoints allow the Inventory Manager system to query turtle state // These endpoints allow the Inventory Manager system to query turtle state
const INVENTORY_SERVER_URL = process.env.INVENTORY_SERVER_URL || ''; // e.g. http://inventory-server:3001
// Get all turtle summaries (for inventory dashboard sidebar widget) // Get all turtle summaries (for inventory dashboard sidebar widget)
app.get('/api/integration/turtle-summary', (req, res) => { app.get('/api/integration/turtle-summary', (req, res) => {
const summaries = []; const summaries = [];
@@ -1902,62 +1879,29 @@ app.get('/api/integration/turtle-summary', (req, res) => {
}); });
// Proxy to inventory server (locate items for turtle pickup) // Proxy to inventory server (locate items for turtle pickup)
app.get('/api/integration/inventory-locate', async (req, res) => { createProxyEndpoint(app, '/api/integration/inventory-locate', 'INVENTORY_SERVER_URL', '/api/integration/locate-item');
if (!INVENTORY_SERVER_URL) {
return res.json({ configured: false, message: 'INVENTORY_SERVER_URL not configured' });
}
try {
const params = new URLSearchParams(req.query);
const resp = await fetch(`${INVENTORY_SERVER_URL}/api/integration/locate-item?${params}`);
const data = await resp.json();
res.json({ configured: true, ...data });
} catch (err) {
res.status(502).json({ configured: true, error: `Cannot reach inventory server: ${err.message}` });
}
});
// Proxy to inventory server (low stock alerts — turtles could auto-mine missing resources) // Proxy to inventory server (low stock alerts — turtles could auto-mine missing resources)
app.get('/api/integration/inventory-alerts', async (req, res) => { createProxyEndpoint(app, '/api/integration/inventory-alerts', 'INVENTORY_SERVER_URL', '/api/integration/low-stock');
if (!INVENTORY_SERVER_URL) {
return res.json({ configured: false, message: 'INVENTORY_SERVER_URL not configured' });
}
try {
const resp = await fetch(`${INVENTORY_SERVER_URL}/api/integration/low-stock`);
const data = await resp.json();
res.json({ configured: true, ...data });
} catch (err) {
res.status(502).json({ configured: true, error: `Cannot reach inventory server: ${err.message}` });
}
});
// Proxy to inventory server (storage space check — should turtles keep mining?) // Proxy to inventory server (storage space check — should turtles keep mining?)
app.get('/api/integration/storage-status', async (req, res) => { createProxyEndpoint(app, '/api/integration/storage-status', 'INVENTORY_SERVER_URL', '/api/integration/storage-status');
if (!INVENTORY_SERVER_URL) {
return res.json({ configured: false, message: 'INVENTORY_SERVER_URL not configured' }); // ========== Graceful Shutdown & Start ==========
}
try { setupGracefulShutdown({
const resp = await fetch(`${INVENTORY_SERVER_URL}/api/integration/storage-status`); serviceName: 'turtle-control',
const data = await resp.json(); cleanup: [
res.json({ configured: true, ...data }); () => taskDispatcher.stop(),
} catch (err) { () => db.closeDatabase(),
res.status(502).json({ configured: true, error: `Cannot reach inventory server: ${err.message}` }); ],
}
}); });
// Graceful shutdown start(() => {
process.on('SIGINT', () => {
console.log('\n🛑 Shutting down server...');
taskDispatcher.stop();
db.closeDatabase();
process.exit(0);
});
server.listen(PORT, () => {
console.log(`✅ Server ready!`);
console.log(`\nConfigured turtles to send updates to:`); console.log(`\nConfigured turtles to send updates to:`);
console.log(` http://localhost:${PORT}/api/turtle/update`); console.log(` http://localhost:${port}/api/turtle/update`);
if (INVENTORY_SERVER_URL) { if (process.env.INVENTORY_SERVER_URL) {
console.log(`📦 Inventory server integration: ${INVENTORY_SERVER_URL}`); console.log(`📦 Inventory server integration: ${process.env.INVENTORY_SERVER_URL}`);
} }
// Start task dispatcher after server is ready // Start task dispatcher after server is ready
taskDispatcher.start(); taskDispatcher.start();