79 lines
2.9 KiB
JavaScript
79 lines
2.9 KiB
JavaScript
import { Job } from '../db/models.js';
|
|
import { enqueueJob } from '../jobs/queue.js';
|
|
import { registerClient } from '../ws/broadcast.js';
|
|
import { findModel, DEFAULT_MODEL_ID } from '../models.js';
|
|
|
|
/**
|
|
* @param {import('fastify').FastifyInstance} fastify
|
|
*/
|
|
export async function jobRoutes(fastify) {
|
|
// ------------------------------------------------------------------
|
|
// WebSocket — all clients connect here to receive live job updates
|
|
// ------------------------------------------------------------------
|
|
fastify.get('/ws', { websocket: true }, (socket) => {
|
|
registerClient(socket);
|
|
|
|
Job.findAll({ order: [['createdAt', 'DESC']], limit: 100 })
|
|
.then((jobs) => {
|
|
if (socket.readyState === 1) {
|
|
socket.send(JSON.stringify({ type: 'init', jobs: jobs.map((j) => j.toJSON()) }));
|
|
}
|
|
})
|
|
.catch(() => {});
|
|
});
|
|
|
|
// ------------------------------------------------------------------
|
|
// POST /api/jobs
|
|
// Multipart form fields: prompt (text), modelId (text), image (file)
|
|
// ------------------------------------------------------------------
|
|
fastify.post('/api/jobs', async (req, reply) => {
|
|
const data = await req.file();
|
|
if (!data) return reply.status(400).send({ error: 'No file uploaded' });
|
|
|
|
const fields = data.fields;
|
|
const prompt = (fields.prompt?.value ?? '').trim();
|
|
const modelId = (fields.modelId?.value ?? DEFAULT_MODEL_ID).trim();
|
|
|
|
if (!prompt) return reply.status(400).send({ error: 'prompt is required' });
|
|
|
|
// Validate that the submitted model ID is in our allowed list
|
|
const modelMeta = findModel(modelId);
|
|
if (!modelMeta) {
|
|
return reply.status(400).send({ error: `Unknown model: ${modelId}` });
|
|
}
|
|
|
|
const chunks = [];
|
|
for await (const chunk of data.file) chunks.push(chunk);
|
|
const buffer = Buffer.concat(chunks);
|
|
const mimeType = data.mimetype || 'image/jpeg';
|
|
const imageDataUrl = `data:${mimeType};base64,${buffer.toString('base64')}`;
|
|
|
|
const job = await Job.create({
|
|
prompt,
|
|
imageDataUrl,
|
|
imageMimeType: mimeType,
|
|
model: modelId,
|
|
});
|
|
|
|
enqueueJob(job.id);
|
|
return reply.status(201).send(job.toJSON());
|
|
});
|
|
|
|
// ------------------------------------------------------------------
|
|
// GET /api/jobs
|
|
// ------------------------------------------------------------------
|
|
fastify.get('/api/jobs', async (_req, reply) => {
|
|
const jobs = await Job.findAll({ order: [['createdAt', 'DESC']], limit: 100 });
|
|
return reply.send(jobs.map((j) => j.toJSON()));
|
|
});
|
|
|
|
// ------------------------------------------------------------------
|
|
// GET /api/jobs/:id
|
|
// ------------------------------------------------------------------
|
|
fastify.get('/api/jobs/:id', async (req, reply) => {
|
|
const job = await Job.findByPk(req.params.id);
|
|
if (!job) return reply.status(404).send({ error: 'Not found' });
|
|
return reply.send(job.toJSON());
|
|
});
|
|
}
|