feat(intake): record whether the submitter asked for a cut-out (#281)
The public intake POST now reads a `removeBackground` multipart field and stores it on the new `item_drafts.remove_background` column. The checkbox on the submission page is ticked by default, so a client that sends nothing gets `true` — only the exact string `'false'` opts out, so a stray or unexpected value is treated as consent rather than a silent refusal. The GET now also reports `backgroundRemoval: isRembgConfigured()` alongside the label, so the submission page knows up front whether the feature exists in this environment at all. Neither handler calls the sidecar or the AI — this task only records intent for the drafting worker to act on later, and the existing ordering of `requireUsableLink` and `requireCapacity` ahead of `uploadImages` is unchanged. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
@@ -8,6 +8,7 @@ import { draftQueued } from '../intake/draftingWorker';
|
||||
import { checkCapacity, countForLinkSince, windowStart } from '../intake/capacity';
|
||||
import { alertCeilingReached, alertLinkThreshold } from '../intake/abuseAlert';
|
||||
import { getSettings } from '../adminSettings';
|
||||
import { isRembgConfigured } from '../intake/rembgClient';
|
||||
|
||||
const router = Router();
|
||||
|
||||
@@ -128,8 +129,9 @@ router.get('/:token', intakeViewLimiter, asyncRoute(async (req: Request, res: Re
|
||||
if (!link) {
|
||||
return res.status(404).json({ error: 'not found' });
|
||||
}
|
||||
// The label only. Nothing about the catalogue, the admin, or other links.
|
||||
res.json({ label: link.label });
|
||||
// The label, and whether the background-removal control has anything behind
|
||||
// it. Still nothing about the catalogue, the admin, or other links.
|
||||
res.json({ label: link.label, backgroundRemoval: isRembgConfigured() });
|
||||
}));
|
||||
|
||||
router.post(
|
||||
@@ -159,6 +161,15 @@ router.post(
|
||||
|
||||
const note = typeof req.body?.note === 'string' ? req.body.note.trim() : '';
|
||||
|
||||
// Absent means yes: the checkbox on the page is ticked by default, so a
|
||||
// client that does not send the field — an older build, or a script — gets
|
||||
// what every other submission gets rather than silently opting out.
|
||||
//
|
||||
// Only the exact string opts out. Multipart fields arrive as strings, and
|
||||
// reading a stray value as "no" would quietly deny somebody something they
|
||||
// asked for.
|
||||
const removeBackground = req.body?.removeBackground !== 'false';
|
||||
|
||||
const client = await pool.connect();
|
||||
try {
|
||||
await client.query('BEGIN');
|
||||
@@ -178,9 +189,9 @@ router.post(
|
||||
await insertItemImages(client, itemId, files, 0);
|
||||
|
||||
await client.query(
|
||||
`INSERT INTO item_drafts (item_id, upload_link_id, submitter_note)
|
||||
VALUES ($1, $2, $3)`,
|
||||
[itemId, link.id, note === '' ? null : note]
|
||||
`INSERT INTO item_drafts (item_id, upload_link_id, submitter_note, remove_background)
|
||||
VALUES ($1, $2, $3, $4)`,
|
||||
[itemId, link.id, note === '' ? null : note, removeBackground]
|
||||
);
|
||||
|
||||
// Counted inside the transaction and guarded on the same conditions as
|
||||
|
||||
Reference in New Issue
Block a user