UNPKG

graphile-settings

Version:
137 lines (136 loc) 5.72 kB
"use strict"; /** * Presigned URL resolver for the Constructive presigned URL plugin. * * Reads CDN/S3 configuration from the standard env system * (getEnvOptions → pgpmDefaults + config files + env vars) and lazily * initializes an S3Client on first use. * * Also provides a per-database bucket name resolver that derives the * S3 bucket name from the database UUID + a configurable prefix. * * Follows the same lazy-init pattern as upload-resolver.ts. */ Object.defineProperty(exports, "__esModule", { value: true }); exports.getPresignedUrlS3Config = getPresignedUrlS3Config; exports.createBucketNameResolver = createBucketNameResolver; exports.getAllowedOrigins = getAllowedOrigins; exports.createEnsureBucketProvisioned = createEnsureBucketProvisioned; const s3_utils_1 = require("@constructive-io/s3-utils"); const graphql_env_1 = require("@constructive-io/graphql-env"); const logger_1 = require("@pgpmjs/logger"); const bucket_provisioner_1 = require("@constructive-io/bucket-provisioner"); const bucket_provisioner_resolver_1 = require("./bucket-provisioner-resolver"); const log = new logger_1.Logger('presigned-url-resolver'); let s3Config = null; /** * Lazily initialize and return the S3Config for the presigned URL plugin. * * Reads CDN config on first call via getEnvOptions() (which already merges * pgpmDefaults → config file → env vars), creates an S3Client, and caches * the result. Same CDN config as upload-resolver.ts. * * NOTE: The `bucket` field here is the global fallback bucket name * (from BUCKET_NAME env var). When `resolveBucketName` is provided, * per-database bucket names take precedence for all S3 operations. */ function getPresignedUrlS3Config() { if (s3Config) return s3Config; const { cdn } = (0, graphql_env_1.getEnvOptions)(); if (!cdn) { throw new Error('[presigned-url-resolver] CDN config not found. ' + 'Ensure CDN environment variables (AWS_ACCESS_KEY, AWS_SECRET_KEY, etc.) ' + 'are set or that pgpmDefaults provides CDN fields.'); } const { bucketName, awsRegion, awsAccessKey, awsSecretKey, endpoint, publicUrlPrefix } = cdn; if (!awsAccessKey || !awsSecretKey) { throw new Error('[presigned-url-resolver] Missing S3 credentials. ' + 'Set AWS_ACCESS_KEY and AWS_SECRET_KEY environment variables.'); } if (!bucketName) { throw new Error('[presigned-url-resolver] Missing CDN bucket name. ' + 'Set CDN_BUCKET_NAME environment variable.'); } log.info(`[presigned-url-resolver] Initializing: bucket=${bucketName} endpoint=${endpoint}`); const client = (0, s3_utils_1.createS3Client)({ provider: (cdn.provider || 'minio'), region: awsRegion, accessKeyId: awsAccessKey, secretAccessKey: awsSecretKey, ...(endpoint ? { endpoint } : {}), }); s3Config = { client, bucket: bucketName, region: awsRegion, publicUrlPrefix, ...(endpoint ? { endpoint, forcePathStyle: true } : {}), }; return s3Config; } /** * Create a per-(database, bucketKey) bucket name resolver. * * Uses the BUCKET_NAME env var as a prefix. For each (database, bucketKey) * pair, the S3 bucket name becomes `{prefix}-{bucketKey}-{databaseId}` * (e.g., "myapp-public-abc123def456"). * * This aligns with the bucket provisioner plugin which creates separate * S3 buckets per logical bucket key. */ function createBucketNameResolver() { const { cdn } = (0, graphql_env_1.getEnvOptions)(); const prefix = cdn?.bucketName || 'test-bucket'; return (databaseId, bucketKey) => { return `${prefix}-${bucketKey}-${databaseId}`; }; } /** * Resolve CORS allowed origins from the env/config system. * * Reads SERVER_ORIGIN from the standard env hierarchy * (pgpmDefaults → config file → env vars) and wraps it in an array. * Falls back to ['http://localhost:3000'] for local development. */ function getAllowedOrigins() { const { server } = (0, graphql_env_1.getEnvOptions)(); if (server?.origin) return [server.origin]; return ['*']; } /** * Create a lazy bucket provisioner callback for the presigned URL plugin. * * On the first upload to an S3 bucket that doesn't exist yet, this callback * uses the BucketProvisioner to create and fully configure the bucket * (Block Public Access, CORS, policies, lifecycle rules for temp buckets). * * Uses the same S3 connection config as the bucket provisioner plugin * (getBucketProvisionerConnection) and reads CORS origins from * SERVER_ORIGIN env var (falls back to localhost for local dev). */ function createEnsureBucketProvisioned() { let provisioner = null; return async (bucketName, accessType, databaseId, allowedOrigins) => { // Per-database origins from storage_module, falling back to global SERVER_ORIGIN const effectiveOrigins = (allowedOrigins && allowedOrigins.length > 0) ? allowedOrigins : getAllowedOrigins(); if (!provisioner) { provisioner = new bucket_provisioner_1.BucketProvisioner({ connection: (0, bucket_provisioner_resolver_1.getBucketProvisionerConnection)(), allowedOrigins: effectiveOrigins, }); } log.info(`[lazy-provision] Provisioning S3 bucket "${bucketName}" ` + `(type=${accessType}) for database ${databaseId}`); await provisioner.provision({ bucketName, accessType, versioning: false, allowedOrigins: effectiveOrigins, }); log.info(`[lazy-provision] S3 bucket "${bucketName}" provisioned successfully`); }; }