Files
remoteturtle/server/database.js
2026-02-20 00:18:52 -05:00

495 lines
13 KiB
JavaScript

import Database from 'better-sqlite3';
import path from 'path';
import { fileURLToPath } from 'url';
const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename);
const db = new Database(path.join(__dirname, 'turtle_control.db'));
// Initialize database schema
export function initializeDatabase() {
// Turtle homes table
db.exec(`
CREATE TABLE IF NOT EXISTS turtle_homes (
turtle_id INTEGER PRIMARY KEY,
x INTEGER NOT NULL,
y INTEGER NOT NULL,
z INTEGER NOT NULL,
updated_at INTEGER NOT NULL
)
`);
// Turtle configuration table
db.exec(`
CREATE TABLE IF NOT EXISTS turtle_config (
turtle_id INTEGER PRIMARY KEY,
max_distance INTEGER DEFAULT 200,
facing INTEGER DEFAULT 0,
config_json TEXT,
updated_at INTEGER NOT NULL
)
`);
// World blocks table
db.exec(`
CREATE TABLE IF NOT EXISTS world_blocks (
x INTEGER NOT NULL,
y INTEGER NOT NULL,
z INTEGER NOT NULL,
block_name TEXT NOT NULL,
metadata INTEGER DEFAULT 0,
discovered_by INTEGER NOT NULL,
discovered_at INTEGER NOT NULL,
PRIMARY KEY (x, y, z)
)
`);
// Turtle paths table (for path recording)
db.exec(`
CREATE TABLE IF NOT EXISTS turtle_paths (
id INTEGER PRIMARY KEY AUTOINCREMENT,
turtle_id INTEGER NOT NULL,
path_name TEXT NOT NULL,
path_data TEXT NOT NULL,
created_at INTEGER NOT NULL,
updated_at INTEGER NOT NULL
)
`);
// Task queue table (for multi-turtle coordination)
db.exec(`
CREATE TABLE IF NOT EXISTS task_queue (
id INTEGER PRIMARY KEY AUTOINCREMENT,
task_type TEXT NOT NULL,
task_data TEXT NOT NULL,
assigned_turtle_id INTEGER,
priority INTEGER DEFAULT 0,
status TEXT DEFAULT 'pending',
created_at INTEGER NOT NULL,
updated_at INTEGER NOT NULL
)
`);
// Mining areas table (for area visualization)
db.exec(`
CREATE TABLE IF NOT EXISTS mining_areas (
id INTEGER PRIMARY KEY AUTOINCREMENT,
turtle_id INTEGER NOT NULL,
min_x INTEGER NOT NULL,
min_y INTEGER NOT NULL,
min_z INTEGER NOT NULL,
max_x INTEGER NOT NULL,
max_y INTEGER NOT NULL,
max_z INTEGER NOT NULL,
status TEXT DEFAULT 'active',
created_at INTEGER NOT NULL,
updated_at INTEGER NOT NULL
)
`);
// Mining statistics table
db.exec(`
CREATE TABLE IF NOT EXISTS mining_stats (
id INTEGER PRIMARY KEY AUTOINCREMENT,
turtle_id INTEGER NOT NULL,
block_type TEXT NOT NULL,
count INTEGER DEFAULT 1,
session_start INTEGER NOT NULL,
last_mined INTEGER NOT NULL
)
`);
// Turtle groups/teams table
db.exec(`
CREATE TABLE IF NOT EXISTS turtle_groups (
id INTEGER PRIMARY KEY AUTOINCREMENT,
group_name TEXT NOT NULL UNIQUE,
color TEXT DEFAULT '#3b82f6',
created_at INTEGER NOT NULL
)
`);
// Turtle group membership table
db.exec(`
CREATE TABLE IF NOT EXISTS turtle_group_members (
turtle_id INTEGER NOT NULL,
group_id INTEGER NOT NULL,
joined_at INTEGER NOT NULL,
PRIMARY KEY (turtle_id, group_id),
FOREIGN KEY (group_id) REFERENCES turtle_groups(id) ON DELETE CASCADE
)
`);
// Session tracking table (for time-based statistics)
db.exec(`
CREATE TABLE IF NOT EXISTS turtle_sessions (
id INTEGER PRIMARY KEY AUTOINCREMENT,
turtle_id INTEGER NOT NULL,
started_at INTEGER NOT NULL,
ended_at INTEGER,
blocks_mined INTEGER DEFAULT 0,
distance_traveled INTEGER DEFAULT 0
)
`);
// Player positions table (for tracking pocket computer users)
db.exec(`
CREATE TABLE IF NOT EXISTS player_positions (
player_id INTEGER PRIMARY KEY,
x INTEGER NOT NULL,
y INTEGER NOT NULL,
z INTEGER NOT NULL,
updated_at INTEGER NOT NULL
)
`);
// Create indexes for better performance
db.exec(`
CREATE INDEX IF NOT EXISTS idx_world_blocks_discovered
ON world_blocks(discovered_by);
`);
db.exec(`
CREATE INDEX IF NOT EXISTS idx_task_queue_status
ON task_queue(status, priority DESC);
`);
console.log('✅ Database initialized');
}
// Turtle Homes
export function saveTurtleHome(turtleId, position) {
const stmt = db.prepare(`
INSERT OR REPLACE INTO turtle_homes (turtle_id, x, y, z, updated_at)
VALUES (?, ?, ?, ?, ?)
`);
stmt.run(turtleId, position.x, position.y, position.z, Date.now());
}
export function getTurtleHome(turtleId) {
const stmt = db.prepare('SELECT x, y, z FROM turtle_homes WHERE turtle_id = ?');
return stmt.get(turtleId);
}
export function getAllTurtleHomes() {
const stmt = db.prepare('SELECT turtle_id, x, y, z FROM turtle_homes');
return stmt.all();
}
// Turtle Configuration
export function saveTurtleConfig(turtleId, config) {
const stmt = db.prepare(`
INSERT OR REPLACE INTO turtle_config (turtle_id, max_distance, facing, config_json, updated_at)
VALUES (?, ?, ?, ?, ?)
`);
stmt.run(
turtleId,
config.maxDistance || 200,
config.facing || 0,
JSON.stringify(config),
Date.now()
);
}
export function getTurtleConfig(turtleId) {
const stmt = db.prepare('SELECT * FROM turtle_config WHERE turtle_id = ?');
const row = stmt.get(turtleId);
if (row && row.config_json) {
return JSON.parse(row.config_json);
}
return null;
}
// World Blocks
export function saveWorldBlock(x, y, z, blockName, metadata, discoveredBy) {
const stmt = db.prepare(`
INSERT OR REPLACE INTO world_blocks (x, y, z, block_name, metadata, discovered_by, discovered_at)
VALUES (?, ?, ?, ?, ?, ?, ?)
`);
stmt.run(x, y, z, blockName, metadata || 0, discoveredBy, Date.now());
}
export function getWorldBlocks(limit = 10000) {
const stmt = db.prepare('SELECT * FROM world_blocks LIMIT ?');
return stmt.all(limit);
}
export function getWorldBlocksInArea(minX, minY, minZ, maxX, maxY, maxZ) {
const stmt = db.prepare(`
SELECT * FROM world_blocks
WHERE x BETWEEN ? AND ?
AND y BETWEEN ? AND ?
AND z BETWEEN ? AND ?
`);
return stmt.all(minX, maxX, minY, maxY, minZ, maxZ);
}
export function clearOldBlocks(daysOld = 7) {
const cutoffTime = Date.now() - (daysOld * 24 * 60 * 60 * 1000);
const stmt = db.prepare('DELETE FROM world_blocks WHERE discovered_at < ?');
const result = stmt.run(cutoffTime);
return result.changes;
}
// Turtle Paths
export function savePath(turtleId, pathName, pathData) {
const stmt = db.prepare(`
INSERT INTO turtle_paths (turtle_id, path_name, path_data, created_at, updated_at)
VALUES (?, ?, ?, ?, ?)
`);
const now = Date.now();
stmt.run(turtleId, pathName, JSON.stringify(pathData), now, now);
}
export function getPaths(turtleId) {
const stmt = db.prepare('SELECT * FROM turtle_paths WHERE turtle_id = ? ORDER BY created_at DESC');
return stmt.all(turtleId).map(row => ({
...row,
path_data: JSON.parse(row.path_data)
}));
}
export function deletePath(pathId) {
const stmt = db.prepare('DELETE FROM turtle_paths WHERE id = ?');
return stmt.run(pathId);
}
// Task Queue
export function createTask(taskType, taskData, priority = 0) {
const stmt = db.prepare(`
INSERT INTO task_queue (task_type, task_data, priority, status, created_at, updated_at)
VALUES (?, ?, ?, 'pending', ?, ?)
`);
const now = Date.now();
const result = stmt.run(taskType, JSON.stringify(taskData), priority, now, now);
return result.lastInsertRowid;
}
export function getNextTask() {
const stmt = db.prepare(`
SELECT * FROM task_queue
WHERE status = 'pending'
ORDER BY priority DESC, created_at ASC
LIMIT 1
`);
const row = stmt.get();
if (row) {
return {
...row,
task_data: JSON.parse(row.task_data)
};
}
return null;
}
export function assignTask(taskId, turtleId) {
const stmt = db.prepare(`
UPDATE task_queue
SET assigned_turtle_id = ?, status = 'assigned', updated_at = ?
WHERE id = ?
`);
stmt.run(turtleId, Date.now(), taskId);
}
export function completeTask(taskId) {
const stmt = db.prepare(`
UPDATE task_queue
SET status = 'completed', updated_at = ?
WHERE id = ?
`);
stmt.run(Date.now(), taskId);
}
export function getAllTasks() {
const stmt = db.prepare('SELECT * FROM task_queue ORDER BY priority DESC, created_at DESC');
return stmt.all().map(row => ({
...row,
task_data: JSON.parse(row.task_data)
}));
}
// Mining Areas
export function saveMiningArea(turtleId, bounds) {
const stmt = db.prepare(`
INSERT INTO mining_areas (turtle_id, min_x, min_y, min_z, max_x, max_y, max_z, status, created_at, updated_at)
VALUES (?, ?, ?, ?, ?, ?, ?, 'active', ?, ?)
`);
const now = Date.now();
stmt.run(
turtleId,
bounds.minX, bounds.minY, bounds.minZ,
bounds.maxX, bounds.maxY, bounds.maxZ,
now, now
);
}
export function getMiningAreas() {
const stmt = db.prepare('SELECT * FROM mining_areas WHERE status = \'active\'');
return stmt.all();
}
export function closeMiningArea(areaId) {
const stmt = db.prepare('UPDATE mining_areas SET status = \'closed\', updated_at = ? WHERE id = ?');
stmt.run(Date.now(), areaId);
}
// Mining Statistics
export function recordBlockMined(turtleId, blockType) {
const stmt = db.prepare(`
INSERT INTO mining_stats (turtle_id, block_type, count, session_start, last_mined)
VALUES (?, ?, 1, ?, ?)
ON CONFLICT(turtle_id, block_type, session_start)
DO UPDATE SET count = count + 1, last_mined = ?
`);
const now = Date.now();
const sessionStart = now - (now % (24 * 60 * 60 * 1000)); // Start of day
stmt.run(turtleId, blockType, sessionStart, now, now);
}
export function getMiningStats(turtleId = null, days = 7) {
const cutoff = Date.now() - (days * 24 * 60 * 60 * 1000);
if (turtleId) {
const stmt = db.prepare(`
SELECT block_type, SUM(count) as total_count
FROM mining_stats
WHERE turtle_id = ? AND session_start >= ?
GROUP BY block_type
ORDER BY total_count DESC
`);
return stmt.all(turtleId, cutoff);
} else {
const stmt = db.prepare(`
SELECT turtle_id, block_type, SUM(count) as total_count
FROM mining_stats
WHERE session_start >= ?
GROUP BY turtle_id, block_type
ORDER BY turtle_id, total_count DESC
`);
return stmt.all(cutoff);
}
}
export function getTopMiners(limit = 10) {
const stmt = db.prepare(`
SELECT turtle_id, SUM(count) as total_blocks
FROM mining_stats
GROUP BY turtle_id
ORDER BY total_blocks DESC
LIMIT ?
`);
return stmt.all(limit);
}
// Turtle Groups/Teams
export function createGroup(groupName, color = '#3b82f6') {
const stmt = db.prepare(`
INSERT INTO turtle_groups (group_name, color, created_at)
VALUES (?, ?, ?)
`);
const result = stmt.run(groupName, color, Date.now());
return result.lastInsertRowid;
}
export function getAllGroups() {
const stmt = db.prepare('SELECT * FROM turtle_groups ORDER BY created_at DESC');
return stmt.all();
}
export function deleteGroup(groupId) {
const stmt = db.prepare('DELETE FROM turtle_groups WHERE id = ?');
stmt.run(groupId);
}
export function addTurtleToGroup(turtleId, groupId) {
const stmt = db.prepare(`
INSERT OR IGNORE INTO turtle_group_members (turtle_id, group_id, joined_at)
VALUES (?, ?, ?)
`);
stmt.run(turtleId, groupId, Date.now());
}
export function removeTurtleFromGroup(turtleId, groupId) {
const stmt = db.prepare(`
DELETE FROM turtle_group_members
WHERE turtle_id = ? AND group_id = ?
`);
stmt.run(turtleId, groupId);
}
export function getGroupMembers(groupId) {
const stmt = db.prepare(`
SELECT turtle_id, joined_at
FROM turtle_group_members
WHERE group_id = ?
`);
return stmt.all(groupId);
}
export function getTurtleGroups(turtleId) {
const stmt = db.prepare(`
SELECT g.*, m.joined_at
FROM turtle_groups g
JOIN turtle_group_members m ON g.id = m.group_id
WHERE m.turtle_id = ?
`);
return stmt.all(turtleId);
}
// Session Tracking
export function startSession(turtleId) {
const stmt = db.prepare(`
INSERT INTO turtle_sessions (turtle_id, started_at)
VALUES (?, ?)
`);
const result = stmt.run(turtleId, Date.now());
return result.lastInsertRowid;
}
export function endSession(sessionId, blocksMined, distanceTraveled) {
const stmt = db.prepare(`
UPDATE turtle_sessions
SET ended_at = ?, blocks_mined = ?, distance_traveled = ?
WHERE id = ?
`);
stmt.run(Date.now(), blocksMined, distanceTraveled, sessionId);
}
export function getSessionStats(turtleId, limit = 10) {
const stmt = db.prepare(`
SELECT * FROM turtle_sessions
WHERE turtle_id = ?
ORDER BY started_at DESC
LIMIT ?
`);
return stmt.all(turtleId, limit);
}
// Player Positions
export function savePlayerPosition(playerId, position) {
const stmt = db.prepare(`
INSERT OR REPLACE INTO player_positions (player_id, x, y, z, updated_at)
VALUES (?, ?, ?, ?, ?)
`);
stmt.run(playerId, position.x, position.y, position.z, Date.now());
}
export function getPlayerPosition(playerId) {
const stmt = db.prepare('SELECT x, y, z, updated_at FROM player_positions WHERE player_id = ?');
return stmt.get(playerId);
}
export function getAllPlayerPositions() {
const stmt = db.prepare('SELECT player_id, x, y, z, updated_at FROM player_positions');
return stmt.all();
}
// Cleanup function
export function closeDatabase() {
db.close();
}
// Export database instance for custom queries if needed
export { db };