Skip to content

Commit 0129a3e

Browse files
committed
feat: wire PresignedUrlPreset into ConstructivePreset (Step 2g)
- Add graphile-presigned-url-plugin as workspace dependency to graphile-settings - Add @aws-sdk/client-s3 dependency for S3Client in presigned-url-resolver - Create presigned-url-resolver.ts with lazy S3 client factory (same env var pattern as upload-resolver.ts) - Wire PresignedUrlPreset into ConstructivePreset extends array - Export getPresignedUrlS3Config from graphile-settings index The presigned URL plugin (requestUploadUrl, confirmUpload, downloadUrl) is now automatically included in ConstructivePreset. S3 config is lazily initialized from CDN env vars (BUCKET_PROVIDER, BUCKET_NAME, AWS_REGION, etc.) on first use.
1 parent fc23b83 commit 0129a3e

5 files changed

Lines changed: 2733 additions & 7656 deletions

File tree

graphile/graphile-settings/package.json

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@
2929
"test:watch": "jest --watch"
3030
},
3131
"dependencies": {
32+
"@aws-sdk/client-s3": "^3.1009.0",
3233
"@constructive-io/graphql-env": "workspace:^",
3334
"@constructive-io/graphql-types": "workspace:^",
3435
"@constructive-io/s3-streamer": "workspace:^",
@@ -48,6 +49,7 @@
4849
"graphile-config": "1.0.0",
4950
"graphile-connection-filter": "workspace:^",
5051
"graphile-postgis": "workspace:^",
52+
"graphile-presigned-url-plugin": "workspace:^",
5153
"graphile-search": "workspace:^",
5254
"graphile-sql-expression-validator": "workspace:^",
5355
"graphile-upload-plugin": "workspace:^",

graphile/graphile-settings/src/index.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,3 +59,6 @@ export { makePgService };
5959

6060
// Upload utilities
6161
export { streamToStorage } from './upload-resolver';
62+
63+
// Presigned URL utilities
64+
export { getPresignedUrlS3Config } from './presigned-url-resolver';

graphile/graphile-settings/src/presets/constructive-preset.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,8 +15,10 @@ import {
1515
import { UnifiedSearchPreset, createMatchesOperatorFactory, createTrgmOperatorFactories } from 'graphile-search';
1616
import { GraphilePostgisPreset, createPostgisOperatorFactory } from 'graphile-postgis';
1717
import { UploadPreset } from 'graphile-upload-plugin';
18+
import { PresignedUrlPreset } from 'graphile-presigned-url-plugin';
1819
import { SqlExpressionValidatorPreset } from 'graphile-sql-expression-validator';
1920
import { constructiveUploadFieldDefinitions } from '../upload-resolver';
21+
import { getPresignedUrlS3Config } from '../presigned-url-resolver';
2022

2123
/**
2224
* Constructive PostGraphile v5 Preset
@@ -36,6 +38,7 @@ import { constructiveUploadFieldDefinitions } from '../upload-resolver';
3638
* - PostGIS support (geometry/geography types, GeoJSON scalar — auto-detects PostGIS extension)
3739
* - PostGIS connection filter operators (spatial filtering on geometry/geography columns)
3840
* - Upload plugin (file upload to S3/MinIO for image, upload, attachment domain columns)
41+
* - Presigned URL plugin (requestUploadUrl, confirmUpload mutations + downloadUrl computed field)
3942
* - SQL expression validator (validates @sqlExpression columns in mutations)
4043
* - PG type mappings (maps custom types like email, url to GraphQL scalars)
4144
* - pgvector search (auto-discovers vector columns: filter fields, distance computed fields,
@@ -83,6 +86,7 @@ export const ConstructivePreset: GraphileConfig.Preset = {
8386
uploadFieldDefinitions: constructiveUploadFieldDefinitions,
8487
maxFileSize: 10 * 1024 * 1024, // 10MB
8588
}),
89+
PresignedUrlPreset({ s3: getPresignedUrlS3Config() }),
8690
SqlExpressionValidatorPreset(),
8791
PgTypeMappingsPreset,
8892
RequiredInputPreset,
Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
1+
/**
2+
* Presigned URL resolver for the Constructive presigned URL plugin.
3+
*
4+
* Reads CDN/S3/MinIO configuration from environment variables (via getEnvOptions)
5+
* and lazily initializes an S3Client on first use to avoid requiring
6+
* env vars at module load time.
7+
*
8+
* Follows the same lazy-init pattern as upload-resolver.ts.
9+
*
10+
* ENV VARS (via CDNOptions):
11+
* BUCKET_PROVIDER - 'minio' | 's3' (default: 'minio')
12+
* BUCKET_NAME - bucket name (default: 'test-bucket')
13+
* AWS_REGION - AWS region (default: 'us-east-1')
14+
* AWS_ACCESS_KEY - access key (default: 'minioadmin')
15+
* AWS_SECRET_KEY - secret key (default: 'minioadmin')
16+
* MINIO_ENDPOINT - MinIO endpoint (default: 'http://localhost:9000')
17+
*/
18+
19+
import { S3Client } from '@aws-sdk/client-s3';
20+
import { getEnvOptions } from '@constructive-io/graphql-env';
21+
import { Logger } from '@pgpmjs/logger';
22+
import type { S3Config } from 'graphile-presigned-url-plugin';
23+
24+
const log = new Logger('presigned-url-resolver');
25+
26+
let s3Config: S3Config | null = null;
27+
28+
/**
29+
* Lazily initialize and return the S3Config for the presigned URL plugin.
30+
*
31+
* Reads CDN env vars on first call, creates an S3Client, and caches the
32+
* config for subsequent calls. Same env vars as upload-resolver.ts.
33+
*
34+
* For MinIO, constructs a publicUrlPrefix from the endpoint + bucket name
35+
* so that public file download URLs resolve correctly.
36+
*/
37+
export function getPresignedUrlS3Config(): S3Config {
38+
if (s3Config) return s3Config;
39+
40+
const opts = getEnvOptions();
41+
const cdn = opts.cdn || {};
42+
43+
const provider = cdn.provider || 'minio';
44+
const isMinio = provider === 'minio';
45+
const bucket = cdn.bucketName || 'test-bucket';
46+
const region = cdn.awsRegion || 'us-east-1';
47+
const accessKey = cdn.awsAccessKey || 'minioadmin';
48+
const secretKey = cdn.awsSecretKey || 'minioadmin';
49+
const endpoint = cdn.minioEndpoint || 'http://localhost:9000';
50+
51+
if (process.env.NODE_ENV === 'production') {
52+
if (!cdn.awsAccessKey || !cdn.awsSecretKey) {
53+
log.warn('[presigned-url-resolver] WARNING: Using default credentials in production.');
54+
}
55+
}
56+
57+
log.info(
58+
`[presigned-url-resolver] Initializing: provider=${provider} bucket=${bucket}`,
59+
);
60+
61+
const client = new S3Client({
62+
region,
63+
credentials: { accessKeyId: accessKey, secretAccessKey: secretKey },
64+
...(isMinio ? { endpoint, forcePathStyle: true } : {}),
65+
});
66+
67+
// For MinIO (path-style), public URL prefix is endpoint/bucket.
68+
// For S3 (virtual-hosted), it's https://{bucket}.s3.{region}.amazonaws.com
69+
const publicUrlPrefix = isMinio
70+
? `${endpoint}/${bucket}`
71+
: `https://${bucket}.s3.${region}.amazonaws.com`;
72+
73+
s3Config = {
74+
client,
75+
bucket,
76+
region,
77+
publicUrlPrefix,
78+
...(isMinio ? { endpoint, forcePathStyle: true } : {}),
79+
};
80+
81+
return s3Config;
82+
}

0 commit comments

Comments
 (0)