CognxSafeTrack
chore: execute Sprint 38 technical debt resolution (Type Safety, Zod validation, Vitest, Mock LLM extracted)
d9879cf
/**
* Storage Service β€” Cloudflare R2 (S3-compatible) with local /tmp fallback
*
* Required env vars for R2 mode:
* R2_ACCOUNT_ID, R2_ACCESS_KEY_ID, R2_SECRET_ACCESS_KEY, R2_BUCKET, R2_PUBLIC_URL
*
* Falls back to saving files in /tmp in development (returns a file:// path).
*/
import fs from 'fs/promises';
import path from 'path';
import crypto from 'crypto';
// ─── R2 / S3 Upload ───────────────────────────────────────────────────────────
async function uploadToR2(buffer: Buffer, filename: string, contentType: string): Promise<string> {
const accountId = process.env.R2_ACCOUNT_ID!;
const bucket = process.env.R2_BUCKET!;
const accessKeyId = process.env.R2_ACCESS_KEY_ID!;
const secretAccessKey = process.env.R2_SECRET_ACCESS_KEY!;
const publicUrl = process.env.R2_PUBLIC_URL!; // e.g. https://pub-xxx.r2.dev
// Use native fetch with AWS Signature V4 (no SDK dependency needed for simple PUT)
const { S3Client, PutObjectCommand } = await import('@aws-sdk/client-s3');
const client = new S3Client({
region: 'auto',
endpoint: `https://${accountId}.r2.cloudflarestorage.com`,
credentials: { accessKeyId, secretAccessKey },
});
await client.send(new PutObjectCommand({
Bucket: bucket,
Key: filename,
Body: buffer,
ContentType: contentType,
}));
const finalUrl = `${publicUrl.replace(/\/$/, "")}/${filename}`;
// ─── Production Hardening: Verify Public Access ─────────
try {
const check = await fetch(finalUrl, { method: 'HEAD' });
if (!check.ok) {
console.warn(`[Storage] ⚠️ Public access check failed for ${finalUrl} (Status: ${check.status})`);
} else {
console.log(`[Storage] βœ… Verified public access: ${finalUrl}`);
}
} catch (err: unknown) {
console.warn(`[Storage] ⚠️ Could not verify public access for ${finalUrl}: ${(err instanceof Error ? (err instanceof Error ? err.message : String(err)) : String(err))}`);
}
return finalUrl;
}
// ─── Local /tmp Fallback ──────────────────────────────────────────────────────
async function saveLocally(buffer: Buffer, filename: string): Promise<string> {
const tmpPath = path.join('/tmp', filename);
await fs.writeFile(tmpPath, buffer);
console.warn(`[Storage] R2 not configured β€” file saved locally to ${tmpPath}`);
return `file://${tmpPath}`;
}
// ─── Public API ───────────────────────────────────────────────────────────────
function isR2Configured(): boolean {
return !!(
process.env.R2_ACCOUNT_ID &&
process.env.R2_ACCESS_KEY_ID &&
process.env.R2_SECRET_ACCESS_KEY &&
process.env.R2_BUCKET &&
process.env.R2_PUBLIC_URL
);
}
/**
* Upload a buffer to Cloudflare R2 (or save locally in dev) and return the public URL.
* @param buffer - File contents
* @param filename - Filename with extension (e.g. "onepager-1234.pdf")
* @param contentType - MIME type (e.g. "application/pdf")
*/
export async function uploadFile(buffer: Buffer, originalFilename: string, contentType: string): Promise<string> {
// ─── Production Hardening: Unique Filenames ─────────────
const ext = path.extname(originalFilename);
const uniqueName = `${crypto.randomUUID()}-${Date.now()}${ext}`;
if (isR2Configured()) {
try {
return await uploadToR2(buffer, uniqueName, contentType);
} catch (err: unknown) {
console.error(`[Storage] R2 Upload Failed: ${(err instanceof Error ? (err instanceof Error ? err.message : String(err)) : String(err))}. Falling back to local.`);
}
}
return saveLocally(buffer, uniqueName);
}