Files
Siteforge/server/r2.js
T
nmemmertandClaude Sonnet 4.6 f525e7e784 Add Cloudflare R2 storage for admin asset uploads; v2.0.37
New uploads route to R2 with folder selection (uploads/images/guides/finished).
Existing local files continue to be served via express.static for backward compat.
Adds R2_ACCOUNT_ID, R2_ACCESS_KEY_ID, R2_SECRET_ACCESS_KEY, R2_BUCKET,
R2_PUBLIC_URL env vars. listUploadedAssets merges R2 objects + local files.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-09-18 12:04:55 -04:00

45 lines
1.4 KiB
JavaScript

import { S3Client, PutObjectCommand, DeleteObjectCommand, ListObjectsV2Command } from '@aws-sdk/client-s3'
import { R2_ACCOUNT_ID, R2_ACCESS_KEY_ID, R2_SECRET_ACCESS_KEY, R2_BUCKET, R2_PUBLIC_URL } from './config.js'
function makeClient() {
return new S3Client({
region: 'auto',
endpoint: `https://${R2_ACCOUNT_ID}.r2.cloudflarestorage.com`,
credentials: { accessKeyId: R2_ACCESS_KEY_ID, secretAccessKey: R2_SECRET_ACCESS_KEY },
})
}
export function isR2Configured() {
return Boolean(R2_ACCOUNT_ID && R2_ACCESS_KEY_ID && R2_SECRET_ACCESS_KEY && R2_BUCKET && R2_PUBLIC_URL)
}
export async function uploadToR2(buffer, key, contentType) {
await makeClient().send(new PutObjectCommand({
Bucket: R2_BUCKET,
Key: key,
Body: buffer,
ContentType: contentType,
}))
return `${R2_PUBLIC_URL}/${key}`
}
export async function deleteFromR2(key) {
await makeClient().send(new DeleteObjectCommand({ Bucket: R2_BUCKET, Key: key }))
}
export async function listR2Objects(prefix) {
const objects = []
let continuationToken
const client = makeClient()
do {
const resp = await client.send(new ListObjectsV2Command({
Bucket: R2_BUCKET,
Prefix: prefix,
ContinuationToken: continuationToken,
}))
for (const obj of resp.Contents ?? []) objects.push(obj)
continuationToken = resp.IsTruncated ? resp.NextContinuationToken : undefined
} while (continuationToken)
return objects
}