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; }; } // Public URL for served media. R2 is private by default; its S3 endpoint only // accepts uploads and won't serve files (403). With a custom domain // (media.klient.pl → bucket) set R2_PUBLIC_URL so Payload generates URLs // pointing there. 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 ; // generateFileURL is a PER-COLLECTION option in @payloadcms/storage-s3 (not a // top-level one) — R2 needs it to point served URLs at the custom domain // instead of the private S3 endpoint. Each collection gets either `true` // (plain offload) or an object carrying generateFileURL when a public URL is set. const generateFileURL = publicUrl ? ({ filename, prefix })=>[ publicUrl, prefix, filename ].filter(Boolean).join('/') : undefined; const collectionsConfig = {}; for (const slug of collections){ collectionsConfig[slug] = generateFileURL ? { generateFileURL } : true; } return s3Storage({ bucket, collections: collectionsConfig, config: { credentials: { accessKeyId, secretAccessKey }, endpoint, region: 'auto', // R2 requires path-style addressing for S3 compatibility. forcePathStyle: true } }); }; //# sourceMappingURL=buildR2Storage.js.map