const router = require('express').Router(); const { authMiddleware, moderatorMiddleware, adminMiddleware } = require('../middleware/auth'); const images = require('../utils/generatedImages'); const db = require('../db/database'); router.use(authMiddleware); function fail(res, e) { res.status(e.statusCode || 503).json({ error: e.statusCode ? e.message : 'Image service unavailable' }); } async function sendAsset(req, res, download) { const image = await images.service().asset(req.params.id, req.user); res.setHeader('Content-Type', image.mime); res.setHeader('Content-Length', image.bytes.length); res.setHeader('X-Content-Type-Options', 'nosniff'); res.setHeader('Cache-Control', 'private, no-store'); res.setHeader('X-Image-SHA256', image.checksum); res.setHeader('X-Image-Owner', String(req.user.id)); // requesting account ticket, not original author res.setHeader('Content-Disposition', (download ? 'attachment' : 'inline') + '; filename="generated-image.' + ({ 'image/png': 'png', 'image/jpeg': 'jpg', 'image/webp': 'webp' })[image.mime] + '"'); res.send(image.bytes); } router.get('/generated-images/:id', async (req, res) => { try { await sendAsset(req, res, req.query.download === '1'); } catch (e) { fail(res, e); } }); router.get('/image-jobs/:workflow', async (req, res) => { try { if (!['clinical_assistant', 'learning_hub'].includes(req.params.workflow)) throw images.failure(404, 'Workflow not found'); const result = await db.query('SELECT id,stage,model,error_code,context_included,context_total,prompt_units,budget FROM generated_image_jobs WHERE owner_id=$1 AND workflow=$2 ORDER BY created_at DESC LIMIT 100', [req.user.id, req.params.workflow]); res.json({ success: true, jobs: result.rows.map(images.publicJob) }); } catch (e) { fail(res, e); } }); // Mounted narrowly before the server's blanket /api/admin guards. const learningRouter = require('express').Router(); learningRouter.use(authMiddleware, moderatorMiddleware); learningRouter.post('/jobs', async (req, res) => { try { res.json(await images.service().enqueue(req.user.id, 'learning_hub', { prompt: req.body.prompt, ...(req.body.layout === undefined ? {} : { layout: req.body.layout }) }, images.requestKey(req.body), false, req.body.content === undefined ? undefined : images.imageContext(req.body.prompt, [{ role: 'user', content: req.body.content }]))); } catch (e) { fail(res, e); } }); learningRouter.get('/jobs/:id', async (req, res) => { try { res.json(await images.service().get(req.params.id, req.user.id, 'learning_hub')); } catch (e) { fail(res, e); } }); router.get('/admin/image-settings', adminMiddleware, async (req, res) => { try { const workflows = {}; for (const workflow of ['clinical_assistant', 'learning_hub']) workflows[workflow] = { model: await db.getSetting(workflow + '.image_model') || '', budget: images.budgetLimit(await db.getSetting(workflow + '.image_budget')), unit: 'UTF-16 code units' }; res.json({ success: true, workflows }); } catch (e) { fail(res, e); } }); router.put('/admin/image-settings/:workflow', adminMiddleware, async (req, res) => { try { const workflow = req.params.workflow; if (!['clinical_assistant', 'learning_hub'].includes(workflow)) throw images.failure(404, 'Workflow not found'); const budget = images.budgetLimit(req.body.budget); if (workflow === 'learning_hub' && (typeof req.body.model !== 'string' || !/^[a-zA-Z0-9_.:/-]{1,200}$/.test(req.body.model))) throw images.failure(400, 'Enter an image model ID enabled at the configured image gateway'); const client = await db.pool.connect(); try { await client.query('BEGIN'); for (const [key, value] of Object.entries(workflow === 'learning_hub' ? { image_model: req.body.model, image_budget: budget } : { image_budget: budget })) { await client.query('INSERT INTO app_settings(key,value) VALUES($1,$2) ON CONFLICT(key) DO UPDATE SET value=$2,updated_at=NOW()', [workflow + '.' + key, String(value)]); } await client.query('COMMIT'); } catch (e) { await client.query('ROLLBACK').catch(() => {}); throw e; } finally { client.release(); } res.json({ success: true }); } catch (e) { fail(res, e); } }); module.exports = router; module.exports.sendAsset = sendAsset; module.exports.learningRouter = learningRouter;