graphile-presigned-url-plugin 0.19.2 β 0.20.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +15 -0
- package/download-url-field.js +1 -1
- package/esm/download-url-field.js +1 -1
- package/esm/plugin.js +1 -1
- package/esm/storage-module-cache.d.ts +1 -1
- package/esm/storage-module-cache.js +21 -22
- package/esm/types.d.ts +2 -2
- package/package.json +4 -4
- package/plugin.js +1 -1
- package/storage-module-cache.d.ts +1 -1
- package/storage-module-cache.js +21 -22
- package/types.d.ts +2 -2
package/README.md
CHANGED
|
@@ -92,6 +92,21 @@ Common issues and solutions for pgpm, PostgreSQL, and testing.
|
|
|
92
92
|
* [@pgsql/types](https://www.npmjs.com/package/@pgsql/types): **π Type definitions** for PostgreSQL AST nodes in TypeScript.
|
|
93
93
|
* [@pgsql/utils](https://www.npmjs.com/package/@pgsql/utils): **π οΈ AST utilities** for constructing and transforming PostgreSQL syntax trees.
|
|
94
94
|
|
|
95
|
+
### π Documentation & Skills
|
|
96
|
+
|
|
97
|
+
* [constructive-skills](https://github.com/constructive-io/constructive-skills): **π Platform documentation and AI agent skills** β feature catalog, blueprint reference, SDK guides (i18n, billing, limits, events, uploads, security, entities, search, AI), and deployment guides.
|
|
98
|
+
|
|
99
|
+
Install skills for AI coding agents:
|
|
100
|
+
|
|
101
|
+
```bash
|
|
102
|
+
# All platform skills (security, blueprints, codegen, billing, etc.)
|
|
103
|
+
npx skills add constructive-io/constructive-skills
|
|
104
|
+
|
|
105
|
+
# Individual repo skills (pgpm, testing, CLI, search, etc.)
|
|
106
|
+
npx skills add https://github.com/constructive-io/constructive --skill pgpm
|
|
107
|
+
npx skills add https://github.com/constructive-io/constructive --skill constructive-testing
|
|
108
|
+
```
|
|
109
|
+
|
|
95
110
|
## Credits
|
|
96
111
|
|
|
97
112
|
**π Built by the [Constructive](https://constructive.io) team β creators of modular Postgres tooling for secure, composable backends. If you like our work, contribute on [GitHub](https://github.com/constructive-io).**
|
package/download-url-field.js
CHANGED
|
@@ -115,7 +115,7 @@ function createDownloadUrlPlugin(options) {
|
|
|
115
115
|
let downloadUrlExpirySeconds = 3600;
|
|
116
116
|
try {
|
|
117
117
|
if (withPgClient && pgSettings) {
|
|
118
|
-
const resolved = await withPgClient(
|
|
118
|
+
const resolved = await withPgClient(pgSettings, async (pgClient) => {
|
|
119
119
|
const dbResult = await pgClient.query({
|
|
120
120
|
text: `SELECT jwt_private.current_database_id() AS id`,
|
|
121
121
|
});
|
|
@@ -112,7 +112,7 @@ export function createDownloadUrlPlugin(options) {
|
|
|
112
112
|
let downloadUrlExpirySeconds = 3600;
|
|
113
113
|
try {
|
|
114
114
|
if (withPgClient && pgSettings) {
|
|
115
|
-
const resolved = await withPgClient(
|
|
115
|
+
const resolved = await withPgClient(pgSettings, async (pgClient) => {
|
|
116
116
|
const dbResult = await pgClient.query({
|
|
117
117
|
text: `SELECT jwt_private.current_database_id() AS id`,
|
|
118
118
|
});
|
package/esm/plugin.js
CHANGED
|
@@ -559,7 +559,7 @@ async function processSingleFile(options, txClient, storageConfig, databaseId, b
|
|
|
559
559
|
// Auto-derive ltree path from custom key directory (only when has_path_shares)
|
|
560
560
|
const derivedPath = isCustomKey && storageConfig.hasPathShares ? derivePathFromKey(s3Key) : null;
|
|
561
561
|
// Create file record
|
|
562
|
-
const hasOwnerColumn = storageConfig.
|
|
562
|
+
const hasOwnerColumn = storageConfig.scope !== 'app';
|
|
563
563
|
const columns = ['bucket_id', 'key', 'content_hash', 'mime_type', 'size', 'filename', 'is_public'];
|
|
564
564
|
const values = [bucket.id, s3Key, contentHash, contentType, size, filename || null, bucket.is_public];
|
|
565
565
|
if (hasOwnerColumn) {
|
|
@@ -3,7 +3,7 @@ import type { StorageModuleConfig, BucketConfig } from './types';
|
|
|
3
3
|
* Resolve the app-level storage module config for a database, using the LRU cache.
|
|
4
4
|
*
|
|
5
5
|
* This is the default path when no ownerId is provided. It returns the
|
|
6
|
-
* storage module with
|
|
6
|
+
* storage module with scope = 'app' (app-level / database-wide).
|
|
7
7
|
*
|
|
8
8
|
* @param pgClient - A pg client from the Graphile context (withPgClient or pgClient)
|
|
9
9
|
* @param databaseId - The metaschema database UUID
|
|
@@ -31,14 +31,14 @@ const storageModuleCache = new LRUCache({
|
|
|
31
31
|
* SQL query to resolve the app-level storage module config for a database.
|
|
32
32
|
*
|
|
33
33
|
* Joins storage_module β table β schema to get fully-qualified table names.
|
|
34
|
-
* Filters to app-level (
|
|
34
|
+
* Filters to app-level (scope = 'app') by default.
|
|
35
35
|
*
|
|
36
|
-
* Requires the multi-scope schema (
|
|
36
|
+
* Requires the multi-scope schema (scope column on storage_module).
|
|
37
37
|
*/
|
|
38
38
|
const APP_STORAGE_MODULE_QUERY = `
|
|
39
39
|
SELECT
|
|
40
40
|
sm.id,
|
|
41
|
-
sm.
|
|
41
|
+
sm.scope,
|
|
42
42
|
sm.entity_table_id,
|
|
43
43
|
bs.schema_name AS buckets_schema,
|
|
44
44
|
bt.name AS buckets_table,
|
|
@@ -64,7 +64,7 @@ const APP_STORAGE_MODULE_QUERY = `
|
|
|
64
64
|
JOIN metaschema_public.table ft ON ft.id = sm.files_table_id
|
|
65
65
|
JOIN metaschema_public.schema fs ON fs.id = ft.schema_id
|
|
66
66
|
WHERE sm.database_id = $1
|
|
67
|
-
AND sm.
|
|
67
|
+
AND sm.scope = 'app'
|
|
68
68
|
LIMIT 1
|
|
69
69
|
`;
|
|
70
70
|
/**
|
|
@@ -76,7 +76,7 @@ const APP_STORAGE_MODULE_QUERY = `
|
|
|
76
76
|
const ALL_STORAGE_MODULES_QUERY = `
|
|
77
77
|
SELECT
|
|
78
78
|
sm.id,
|
|
79
|
-
sm.
|
|
79
|
+
sm.scope,
|
|
80
80
|
sm.entity_table_id,
|
|
81
81
|
bs.schema_name AS buckets_schema,
|
|
82
82
|
bt.name AS buckets_table,
|
|
@@ -117,7 +117,7 @@ function buildConfig(row) {
|
|
|
117
117
|
schemaName: row.buckets_schema,
|
|
118
118
|
bucketsTableName: row.buckets_table,
|
|
119
119
|
filesTableName: row.files_table,
|
|
120
|
-
|
|
120
|
+
scope: row.scope,
|
|
121
121
|
entityTableId: row.entity_table_id,
|
|
122
122
|
entityQualifiedName: row.entity_schema && row.entity_table
|
|
123
123
|
? QuoteUtils.quoteQualifiedIdentifier(row.entity_schema, row.entity_table)
|
|
@@ -140,7 +140,7 @@ function buildConfig(row) {
|
|
|
140
140
|
* Resolve the app-level storage module config for a database, using the LRU cache.
|
|
141
141
|
*
|
|
142
142
|
* This is the default path when no ownerId is provided. It returns the
|
|
143
|
-
* storage module with
|
|
143
|
+
* storage module with scope = 'app' (app-level / database-wide).
|
|
144
144
|
*
|
|
145
145
|
* @param pgClient - A pg client from the Graphile context (withPgClient or pgClient)
|
|
146
146
|
* @param databaseId - The metaschema database UUID
|
|
@@ -201,11 +201,9 @@ export async function getStorageModuleConfigForOwner(pgClient, databaseId, owner
|
|
|
201
201
|
log.debug(`Loading all storage modules for database ${databaseId} to resolve ownerId ${ownerId}`);
|
|
202
202
|
const result = await pgClient.query({ text: ALL_STORAGE_MODULES_QUERY, values: [databaseId] });
|
|
203
203
|
allConfigs = result.rows.map(buildConfig);
|
|
204
|
-
// Cache each individual config by its
|
|
204
|
+
// Cache each individual config by its scope
|
|
205
205
|
for (const config of allConfigs) {
|
|
206
|
-
const key = config.
|
|
207
|
-
? `storage:${databaseId}:app`
|
|
208
|
-
: `storage:${databaseId}:mt:${config.membershipType}`;
|
|
206
|
+
const key = `storage:${databaseId}:scope:${config.scope}`;
|
|
209
207
|
storageModuleCache.set(key, config);
|
|
210
208
|
}
|
|
211
209
|
}
|
|
@@ -220,7 +218,7 @@ export async function getStorageModuleConfigForOwner(pgClient, databaseId, owner
|
|
|
220
218
|
// Found the matching module β cache the ownerIdβmodule mapping
|
|
221
219
|
storageModuleCache.set(ownerCacheKey, mod);
|
|
222
220
|
log.debug(`Resolved ownerId ${ownerId} to storage module ${mod.id} ` +
|
|
223
|
-
`(
|
|
221
|
+
`(scope=${mod.scope}, table=${mod.bucketsQualifiedName})`);
|
|
224
222
|
return mod;
|
|
225
223
|
}
|
|
226
224
|
}
|
|
@@ -272,16 +270,16 @@ export async function loadAllStorageModules(pgClient, databaseId) {
|
|
|
272
270
|
log.debug(`Loading all storage modules for database ${databaseId}`);
|
|
273
271
|
const result = await pgClient.query({ text: ALL_STORAGE_MODULES_QUERY, values: [databaseId] });
|
|
274
272
|
const configs = result.rows.map(buildConfig);
|
|
275
|
-
// Cache each individual config by its
|
|
273
|
+
// Cache each individual config by its scope
|
|
276
274
|
for (const config of configs) {
|
|
277
|
-
const key = config.
|
|
278
|
-
? `storage:${databaseId}:app`
|
|
279
|
-
: `storage:${databaseId}:mt:${config.membershipType}`;
|
|
275
|
+
const key = `storage:${databaseId}:scope:${config.scope}`;
|
|
280
276
|
storageModuleCache.set(key, config);
|
|
281
277
|
}
|
|
282
|
-
// Store the full list under a sentinel key
|
|
283
|
-
|
|
284
|
-
|
|
278
|
+
// Store the full list under a sentinel key (only if non-empty to avoid caching failed lookups)
|
|
279
|
+
if (configs.length > 0) {
|
|
280
|
+
const sentinel = { ...configs[0], _allConfigs: configs };
|
|
281
|
+
storageModuleCache.set(cacheKey, sentinel);
|
|
282
|
+
}
|
|
285
283
|
return configs;
|
|
286
284
|
}
|
|
287
285
|
/**
|
|
@@ -344,14 +342,15 @@ export async function getBucketConfig(pgClient, storageConfig, databaseId, bucke
|
|
|
344
342
|
log.debug(`Bucket cache miss for ${databaseId}:${bucketKey}${ownerId ? ` (owner=${ownerId})` : ''}, querying DB...`);
|
|
345
343
|
// Entity-scoped buckets use (owner_id, key) composite lookup;
|
|
346
344
|
// app-level buckets just use key.
|
|
347
|
-
const
|
|
345
|
+
const isEntityScoped = storageConfig.scope !== 'app';
|
|
346
|
+
const hasOwner = ownerId && isEntityScoped;
|
|
348
347
|
const result = await pgClient.query({
|
|
349
348
|
text: hasOwner
|
|
350
349
|
? `SELECT id, key, type, is_public, owner_id, allowed_mime_types, max_file_size, allow_custom_keys
|
|
351
350
|
FROM ${storageConfig.bucketsQualifiedName}
|
|
352
351
|
WHERE key = $1 AND owner_id = $2
|
|
353
352
|
LIMIT 1`
|
|
354
|
-
: `SELECT id, key, type, is_public, ${
|
|
353
|
+
: `SELECT id, key, type, is_public, ${isEntityScoped ? 'owner_id,' : ''} allowed_mime_types, max_file_size, allow_custom_keys
|
|
355
354
|
FROM ${storageConfig.bucketsQualifiedName}
|
|
356
355
|
WHERE key = $1
|
|
357
356
|
LIMIT 1`,
|
|
@@ -372,7 +371,7 @@ export async function getBucketConfig(pgClient, storageConfig, databaseId, bucke
|
|
|
372
371
|
allow_custom_keys: row.allow_custom_keys ?? false,
|
|
373
372
|
};
|
|
374
373
|
bucketCache.set(cacheKey, config);
|
|
375
|
-
log.debug(`Cached bucket config for ${databaseId}:${bucketKey} (id=${config.id}, scope=${storageConfig.
|
|
374
|
+
log.debug(`Cached bucket config for ${databaseId}:${bucketKey} (id=${config.id}, scope=${storageConfig.scope})`);
|
|
376
375
|
return config;
|
|
377
376
|
}
|
|
378
377
|
// --- S3 bucket existence cache ---
|
package/esm/types.d.ts
CHANGED
|
@@ -28,8 +28,8 @@ export interface StorageModuleConfig {
|
|
|
28
28
|
bucketsTableName: string;
|
|
29
29
|
/** Files table name */
|
|
30
30
|
filesTableName: string;
|
|
31
|
-
/**
|
|
32
|
-
|
|
31
|
+
/** Scope name (e.g., 'app', 'org', 'team') */
|
|
32
|
+
scope: string;
|
|
33
33
|
/** Entity table ID for entity-scoped storage (NULL for app-level) */
|
|
34
34
|
entityTableId: string | null;
|
|
35
35
|
/** Qualified entity table name for ownerId lookups (NULL for app-level) */
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "graphile-presigned-url-plugin",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.20.1",
|
|
4
4
|
"description": "Presigned URL upload plugin for PostGraphile v5 β requestUploadUrl mutation and downloadUrl computed field",
|
|
5
5
|
"author": "Constructive <developers@constructive.io>",
|
|
6
6
|
"homepage": "https://github.com/constructive-io/constructive",
|
|
@@ -42,7 +42,7 @@
|
|
|
42
42
|
"dependencies": {
|
|
43
43
|
"@aws-sdk/client-s3": "^3.1052.0",
|
|
44
44
|
"@aws-sdk/s3-request-presigner": "^3.1052.0",
|
|
45
|
-
"@pgpmjs/logger": "^2.
|
|
45
|
+
"@pgpmjs/logger": "^2.12.0",
|
|
46
46
|
"@pgsql/quotes": "^17.1.0",
|
|
47
47
|
"lru-cache": "^11.2.7"
|
|
48
48
|
},
|
|
@@ -56,9 +56,9 @@
|
|
|
56
56
|
"postgraphile": "5.0.3"
|
|
57
57
|
},
|
|
58
58
|
"devDependencies": {
|
|
59
|
-
"@constructive-io/s3-utils": "^2.
|
|
59
|
+
"@constructive-io/s3-utils": "^2.18.0",
|
|
60
60
|
"@types/node": "^22.19.11",
|
|
61
61
|
"makage": "^0.3.0"
|
|
62
62
|
},
|
|
63
|
-
"gitHead": "
|
|
63
|
+
"gitHead": "d398ac2288f57fa02713353ce36024a63dd6b9f1"
|
|
64
64
|
}
|
package/plugin.js
CHANGED
|
@@ -563,7 +563,7 @@ async function processSingleFile(options, txClient, storageConfig, databaseId, b
|
|
|
563
563
|
// Auto-derive ltree path from custom key directory (only when has_path_shares)
|
|
564
564
|
const derivedPath = isCustomKey && storageConfig.hasPathShares ? derivePathFromKey(s3Key) : null;
|
|
565
565
|
// Create file record
|
|
566
|
-
const hasOwnerColumn = storageConfig.
|
|
566
|
+
const hasOwnerColumn = storageConfig.scope !== 'app';
|
|
567
567
|
const columns = ['bucket_id', 'key', 'content_hash', 'mime_type', 'size', 'filename', 'is_public'];
|
|
568
568
|
const values = [bucket.id, s3Key, contentHash, contentType, size, filename || null, bucket.is_public];
|
|
569
569
|
if (hasOwnerColumn) {
|
|
@@ -3,7 +3,7 @@ import type { StorageModuleConfig, BucketConfig } from './types';
|
|
|
3
3
|
* Resolve the app-level storage module config for a database, using the LRU cache.
|
|
4
4
|
*
|
|
5
5
|
* This is the default path when no ownerId is provided. It returns the
|
|
6
|
-
* storage module with
|
|
6
|
+
* storage module with scope = 'app' (app-level / database-wide).
|
|
7
7
|
*
|
|
8
8
|
* @param pgClient - A pg client from the Graphile context (withPgClient or pgClient)
|
|
9
9
|
* @param databaseId - The metaschema database UUID
|
package/storage-module-cache.js
CHANGED
|
@@ -43,14 +43,14 @@ const storageModuleCache = new lru_cache_1.LRUCache({
|
|
|
43
43
|
* SQL query to resolve the app-level storage module config for a database.
|
|
44
44
|
*
|
|
45
45
|
* Joins storage_module β table β schema to get fully-qualified table names.
|
|
46
|
-
* Filters to app-level (
|
|
46
|
+
* Filters to app-level (scope = 'app') by default.
|
|
47
47
|
*
|
|
48
|
-
* Requires the multi-scope schema (
|
|
48
|
+
* Requires the multi-scope schema (scope column on storage_module).
|
|
49
49
|
*/
|
|
50
50
|
const APP_STORAGE_MODULE_QUERY = `
|
|
51
51
|
SELECT
|
|
52
52
|
sm.id,
|
|
53
|
-
sm.
|
|
53
|
+
sm.scope,
|
|
54
54
|
sm.entity_table_id,
|
|
55
55
|
bs.schema_name AS buckets_schema,
|
|
56
56
|
bt.name AS buckets_table,
|
|
@@ -76,7 +76,7 @@ const APP_STORAGE_MODULE_QUERY = `
|
|
|
76
76
|
JOIN metaschema_public.table ft ON ft.id = sm.files_table_id
|
|
77
77
|
JOIN metaschema_public.schema fs ON fs.id = ft.schema_id
|
|
78
78
|
WHERE sm.database_id = $1
|
|
79
|
-
AND sm.
|
|
79
|
+
AND sm.scope = 'app'
|
|
80
80
|
LIMIT 1
|
|
81
81
|
`;
|
|
82
82
|
/**
|
|
@@ -88,7 +88,7 @@ const APP_STORAGE_MODULE_QUERY = `
|
|
|
88
88
|
const ALL_STORAGE_MODULES_QUERY = `
|
|
89
89
|
SELECT
|
|
90
90
|
sm.id,
|
|
91
|
-
sm.
|
|
91
|
+
sm.scope,
|
|
92
92
|
sm.entity_table_id,
|
|
93
93
|
bs.schema_name AS buckets_schema,
|
|
94
94
|
bt.name AS buckets_table,
|
|
@@ -129,7 +129,7 @@ function buildConfig(row) {
|
|
|
129
129
|
schemaName: row.buckets_schema,
|
|
130
130
|
bucketsTableName: row.buckets_table,
|
|
131
131
|
filesTableName: row.files_table,
|
|
132
|
-
|
|
132
|
+
scope: row.scope,
|
|
133
133
|
entityTableId: row.entity_table_id,
|
|
134
134
|
entityQualifiedName: row.entity_schema && row.entity_table
|
|
135
135
|
? quotes_1.QuoteUtils.quoteQualifiedIdentifier(row.entity_schema, row.entity_table)
|
|
@@ -152,7 +152,7 @@ function buildConfig(row) {
|
|
|
152
152
|
* Resolve the app-level storage module config for a database, using the LRU cache.
|
|
153
153
|
*
|
|
154
154
|
* This is the default path when no ownerId is provided. It returns the
|
|
155
|
-
* storage module with
|
|
155
|
+
* storage module with scope = 'app' (app-level / database-wide).
|
|
156
156
|
*
|
|
157
157
|
* @param pgClient - A pg client from the Graphile context (withPgClient or pgClient)
|
|
158
158
|
* @param databaseId - The metaschema database UUID
|
|
@@ -213,11 +213,9 @@ async function getStorageModuleConfigForOwner(pgClient, databaseId, ownerId) {
|
|
|
213
213
|
log.debug(`Loading all storage modules for database ${databaseId} to resolve ownerId ${ownerId}`);
|
|
214
214
|
const result = await pgClient.query({ text: ALL_STORAGE_MODULES_QUERY, values: [databaseId] });
|
|
215
215
|
allConfigs = result.rows.map(buildConfig);
|
|
216
|
-
// Cache each individual config by its
|
|
216
|
+
// Cache each individual config by its scope
|
|
217
217
|
for (const config of allConfigs) {
|
|
218
|
-
const key = config.
|
|
219
|
-
? `storage:${databaseId}:app`
|
|
220
|
-
: `storage:${databaseId}:mt:${config.membershipType}`;
|
|
218
|
+
const key = `storage:${databaseId}:scope:${config.scope}`;
|
|
221
219
|
storageModuleCache.set(key, config);
|
|
222
220
|
}
|
|
223
221
|
}
|
|
@@ -232,7 +230,7 @@ async function getStorageModuleConfigForOwner(pgClient, databaseId, ownerId) {
|
|
|
232
230
|
// Found the matching module β cache the ownerIdβmodule mapping
|
|
233
231
|
storageModuleCache.set(ownerCacheKey, mod);
|
|
234
232
|
log.debug(`Resolved ownerId ${ownerId} to storage module ${mod.id} ` +
|
|
235
|
-
`(
|
|
233
|
+
`(scope=${mod.scope}, table=${mod.bucketsQualifiedName})`);
|
|
236
234
|
return mod;
|
|
237
235
|
}
|
|
238
236
|
}
|
|
@@ -284,16 +282,16 @@ async function loadAllStorageModules(pgClient, databaseId) {
|
|
|
284
282
|
log.debug(`Loading all storage modules for database ${databaseId}`);
|
|
285
283
|
const result = await pgClient.query({ text: ALL_STORAGE_MODULES_QUERY, values: [databaseId] });
|
|
286
284
|
const configs = result.rows.map(buildConfig);
|
|
287
|
-
// Cache each individual config by its
|
|
285
|
+
// Cache each individual config by its scope
|
|
288
286
|
for (const config of configs) {
|
|
289
|
-
const key = config.
|
|
290
|
-
? `storage:${databaseId}:app`
|
|
291
|
-
: `storage:${databaseId}:mt:${config.membershipType}`;
|
|
287
|
+
const key = `storage:${databaseId}:scope:${config.scope}`;
|
|
292
288
|
storageModuleCache.set(key, config);
|
|
293
289
|
}
|
|
294
|
-
// Store the full list under a sentinel key
|
|
295
|
-
|
|
296
|
-
|
|
290
|
+
// Store the full list under a sentinel key (only if non-empty to avoid caching failed lookups)
|
|
291
|
+
if (configs.length > 0) {
|
|
292
|
+
const sentinel = { ...configs[0], _allConfigs: configs };
|
|
293
|
+
storageModuleCache.set(cacheKey, sentinel);
|
|
294
|
+
}
|
|
297
295
|
return configs;
|
|
298
296
|
}
|
|
299
297
|
/**
|
|
@@ -356,14 +354,15 @@ async function getBucketConfig(pgClient, storageConfig, databaseId, bucketKey, o
|
|
|
356
354
|
log.debug(`Bucket cache miss for ${databaseId}:${bucketKey}${ownerId ? ` (owner=${ownerId})` : ''}, querying DB...`);
|
|
357
355
|
// Entity-scoped buckets use (owner_id, key) composite lookup;
|
|
358
356
|
// app-level buckets just use key.
|
|
359
|
-
const
|
|
357
|
+
const isEntityScoped = storageConfig.scope !== 'app';
|
|
358
|
+
const hasOwner = ownerId && isEntityScoped;
|
|
360
359
|
const result = await pgClient.query({
|
|
361
360
|
text: hasOwner
|
|
362
361
|
? `SELECT id, key, type, is_public, owner_id, allowed_mime_types, max_file_size, allow_custom_keys
|
|
363
362
|
FROM ${storageConfig.bucketsQualifiedName}
|
|
364
363
|
WHERE key = $1 AND owner_id = $2
|
|
365
364
|
LIMIT 1`
|
|
366
|
-
: `SELECT id, key, type, is_public, ${
|
|
365
|
+
: `SELECT id, key, type, is_public, ${isEntityScoped ? 'owner_id,' : ''} allowed_mime_types, max_file_size, allow_custom_keys
|
|
367
366
|
FROM ${storageConfig.bucketsQualifiedName}
|
|
368
367
|
WHERE key = $1
|
|
369
368
|
LIMIT 1`,
|
|
@@ -384,7 +383,7 @@ async function getBucketConfig(pgClient, storageConfig, databaseId, bucketKey, o
|
|
|
384
383
|
allow_custom_keys: row.allow_custom_keys ?? false,
|
|
385
384
|
};
|
|
386
385
|
bucketCache.set(cacheKey, config);
|
|
387
|
-
log.debug(`Cached bucket config for ${databaseId}:${bucketKey} (id=${config.id}, scope=${storageConfig.
|
|
386
|
+
log.debug(`Cached bucket config for ${databaseId}:${bucketKey} (id=${config.id}, scope=${storageConfig.scope})`);
|
|
388
387
|
return config;
|
|
389
388
|
}
|
|
390
389
|
// --- S3 bucket existence cache ---
|
package/types.d.ts
CHANGED
|
@@ -28,8 +28,8 @@ export interface StorageModuleConfig {
|
|
|
28
28
|
bucketsTableName: string;
|
|
29
29
|
/** Files table name */
|
|
30
30
|
filesTableName: string;
|
|
31
|
-
/**
|
|
32
|
-
|
|
31
|
+
/** Scope name (e.g., 'app', 'org', 'team') */
|
|
32
|
+
scope: string;
|
|
33
33
|
/** Entity table ID for entity-scoped storage (NULL for app-level) */
|
|
34
34
|
entityTableId: string | null;
|
|
35
35
|
/** Qualified entity table name for ownerId lookups (NULL for app-level) */
|