Skip to content

Commit d6c68ac

Browse files
authored
Merge pull request #945 from constructive-io/devin/1775021959-cleanup-fixture-deps
chore: remove old deps from sqitch test fixtures (pg@6, babel-cli@6, pg-promise@6)
2 parents f7bfaab + 9d8fde3 commit d6c68ac

19 files changed

Lines changed: 4220 additions & 7516 deletions

File tree

__fixtures__/sqitch/broken/packages/secrets/package.json

Lines changed: 3 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -8,19 +8,6 @@
88
"test": "FAST_TEST=1 launchql-templatedb && jest",
99
"test:watch": "FAST_TEST=1 jest --watch"
1010
},
11-
"devDependencies": {
12-
"@types/jest": "21.1.0",
13-
"@types/node": "8.0.0",
14-
"babel-cli": "6.24.1",
15-
"babel-jest": "20.0.3",
16-
"babel-preset-react-app": "3.0.0",
17-
"dotenv": "5.0.1",
18-
"jest": "20.0.4"
19-
},
20-
"dependencies": {
21-
"pg": "6.4.0",
22-
"pg-promise": "6.10.3",
23-
"@launchql/db-testing": "latest",
24-
"uuid": "3.1.0"
25-
}
26-
}
11+
"devDependencies": {},
12+
"dependencies": {}
13+
}

__fixtures__/sqitch/constructive/packages/secrets/package.json

Lines changed: 3 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -8,19 +8,6 @@
88
"test": "FAST_TEST=1 launchql-templatedb && jest",
99
"test:watch": "FAST_TEST=1 jest --watch"
1010
},
11-
"devDependencies": {
12-
"@types/jest": "21.1.0",
13-
"@types/node": "8.0.0",
14-
"babel-cli": "6.24.1",
15-
"babel-jest": "20.0.3",
16-
"babel-preset-react-app": "3.0.0",
17-
"dotenv": "5.0.1",
18-
"jest": "20.0.4"
19-
},
20-
"dependencies": {
21-
"pg": "6.4.0",
22-
"pg-promise": "6.10.3",
23-
"@launchql/db-testing": "latest",
24-
"uuid": "3.1.0"
25-
}
26-
}
11+
"devDependencies": {},
12+
"dependencies": {}
13+
}

__fixtures__/sqitch/constructive/packages/totp/package.json

Lines changed: 3 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -8,19 +8,6 @@
88
"test": "FAST_TEST=1 launchql-templatedb && jest",
99
"test:watch": "FAST_TEST=1 jest --watch"
1010
},
11-
"devDependencies": {
12-
"@types/jest": "21.1.0",
13-
"@types/node": "8.0.0",
14-
"babel-cli": "6.24.1",
15-
"babel-jest": "20.0.3",
16-
"babel-preset-react-app": "3.0.0",
17-
"dotenv": "5.0.1",
18-
"jest": "20.0.4"
19-
},
20-
"dependencies": {
21-
"pg": "6.4.0",
22-
"pg-promise": "6.10.3",
23-
"@launchql/db-testing": "latest",
24-
"uuid": "3.1.0"
25-
}
26-
}
11+
"devDependencies": {},
12+
"dependencies": {}
13+
}

__fixtures__/sqitch/constructive/packages/utilities/package.json

Lines changed: 3 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -8,19 +8,6 @@
88
"test": "FAST_TEST=1 launchql-templatedb && jest",
99
"test:watch": "FAST_TEST=1 launchql-templatedb && jest --watch"
1010
},
11-
"devDependencies": {
12-
"@types/jest": "21.1.0",
13-
"@types/node": "8.0.0",
14-
"babel-cli": "6.24.1",
15-
"babel-jest": "20.0.3",
16-
"babel-preset-react-app": "3.0.0",
17-
"dotenv": "5.0.1",
18-
"jest": "20.0.4"
19-
},
20-
"dependencies": {
21-
"pg": "6.4.0",
22-
"pg-promise": "6.10.3",
23-
"@launchql/db-testing": "latest",
24-
"uuid": "3.1.0"
25-
}
26-
}
11+
"devDependencies": {},
12+
"dependencies": {}
13+
}

__fixtures__/sqitch/constructive/packages/utils/package.json

Lines changed: 3 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -8,19 +8,6 @@
88
"test": "FAST_TEST=1 launchql-templatedb && jest",
99
"test:watch": "FAST_TEST=1 jest --watch"
1010
},
11-
"devDependencies": {
12-
"@types/jest": "21.1.0",
13-
"@types/node": "8.0.0",
14-
"babel-cli": "6.24.1",
15-
"babel-jest": "20.0.3",
16-
"babel-preset-react-app": "3.0.0",
17-
"dotenv": "5.0.1",
18-
"jest": "20.0.4"
19-
},
20-
"dependencies": {
21-
"pg": "6.4.0",
22-
"pg-promise": "6.10.3",
23-
"@launchql/db-testing": "latest",
24-
"uuid": "3.1.0"
25-
}
26-
}
11+
"devDependencies": {},
12+
"dependencies": {}
13+
}

__fixtures__/sqitch/constructive/packages/verify/package.json

