diff --git a/server/server.js b/server/server.js index 94b7074..e0c4c4a 100644 --- a/server/server.js +++ b/server/server.js @@ -2,6 +2,7 @@ import express from 'express'; import { WebSocketServer } from 'ws'; import cors from 'cors'; import { createServer } from 'http'; +import * as db from './database.js'; const app = express(); const PORT = 3001; @@ -10,6 +11,16 @@ const WS_PORT = 3002; app.use(cors()); app.use(express.json()); +// Initialize database +db.initializeDatabase(); + +// Load persisted data from database +console.log('šŸ“‚ Loading persisted data from database...'); +const savedHomes = db.getAllTurtleHomes(); +const savedBlocks = db.getWorldBlocks(); +console.log(` Loaded ${savedHomes.length} turtle homes`); +console.log(` Loaded ${savedBlocks.length} world blocks`); + // Store connected web clients and turtle data const webClients = new Set(); const turtleData = new Map(); // turtleID -> turtle state @@ -17,6 +28,22 @@ const worldBlocks = new Map(); // "x,y,z" -> {name, metadata, discoveredBy, time const turtleHomes = new Map(); // turtleID -> {x, y, z} home position const turtleConfig = new Map(); // turtleID -> {maxDistance, facing, etc} +// Load saved homes into memory +for (const home of savedHomes) { + turtleHomes.set(home.turtle_id, { x: home.x, y: home.y, z: home.z }); +} + +// Load saved blocks into memory +for (const block of savedBlocks) { + const key = `${block.x},${block.y},${block.z}`; + worldBlocks.set(key, { + name: block.block_name, + metadata: block.metadata, + discoveredBy: block.discovered_by, + timestamp: block.discovered_at + }); +} + // Timeout for considering turtles offline (30 seconds) const TURTLE_TIMEOUT = 30000; @@ -62,6 +89,9 @@ function storeBlock(x, y, z, blockData, turtleID) { discoveredBy: turtleID, timestamp: Date.now() }); + + // Persist to database + db.saveWorldBlock(x, y, z, blockData.name, blockData.metadata, turtleID); } // Helper to calculate block position based on turtle position and facing @@ -250,13 +280,16 @@ app.post('/api/turtle/:id/home', (req, res) => { const turtleID = parseInt(req.params.id); const { position } = req.body; - if (!position || !position.x || !position.y || !position.z) { + if (!position || position.x === undefined || position.y === undefined || position.z === undefined) { return res.status(400).json({ error: 'Invalid position' }); } turtleHomes.set(turtleID, position); console.log(`šŸ“ Set home for turtle ${turtleID}:`, position); + // Persist to database + db.saveTurtleHome(turtleID, position); + // Update turtle data if (turtleData.has(turtleID)) { const turtle = turtleData.get(turtleID); @@ -350,6 +383,192 @@ setInterval(() => { } }, 10000); // Check every 10 seconds +// ========== PATH RECORDING ENDPOINTS ========== + +// Save a recorded path +app.post('/api/paths', (req, res) => { + try { + const { turtleId, pathName, pathData } = req.body; + db.savePath(turtleId, pathName, pathData); + res.json({ success: true, message: 'Path saved' }); + } catch (error) { + res.status(500).json({ error: error.message }); + } +}); + +// Get all paths for a turtle +app.get('/api/paths/:turtleId', (req, res) => { + try { + const turtleId = parseInt(req.params.turtleId); + const paths = db.getPaths(turtleId); + res.json({ paths }); + } catch (error) { + res.status(500).json({ error: error.message }); + } +}); + +// Delete a path +app.delete('/api/paths/:pathId', (req, res) => { + try { + const pathId = parseInt(req.params.pathId); + db.deletePath(pathId); + res.json({ success: true }); + } catch (error) { + res.status(500).json({ error: error.message }); + } +}); + +// ========== TASK QUEUE ENDPOINTS ========== + +// Create a new task +app.post('/api/tasks', (req, res) => { + try { + const { taskType, taskData, priority } = req.body; + const taskId = db.createTask(taskType, taskData, priority || 0); + + broadcastToClients({ + type: 'task_created', + taskId, + taskType, + priority + }); + + res.json({ success: true, taskId }); + } catch (error) { + res.status(500).json({ error: error.message }); + } +}); + +// Get all tasks +app.get('/api/tasks', (req, res) => { + try { + const tasks = db.getAllTasks(); + res.json({ tasks }); + } catch (error) { + res.status(500).json({ error: error.message }); + } +}); + +// Get next available task +app.get('/api/tasks/next', (req, res) => { + try { + const task = db.getNextTask(); + res.json({ task }); + } catch (error) { + res.status(500).json({ error: error.message }); + } +}); + +// Assign task to turtle +app.post('/api/tasks/:taskId/assign', (req, res) => { + try { + const taskId = parseInt(req.params.taskId); + const { turtleId } = req.body; + db.assignTask(taskId, turtleId); + + broadcastToClients({ + type: 'task_assigned', + taskId, + turtleId + }); + + res.json({ success: true }); + } catch (error) { + res.status(500).json({ error: error.message }); + } +}); + +// Complete a task +app.post('/api/tasks/:taskId/complete', (req, res) => { + try { + const taskId = parseInt(req.params.taskId); + db.completeTask(taskId); + + broadcastToClients({ + type: 'task_completed', + taskId + }); + + res.json({ success: true }); + } catch (error) { + res.status(500).json({ error: error.message }); + } +}); + +// ========== MINING AREA ENDPOINTS ========== + +// Save a mining area claim +app.post('/api/mining-areas', (req, res) => { + try { + const { turtleId, bounds } = req.body; + db.saveMiningArea(turtleId, bounds); + + const areas = db.getMiningAreas(); + broadcastToClients({ + type: 'mining_areas_updated', + areas + }); + + res.json({ success: true }); + } catch (error) { + res.status(500).json({ error: error.message }); + } +}); + +// Get all active mining areas +app.get('/api/mining-areas', (req, res) => { + try { + const areas = db.getMiningAreas(); + res.json({ areas }); + } catch (error) { + res.status(500).json({ error: error.message }); + } +}); + +// Close a mining area +app.post('/api/mining-areas/:areaId/close', (req, res) => { + try { + const areaId = parseInt(req.params.areaId); + db.closeMiningArea(areaId); + + const areas = db.getMiningAreas(); + broadcastToClients({ + type: 'mining_areas_updated', + areas + }); + + res.json({ success: true }); + } catch (error) { + res.status(500).json({ error: error.message }); + } +}); + +// ========== STATISTICS ENDPOINTS ========== + +// Get server statistics +app.get('/api/stats', (req, res) => { + try { + const stats = { + activeTurtles: turtleData.size, + totalBlocks: worldBlocks.size, + savedHomes: turtleHomes.size, + connectedClients: webClients.size, + tasks: db.getAllTasks().length, + miningAreas: db.getMiningAreas().length + }; + res.json(stats); + } catch (error) { + res.status(500).json({ error: error.message }); + } +}); + +// Graceful shutdown +process.on('SIGINT', () => { + console.log('\nšŸ›‘ Shutting down server...'); + db.closeDatabase(); + process.exit(0); +}); + server.listen(PORT, () => { console.log(`āœ… Server ready!`); console.log(`\nConfigured turtles to send updates to:`);