73 lines
3.3 KiB
JavaScript
73 lines
3.3 KiB
JavaScript
import { s3Storage } from '@payloadcms/storage-s3';
|
|
/**
|
|
* Cloudflare R2 media storage — configured from environment variables (agency
|
|
* infrastructure, not per-project panel data). R2 is S3-compatible, so we use
|
|
* @payloadcms/storage-s3 pointed at the R2 endpoint.
|
|
*
|
|
* Storage is infrastructure (like the database or PAYLOAD_SECRET): it binds at
|
|
* boot, and its credentials are agency-owned — so it lives in .env, not the
|
|
* panel. See docs/storage.md for the required variables.
|
|
*
|
|
* Returns the storage plugin when all R2 vars are present; otherwise returns a
|
|
* no-op passthrough so the project falls back to Payload's default local disk
|
|
* storage (useful in dev without R2). This mirrors how mailAdapter degrades
|
|
* gracefully when a transport isn't configured.
|
|
*
|
|
* @param collections - slugs of upload collections to offload to R2 (e.g. ['media'])
|
|
*/ export const buildR2Storage = (collections = [
|
|
'media'
|
|
])=>{
|
|
const bucket = process.env.R2_BUCKET;
|
|
const endpoint = process.env.R2_ENDPOINT;
|
|
const accessKeyId = process.env.R2_ACCESS_KEY_ID;
|
|
const secretAccessKey = process.env.R2_SECRET_ACCESS_KEY;
|
|
// Any missing → skip R2, fall back to local disk. Warn so it's not silent.
|
|
if (!bucket || !endpoint || !accessKeyId || !secretAccessKey) {
|
|
return (config)=>{
|
|
// Only warn when SOME vars are set (partial config = likely a mistake).
|
|
if (bucket || endpoint || accessKeyId || secretAccessKey) {
|
|
console.warn('[ipal] R2 storage: incomplete env (need R2_BUCKET, R2_ENDPOINT, ' + 'R2_ACCESS_KEY_ID, R2_SECRET_ACCESS_KEY). Falling back to local disk.');
|
|
}
|
|
return config;
|
|
};
|
|
}
|
|
// s3Storage wants Record<string, true> (the literal true, per collection),
|
|
// not Record<string, boolean>. Object.fromEntries widens true → boolean, so
|
|
// build the map with an explicitly-typed accumulator to keep the literal.
|
|
const collectionsConfig = {};
|
|
for (const slug of collections){
|
|
collectionsConfig[slug] = true;
|
|
}
|
|
// Public URL for served media. R2 is private by default; with a custom domain
|
|
// (media.klient.pl → bucket) set R2_PUBLIC_URL so Payload generates URLs
|
|
// pointing there instead of the private S3 endpoint (which 403s on the front).
|
|
// Without it, uploads work but images don't display publicly. See docs/storage.md.
|
|
const publicUrl = process.env.R2_PUBLIC_URL?.replace(/\/$/, '') // strip trailing slash
|
|
;
|
|
return s3Storage({
|
|
bucket,
|
|
collections: collectionsConfig,
|
|
...publicUrl ? {
|
|
// generateFileURL overrides the stored/returned URL to the CDN domain.
|
|
// Params come from Payload's storage plugin; type them explicitly since
|
|
// the callback shape isn't inferred here (would be implicit any).
|
|
generateFileURL: ({ filename, prefix })=>[
|
|
publicUrl,
|
|
prefix,
|
|
filename
|
|
].filter(Boolean).join('/')
|
|
} : {},
|
|
config: {
|
|
credentials: {
|
|
accessKeyId,
|
|
secretAccessKey
|
|
},
|
|
endpoint,
|
|
region: 'auto',
|
|
// R2 requires path-style addressing for S3 compatibility.
|
|
forcePathStyle: true
|
|
}
|
|
});
|
|
};
|
|
|
|
//# sourceMappingURL=buildR2Storage.js.map
|