nexus/.planning/phases/42-wallpapers-social-format-conversion-voice/42-03-SUMMARY.md

108 lines
5.1 KiB
Markdown

---
phase: 42-wallpapers-social-format-conversion-voice
plan: 03
subsystem: api
tags: [sharp, ffmpeg-static, xlsx, csv-parse, file-type, convert-renderer, multipart, magic-byte, content-jobs, typescript]
# Dependency graph
requires:
- phase: 42-wallpapers-social-format-conversion-voice
plan: 01
provides: ConvertBundle type, convert case in renderContent switch, converter-capabilities service
- phase: 40-content-job-infra
provides: contentJobStore, contentJobRunner, async job pattern
provides:
- server/src/services/renderers/convert-renderer.ts — full format conversion router (sharp/ffmpeg/xlsx/AI-bridge)
- server/src/routes/convert.ts — POST /api/companies/:companyId/convert (multipart, 202)
- server/src/routes/convert.ts — GET /api/system/converters (capability map)
- convert route mounted in app.ts
affects:
- 42-06 (UI wiring — ConvertPanel calls these endpoints)
# Tech tracking
tech-stack:
added: []
patterns:
- ffmpeg-static stdin/stdout pipe pattern for audio/video conversion
- csv-parse/sync for synchronous CSV parsing with column headers
- xlsx.read/write for XLSX ↔ JSON ↔ CSV round-trips
- fileTypeFromBuffer magic-byte MIME validation with text-based extension allowlist
- multer memoryStorage + runSingleFileUpload wrapper (follows chat-files.ts pattern)
key-files:
created:
- server/src/routes/convert.ts
modified:
- server/src/services/renderers/convert-renderer.ts
- server/src/app.ts
key-decisions:
- "ffmpegPath cast as unknown as string required — ffmpeg-static typings return string|null but binary is always present; matches documented Pitfall 3 from research"
- "TEXT_BASED_EXTENSIONS allowlist prevents false 422 rejections for CSV/JSON/SVG which have no magic bytes"
- "sourceMime resolved from extension map rather than multer claim to avoid browser MIME inconsistencies"
- "StorageService passed through to convertRoutes for future asset storage but not used in job dispatch path (job runner handles storage)"
# Metrics
duration: 3min
completed: 2026-04-04
---
# Phase 42 Plan 03: Format Conversion Renderer and Multipart Route
**Full convert-renderer.ts routing sharp/ffmpeg/xlsx/AI-bridge by format pair, multipart upload route with magic-byte MIME validation returning 202, and GET /api/system/converters capability endpoint**
## Performance
- **Duration:** 3 min
- **Started:** 2026-04-04T22:12:21Z
- **Completed:** 2026-04-04T22:15:16Z
- **Tasks:** 2
- **Files modified:** 3 (1 replaced, 1 created, 1 modified)
## Accomplishments
- Replaced stub convert-renderer.ts with full implementation routing four format categories:
- Image pairs (PNG/JPG/WebP/GIF/SVG) → sharp with density:300 for SVG input
- Audio/video pairs → ffmpeg-static via stdin/stdout pipe (`ffmpegPath as unknown as string`)
- Data pairs (CSV/JSON/XLSX) → csv-parse/sync + xlsx for all six conversion combinations
- All other pairs → AI-bridge via puterChatComplete with system prompt
- Created convert.ts route with multer memoryStorage, fileTypeFromBuffer magic-byte validation, and 202 job dispatch
- Implemented MIME mismatch detection: returns 422 with `{ error, actualMime, claimedMime }` for misnamed binary files
- Text-based formats (CSV, JSON, SVG, TXT, HTML, MD, XML) allowed through without magic-byte check (they return null)
- Wired GET /api/system/converters endpoint returning converterCapabilitiesService result
- Mounted convertRoutes in app.ts after contentJobRoutes
## Task Commits
Each task was committed atomically:
1. **Task 1: Implement convert renderer with format routing** - `d5f7586d` (feat)
2. **Task 2: Create multipart convert route with MIME validation and wire to app.ts** - `84f97a43` (feat)
## Files Created/Modified
- `server/src/services/renderers/convert-renderer.ts` - Full implementation: isImageFormat/isAVFormat/isDataFormat helpers, convertImage/convertAV/convertData/convertViaAiBridge functions, renderConvert entry point
- `server/src/routes/convert.ts` - Multipart upload route: multer, fileTypeFromBuffer MIME validation, content job dispatch, converter capability endpoint
- `server/src/app.ts` - Import and mount convertRoutes after contentJobRoutes
## Decisions Made
- `ffmpegPath as unknown as string` cast used in spawn — ffmpeg-static typings declare `string | null` but the binary is always present when the package is installed; matches Pitfall 3 documented in 42-RESEARCH.md
- `TEXT_BASED_EXTENSIONS` allowlist prevents false 422 rejection for text-based formats whose `fileTypeFromBuffer` returns null (no magic bytes)
- Source MIME resolved from extension map rather than multer's `file.mimetype` to avoid browser-reported MIME inconsistencies (e.g. Chrome reports `text/csv` but Safari may report `application/csv`)
## Deviations from Plan
None — plan executed exactly as written.
## Known Stubs
None — all conversion paths are fully implemented.
## Self-Check: PASSED
- `server/src/services/renderers/convert-renderer.ts` — FOUND
- `server/src/routes/convert.ts` — FOUND
- Task 1 commit `d5f7586d` — FOUND
- Task 2 commit `84f97a43` — FOUND
- `tsc --noEmit` — zero errors