feat: add auto-increment id, size/duration tracking, and soft cleanup to conversion_jobs

Rename the old CHAR(36) id to uuid (still used for public URLs and file
naming) and add a real auto-increment id as the primary key. Track
input/output file size and conversion duration per job. Cleanup no
longer deletes rows; it marks cleaned_at and skips already-cleaned
expired jobs on later runs.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
2026-07-30 22:06:32 +02:00
co-authored by Claude Sonnet 5
parent 24f7ab2625
commit ff5a257a0e
10 changed files with 208 additions and 117 deletions
+9 -8
View File
@@ -10,7 +10,7 @@ import { registerImageToPdfConverter } from './converters/imageToPdf.js';
import { registerDocumentConverters } from './converters/document.js';
import { resolveInputFormat } from './mime.js';
import { deleteIfExists } from './storage.js';
import { createJob, getJobById } from './jobs/jobRepository.js';
import { createJob, getJobByUuid } from './jobs/jobRepository.js';
import { outputPath } from './storage.js';
let convertersRegistered = false;
@@ -77,7 +77,7 @@ export function createApp(config, pool) {
for (let i = 0; i < req.files.length; i += 1) {
const file = req.files[i];
const targetFormat = targetFormats[i];
const id = path.basename(file.filename, path.extname(file.filename));
const uuid = path.basename(file.filename, path.extname(file.filename));
const sourceFormat = path.extname(file.filename).slice(1).toLowerCase();
const { mime, valid } = await resolveInputFormat(file.path, sourceFormat);
@@ -99,30 +99,31 @@ export function createApp(config, pool) {
const expiresAt = new Date(Date.now() + config.retentionHours * 3600 * 1000);
await createJob(pool, {
id,
uuid,
family: registryEntry.family,
sourceFormat,
targetFormat,
originalFilename: file.originalname,
inputPath: file.filename,
inputMimeType: mime,
inputSizeBytes: file.size,
expiresAt,
});
results.push({ file: file.originalname, id, status: 'pending' });
results.push({ file: file.originalname, id: uuid, status: 'pending' });
}
res.status(201).json({ jobs: results });
});
app.get('/api/jobs/:id', async (req, res) => {
const job = await getJobById(pool, req.params.id);
const job = await getJobByUuid(pool, req.params.id);
if (!job) {
return res.status(404).json({ error: 'Job not found' });
}
res.json({
id: job.id,
id: job.uuid,
status: job.status,
originalFilename: job.originalFilename,
sourceFormat: job.sourceFormat,
@@ -138,7 +139,7 @@ export function createApp(config, pool) {
}
app.get('/api/jobs/:id/download', async (req, res) => {
const job = await getJobById(pool, req.params.id);
const job = await getJobByUuid(pool, req.params.id);
if (!job) {
return res.status(404).json({ error: 'Job not found' });
}
@@ -146,7 +147,7 @@ export function createApp(config, pool) {
return res.status(409).json({ error: `Job is not ready yet (status: ${job.status})` });
}
const filePath = outputPath(config, job.id, job.targetFormat);
const filePath = outputPath(config, job.uuid, job.targetFormat);
const downloadFilename = `${path.parse(job.originalFilename).name}.${job.targetFormat}`;
res.set('Content-Type', job.outputMimeType);
res.set('Content-Disposition', contentDispositionHeader(downloadFilename));