diff --git a/graphile/graphile-settings/src/index.ts b/graphile/graphile-settings/src/index.ts index 35264135d..4ce794be6 100644 --- a/graphile/graphile-settings/src/index.ts +++ b/graphile/graphile-settings/src/index.ts @@ -32,6 +32,7 @@ export const getGraphileSettings = ( awsAccessKey: cdn.awsAccessKey!, awsSecretKey: cdn.awsSecretKey!, minioEndpoint: cdn.minioEndpoint, + provider: cdn.provider, }); const resolveUpload = uploader.resolveUpload.bind(uploader); diff --git a/graphile/graphile-upload-plugin/__tests__/plugin.test.ts b/graphile/graphile-upload-plugin/__tests__/plugin.test.ts index bdce280de..a5fb1b46b 100644 --- a/graphile/graphile-upload-plugin/__tests__/plugin.test.ts +++ b/graphile/graphile-upload-plugin/__tests__/plugin.test.ts @@ -34,7 +34,9 @@ import gql from 'graphql-tag'; const SCHEMA = process.env.SCHEMA ?? 'app_public'; const sql = (f: string) => join(__dirname, '../sql', f); -// Use defaults with optional overrides +// Use defaults with optional overrides. +// Defaults are MinIO‑style (provider: 'minio', localhost endpoint), +// which is suitable for local/CI test environments. const config = getEnvOptions({ cdn: { bucketName: 'test-upload-bucket' diff --git a/graphile/graphile-upload-plugin/src/resolvers/upload.ts b/graphile/graphile-upload-plugin/src/resolvers/upload.ts index 19be32577..c019af933 100644 --- a/graphile/graphile-upload-plugin/src/resolvers/upload.ts +++ b/graphile/graphile-upload-plugin/src/resolvers/upload.ts @@ -7,6 +7,7 @@ export interface UploaderOptions { awsSecretKey: string; awsAccessKey: string; minioEndpoint?: string; + provider?: 's3' | 'minio' | string; } export class Uploader { @@ -18,7 +19,8 @@ export class Uploader { awsRegion, awsSecretKey, awsAccessKey, - minioEndpoint + minioEndpoint, + provider } = this.opts; this.streamerInstance = new streamer({ @@ -27,6 +29,7 @@ export class Uploader { awsSecretKey, awsAccessKey, minioEndpoint, + provider }); } diff --git a/graphql/env/__fixtures__/env.keys.json b/graphql/env/__fixtures__/env.keys.json index 3acbf1cb1..a665d45ba 100644 --- a/graphql/env/__fixtures__/env.keys.json +++ b/graphql/env/__fixtures__/env.keys.json @@ -27,6 +27,7 @@ "AWS_ACCESS_KEY_ID", "AWS_SECRET_KEY", "AWS_SECRET_ACCESS_KEY", + "BUCKET_PROVIDER", "MINIO_ENDPOINT", "DEPLOYMENT_USE_TX", "DEPLOYMENT_FAST", diff --git a/graphql/explorer/src/resolvers/uploads.ts b/graphql/explorer/src/resolvers/uploads.ts index 637cb18f5..a2d3ebb47 100644 --- a/graphql/explorer/src/resolvers/uploads.ts +++ b/graphql/explorer/src/resolvers/uploads.ts @@ -9,6 +9,7 @@ interface UploaderOptions { awsSecretKey: string; awsAccessKey: string; minioEndpoint?: string; + provider?: 's3' | 'minio' | string; } interface Upload { @@ -32,7 +33,8 @@ export class UploadHandler { awsRegion: options.awsRegion, awsSecretKey: options.awsSecretKey, awsAccessKey: options.awsAccessKey, - minioEndpoint: options.minioEndpoint + minioEndpoint: options.minioEndpoint, + provider: options.provider }); } diff --git a/pgpm/env/__fixtures__/env.keys.json b/pgpm/env/__fixtures__/env.keys.json index f1fd1fca5..c7a5f2686 100644 --- a/pgpm/env/__fixtures__/env.keys.json +++ b/pgpm/env/__fixtures__/env.keys.json @@ -27,6 +27,7 @@ "AWS_ACCESS_KEY_ID", "AWS_SECRET_KEY", "AWS_SECRET_ACCESS_KEY", + "BUCKET_PROVIDER", "MINIO_ENDPOINT", "DEPLOYMENT_USE_TX", "DEPLOYMENT_FAST", diff --git a/pgpm/env/__tests__/__snapshots__/merge.test.ts.snap b/pgpm/env/__tests__/__snapshots__/merge.test.ts.snap index beffa4d1a..9570028de 100644 --- a/pgpm/env/__tests__/__snapshots__/merge.test.ts.snap +++ b/pgpm/env/__tests__/__snapshots__/merge.test.ts.snap @@ -8,6 +8,7 @@ exports[`getEnvOptions merges defaults, config, env, and overrides 1`] = ` "awsSecretKey": "minioadmin", "bucketName": "test-bucket", "minioEndpoint": "http://localhost:9000", + "provider": "minio", }, "db": { "connections": { diff --git a/pgpm/env/src/env.ts b/pgpm/env/src/env.ts index e73162533..aecb76e6c 100644 --- a/pgpm/env/src/env.ts +++ b/pgpm/env/src/env.ts @@ -57,6 +57,7 @@ export const getEnvVars = (): PgpmOptions => { AWS_SECRET_KEY, AWS_SECRET_ACCESS_KEY, MINIO_ENDPOINT, + BUCKET_PROVIDER, DEPLOYMENT_USE_TX, DEPLOYMENT_FAST, @@ -121,6 +122,7 @@ export const getEnvVars = (): PgpmOptions => { ...(PGDATABASE && { database: PGDATABASE }), }, cdn: { + ...(BUCKET_PROVIDER && { provider: BUCKET_PROVIDER as any }), ...(BUCKET_NAME && { bucketName: BUCKET_NAME }), ...(AWS_REGION && { awsRegion: AWS_REGION }), ...((AWS_ACCESS_KEY || AWS_ACCESS_KEY_ID) && { awsAccessKey: AWS_ACCESS_KEY || AWS_ACCESS_KEY_ID }), diff --git a/pgpm/types/src/pgpm.ts b/pgpm/types/src/pgpm.ts index c48065ab5..f43da463b 100644 --- a/pgpm/types/src/pgpm.ts +++ b/pgpm/types/src/pgpm.ts @@ -110,6 +110,13 @@ export interface ServerOptions { * CDN and file storage configuration */ export interface CDNOptions { + /** + * CDN / object storage provider. + * - 's3' → AWS S3 (default) + * - 'minio' → MinIO / S3‑compatible endpoint + * - string → reserved for future providers + */ + provider?: 's3' | 'minio' | string; /** S3 bucket name for file storage */ bucketName?: string; /** AWS region for S3 bucket */ @@ -241,6 +248,9 @@ export const pgpmDefaults: PgpmOptions = { strictAuth: false, }, cdn: { + // Defaults are MinIO‑style for local/dev; + // production should override via BUCKET_PROVIDER=s3. + provider: 'minio', bucketName: 'test-bucket', awsRegion: 'us-east-1', awsAccessKey: 'minioadmin', diff --git a/uploads/s3-streamer/__tests__/uploads.test.ts b/uploads/s3-streamer/__tests__/uploads.test.ts index e75b9c64e..8318391b4 100644 --- a/uploads/s3-streamer/__tests__/uploads.test.ts +++ b/uploads/s3-streamer/__tests__/uploads.test.ts @@ -9,6 +9,8 @@ import { getClient, Streamer, upload } from '../src'; import type { AsyncUploadResult } from '../src/utils'; // Use Constructive defaults with optional overrides +// Defaults are MinIO‑style (provider: 'minio', localhost endpoint), +// which is suitable for local/CI test environments. const config = getEnvOptions({ cdn: { bucketName: 'test-bucket' diff --git a/uploads/s3-streamer/src/s3.ts b/uploads/s3-streamer/src/s3.ts index 1f444f742..f354c41a3 100644 --- a/uploads/s3-streamer/src/s3.ts +++ b/uploads/s3-streamer/src/s3.ts @@ -5,10 +5,21 @@ interface S3Options { awsSecretKey: string; awsRegion: string; minioEndpoint?: string; + /** + * Object storage provider. + * - 's3' → AWS S3 (default) + * - 'minio' → MinIO / S3‑compatible endpoint + * If omitted, presence of `minioEndpoint` will imply MinIO for + * backwards compatibility. + */ + provider?: 's3' | 'minio' | string; } export default function getS3(opts: S3Options): S3Client { - const isMinio = Boolean(opts.minioEndpoint); + // Prefer explicit provider; fall back to endpoint for legacy callers + const isMinio = + opts.provider === 'minio' || + (!opts.provider && Boolean(opts.minioEndpoint)); const awsConfig: S3ClientConfig = { region: opts.awsRegion, diff --git a/uploads/s3-streamer/src/streamer.ts b/uploads/s3-streamer/src/streamer.ts index 4a8f84703..5b5fa6572 100644 --- a/uploads/s3-streamer/src/streamer.ts +++ b/uploads/s3-streamer/src/streamer.ts @@ -9,6 +9,7 @@ interface StreamerOptions { awsSecretKey: string; awsAccessKey: string; minioEndpoint?: string; + provider?: 's3' | 'minio' | string; defaultBucket: string; } @@ -28,13 +29,15 @@ export class Streamer { awsSecretKey, awsAccessKey, minioEndpoint, + provider, defaultBucket }: StreamerOptions) { this.s3 = getS3({ awsRegion, awsSecretKey, awsAccessKey, - minioEndpoint + minioEndpoint, + provider }); this.defaultBucket = defaultBucket; }