refactor: replace express server setup with platform server integration and streamline proxy endpoints
This commit is contained in:
118
server/server.js
118
server/server.js
@@ -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();
|
||||||
|
|||||||
Reference in New Issue
Block a user