Integrate metadata-cleaner service into the file upload flow #47

Closed
opened 2026-07-15 19:37:01 +00:00 by laoc · 4 comments
Owner

Motivation

A user requested being able to use our metadata-cleaner service from inside the app. Uploaded files (PDFs from Canva & co., photos with EXIF data) carry tool provenance and potentially sensitive metadata. The service (deployed at cleaner.edufeed.org) can inspect, strip, and — for PDFs — recompress embedded images.

Design (approved)

UX: optional review step. After picking a file in the upload components, a "Check metadata" button appears (only when the feature is configured and the file type is supported: PDF, JPG, PNG, TIFF, WebP). It opens a modal that:

  1. Sends the file to the cleaner and shows all metadata, grouped by store (DocInfo/XMP/EXIF/IPTC/…), sensitive fields flagged
  2. Offers "Remove tool provenance" (the service's strip preset, shown as a reviewable list) and — PDF only — a compression picker (off / balanced / strong)
  3. Applies and shows the verified result: metadata before/after, file size before/after, leak-scan result
  4. "Use cleaned file" replaces the pending file; the normal license-attestation + Blossom upload continues with the cleaned copy. "Keep original" backs out unchanged.

Skipping the button = today's behavior. Service unreachable → friendly error, uploading the original still works.

Scope: content uploads. LicensedFileInput (resource/publication attachments — the only PDF surface, so the only one with compression) and LicensedImageInput (cover images: resource wizard, article, calendar event). Avatars, banners, and MarkdownEditor inline images are out of scope for now.

Integration: SvelteKit server proxy /api/metaclean/[...path] (the service sends no CORS headers, and this matches the /api/oer / /api/enrich pattern):

App route Forwards to
POST /api/metaclean/files POST /api/files (upload → session + inspected fields)
GET /api/metaclean/files/{id}/ops/strip strip-provenance ops
POST /api/metaclean/files/{id}/apply apply ops + compress
GET /api/metaclean/files/{id}/download cleaned file

Strict allowlist; anything else → 404.

Configuration: new env var METADATA_CLEANER_URL (server-side only, e.g. https://cleaner.edufeed.org). /api/config exposes only metadataCleaner: { enabled: boolean }. Unset → proxy returns 503, UI hidden entirely.

i18n: all strings via Paraglide (DE/EN).

Testing: unit tests for config exposure, proxy allowlist/503/forwarding, client helper; component test for the modal with mocked API. No E2E (external service).

Out of scope (possible follow-ups)

  • Avatars/banners/inline markdown images
  • OER provenance writing (POST /api/oer-ops + sidecar) hooked into the resource wizard
  • Standalone image compression (client-side or service extension)
  • Field-level metadata editing (set/add)
## Motivation A user requested being able to use our [metadata-cleaner service](https://git.edufeed.org/edufeed/metadata-cleaner) from inside the app. Uploaded files (PDFs from Canva & co., photos with EXIF data) carry tool provenance and potentially sensitive metadata. The service (deployed at `cleaner.edufeed.org`) can inspect, strip, and — for PDFs — recompress embedded images. ## Design (approved) **UX: optional review step.** After picking a file in the upload components, a **"Check metadata"** button appears (only when the feature is configured and the file type is supported: PDF, JPG, PNG, TIFF, WebP). It opens a modal that: 1. Sends the file to the cleaner and shows **all metadata**, grouped by store (DocInfo/XMP/EXIF/IPTC/…), sensitive fields flagged 2. Offers **"Remove tool provenance"** (the service's strip preset, shown as a reviewable list) and — **PDF only** — a compression picker (off / balanced / strong) 3. Applies and shows the verified result: metadata before/after, file size before/after, leak-scan result 4. **"Use cleaned file"** replaces the pending file; the normal license-attestation + Blossom upload continues with the cleaned copy. "Keep original" backs out unchanged. Skipping the button = today's behavior. Service unreachable → friendly error, uploading the original still works. **Scope: content uploads.** `LicensedFileInput` (resource/publication attachments — the only PDF surface, so the only one with compression) and `LicensedImageInput` (cover images: resource wizard, article, calendar event). Avatars, banners, and MarkdownEditor inline images are out of scope for now. **Integration: SvelteKit server proxy** `/api/metaclean/[...path]` (the service sends no CORS headers, and this matches the `/api/oer` / `/api/enrich` pattern): | App route | Forwards to | |---|---| | `POST /api/metaclean/files` | `POST /api/files` (upload → session + inspected fields) | | `GET /api/metaclean/files/{id}/ops/strip` | strip-provenance ops | | `POST /api/metaclean/files/{id}/apply` | apply ops + compress | | `GET /api/metaclean/files/{id}/download` | cleaned file | Strict allowlist; anything else → 404. **Configuration:** new env var `METADATA_CLEANER_URL` (server-side only, e.g. `https://cleaner.edufeed.org`). `/api/config` exposes only `metadataCleaner: { enabled: boolean }`. Unset → proxy returns 503, UI hidden entirely. **i18n:** all strings via Paraglide (DE/EN). **Testing:** unit tests for config exposure, proxy allowlist/503/forwarding, client helper; component test for the modal with mocked API. No E2E (external service). ## Out of scope (possible follow-ups) - Avatars/banners/inline markdown images - OER provenance writing (`POST /api/oer-ops` + sidecar) hooked into the resource wizard - Standalone image compression (client-side or service extension) - Field-level metadata editing (set/add)
Author
Owner

Implemented and merged into dev (e4960690).

  • Optional review step (interstitial modal) in LicensedFileInput + LicensedImageInput: shows all metadata grouped by store with sensitive flags, strip-provenance preset with reviewable field list, PDF compression (off/balanced/strong), verified result with leak scan.
  • Server proxy /api/metaclean/[...path] (strict allowlist, 503 when METADATA_CLEANER_URL unset, body cap via METADATA_CLEANER_MAX_UPLOAD_MB, default 200).
  • Oversized PDFs are routed through the cleaner so compression can bring them under the BLOSSOM_MAX_FILE_SIZE limit (live-verified: 15.5 MB → 906 KB).
  • Fully inert when the env var is unset. i18n DE/EN.

Deployment note: adapter-node needs BODY_SIZE_LIMIT raised (default 512K) for /api/metaclean uploads.

Out of scope / follow-ups: avatars/banners/inline markdown images, OER provenance writing (oer-ops + sidecar), field-level editing.

Implemented and merged into `dev` (e4960690). - Optional review step (interstitial modal) in `LicensedFileInput` + `LicensedImageInput`: shows all metadata grouped by store with sensitive flags, strip-provenance preset with reviewable field list, PDF compression (off/balanced/strong), verified result with leak scan. - Server proxy `/api/metaclean/[...path]` (strict allowlist, 503 when `METADATA_CLEANER_URL` unset, body cap via `METADATA_CLEANER_MAX_UPLOAD_MB`, default 200). - Oversized PDFs are routed through the cleaner so compression can bring them under the `BLOSSOM_MAX_FILE_SIZE` limit (live-verified: 15.5 MB → 906 KB). - Fully inert when the env var is unset. i18n DE/EN. Deployment note: adapter-node needs `BODY_SIZE_LIMIT` raised (default 512K) for `/api/metaclean` uploads. Out of scope / follow-ups: avatars/banners/inline markdown images, OER provenance writing (`oer-ops` + sidecar), field-level editing.
Author
Owner

UX revision in progress (user feedback): the auto-opening review modal is too prominent/confusing for non-technical users.

Revised design:

  • Normal files: no interstitial. A plain-language checkbox in the license modal ("Remove hidden file metadata before upload", unchecked by default) + compress select for PDFs + a read-only "show details" metadata view. Cleaning runs silently during the deferred Blossom upload; subtle "metadata removed (N fields)" confirmation afterwards; service failure falls back to uploading the original.
  • Oversized PDFs: modal still auto-opens but reframed compression-first ("file is X MB, limit is 5 MB — we can compress it", balanced preselected, metadata table collapsed).

Branch: worktree-metaclean-quiet-flow.

**UX revision in progress** (user feedback): the auto-opening review modal is too prominent/confusing for non-technical users. Revised design: - **Normal files:** no interstitial. A plain-language checkbox in the license modal ("Remove hidden file metadata before upload", unchecked by default) + compress select for PDFs + a read-only "show details" metadata view. Cleaning runs silently during the deferred Blossom upload; subtle "metadata removed (N fields)" confirmation afterwards; service failure falls back to uploading the original. - **Oversized PDFs:** modal still auto-opens but reframed compression-first ("file is X MB, limit is 5 MB — we can compress it", balanced preselected, metadata table collapsed). Branch: `worktree-metaclean-quiet-flow`.
Author
Owner

Quiet flow shipped and merged into dev (9677aebe):

  • Normal files: no interruption — opt-in block in the license dialog (checkbox "Remove hidden file metadata", PDF compress select, read-only "Show details" view). Cleaning runs silently during the upload; the attestation references the cleaned bytes; subtle "metadata removed (N fields)" note afterwards; service failure falls back to the original.
  • Oversized PDFs: the auto-modal remains, reframed compression-first (size vs. limit lead, Balanced preselected, metadata collapsed).

Verified live against cleaner.edufeed.org (15.6 MB → 906 KB rescue, inspect view, quiet checkbox flow).

Quiet flow shipped and merged into `dev` (9677aebe): - Normal files: no interruption — opt-in block in the license dialog (checkbox "Remove hidden file metadata", PDF compress select, read-only "Show details" view). Cleaning runs silently during the upload; the attestation references the cleaned bytes; subtle "metadata removed (N fields)" note afterwards; service failure falls back to the original. - Oversized PDFs: the auto-modal remains, reframed compression-first (size vs. limit lead, Balanced preselected, metadata collapsed). Verified live against cleaner.edufeed.org (15.6 MB → 906 KB rescue, inspect view, quiet checkbox flow).
Author
Owner

Closing — implemented, revised after user feedback, and both rounds are on dev.

Verified at dev @ 09842a48 rather than relying on the merge notes above:

  • src/routes/api/metaclean/[...path]/+server.js — the proxy
  • src/lib/helpers/metaclean.js — client helper
  • src/lib/components/shared/MetadataCleanerModal.svelte, wired into both LicensedFileInput.svelte and LicensedImageInput.svelte (the approved scope: content uploads, not avatars/banners/inline images)
  • metadataCleaner: { enabled } in /api/config (+server.js:411), enabled: Boolean(env.METADATA_CLEANER_URL) — inert when unset, as designed
  • i18n present in DE + EN (metaclean_*)
  • Tests: api-metaclean-route, api-config-metaclean, metaclean-helper, MetadataCleanerModal, plus LicensedFileInput.metaclean and LicensedImageInput.metaclean

e1257495, f907e426, 638f79f7, e4960690 and 9677aebe are all ancestors of dev.

Deployment note preserved from the thread, because it is easy to lose: METADATA_CLEANER_URL is set on the dev instance only, so the feature is currently absent from edufeed.org — and adapter-node needs BODY_SIZE_LIMIT raised above its 512K default for the uploads. Both are tracked in the dev -> main release checklist (PR #56), so closing this does not drop them.

The follow-ups listed in the last comment (avatars/banners/inline markdown images, OER provenance writing) were explicitly out of scope; if they are wanted they deserve their own issue rather than keeping this one open.

Closing — implemented, revised after user feedback, and both rounds are on `dev`. Verified at `dev` @ `09842a48` rather than relying on the merge notes above: - `src/routes/api/metaclean/[...path]/+server.js` — the proxy - `src/lib/helpers/metaclean.js` — client helper - `src/lib/components/shared/MetadataCleanerModal.svelte`, wired into both `LicensedFileInput.svelte` and `LicensedImageInput.svelte` (the approved scope: content uploads, not avatars/banners/inline images) - `metadataCleaner: { enabled }` in `/api/config` (`+server.js:411`), `enabled: Boolean(env.METADATA_CLEANER_URL)` — inert when unset, as designed - i18n present in DE + EN (`metaclean_*`) - Tests: `api-metaclean-route`, `api-config-metaclean`, `metaclean-helper`, `MetadataCleanerModal`, plus `LicensedFileInput.metaclean` and `LicensedImageInput.metaclean` `e1257495`, `f907e426`, `638f79f7`, `e4960690` and `9677aebe` are all ancestors of `dev`. **Deployment note preserved from the thread, because it is easy to lose:** `METADATA_CLEANER_URL` is set on the **dev instance only**, so the feature is currently absent from edufeed.org — and adapter-node needs `BODY_SIZE_LIMIT` raised above its 512K default for the uploads. Both are tracked in the `dev -> main` release checklist (PR #56), so closing this does not drop them. The follow-ups listed in the last comment (avatars/banners/inline markdown images, OER provenance writing) were explicitly out of scope; if they are wanted they deserve their own issue rather than keeping this one open.
laoc closed this issue 2026-07-30 09:50:07 +00:00
Sign in to join this conversation.
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set

Reference
edufeed/edufeed-app#47
No description provided.