Publish Container / build (push) Successful in 16m39s
- Asset Manager: search/filter/sort, list+grid views, rename, move between R2 folders, tag chips, bulk actions, used-in counts; fix R2 delete lookup - Inbox: readable wrapped rows with subject/preview, real read state, type filters, bulk actions, mojibake repair - Broadcast: pick a Resend template to fill subject and body Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
54 lines
1.7 KiB
JavaScript
54 lines
1.7 KiB
JavaScript
import { S3Client, PutObjectCommand, DeleteObjectCommand, CopyObjectCommand, 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 copyInR2(fromKey, toKey) {
|
|
await makeClient().send(new CopyObjectCommand({
|
|
Bucket: R2_BUCKET,
|
|
CopySource: `${R2_BUCKET}/${fromKey.split('/').map(encodeURIComponent).join('/')}`,
|
|
Key: toKey,
|
|
}))
|
|
return `${R2_PUBLIC_URL}/${toKey}`
|
|
}
|
|
|
|
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
|
|
}
|