Files
test_rundev/server.js
2025-06-25 14:12:45 +00:00

1117 lines
31 KiB
JavaScript

import express from 'express';
import cors from 'cors';
import dotenv from 'dotenv';
import crypto from 'crypto';
import fs from 'fs/promises';
import path from 'path';
dotenv.config();
const app = express();
const CONFIG_FILE = path.join(process.cwd(), 'config.json');
// Load configuration
let config = {};
async function loadConfig() {
try {
const configData = await fs.readFile(CONFIG_FILE, 'utf8');
const newConfig = JSON.parse(configData);
// Check if root directory changed
const oldRootDirectory = config.server?.rootDirectory;
const newRootDirectory = newConfig.server?.rootDirectory;
config = newConfig;
console.log('✅ API Server: Configuration loaded successfully');
// If root directory changed, refresh file system cache
if (oldRootDirectory && newRootDirectory && oldRootDirectory !== newRootDirectory) {
console.log(`📁 API Server: Root directory changed from ${oldRootDirectory} to ${newRootDirectory}`);
console.log('🔄 API Server: Refreshing file system cache...');
await refreshFileSystemCache();
}
return config;
} catch (error) {
console.error('❌ API Server: Error loading config:', error.message);
// Create default config if file doesn't exist
config = {
server: {
name: "File Manager API Server",
port: 3001,
rootDirectory: "/home/project",
logLevel: "info",
environment: "development",
capabilities: [
"file-transfer",
"server-management",
"health-monitoring",
"filesystem-browsing"
]
},
filesystem: {
maxDepth: 10,
cacheSettings: {
ttl: 60000,
refreshInterval: 300000
},
watchEnabled: true,
maxFileSize: 104857600
},
transfer: {
maxConcurrentTransfers: 5,
uploadTimeout: 300000,
maxRequestSize: 104857600
},
security: {
corsOrigins: [
"http://localhost:3000",
"http://localhost:5173"
],
requestTimeout: 30000,
rateLimiting: {
windowMs: 900000,
maxRequests: 1000
}
},
logging: {
requests: true,
errors: true,
filePath: "./logs/api-server.log"
},
health: {
checkEnabled: true,
checkInterval: 60000
}
};
await saveConfig();
return config;
}
}
async function saveConfig() {
try {
await fs.writeFile(CONFIG_FILE, JSON.stringify(config, null, 2), 'utf8');
console.log('✅ API Server: Configuration saved successfully');
return true;
} catch (error) {
console.error('❌ API Server: Error saving config:', error.message);
return false;
}
}
// Watch config file for changes
async function watchConfigFile() {
try {
const { watch } = await import('fs');
console.log('👁️ API Server: Starting config file watcher...');
const watcher = watch(CONFIG_FILE, { persistent: false }, async (eventType, filename) => {
if (eventType === 'change' && filename === 'config.json') {
console.log('📝 API Server: Config file changed, reloading...');
// Add a small delay to ensure file write is complete
setTimeout(async () => {
try {
await loadConfig();
console.log('✅ API Server: Configuration reloaded successfully');
} catch (error) {
console.error('❌ API Server: Error reloading configuration:', error.message);
}
}, 100);
}
});
watcher.on('error', (error) => {
console.error('❌ API Server: Config file watcher error:', error.message);
});
console.log('✅ API Server: Config file watcher started');
return watcher;
} catch (error) {
console.error('❌ API Server: Failed to start config file watcher:', error.message);
return null;
}
}
// Get current configuration
function getConfig() {
return config;
}
const API_KEY = process.env.API_KEY;
const PORT = process.env.PORT || config.server?.port || 3001;
// Middleware
app.use(cors({
origin: config.security?.corsOrigins || ["http://localhost:3000", "http://localhost:5173"],
credentials: true
}));
app.use(express.json());
// API Key validation middleware
const validateApiKey = (req, res, next) => {
const authHeader = req.headers.authorization;
if (!authHeader) {
return res.status(401).json({
success: false,
error: 'Authorization header missing'
});
}
const token = authHeader.replace('Bearer ', '');
if (token !== API_KEY) {
return res.status(401).json({
success: false,
error: 'Invalid API key'
});
}
next();
};
// Request logging middleware
app.use((req, res, next) => {
if (config.logging?.requests) {
const timestamp = new Date().toISOString();
console.log(`[${timestamp}] API Server: ${req.method} ${req.path} - ${req.ip}`);
}
next();
});
// File system cache
let fileSystemCache = {
data: null,
lastUpdate: null,
ttl: config.filesystem?.cacheSettings?.ttl || 60000
};
// Helper function to resolve file path correctly
function resolveFilePath(relativePath) {
const rootDir = config.server.rootDirectory;
// If relativePath is already absolute and starts with rootDir, use it as is
if (path.isAbsolute(relativePath) && relativePath.startsWith(rootDir)) {
return relativePath;
}
// If relativePath is absolute but doesn't start with rootDir,
// treat it as relative to rootDir
if (path.isAbsolute(relativePath)) {
// Remove leading slash and join with rootDir
const cleanPath = relativePath.startsWith('/') ? relativePath.slice(1) : relativePath;
return path.join(rootDir, cleanPath);
}
// If relativePath is relative, join with rootDir
return path.join(rootDir, relativePath);
}
// Helper function to get relative path from absolute path
function getRelativePath(absolutePath) {
const rootDir = config.server.rootDirectory;
// If path starts with rootDir, remove it to get relative path
if (absolutePath.startsWith(rootDir)) {
const relativePath = absolutePath.slice(rootDir.length);
return relativePath.startsWith('/') ? relativePath : '/' + relativePath;
}
// If path doesn't start with rootDir, return as is
return absolutePath;
}
// Dangerous directories to skip when scanning from root
const DANGEROUS_DIRECTORIES = new Set([
'/proc',
'/sys',
'/dev',
'/run',
'/tmp',
'/var/run',
'/var/lock',
'/var/tmp',
'/boot',
'/lost+found'
]);
// Helper function to check if directory should be skipped
function shouldSkipDirectory(dirPath) {
// Skip dangerous system directories
if (DANGEROUS_DIRECTORIES.has(dirPath)) {
return true;
}
// Skip any subdirectories of dangerous directories
for (const dangerousDir of DANGEROUS_DIRECTORIES) {
if (dirPath.startsWith(dangerousDir + '/')) {
return true;
}
}
// Skip hidden directories when scanning from root
const rootDir = config.server.rootDirectory;
if (rootDir === '/' && path.basename(dirPath).startsWith('.')) {
return true;
}
return false;
}
// Helper function to get file stats safely
async function getFileStats(filePath) {
try {
const stats = await fs.stat(filePath);
return {
isDirectory: stats.isDirectory(),
isFile: stats.isFile(),
size: stats.size,
lastModified: stats.mtime,
created: stats.birthtime,
permissions: stats.mode
};
} catch (error) {
return null;
}
}
// Helper function to scan directory recursively with safety limits
async function scanDirectory(dirPath, currentDepth = 0, maxDepth = 10) {
if (currentDepth >= maxDepth) {
console.log(`⚠️ API Server: Max depth ${maxDepth} reached for ${dirPath}`);
return [];
}
// Check if directory should be skipped for safety
if (shouldSkipDirectory(dirPath)) {
console.log(`⚠️ API Server: Skipping dangerous directory: ${dirPath}`);
return [];
}
try {
const entries = await fs.readdir(dirPath);
const files = [];
let processedCount = 0;
const maxEntriesPerDirectory = 1000; // Limit entries per directory
for (const entry of entries) {
// Limit number of entries processed per directory
if (processedCount >= maxEntriesPerDirectory) {
console.log(`⚠️ API Server: Directory ${dirPath} has too many entries, limiting to ${maxEntriesPerDirectory}`);
break;
}
const fullPath = path.join(dirPath, entry);
// Skip dangerous directories
if (shouldSkipDirectory(fullPath)) {
continue;
}
const stats = await getFileStats(fullPath);
if (!stats) continue;
const relativePath = getRelativePath(fullPath);
const fileItem = {
id: crypto.randomUUID(),
name: entry,
path: relativePath,
type: stats.isDirectory ? 'folder' : 'file',
size: stats.isFile ? stats.size : undefined,
lastModified: stats.lastModified,
created: stats.created,
permissions: stats.permissions
};
if (stats.isDirectory && currentDepth < maxDepth - 1) {
try {
fileItem.children = await scanDirectory(fullPath, currentDepth + 1, maxDepth);
} catch (error) {
console.warn(`⚠️ API Server: Cannot read directory ${fullPath}: ${error.message}`);
fileItem.children = [];
}
}
files.push(fileItem);
processedCount++;
}
return files.sort((a, b) => {
// Folders first, then files, both alphabetically
if (a.type !== b.type) {
return a.type === 'folder' ? -1 : 1;
}
return a.name.localeCompare(b.name);
});
} catch (error) {
console.error(`❌ API Server: Error scanning directory ${dirPath}: ${error.message}`);
return [];
}
}
// Helper function to refresh file system cache with safety limits
async function refreshFileSystemCache() {
try {
const rootDir = config.server.rootDirectory;
console.log(`🔍 API Server: Scanning file system from root: ${rootDir}`);
// Special handling for root directory
if (rootDir === '/') {
console.log(`⚠️ API Server: Root directory is '/', applying safety limits`);
}
const startTime = Date.now();
// Use reduced max depth for root directory scanning
const maxDepth = rootDir === '/' ? 3 : (config.filesystem?.maxDepth || 10);
console.log(`📊 API Server: Using max depth: ${maxDepth}`);
const files = await scanDirectory(rootDir, 0, maxDepth);
fileSystemCache = {
data: files,
lastUpdate: new Date().toISOString(),
ttl: config.filesystem?.cacheSettings?.ttl || 60000,
scanTime: Date.now() - startTime,
totalFiles: countFiles(files),
rootDirectory: rootDir,
maxDepth: maxDepth
};
console.log(`✅ API Server: File system scan completed in ${fileSystemCache.scanTime}ms, found ${fileSystemCache.totalFiles} items`);
return true;
} catch (error) {
console.error('❌ API Server: Error refreshing file system cache:', error);
return false;
}
}
// Helper function to count files recursively
function countFiles(files) {
let count = 0;
for (const file of files) {
count++;
if (file.children) {
count += countFiles(file.children);
}
}
return count;
}
// Check if cache is valid
function isCacheValid() {
if (!fileSystemCache.data || !fileSystemCache.lastUpdate) {
return false;
}
const now = Date.now();
const lastUpdate = new Date(fileSystemCache.lastUpdate).getTime();
return (now - lastUpdate) < fileSystemCache.ttl;
}
// Mock data for servers (keeping existing functionality)
const mockServers = [
{
id: 'server1',
name: 'Production Server',
address: '192.168.1.100',
port: 22,
type: 'sftp',
status: 'online',
lastSeen: new Date().toISOString()
},
{
id: 'server2',
name: 'Development Server',
address: '192.168.1.101',
port: 22,
type: 'sftp',
status: 'offline',
lastSeen: new Date(Date.now() - 3600000).toISOString()
}
];
// Routes
// Get configuration
app.get('/api/v1/config', validateApiKey, (req, res) => {
res.json({
success: true,
data: config
});
});
// Update configuration
app.post('/api/v1/config', validateApiKey, async (req, res) => {
try {
const updates = req.body;
// Deep merge configuration
function deepMerge(target, source) {
for (const key in source) {
if (source[key] && typeof source[key] === 'object' && !Array.isArray(source[key])) {
if (!target[key]) target[key] = {};
deepMerge(target[key], source[key]);
} else {
target[key] = source[key];
}
}
}
const oldRootDirectory = config.server?.rootDirectory;
deepMerge(config, updates);
const saved = await saveConfig();
if (saved) {
// Refresh file system if root directory changed
const newRootDirectory = config.server?.rootDirectory;
if (oldRootDirectory !== newRootDirectory) {
console.log(`📁 API Server: Root directory updated from ${oldRootDirectory} to ${newRootDirectory}`);
await refreshFileSystemCache();
}
res.json({
success: true,
message: 'Configuration updated successfully',
data: config
});
} else {
res.status(500).json({
success: false,
error: 'Failed to save configuration'
});
}
} catch (error) {
res.status(500).json({
success: false,
error: 'Failed to update configuration',
details: error.message
});
}
});
// Update root directory
app.post('/api/v1/config/root-directory', validateApiKey, async (req, res) => {
try {
const { rootDirectory } = req.body;
console.log(`📡 API Server: Received root directory update request`);
console.log(`📡 API Server: Request body:`, req.body);
console.log(`📡 API Server: Headers:`, req.headers);
if (!rootDirectory) {
console.log(`❌ API Server: Root directory is missing from request`);
return res.status(400).json({
success: false,
error: 'Root directory is required'
});
}
const oldRootDirectory = config.server.rootDirectory;
console.log(`📁 API Server: Updating root directory from "${oldRootDirectory}" to "${rootDirectory}"`);
// Validate root directory
if (rootDirectory === '/') {
console.log(`⚠️ API Server: Warning - Setting root directory to '/' will apply safety limits`);
}
// Update the configuration
config.server.rootDirectory = rootDirectory;
// Save the configuration to file
console.log(`💾 API Server: Saving configuration to ${CONFIG_FILE}`);
const saved = await saveConfig();
if (saved) {
console.log(`✅ API Server: Root directory updated successfully to "${rootDirectory}"`);
console.log(`🔄 API Server: Refreshing file system cache...`);
// Refresh file system cache with new root directory
const refreshed = await refreshFileSystemCache();
console.log(`✅ API Server: File system cache refresh ${refreshed ? 'successful' : 'failed'}`);
res.json({
success: true,
message: 'Root directory updated successfully',
data: {
rootDirectory,
previousDirectory: oldRootDirectory,
configSaved: true,
cacheRefreshed: refreshed,
safetyLimitsApplied: rootDirectory === '/'
}
});
} else {
console.error(`❌ API Server: Failed to save configuration`);
res.status(500).json({
success: false,
error: 'Failed to save configuration'
});
}
} catch (error) {
console.error(`❌ API Server: Error updating root directory:`, error);
res.status(500).json({
success: false,
error: 'Failed to update root directory',
details: error.message
});
}
});
// Health check endpoint
app.get('/api/v1/health', validateApiKey, (req, res) => {
const uptime = process.uptime();
const timestamp = new Date().toISOString();
res.json({
success: true,
data: {
status: 'healthy',
timestamp,
uptime: Math.floor(uptime),
version: '1.0.0',
environment: config.server?.environment || 'development',
rootDirectory: config.server.rootDirectory,
fileSystemCache: {
lastUpdate: fileSystemCache.lastUpdate,
isValid: isCacheValid(),
totalFiles: fileSystemCache.totalFiles || 0,
maxDepth: fileSystemCache.maxDepth,
safetyLimitsActive: config.server.rootDirectory === '/'
}
}
});
});
// Get server information
app.get('/api/v1/server-info', validateApiKey, (req, res) => {
res.json({
success: true,
data: {
name: config.server.name,
version: '1.0.0',
rootDirectory: config.server.rootDirectory,
capabilities: config.server.capabilities,
limits: {
maxFileSize: `${Math.round(config.filesystem.maxFileSize / 1024 / 1024)}MB`,
maxConcurrentTransfers: config.transfer.maxConcurrentTransfers,
maxScanDepth: config.filesystem.maxDepth,
safetyLimitsActive: config.server.rootDirectory === '/'
}
}
});
});
// Get file system structure
app.get('/api/v1/filesystem', validateApiKey, async (req, res) => {
try {
// Check if cache is valid, refresh if needed
if (!isCacheValid()) {
const refreshed = await refreshFileSystemCache();
if (!refreshed) {
return res.status(500).json({
success: false,
error: 'Failed to scan file system'
});
}
}
res.json({
success: true,
data: {
files: fileSystemCache.data || [],
rootDirectory: config.server.rootDirectory,
lastUpdate: fileSystemCache.lastUpdate,
scanTime: fileSystemCache.scanTime,
totalFiles: fileSystemCache.totalFiles,
maxDepth: fileSystemCache.maxDepth,
safetyLimitsActive: config.server.rootDirectory === '/',
cached: true
}
});
} catch (error) {
console.error('❌ API Server: Error getting file system:', error);
res.status(500).json({
success: false,
error: 'Failed to get file system structure',
details: error.message
});
}
});
// Refresh file system cache manually
app.post('/api/v1/filesystem/refresh', validateApiKey, async (req, res) => {
try {
const refreshed = await refreshFileSystemCache();
if (refreshed) {
res.json({
success: true,
data: {
message: 'File system cache refreshed successfully',
lastUpdate: fileSystemCache.lastUpdate,
scanTime: fileSystemCache.scanTime,
totalFiles: fileSystemCache.totalFiles,
maxDepth: fileSystemCache.maxDepth,
safetyLimitsActive: config.server.rootDirectory === '/'
}
});
} else {
res.status(500).json({
success: false,
error: 'Failed to refresh file system cache'
});
}
} catch (error) {
console.error('❌ API Server: Error refreshing file system:', error);
res.status(500).json({
success: false,
error: 'Failed to refresh file system cache',
details: error.message
});
}
});
// Get specific directory contents
app.get('/api/v1/filesystem/directory', validateApiKey, async (req, res) => {
const { path: dirPath = '/' } = req.query;
try {
const fullPath = resolveFilePath(dirPath);
console.log(`📁 API Server: Getting directory contents for: ${dirPath} -> ${fullPath}`);
const files = await scanDirectory(fullPath, 0, 2); // Limit depth for directory listing
res.json({
success: true,
data: {
path: dirPath,
files,
timestamp: new Date().toISOString()
}
});
} catch (error) {
console.error('❌ API Server: Error getting directory contents:', error);
res.status(500).json({
success: false,
error: 'Failed to get directory contents',
details: error.message
});
}
});
// Read file content
app.get('/api/v1/filesystem/file-content', validateApiKey, async (req, res) => {
try {
const { path: filePath } = req.query;
if (!filePath) {
return res.status(400).json({
success: false,
error: 'File path is required'
});
}
const fullPath = resolveFilePath(filePath);
console.log(`📄 API Server: Reading file content from: ${filePath} -> ${fullPath}`);
// Check if file exists and is a file
const stats = await getFileStats(fullPath);
if (!stats) {
return res.status(404).json({
success: false,
error: 'File not found'
});
}
if (!stats.isFile) {
return res.status(400).json({
success: false,
error: 'Path is not a file'
});
}
// Check file size limit (100MB default)
const maxFileSize = config.filesystem?.maxFileSize || 104857600;
if (stats.size > maxFileSize) {
return res.status(413).json({
success: false,
error: `File too large. Maximum size: ${Math.round(maxFileSize / 1024 / 1024)}MB`
});
}
const content = await fs.readFile(fullPath, 'utf8');
console.log(`✅ API Server: File content read successfully, size: ${content.length} characters`);
res.json({
success: true,
data: {
content,
path: filePath,
size: stats.size,
lastModified: stats.lastModified,
encoding: 'utf8'
}
});
} catch (error) {
console.error('❌ API Server: Error reading file content:', error);
if (error.code === 'ENOENT') {
res.status(404).json({
success: false,
error: 'File not found'
});
} else if (error.code === 'EACCES') {
res.status(403).json({
success: false,
error: 'Permission denied'
});
} else if (error.code === 'EISDIR') {
res.status(400).json({
success: false,
error: 'Path is a directory, not a file'
});
} else {
res.status(500).json({
success: false,
error: 'Failed to read file content',
details: error.message
});
}
}
});
// Write file content
app.post('/api/v1/filesystem/file-content', validateApiKey, async (req, res) => {
try {
const { path: filePath, content } = req.body;
if (!filePath) {
return res.status(400).json({
success: false,
error: 'File path is required'
});
}
if (content === undefined || content === null) {
return res.status(400).json({
success: false,
error: 'File content is required'
});
}
const fullPath = resolveFilePath(filePath);
console.log(`📄 API Server: Writing file content to: ${filePath} -> ${fullPath}`);
console.log(`📄 API Server: Content length: ${content.length} characters`);
// Ensure parent directory exists
const parentDir = path.dirname(fullPath);
await fs.mkdir(parentDir, { recursive: true });
// Write file content
await fs.writeFile(fullPath, content, 'utf8');
// Get file stats after writing
const stats = await getFileStats(fullPath);
// Refresh file system cache
await refreshFileSystemCache();
console.log(`✅ API Server: File content written successfully`);
res.json({
success: true,
message: 'File content written successfully',
data: {
path: filePath,
size: stats?.size || content.length,
lastModified: stats?.lastModified || new Date(),
encoding: 'utf8'
}
});
} catch (error) {
console.error('❌ API Server: Error writing file content:', error);
if (error.code === 'EACCES') {
res.status(403).json({
success: false,
error: 'Permission denied'
});
} else if (error.code === 'ENOSPC') {
res.status(507).json({
success: false,
error: 'Insufficient storage space'
});
} else {
res.status(500).json({
success: false,
error: 'Failed to write file content',
details: error.message
});
}
}
});
// Create folder
app.post('/api/v1/filesystem/folder', validateApiKey, async (req, res) => {
try {
const { parentPath, name } = req.body;
if (!parentPath || !name) {
return res.status(400).json({
success: false,
error: 'Parent path and name are required'
});
}
const fullParentPath = resolveFilePath(parentPath);
const newFolderPath = path.join(fullParentPath, name);
console.log(`📁 API Server: Creating folder: ${parentPath}/${name} -> ${newFolderPath}`);
await fs.mkdir(newFolderPath, { recursive: true });
await refreshFileSystemCache();
console.log(`✅ API Server: Folder created successfully: ${newFolderPath}`);
res.json({
success: true,
message: 'Folder created successfully',
data: { path: newFolderPath }
});
} catch (error) {
console.error('❌ API Server: Error creating folder:', error);
res.status(500).json({
success: false,
error: 'Failed to create folder',
details: error.message
});
}
});
// Create file
app.post('/api/v1/filesystem/file', validateApiKey, async (req, res) => {
try {
const { parentPath, name } = req.body;
if (!parentPath || !name) {
return res.status(400).json({
success: false,
error: 'Parent path and name are required'
});
}
const fullParentPath = resolveFilePath(parentPath);
const newFilePath = path.join(fullParentPath, name);
console.log(`📄 API Server: Creating file: ${parentPath}/${name} -> ${newFilePath}`);
await fs.writeFile(newFilePath, '', 'utf8');
await refreshFileSystemCache();
console.log(`✅ API Server: File created successfully: ${newFilePath}`);
res.json({
success: true,
message: 'File created successfully',
data: { path: newFilePath }
});
} catch (error) {
console.error('❌ API Server: Error creating file:', error);
res.status(500).json({
success: false,
error: 'Failed to create file',
details: error.message
});
}
});
// Delete item - Updated to read path from query parameter
app.delete('/api/v1/filesystem/item', validateApiKey, async (req, res) => {
try {
const itemPath = req.query.path;
if (!itemPath) {
return res.status(400).json({
success: false,
error: 'Item path is required'
});
}
const fullPath = resolveFilePath(itemPath);
console.log(`🗑️ API Server: Attempting to delete item: ${itemPath} -> ${fullPath}`);
const stats = await getFileStats(fullPath);
if (!stats) {
console.log(`❌ API Server: Item not found: ${fullPath}`);
return res.status(404).json({
success: false,
error: 'Item not found'
});
}
if (stats.isDirectory) {
console.log(`📁 API Server: Deleting directory: ${fullPath}`);
await fs.rm(fullPath, { recursive: true, force: true });
} else {
console.log(`📄 API Server: Deleting file: ${fullPath}`);
await fs.unlink(fullPath);
}
await refreshFileSystemCache();
console.log(`✅ API Server: Item deleted successfully: ${fullPath}`);
res.json({
success: true,
message: 'Item deleted successfully'
});
} catch (error) {
console.error('❌ API Server: Error deleting item:', error);
res.status(500).json({
success: false,
error: 'Failed to delete item',
details: error.message
});
}
});
// Get servers list (existing functionality)
app.get('/api/v1/servers', validateApiKey, (req, res) => {
res.json({
success: true,
data: {
servers: mockServers,
total: mockServers.length
}
});
});
// Get specific server (existing functionality)
app.get('/api/v1/servers/:serverId', validateApiKey, (req, res) => {
const { serverId } = req.params;
const server = mockServers.find(s => s.id === serverId);
if (!server) {
return res.status(404).json({
success: false,
error: 'Server not found'
});
}
res.json({
success: true,
data: server
});
});
// Test server connection (existing functionality)
app.post('/api/v1/servers/:serverId/test', validateApiKey, (req, res) => {
const { serverId } = req.params;
const server = mockServers.find(s => s.id === serverId);
if (!server) {
return res.status(404).json({
success: false,
error: 'Server not found'
});
}
const isOnline = Math.random() > 0.3;
setTimeout(() => {
res.json({
success: true,
data: {
serverId,
connected: isOnline,
responseTime: Math.floor(Math.random() * 200) + 50,
timestamp: new Date().toISOString()
}
});
}, 1000 + Math.random() * 2000);
});
// Error handling middleware
app.use((error, req, res, next) => {
if (config.logging?.errors) {
console.error('❌ API Server Error:', error);
}
res.status(500).json({
success: false,
error: 'Internal server error',
details: process.env.NODE_ENV === 'development' ? error.message : undefined
});
});
// 404 handler
app.use('*', (req, res) => {
res.status(404).json({
success: false,
error: 'Endpoint not found'
});
});
// Initialize file system cache on startup
async function initializeServer() {
console.log(`🔧 API Server starting...`);
// Load configuration first
await loadConfig();
console.log(`📁 API Server: Root directory: ${config.server.rootDirectory}`);
console.log(`🔍 API Server: Max scan depth: ${config.filesystem.maxDepth}`);
if (config.server.rootDirectory === '/') {
console.log(`⚠️ API Server: WARNING - Root directory is '/', safety limits will be applied`);
}
// Start config file watcher
await watchConfigFile();
// Initial file system scan
await refreshFileSystemCache();
// Set up periodic cache refresh
const refreshInterval = config.filesystem?.cacheSettings?.refreshInterval || 300000; // 5 minutes default
setInterval(async () => {
console.log('🔄 API Server: Performing scheduled file system cache refresh...');
await refreshFileSystemCache();
}, refreshInterval);
}
// Start server
app.listen(PORT, async () => {
await initializeServer();
console.log(`🔧 API Server running on port ${PORT}`);
console.log(`🔑 API Server: API Key: ${API_KEY ? 'Configured' : 'Missing'}`);
console.log(`🌍 API Server: Environment: ${config.server?.environment || 'development'}`);
console.log(`👁️ API Server: Config file watching: Enabled`);
console.log(`📋 API Server: Available endpoints:`);
console.log(` GET /api/v1/config`);
console.log(` POST /api/v1/config`);
console.log(` POST /api/v1/config/root-directory`);
console.log(` GET /api/v1/health`);
console.log(` GET /api/v1/server-info`);
console.log(` GET /api/v1/filesystem`);
console.log(` POST /api/v1/filesystem/refresh`);
console.log(` GET /api/v1/filesystem/directory`);
console.log(` GET /api/v1/filesystem/file-content`);
console.log(` POST /api/v1/filesystem/file-content`);
console.log(` POST /api/v1/filesystem/folder`);
console.log(` POST /api/v1/filesystem/file`);
console.log(` DELETE /api/v1/filesystem/item`);
console.log(` GET /api/v1/servers`);
console.log(` GET /api/v1/servers/:id`);
console.log(` POST /api/v1/servers/:id/test`);
});