import express from 'express'; import cors from 'cors'; import fs from 'fs/promises'; import path from 'path'; import { fileURLToPath } from 'url'; import dotenv from 'dotenv'; // Load environment variables dotenv.config(); const __filename = fileURLToPath(import.meta.url); const __dirname = path.dirname(__filename); const app = express(); const PORT = process.env.BACKEND_PORT || 3001; // Configuration from environment variables const STORAGE_ENABLED = process.env.ENABLE_SERVER_STORAGE === 'true'; const STORAGE_PATH = process.env.STORAGE_PATH || '/data/diagrams'; const ENABLE_GIT_BACKUP = process.env.ENABLE_GIT_BACKUP === 'true'; // Middleware app.use(cors()); app.use(express.json({ limit: '10mb' })); // Health check / Storage status endpoint app.get('/api/storage/status', (req, res) => { res.json({ enabled: STORAGE_ENABLED, gitBackup: ENABLE_GIT_BACKUP, version: '1.0.0' }); }); // Only enable storage endpoints if storage is enabled if (STORAGE_ENABLED) { // Ensure storage directory exists async function ensureStorageDir() { try { await fs.access(STORAGE_PATH); console.log(`Storage directory exists: ${STORAGE_PATH}`); // Log current files const files = await fs.readdir(STORAGE_PATH); console.log(`Current files in storage: ${files.length} files`); if (files.length > 0) { console.log('Files:', files.join(', ')); } } catch { console.log(`Creating storage directory: ${STORAGE_PATH}`); await fs.mkdir(STORAGE_PATH, { recursive: true }); console.log(`Created storage directory: ${STORAGE_PATH}`); } } // Initialize storage ensureStorageDir().catch((err) => { console.error('Failed to initialize storage:', err); }); // List all diagrams app.get('/api/diagrams', async (req, res) => { try { // First check if storage directory exists try { await fs.access(STORAGE_PATH); } catch (err) { console.error(`Storage directory does not exist: ${STORAGE_PATH}`); return res.json([]); // Return empty array if directory doesn't exist } const files = await fs.readdir(STORAGE_PATH); console.log(`Found ${files.length} files in ${STORAGE_PATH}:`, files); const diagrams = []; for (const file of files) { if (file.endsWith('.json') && file !== 'metadata.json') { try { const filePath = path.join(STORAGE_PATH, file); const stats = await fs.stat(filePath); const content = await fs.readFile(filePath, 'utf-8'); const data = JSON.parse(content); // Extract name from various possible locations const name = data.name || data.title || 'Untitled Diagram'; console.log(`Successfully read diagram: ${file} (name: ${name})`); diagrams.push({ id: file.replace('.json', ''), name: name, lastModified: stats.mtime, size: stats.size }); } catch (fileError) { console.error(`Error reading diagram file ${file}:`, fileError.message); // Skip this file and continue with others continue; } } } console.log(`Returning ${diagrams.length} diagrams`); res.json(diagrams); } catch (error) { console.error('Error listing diagrams:', error); res.status(500).json({ error: 'Failed to list diagrams', details: error.message }); } }); // Get specific diagram app.get('/api/diagrams/:id', async (req, res) => { const diagramId = req.params.id; console.log(`[GET /api/diagrams/${diagramId}] Loading diagram...`); try { const filePath = path.join(STORAGE_PATH, `${diagramId}.json`); console.log(`[GET /api/diagrams/${diagramId}] Reading from: ${filePath}`); const content = await fs.readFile(filePath, 'utf-8'); const data = JSON.parse(content); console.log(`[GET /api/diagrams/${diagramId}] Successfully loaded, size: ${content.length} bytes, items: ${data.items?.length || 0}`); res.json(data); } catch (error) { if (error.code === 'ENOENT') { console.error(`[GET /api/diagrams/${diagramId}] Diagram not found`); res.status(404).json({ error: 'Diagram not found' }); } else { console.error(`[GET /api/diagrams/${diagramId}] Error reading diagram:`, error); res.status(500).json({ error: 'Failed to read diagram' }); } } }); // Save or update diagram app.put('/api/diagrams/:id', async (req, res) => { const diagramId = req.params.id; console.log(`[PUT /api/diagrams/${diagramId}] Saving diagram...`); try { const filePath = path.join(STORAGE_PATH, `${diagramId}.json`); const data = { ...req.body, id: diagramId, lastModified: new Date().toISOString() }; const iconCount = data.icons?.length || 0; const importedIconCount = (data.icons || []).filter(icon => icon.collection === 'imported').length; console.log(`[PUT /api/diagrams/${diagramId}] Writing to: ${filePath}`); console.log(`[PUT /api/diagrams/${diagramId}] Items: ${data.items?.length || 0}, Icons: ${iconCount} (${importedIconCount} imported)`); await fs.writeFile(filePath, JSON.stringify(data, null, 2)); console.log(`[PUT /api/diagrams/${diagramId}] Successfully saved`); // Git backup if enabled if (ENABLE_GIT_BACKUP) { // TODO: Implement git commit console.log('[PUT] Git backup not yet implemented'); } res.json({ success: true, id: diagramId }); } catch (error) { console.error(`[PUT /api/diagrams/${diagramId}] Error saving diagram:`, error); res.status(500).json({ error: 'Failed to save diagram' }); } }); // Delete diagram app.delete('/api/diagrams/:id', async (req, res) => { try { const filePath = path.join(STORAGE_PATH, `${req.params.id}.json`); await fs.unlink(filePath); res.json({ success: true }); } catch (error) { if (error.code === 'ENOENT') { res.status(404).json({ error: 'Diagram not found' }); } else { console.error('Error deleting diagram:', error); res.status(500).json({ error: 'Failed to delete diagram' }); } } }); // Create a new diagram app.post('/api/diagrams', async (req, res) => { try { const id = req.body.id || `diagram_${Date.now()}`; const filePath = path.join(STORAGE_PATH, `${id}.json`); // Check if already exists try { await fs.access(filePath); return res.status(409).json({ error: 'Diagram already exists' }); } catch { // File doesn't exist, proceed } const data = { ...req.body, id, created: new Date().toISOString(), lastModified: new Date().toISOString() }; await fs.writeFile(filePath, JSON.stringify(data, null, 2)); res.status(201).json({ success: true, id }); } catch (error) { console.error('Error creating diagram:', error); res.status(500).json({ error: 'Failed to create diagram' }); } }); } else { // Storage disabled - return appropriate responses app.get('/api/diagrams', (req, res) => { res.status(503).json({ error: 'Server storage is disabled' }); }); app.get('/api/diagrams/:id', (req, res) => { res.status(503).json({ error: 'Server storage is disabled' }); }); app.put('/api/diagrams/:id', (req, res) => { res.status(503).json({ error: 'Server storage is disabled' }); }); app.delete('/api/diagrams/:id', (req, res) => { res.status(503).json({ error: 'Server storage is disabled' }); }); app.post('/api/diagrams', (req, res) => { res.status(503).json({ error: 'Server storage is disabled' }); }); } // Start server app.listen(PORT, () => { console.log(`FossFLOW Backend Server running on port ${PORT}`); console.log(`Server storage: ${STORAGE_ENABLED ? 'ENABLED' : 'DISABLED'}`); if (STORAGE_ENABLED) { console.log(`Storage path: ${STORAGE_PATH}`); console.log(`Git backup: ${ENABLE_GIT_BACKUP ? 'ENABLED' : 'DISABLED'}`); } });