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

11 KiB

phase plan type wave depends_on files_modified autonomous requirements must_haves
42-wallpapers-social-format-conversion-voice 03 execute 2
42-01
server/src/services/renderers/convert-renderer.ts
server/src/routes/convert.ts
server/src/app.ts
true
CONV-01
CONV-02
CONV-03
CONV-04
CONV-05
CONV-06
CONV-07
CONV-09
truths artifacts key_links
Image format conversion (PNG, JPG, SVG, WebP, GIF) works via sharp
Audio/video format conversion works via ffmpeg-static
Data format conversion (CSV, JSON, XLSX) works via xlsx + csv-parse
Unsupported format pairs fall through to AI-bridge via puterChatComplete
Uploaded files are validated via magic-byte detection before processing
POST /api/companies/:companyId/convert accepts multipart upload and returns 202
GET /api/system/converters returns capability map
path provides exports
server/src/services/renderers/convert-renderer.ts Format conversion router: sharp, ffmpeg, xlsx/csv, AI-bridge
renderConvert
path provides contains
server/src/routes/convert.ts Multipart upload route + capability endpoint fileTypeFromBuffer
path provides contains
server/src/app.ts Mount point for convert routes convertRoutes
from to via pattern
server/src/routes/convert.ts server/src/services/renderers/convert-renderer.ts content job dispatch with convert type jobType.*convert
from to via pattern
server/src/routes/convert.ts file-type magic-byte MIME validation fileTypeFromBuffer
from to via pattern
server/src/services/renderers/convert-renderer.ts sharp image format conversion sharp
from to via pattern
server/src/services/renderers/convert-renderer.ts ffmpeg-static audio/video conversion ffmpegPath
Implement the format conversion renderer (routing to sharp/ffmpeg/xlsx/AI-bridge by format pair) and the multipart upload route with magic-byte MIME validation.

Purpose: This plan fulfills all CONV requirements on the server side. The conversion UI in Plan 06 depends on these endpoints. Output: Working convert-renderer.ts, convert route with MIME validation, capability endpoint.

<execution_context> @$HOME/.claude/get-shit-done/workflows/execute-plan.md @$HOME/.claude/get-shit-done/templates/summary.md </execution_context>

@.planning/PROJECT.md @.planning/ROADMAP.md @.planning/phases/42-wallpapers-social-format-conversion-voice/42-RESEARCH.md @.planning/phases/42-wallpapers-social-format-conversion-voice/42-01-SUMMARY.md

@server/src/services/renderers/types.ts @server/src/services/converter-capabilities.ts @server/src/routes/chat-files.ts @server/src/routes/content-jobs.ts @server/src/app.ts @server/src/services/puter-inference.ts

From server/src/services/renderers/types.ts (after Plan 01): ```typescript export interface ConvertBundle { type: "convert-bundle"; outputFilename: string; outputMime: string; outputBase64: string; method: "direct" | "ai-bridge"; } export interface RenderResult { filename: string; contentType: string; buffer: Buffer; } ```

From server/src/services/converter-capabilities.ts (Plan 01):

export interface ConverterCapabilities {
  imageConverter: boolean;
  audioVideoConverter: boolean;
  docConverter: boolean;
  dataConverter: boolean;
}
export function converterCapabilitiesService(): { get(): Promise<ConverterCapabilities> };

From server/src/routes/chat-files.ts (multer pattern):

