sawa-control-panel/backend/routes/apps.js

77 lines
2.2 KiB
JavaScript

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;