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>
45 lines
1.4 KiB
JavaScript
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
|
|
}
|