const fileUpload = multer({ storage: multer.memoryStorage() });
Task 1: Implement convert renderer with format routing server/src/services/renderers/convert-renderer.ts server/src/services/renderers/icon-renderer.ts, server/src/services/renderers/types.ts, server/src/services/puter-inference.ts Replace the stub convert-renderer.ts with a full implementation:
  1. Export async function renderConvert(input: Record<string, unknown>): Promise<RenderResult>:

    • Extract fileBase64 (string), sourceMime (string), targetFormat (string), originalFilename (string) from input
    • Decode fileBase64 to Buffer
    • Route by format category using helper functions:
  2. Image conversion (isImagePair): use sharp

    • sharp(fileBuffer).toFormat(targetFormat as keyof sharp.FormatEnum).toBuffer()
    • Supported: png, jpg/jpeg, webp, gif, svg (SVG input handled by sharp with density: 300)
    • Return ConvertBundle with method: "direct"
  3. Audio/video conversion (isAVPair): use ffmpeg-static

    • Copy the ffmpeg spawn pattern from research (Pitfall 3: use ffmpegPath as unknown as string)
    • Spawn ffmpeg with -f {sourceFormat} -i pipe:0 -f {targetFormat} pipe:1
    • stdin.write(fileBuffer), stdin.end(), collect stdout chunks
    • Return ConvertBundle with method: "direct"
  4. Data conversion (isDataPair): use xlsx + csv-parse

    • CSV to JSON: parse CSV with csv-parse, return JSON array
    • JSON to CSV: read JSON array, generate CSV with headers from first object keys
    • XLSX to JSON: xlsx.read(buffer), first sheet to json
    • JSON to XLSX: build worksheet from JSON array, write xlsx buffer
    • CSV to XLSX: parse CSV first, then build xlsx
    • XLSX to CSV: read xlsx to JSON, then serialize CSV
    • Return ConvertBundle with method: "direct"
  5. AI-bridge fallback (all other pairs): use puterChatComplete

    • System prompt: "Convert the following {sourceMime} content to {targetFormat} format. Return ONLY the converted content, no explanation."
    • For text-based formats: pass file content as string
    • For binary formats: describe the conversion needed and return best-effort result
    • Return ConvertBundle with method: "ai-bridge"
  6. Helper functions (not exported):

    • isImageFormat(mime: string): boolean — checks for image/png, image/jpeg, image/webp, image/gif, image/svg+xml
    • isAVFormat(mime: string): boolean — checks for audio/* and video/*
    • isDataFormat(mime: string): boolean — checks for text/csv, application/json, application/vnd.openxmlformats-officedocument.spreadsheetml.sheet
    • getTargetMime(targetFormat: string): string — maps format string to MIME type cd /opt/nexus/server && npx tsc --noEmit 2>&1 | head -20 <acceptance_criteria>
    • grep "renderConvert" server/src/services/renderers/convert-renderer.ts
    • grep "sharp" server/src/services/renderers/convert-renderer.ts
    • grep "ffmpegPath" server/src/services/renderers/convert-renderer.ts
    • grep "xlsx" server/src/services/renderers/convert-renderer.ts
    • grep "csv-parse" server/src/services/renderers/convert-renderer.ts
    • grep "puterChatComplete" server/src/services/renderers/convert-renderer.ts
    • grep "ai-bridge" server/src/services/renderers/convert-renderer.ts
    • grep "isImageFormat" server/src/services/renderers/convert-renderer.ts </acceptance_criteria> Convert renderer routes to sharp (images), ffmpeg (audio/video), xlsx/csv-parse (data), and AI-bridge (fallback) based on format pair.
Task 2: Create multipart convert route with MIME validation and wire to app.ts server/src/routes/convert.ts, server/src/app.ts server/src/routes/chat-files.ts, server/src/routes/content-jobs.ts, server/src/app.ts 1. Create server/src/routes/convert.ts following the chat-files.ts multer pattern:
import { Router } from "express";
import multer from "multer";
import { fileTypeFromBuffer } from "file-type";

POST /api/companies/:companyId/convert:

  • multer.memoryStorage() with single("file") upload
  • Extract targetFormat from req.body.targetFormat
  • Run fileTypeFromBuffer(file.buffer) for magic-byte detection
  • Build a map of extension-to-expected-MIME for validation: png→image/png, jpg/jpeg→image/jpeg, gif→image/gif, webp→image/webp, svg→image/svg+xml, mp3→audio/mpeg, mp4→video/mp4, wav→audio/wav, ogg→audio/ogg, csv→text/csv, json→application/json, xlsx→application/vnd.openxmlformats-officedocument.spreadsheetml.sheet
  • If fileTypeFromBuffer returns a result AND the detected MIME differs from the extension-implied MIME, return 422 with { error, actualMime, claimedMime } (CONV-09)
  • Note: text-based files (CSV, JSON, SVG, MD, HTML) may return null from fileTypeFromBuffer — allow these through (they have no magic bytes)
  • Create content job via contentJobStore (follow content-jobs.ts pattern): jobType "convert", input { fileBase64: buffer.toString('base64'), sourceMime, targetFormat, originalFilename }
  • Dispatch via contentJobRunner
  • Return 202 with { jobId, status: "queued" }

GET /api/system/converters:

  • Import converterCapabilitiesService from converter-capabilities.ts
  • Return JSON of capabilities
  1. Wire in server/src/app.ts:
    • Import convertRoutes from "./routes/convert.js"
    • Mount on the api router alongside existing content-jobs routes
    • Place AFTER the body-parser middleware (multer needs raw body access)

CRITICAL: Use file-type ESM named import { fileTypeFromBuffer } — NOT default import (Pitfall 2 from research). CRITICAL: Do NOT validate text-based files that return null from fileTypeFromBuffer — they have no magic bytes. cd /opt/nexus/server && npx tsc --noEmit 2>&1 | head -20 <acceptance_criteria> - grep "fileTypeFromBuffer" server/src/routes/convert.ts - grep "multer" server/src/routes/convert.ts - grep "targetFormat" server/src/routes/convert.ts - grep "422" server/src/routes/convert.ts - grep "actualMime" server/src/routes/convert.ts - grep "system/converters" server/src/routes/convert.ts - grep "convertRoutes" server/src/app.ts - grep "202" server/src/routes/convert.ts </acceptance_criteria> Multipart convert route validates MIME via magic bytes, dispatches async job, returns 202. Capability endpoint exposes converter availability. Route mounted in app.ts.

- `cd /opt/nexus/server && npx tsc --noEmit` passes with zero errors - Convert route handles multipart upload with MIME validation - GET /api/system/converters endpoint exists - All format categories (image, AV, data, AI-bridge) have conversion paths

<success_criteria>

  • Convert renderer routes all format pairs to appropriate converter or AI fallback
  • Multipart upload route validates MIME via magic bytes (CONV-09)
  • Route returns 202 with job ID (async pattern)
  • Capability endpoint returns converter availability map (CONV-08)
  • tsc compiles cleanly </success_criteria>
After completion, create `.planning/phases/42-wallpapers-social-format-conversion-voice/42-03-SUMMARY.md`