const express = require('express'); const jwt = require('jsonwebtoken'); const fs = require('fs'); const path = require('path'); const app = express(); const PORT = process.env.PORT || 3000; const JWT_SECRET = process.env.JWT_SECRET || 'fallback-secret-key'; const APPS_FILE = path.join(__dirname, 'data', 'apps.json'); app.use(express.json()); app.use((req, res, next) => { res.setHeader('Access-Control-Allow-Origin', '*'); res.setHeader('Access-Control-Allow-Methods', 'GET, POST, PUT, DELETE, OPTIONS'); res.setHeader('Access-Control-Allow-Headers', 'Content-Type, Authorization'); if (req.method === 'OPTIONS') { return res.status(200).end(); } next(); }); // Health check app.get('/health', (req, res) => { res.json({ status: 'ok', timestamp: new Date().toISOString() }); }); function verifyToken(req, res, next) { const authHeader = req.headers.authorization; if (!authHeader) { return res.status(401).json({ error: 'Authorization header required' }); } try { const token = authHeader.split(' ')[1]; const decoded = jwt.verify(token, JWT_SECRET); req.auth = decoded; next(); } catch (err) { return res.status(401).json({ error: 'Invalid or expired token' }); } } function readAppsFile() { try { if (fs.existsSync(APPS_FILE)) { const data = fs.readFileSync(APPS_FILE, 'utf8'); return JSON.parse(data); } } catch (err) { console.error('Error reading apps file:', err); } return []; } function writeAppsFile(apps) { try { const tempFile = `${APPS_FILE}.tmp`; fs.writeFileSync(tempFile, JSON.stringify(apps, null, 2)); fs.renameSync(tempFile, APPS_FILE); return true; } catch (err) { console.error('Error writing apps file:', err); return false; } } app.post('/api/admin/login', (req, res) => { const { token } = req.body; const adminToken = process.env.ADMIN_TOKEN; if (!adminToken) { return res.status(500).json({ error: 'ADMIN_TOKEN not configured' }); } if (token === adminToken) { const payload = { admin: true, iat: Math.floor(Date.now() / 1000), exp: Math.floor(Date.now() / 1000) + (60 * 60 * 24) }; const secret = process.env.JWT_SECRET || 'fallback-secret-key'; const apiKey = jwt.sign(payload, secret); return res.json({ apiKey }); } return res.status(401).json({ error: 'Invalid token' }); }); app.get('/api/apps', (req, res) => { const apps = readAppsFile(); return res.json(apps); }); app.post('/api/admin/apps', verifyToken, (req, res) => { if (!req.auth || !req.auth.admin) { return res.status(403).json({ error: 'Admin access required' }); } const { name, description, icon, colors, packages } = req.body; if (!name || !description) { return res.status(400).json({ error: 'Name and description are required' }); } let apps = readAppsFile(); const newId = apps.length > 0 ? Math.max(...apps.map(a => a.id)) + 1 : 1; const newApp = { id: newId, name, description, icon: icon || '', colors: colors || { background: '', text: '' }, packages: packages || { apt: '', appstream: '', flatpak: '' } }; apps.push(newApp); if (writeAppsFile(apps)) { return res.status(201).json(newApp); } return res.status(500).json({ error: 'Failed to save app' }); }); app.put('/api/admin/apps/:id', verifyToken, (req, res) => { if (!req.auth || !req.auth.admin) { return res.status(403).json({ error: 'Admin access required' }); } const { id } = req.params; const updates = req.body; let apps = readAppsFile(); const appIndex = apps.findIndex(a => a.id === parseInt(id)); if (appIndex === -1) { return res.status(404).json({ error: 'App not found' }); } const updatedApp = { ...apps[appIndex], ...updates, id: parseInt(id) }; apps[appIndex] = updatedApp; if (writeAppsFile(apps)) { return res.json(updatedApp); } return res.status(500).json({ error: 'Failed to update app' }); }); app.delete('/api/admin/apps/:id', verifyToken, (req, res) => { if (!req.auth || !req.auth.admin) { return res.status(403).json({ error: 'Admin access required' }); } const { id } = req.params; let apps = readAppsFile(); const appIndex = apps.findIndex(a => a.id === parseInt(id)); if (appIndex === -1) { return res.status(404).json({ error: 'App not found' }); } apps.splice(appIndex, 1); if (writeAppsFile(apps)) { return res.json({ message: 'App deleted successfully' }); } return res.status(500).json({ error: 'Failed to delete app' }); }); app.listen(PORT, () => { console.log(`Admin API running on port ${PORT}`); });