| import { logger } from '../logger'; |
| |
| |
| |
| |
| |
| |
| |
| |
|
|
| import fs from 'fs/promises'; |
| import path from 'path'; |
| import crypto from 'crypto'; |
|
|
| |
| 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!; |
|
|
| |
| 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}`; |
|
|
| |
| try { |
| const check = await fetch(finalUrl, { method: 'HEAD' }); |
| if (!check.ok) { |
| logger.warn(`[Storage] β οΈ Public access check failed for ${finalUrl} (Status: ${check.status})`); |
| } else { |
| logger.info(`[Storage] β
Verified public access: ${finalUrl}`); |
| } |
| } catch (err: unknown) { |
| logger.warn(`[Storage] β οΈ Could not verify public access for ${finalUrl}: ${(err instanceof Error ? (err instanceof Error ? err.message : String(err)) : String(err))}`); |
| } |
|
|
| return finalUrl; |
| } |
|
|
| |
| async function saveLocally(buffer: Buffer, filename: string): Promise<string> { |
| const tmpPath = path.join('/tmp', filename); |
| await fs.writeFile(tmpPath, buffer); |
| logger.warn(`[Storage] R2 not configured β file saved locally to ${tmpPath}`); |
| return `file://${tmpPath}`; |
| } |
|
|
| |
| 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 |
| ); |
| } |
|
|
| |
| |
| |
| |
| |
| |
| |
| export async function uploadFile(buffer: Buffer, originalFilename: string, contentType: string, organizationId?: string): Promise<string> { |
| |
| const ext = path.extname(originalFilename); |
| const prefix = organizationId ? `orgs/${organizationId}/` : ''; |
| const uniqueName = `${prefix}${crypto.randomUUID()}-${Date.now()}${ext}`; |
|
|
| if (isR2Configured()) { |
| try { |
| return await uploadToR2(buffer, uniqueName, contentType); |
| } catch (err: unknown) { |
| logger.error(`[Storage] R2 Upload Failed: ${(err instanceof Error ? err.message : String(err))}. Falling back to local.`); |
| } |
| } |
| return saveLocally(buffer, uniqueName); |
| } |
|
|