--- 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