Lines changed: 3 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -8,19 +8,6 @@
88
"test": "FAST_TEST=1 launchql-templatedb && jest",
99
"test:watch": "FAST_TEST=1 launchql-templatedb && jest --watch"
1010
},
11-
"devDependencies": {
12-
"@types/jest": "21.1.0",
13-
"@types/node": "8.0.0",
14-
"babel-cli": "6.24.1",
15-
"babel-jest": "20.0.3",
16-
"babel-preset-react-app": "3.0.0",
17-
"dotenv": "5.0.1",
18-
"jest": "20.0.4"
19-
},
20-
"dependencies": {
21-
"pg": "6.4.0",
22-
"pg-promise": "6.10.3",
23-
"@launchql/db-testing": "latest",
24-
"uuid": "3.1.0"
25-
}
26-
}
11+
"devDependencies": {},
12+
"dependencies": {}
13+
}
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
# graphile-presigned-url-plugin
2+
3+
Presigned URL upload plugin for PostGraphile v5.
4+
5+
## Features
6+
7+
- `requestUploadUrl` mutation — generates presigned PUT URLs for direct client-to-S3 upload
8+
- `confirmUpload` mutation — verifies upload and transitions file status to 'ready'
9+
- `downloadUrl` computed field — presigned GET URLs for private files, public URLs for public files
10+
- Content-hash based S3 keys (SHA-256) with automatic deduplication
11+
- Per-bucket MIME type and file size validation
12+
- Upload request tracking for audit and rate limiting
13+
14+
## Usage
15+
16+
```typescript
17+
import { PresignedUrlPreset } from 'graphile-presigned-url-plugin';
18+
import { S3Client } from '@aws-sdk/client-s3';
19+
20+
const s3Client = new S3Client({ region: 'us-east-1' });
21+
22+
const preset = {
23+
extends: [
24+
PresignedUrlPreset({
25+
s3: {
26+
client: s3Client,
27+
bucket: 'my-uploads',
28+
publicUrlPrefix: 'https://cdn.example.com',
29+
},
30+
urlExpirySeconds: 900,
31+
maxFileSize: 200 * 1024 * 1024,
32+
}),
33+
],
34+
};
35+
```
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
/** @type {import('ts-jest').JestConfigWithTsJest} */
2+
module.exports = {
3+
preset: 'ts-jest',
4+
testEnvironment: 'node',
5+
transform: {
6+
'^.+\\.tsx?$': [
7+
'ts-jest',
8+
{
9+
babelConfig: false,
10+
tsconfig: 'tsconfig.json'
11+
}
12+
]
13+
},
14+
transformIgnorePatterns: [`/node_modules/*`],
15+
testRegex: '(/__tests__/.*|(\\.|/)(test|spec))\\.(jsx?|tsx?)$',
16+
moduleFileExtensions: ['ts', 'tsx', 'js', 'jsx', 'json', 'node'],
17+
modulePathIgnorePatterns: ['dist/*']
18+
};
Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
{
2+
"name": "graphile-presigned-url-plugin",
3+
"version": "0.1.0",
4+
"description": "Presigned URL upload plugin for PostGraphile v5 — requestUploadUrl, confirmUpload mutations and downloadUrl computed field",
5+
"author": "Constructive <developers@constructive.io>",
6+
"homepage": "https://github.com/constructive-io/constructive",
7+
"license": "MIT",
8+
"main": "index.js",
9+
"module": "esm/index.js",
10+
"types": "index.d.ts",
11+
"scripts": {
12+
"clean": "makage clean",
13+
"prepack": "npm run build",
14+
"build": "makage build",
15+
"build:dev": "makage build --dev",
16+
"lint": "eslint . --fix",
17+
"test": "jest --passWithNoTests",
18+
"test:watch": "jest --watch"
19+
},
20+
"publishConfig": {
21+
"access": "public",
22+
"directory": "dist"
23+
},
24+
"repository": {
25+
"type": "git",
26+
"url": "https://github.com/constructive-io/constructive"
27+
},
28+
"keywords": [
29+
"postgraphile",
30+
"graphile",
31+
"constructive",
32+
"plugin",
33+
"postgres",
34+
"graphql",
35+
"presigned-url",
36+
"upload",
37+
"s3"
38+
],
39+
"bugs": {
40+
"url": "https://github.com/constructive-io/constructive/issues"
41+
},
42+
"dependencies": {
43+
"@aws-sdk/client-s3": "^3.1009.0",
44+
"@aws-sdk/s3-request-presigner": "^3.1009.0",
45+
"@pgpmjs/logger": "workspace:^",
46+
"lru-cache": "^11.2.7"
47+
},
48+
"peerDependencies": {
49+
"grafast": "1.0.0",
50+
"graphile-build": "5.0.0",
51+
"graphile-build-pg": "5.0.0",
52+
"graphile-config": "1.0.0",
53+
"graphile-utils": "5.0.0",
54+
"graphql": "16.13.0",
55+
"postgraphile": "5.0.0"
56+
},
57+
"devDependencies": {
58+
"@types/node": "^22.19.11",
59+
"makage": "^0.1.10"
60+
}
61+
}
Lines changed: 138 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,138 @@
1+
/**
2+
* downloadUrl Computed Field Plugin
3+
*
4+
* Adds a `downloadUrl` computed field to File types in the GraphQL schema.
5+
* For public files, returns the public URL prefix + key.
6+
* For private files, generates a presigned GET URL.
7+
*
8+
* Detection: Uses the `@storageFiles` smart tag on the codec (table).
9+
* The storage module generator in constructive-db sets this tag on the
10+
* generated files table via a smart comment:
11+
* COMMENT ON TABLE files IS E'@storageFiles\nStorage files table';
12+
*
13+
* This is explicit and reliable — no duck-typing on column names.
14+
*/
15+
16+
import type { GraphileConfig } from 'graphile-config';
17+
import { Logger } from '@pgpmjs/logger';
18+
19+
import type { PresignedUrlPluginOptions } from './types';
20+
import { generatePresignedGetUrl } from './s3-signer';
21+
import { getStorageModuleConfig } from './storage-module-cache';
22+
23+
const log = new Logger('graphile-presigned-url:download-url');
24+
25+
/**
26+
* Creates the downloadUrl computed field plugin.
27+
*
28+
* This is a separate plugin from the main presigned URL plugin because it
29+
* uses the GraphQLObjectType_fields hook (low-level) rather than extendSchema.
30+
* The downloadUrl field needs to be added dynamically to whatever table is
31+
* the storage module's files table, which we discover at schema-build time
32+
* via the `@storageFiles` smart tag.
33+
*/
34+
export function createDownloadUrlPlugin(
35+
options: PresignedUrlPluginOptions,
36+
): GraphileConfig.Plugin {
37+
const { s3 } = options;
38+
39+
return {
40+
name: 'PresignedUrlDownloadPlugin',
41+
version: '0.1.0',
42+
description: 'Adds downloadUrl computed field to File types tagged with @storageFiles',
43+
44+
schema: {
45+
hooks: {
46+
GraphQLObjectType_fields(fields, build, context) {
47+
const {
48+
scope: { pgCodec, isPgClassType },
49+
} = context as any;
50+
51+
// Only process PG class types (table row types)
52+
if (!isPgClassType || !pgCodec || !pgCodec.attributes) {
53+
return fields;
54+
}
55+
56+
// Check for @storageFiles smart tag — set by the storage module generator
57+
const tags = (pgCodec.extensions as any)?.tags;
58+
if (!tags?.storageFiles) {
59+
return fields;
60+
}
61+
62+
log.debug(`Adding downloadUrl field to type: ${pgCodec.name} (has @storageFiles tag)`);
63+
64+
const {
65+
graphql: { GraphQLString },
66+
} = build;
67+
68+
return build.extend(
69+
fields,
70+
{
71+
downloadUrl: context.fieldWithHooks(
72+
{ fieldName: 'downloadUrl' } as any,
73+
{
74+
description:
75+
'URL to download this file. For public files, returns the public URL. ' +
76+
'For private files, returns a time-limited presigned URL.',
77+
type: GraphQLString,
78+
async resolve(parent: any, _args: any, context: any) {
79+
const key = parent.key || parent.get?.('key');
80+
const isPublic = parent.is_public ?? parent.get?.('is_public');
81+
const filename = parent.filename || parent.get?.('filename');
82+
const status = parent.status || parent.get?.('status');
83+
84+
if (!key) return null;
85+
86+
// Only provide download URLs for ready/processed files
87+
if (status !== 'ready' && status !== 'processed') {
88+
return null;
89+
}
90+
91+
if (isPublic && s3.publicUrlPrefix) {
92+
// Public file: return direct URL
93+
return `${s3.publicUrlPrefix}/${key}`;
94+
}
95+
96+
// Resolve download URL expiry from storage module config (per-database)
97+
let downloadUrlExpirySeconds = 3600; // fallback default
98+
try {
99+
const withPgClient = context.pgSettings
100+
? context.withPgClient
101+
: null;
102+
if (withPgClient) {
103+
const config = await withPgClient(null, async (pgClient: any) => {
104+
const dbResult = await pgClient.query(
105+
`SELECT jwt_private.current_database_id() AS id`,
106+
);
107+
const databaseId = dbResult.rows[0]?.id;
108+
if (!databaseId) return null;
109+
return getStorageModuleConfig(pgClient, databaseId);
110+
});
111+
if (config) {
112+
downloadUrlExpirySeconds = config.downloadUrlExpirySeconds;
113+
}
114+
}
115+
} catch {
116+
// Fall back to default if config lookup fails
117+
}
118+
119+
// Private file: generate presigned GET URL
120+
return generatePresignedGetUrl(
121+
s3,
122+
key,
123+
downloadUrlExpirySeconds,
124+
filename || undefined,
125+
);
126+
},
127+
},
128+
),
129+
},
130+
'PresignedUrlDownloadPlugin adding downloadUrl field',
131+
);
132+
},
133+
},
134+
},
135+
};
136+
}
137+
138+
export default createDownloadUrlPlugin;

0 commit comments

Comments
 (0)