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>
This commit is contained in:
1 parent
f254e3ec19
commit
f525e7e784
6 files changed
+546
-26
No files matched your search
@@ -0,0 +1,44 @@
|
||||
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
|
||||
}
|
||||
Reference in new issue
Block a user