Fix structured attachment rendering (#69)
* review: harden readonly git checks and lint verification * test: satisfy readonly reviewer sandbox typing * test: fix readonly reviewer sandbox assertions * test: remove impossible readonly sandbox guards * test: relax readonly sandbox assertions * test: cast readonly sandbox expectation shape * test: cast readonly sandbox expectation through unknown * Phase 0 — STEP_DONE/TASK_DONE split + owner-follow-up integration smoke * Add STEP_DONE guards, verdict storage, and stale delivery suppression * style: format paired stepdone telemetry files * Route STEP_DONE through reviewer * Add structured Discord attachments * style: format structured attachment test * Fix room thread bot output parity * Remove duplicate work item attachment migration from owner branch * Remove legacy dashboard rooms renderer * Extract dashboard parsed body renderer * Extract dashboard redaction helpers * Extract dashboard RoomCardV2 component * Include RoomCardV2 test in vitest suite * Extract dashboard RoomBoardV2 component * Extract dashboard EmptyState component * Extract dashboard InboxPanel component * Split dashboard InboxPanel card renderer * Extract dashboard TaskPanel component * Extract dashboard UsagePanel component * Fix dashboard room thread chunk rendering * Extract dashboard ServicePanel component * Render dashboard live progress markdown * Extract dashboard SettingsPanel component * Fix structured attachment rendering
This commit is contained in:
50
src/web-dashboard-attachments.ts
Normal file
50
src/web-dashboard-attachments.ts
Normal file
@@ -0,0 +1,50 @@
|
||||
import fs from 'fs';
|
||||
import path from 'path';
|
||||
|
||||
import { validateOutboundAttachments } from './outbound-attachments.js';
|
||||
|
||||
function attachmentJsonResponse(value: unknown, init?: ResponseInit): Response {
|
||||
return new Response(JSON.stringify(value), {
|
||||
...init,
|
||||
headers: {
|
||||
'content-type': 'application/json; charset=utf-8',
|
||||
...(init?.headers as Record<string, string> | undefined),
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
function getAttachmentContentType(filePath: string): string {
|
||||
const ext = path.extname(filePath).toLowerCase();
|
||||
if (ext === '.png') return 'image/png';
|
||||
if (ext === '.jpg' || ext === '.jpeg') return 'image/jpeg';
|
||||
if (ext === '.gif') return 'image/gif';
|
||||
if (ext === '.webp') return 'image/webp';
|
||||
if (ext === '.bmp') return 'image/bmp';
|
||||
return 'application/octet-stream';
|
||||
}
|
||||
|
||||
export function serveValidatedAttachment(url: URL): Response {
|
||||
const requestedPath = url.searchParams.get('path');
|
||||
if (!requestedPath) {
|
||||
return attachmentJsonResponse(
|
||||
{ error: 'Missing attachment path' },
|
||||
{ status: 400 },
|
||||
);
|
||||
}
|
||||
|
||||
const validation = validateOutboundAttachments([{ path: requestedPath }]);
|
||||
const file = validation.files[0];
|
||||
if (!file) {
|
||||
return attachmentJsonResponse(
|
||||
{ error: 'Attachment not found or not allowed' },
|
||||
{ status: 404 },
|
||||
);
|
||||
}
|
||||
|
||||
return new Response(fs.readFileSync(file.attachment), {
|
||||
headers: {
|
||||
'content-type': getAttachmentContentType(file.attachment),
|
||||
'cache-control': 'private, max-age=300',
|
||||
},
|
||||
});
|
||||
}
|
||||
Reference in New Issue
Block a user