const express = require('express'); const router = express.Router(); const appInstaller = require('../services/appInstaller'); /** * GET /api/v1/apps * Lists all recipes merged with installed status. */ router.get('/', (req, res) => { try { const recipes = appInstaller.loadRecipes(); const installed = appInstaller.getInstalledApps(); const merged = recipes.map(recipe => ({ ...recipe, installed: !!installed[recipe.id], installedInfo: installed[recipe.id] || null })); res.json({ success: true, apps: merged }); } catch (err) { res.status(500).json({ success: false, error: err.message }); } }); /** * POST /api/v1/apps/:id/install * SSE endpoint for app installation streaming log. */ router.post('/:id/install', async (req, res) => { const { id } = req.params; const { domain, dbPassword, port } = req.body; // Only domain is strictly required at this layer. // appInstaller.js handles dbPassword validation if the recipe needs it. if (!domain) { return res.status(400).json({ success: false, error: 'Domain is required' }); } // Set headers for SSE res.setHeader('Content-Type', 'text/event-stream'); res.setHeader('Cache-Control', 'no-cache'); res.setHeader('Connection', 'keep-alive'); res.flushHeaders(); const onLog = (line) => { res.write(`data: ${JSON.stringify({ log: line })}\n\n`); }; try { const result = await appInstaller.streamInstall(id, { domain, dbPassword, port }, onLog); res.write(`data: ${JSON.stringify({ success: result.success, completed: true })}\n\n`); } catch (err) { res.write(`data: ${JSON.stringify({ error: err.message, completed: true })}\n\n`); } finally { res.end(); } }); /** * GET /api/v1/apps/:id/status */ router.get('/:id/status', (req, res) => { try { const installed = appInstaller.getInstalledApps(); const info = installed[req.params.id]; res.json({ success: true, installed: !!info, info: info || null }); } catch (err) { res.status(500).json({ success: false, error: err.message }); } }); module.exports = router;