graphile-presigned-url-plugin 0.6.0 → 0.6.2
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/esm/plugin.js +16 -18
- package/esm/storage-module-cache.d.ts +1 -1
- package/esm/storage-module-cache.js +1 -1
- package/package.json +2 -2
- package/plugin.js +16 -18
- package/storage-module-cache.d.ts +1 -1
- package/storage-module-cache.js +1 -1
package/esm/plugin.js
CHANGED
|
@@ -259,15 +259,15 @@ export function createPresignedUrlPlugin(options) {
|
|
|
259
259
|
throw new Error(`FILE_TOO_LARGE: exceeds bucket max of ${bucket.max_file_size} bytes`);
|
|
260
260
|
}
|
|
261
261
|
const s3Key = buildS3Key(contentHash);
|
|
262
|
-
// --- Dedup check: look for existing file with same
|
|
262
|
+
// --- Dedup check: look for existing file with same key (content hash) in this bucket ---
|
|
263
263
|
const dedupResult = await txClient.query({
|
|
264
264
|
text: `SELECT id, status
|
|
265
265
|
FROM ${storageConfig.filesQualifiedName}
|
|
266
|
-
WHERE
|
|
266
|
+
WHERE key = $1
|
|
267
267
|
AND bucket_id = $2
|
|
268
268
|
AND status IN ('ready', 'processed')
|
|
269
269
|
LIMIT 1`,
|
|
270
|
-
values: [
|
|
270
|
+
values: [s3Key, bucket.id],
|
|
271
271
|
});
|
|
272
272
|
if (dedupResult.rows.length > 0) {
|
|
273
273
|
const existingFile = dedupResult.rows[0];
|
|
@@ -275,9 +275,9 @@ export function createPresignedUrlPlugin(options) {
|
|
|
275
275
|
// Track the dedup request
|
|
276
276
|
await txClient.query({
|
|
277
277
|
text: `INSERT INTO ${storageConfig.uploadRequestsQualifiedName}
|
|
278
|
-
(file_id, bucket_id, key, content_type, content_hash,
|
|
279
|
-
VALUES ($1, $2, $3, $4, $5,
|
|
280
|
-
values: [existingFile.id, bucket.id, s3Key, contentType, contentHash
|
|
278
|
+
(file_id, bucket_id, key, content_type, content_hash, status, expires_at)
|
|
279
|
+
VALUES ($1, $2, $3, $4, $5, 'confirmed', NOW())`,
|
|
280
|
+
values: [existingFile.id, bucket.id, s3Key, contentType, contentHash],
|
|
281
281
|
});
|
|
282
282
|
return {
|
|
283
283
|
uploadUrl: null,
|
|
@@ -293,19 +293,18 @@ export function createPresignedUrlPlugin(options) {
|
|
|
293
293
|
const fileResult = await txClient.query({
|
|
294
294
|
text: hasOwnerColumn
|
|
295
295
|
? `INSERT INTO ${storageConfig.filesQualifiedName}
|
|
296
|
-
(bucket_id, key,
|
|
297
|
-
VALUES ($1, $2, $3, $4, $5, $6, $7,
|
|
296
|
+
(bucket_id, key, mime_type, size, filename, owner_id, is_public, status)
|
|
297
|
+
VALUES ($1, $2, $3, $4, $5, $6, $7, 'pending')
|
|
298
298
|
RETURNING id`
|
|
299
299
|
: `INSERT INTO ${storageConfig.filesQualifiedName}
|
|
300
|
-
(bucket_id, key,
|
|
301
|
-
VALUES ($1, $2, $3, $4, $5, $6,
|
|
300
|
+
(bucket_id, key, mime_type, size, filename, is_public, status)
|
|
301
|
+
VALUES ($1, $2, $3, $4, $5, $6, 'pending')
|
|
302
302
|
RETURNING id`,
|
|
303
303
|
values: hasOwnerColumn
|
|
304
304
|
? [
|
|
305
305
|
bucket.id,
|
|
306
306
|
s3Key,
|
|
307
307
|
contentType,
|
|
308
|
-
contentHash,
|
|
309
308
|
size,
|
|
310
309
|
filename || null,
|
|
311
310
|
bucket.owner_id,
|
|
@@ -315,7 +314,6 @@ export function createPresignedUrlPlugin(options) {
|
|
|
315
314
|
bucket.id,
|
|
316
315
|
s3Key,
|
|
317
316
|
contentType,
|
|
318
|
-
contentHash,
|
|
319
317
|
size,
|
|
320
318
|
filename || null,
|
|
321
319
|
bucket.is_public,
|
|
@@ -331,9 +329,9 @@ export function createPresignedUrlPlugin(options) {
|
|
|
331
329
|
// --- Track the upload request ---
|
|
332
330
|
await txClient.query({
|
|
333
331
|
text: `INSERT INTO ${storageConfig.uploadRequestsQualifiedName}
|
|
334
|
-
(file_id, bucket_id, key, content_type, content_hash,
|
|
335
|
-
VALUES ($1, $2, $3, $4, $5,
|
|
336
|
-
values: [fileId, bucket.id, s3Key, contentType, contentHash,
|
|
332
|
+
(file_id, bucket_id, key, content_type, content_hash, status, expires_at)
|
|
333
|
+
VALUES ($1, $2, $3, $4, $5, 'issued', $6)`,
|
|
334
|
+
values: [fileId, bucket.id, s3Key, contentType, contentHash, expiresAt],
|
|
337
335
|
});
|
|
338
336
|
return {
|
|
339
337
|
uploadUrl,
|
|
@@ -382,12 +380,12 @@ export function createPresignedUrlPlugin(options) {
|
|
|
382
380
|
}
|
|
383
381
|
// --- Verify file exists in S3 (per-database bucket) ---
|
|
384
382
|
const s3ForDb = resolveS3ForDatabase(options, storageConfig, databaseId);
|
|
385
|
-
const s3Head = await headObject(s3ForDb, file.key, file.
|
|
383
|
+
const s3Head = await headObject(s3ForDb, file.key, file.mime_type);
|
|
386
384
|
if (!s3Head) {
|
|
387
385
|
throw new Error('FILE_NOT_IN_S3: the file has not been uploaded yet');
|
|
388
386
|
}
|
|
389
387
|
// --- Content-type verification ---
|
|
390
|
-
if (s3Head.contentType && s3Head.contentType !== file.
|
|
388
|
+
if (s3Head.contentType && s3Head.contentType !== file.mime_type) {
|
|
391
389
|
// Mark upload_request as rejected
|
|
392
390
|
await txClient.query({
|
|
393
391
|
text: `UPDATE ${storageConfig.uploadRequestsQualifiedName}
|
|
@@ -395,7 +393,7 @@ export function createPresignedUrlPlugin(options) {
|
|
|
395
393
|
WHERE file_id = $1 AND status = 'issued'`,
|
|
396
394
|
values: [fileId],
|
|
397
395
|
});
|
|
398
|
-
throw new Error(`CONTENT_TYPE_MISMATCH: expected ${file.
|
|
396
|
+
throw new Error(`CONTENT_TYPE_MISMATCH: expected ${file.mime_type}, got ${s3Head.contentType}`);
|
|
399
397
|
}
|
|
400
398
|
// --- Transition file to 'ready' ---
|
|
401
399
|
await txClient.query({
|
|
@@ -244,7 +244,7 @@ export async function resolveStorageModuleByFileId(pgClient, databaseId, fileId)
|
|
|
244
244
|
// Probe each module's files table for the fileId
|
|
245
245
|
for (const config of allConfigs) {
|
|
246
246
|
const fileResult = await pgClient.query({
|
|
247
|
-
text: `SELECT id, key,
|
|
247
|
+
text: `SELECT id, key, mime_type, status, bucket_id
|
|
248
248
|
FROM ${config.filesQualifiedName}
|
|
249
249
|
WHERE id = $1
|
|
250
250
|
LIMIT 1`,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "graphile-presigned-url-plugin",
|
|
3
|
-
"version": "0.6.
|
|
3
|
+
"version": "0.6.2",
|
|
4
4
|
"description": "Presigned URL upload plugin for PostGraphile v5 — requestUploadUrl, confirmUpload mutations and downloadUrl computed field",
|
|
5
5
|
"author": "Constructive <developers@constructive.io>",
|
|
6
6
|
"homepage": "https://github.com/constructive-io/constructive",
|
|
@@ -60,5 +60,5 @@
|
|
|
60
60
|
"@types/node": "^22.19.11",
|
|
61
61
|
"makage": "^0.1.10"
|
|
62
62
|
},
|
|
63
|
-
"gitHead": "
|
|
63
|
+
"gitHead": "15a46bcefd6cf72ba0f40228a6a8279f07feb3f4"
|
|
64
64
|
}
|
package/plugin.js
CHANGED
|
@@ -263,15 +263,15 @@ function createPresignedUrlPlugin(options) {
|
|
|
263
263
|
throw new Error(`FILE_TOO_LARGE: exceeds bucket max of ${bucket.max_file_size} bytes`);
|
|
264
264
|
}
|
|
265
265
|
const s3Key = buildS3Key(contentHash);
|
|
266
|
-
// --- Dedup check: look for existing file with same
|
|
266
|
+
// --- Dedup check: look for existing file with same key (content hash) in this bucket ---
|
|
267
267
|
const dedupResult = await txClient.query({
|
|
268
268
|
text: `SELECT id, status
|
|
269
269
|
FROM ${storageConfig.filesQualifiedName}
|
|
270
|
-
WHERE
|
|
270
|
+
WHERE key = $1
|
|
271
271
|
AND bucket_id = $2
|
|
272
272
|
AND status IN ('ready', 'processed')
|
|
273
273
|
LIMIT 1`,
|
|
274
|
-
values: [
|
|
274
|
+
values: [s3Key, bucket.id],
|
|
275
275
|
});
|
|
276
276
|
if (dedupResult.rows.length > 0) {
|
|
277
277
|
const existingFile = dedupResult.rows[0];
|
|
@@ -279,9 +279,9 @@ function createPresignedUrlPlugin(options) {
|
|
|
279
279
|
// Track the dedup request
|
|
280
280
|
await txClient.query({
|
|
281
281
|
text: `INSERT INTO ${storageConfig.uploadRequestsQualifiedName}
|
|
282
|
-
(file_id, bucket_id, key, content_type, content_hash,
|
|
283
|
-
VALUES ($1, $2, $3, $4, $5,
|
|
284
|
-
values: [existingFile.id, bucket.id, s3Key, contentType, contentHash
|
|
282
|
+
(file_id, bucket_id, key, content_type, content_hash, status, expires_at)
|
|
283
|
+
VALUES ($1, $2, $3, $4, $5, 'confirmed', NOW())`,
|
|
284
|
+
values: [existingFile.id, bucket.id, s3Key, contentType, contentHash],
|
|
285
285
|
});
|
|
286
286
|
return {
|
|
287
287
|
uploadUrl: null,
|
|
@@ -297,19 +297,18 @@ function createPresignedUrlPlugin(options) {
|
|
|
297
297
|
const fileResult = await txClient.query({
|
|
298
298
|
text: hasOwnerColumn
|
|
299
299
|
? `INSERT INTO ${storageConfig.filesQualifiedName}
|
|
300
|
-
(bucket_id, key,
|
|
301
|
-
VALUES ($1, $2, $3, $4, $5, $6, $7,
|
|
300
|
+
(bucket_id, key, mime_type, size, filename, owner_id, is_public, status)
|
|
301
|
+
VALUES ($1, $2, $3, $4, $5, $6, $7, 'pending')
|
|
302
302
|
RETURNING id`
|
|
303
303
|
: `INSERT INTO ${storageConfig.filesQualifiedName}
|
|
304
|
-
(bucket_id, key,
|
|
305
|
-
VALUES ($1, $2, $3, $4, $5, $6,
|
|
304
|
+
(bucket_id, key, mime_type, size, filename, is_public, status)
|
|
305
|
+
VALUES ($1, $2, $3, $4, $5, $6, 'pending')
|
|
306
306
|
RETURNING id`,
|
|
307
307
|
values: hasOwnerColumn
|
|
308
308
|
? [
|
|
309
309
|
bucket.id,
|
|
310
310
|
s3Key,
|
|
311
311
|
contentType,
|
|
312
|
-
contentHash,
|
|
313
312
|
size,
|
|
314
313
|
filename || null,
|
|
315
314
|
bucket.owner_id,
|
|
@@ -319,7 +318,6 @@ function createPresignedUrlPlugin(options) {
|
|
|
319
318
|
bucket.id,
|
|
320
319
|
s3Key,
|
|
321
320
|
contentType,
|
|
322
|
-
contentHash,
|
|
323
321
|
size,
|
|
324
322
|
filename || null,
|
|
325
323
|
bucket.is_public,
|
|
@@ -335,9 +333,9 @@ function createPresignedUrlPlugin(options) {
|
|
|
335
333
|
// --- Track the upload request ---
|
|
336
334
|
await txClient.query({
|
|
337
335
|
text: `INSERT INTO ${storageConfig.uploadRequestsQualifiedName}
|
|
338
|
-
(file_id, bucket_id, key, content_type, content_hash,
|
|
339
|
-
VALUES ($1, $2, $3, $4, $5,
|
|
340
|
-
values: [fileId, bucket.id, s3Key, contentType, contentHash,
|
|
336
|
+
(file_id, bucket_id, key, content_type, content_hash, status, expires_at)
|
|
337
|
+
VALUES ($1, $2, $3, $4, $5, 'issued', $6)`,
|
|
338
|
+
values: [fileId, bucket.id, s3Key, contentType, contentHash, expiresAt],
|
|
341
339
|
});
|
|
342
340
|
return {
|
|
343
341
|
uploadUrl,
|
|
@@ -386,12 +384,12 @@ function createPresignedUrlPlugin(options) {
|
|
|
386
384
|
}
|
|
387
385
|
// --- Verify file exists in S3 (per-database bucket) ---
|
|
388
386
|
const s3ForDb = resolveS3ForDatabase(options, storageConfig, databaseId);
|
|
389
|
-
const s3Head = await (0, s3_signer_1.headObject)(s3ForDb, file.key, file.
|
|
387
|
+
const s3Head = await (0, s3_signer_1.headObject)(s3ForDb, file.key, file.mime_type);
|
|
390
388
|
if (!s3Head) {
|
|
391
389
|
throw new Error('FILE_NOT_IN_S3: the file has not been uploaded yet');
|
|
392
390
|
}
|
|
393
391
|
// --- Content-type verification ---
|
|
394
|
-
if (s3Head.contentType && s3Head.contentType !== file.
|
|
392
|
+
if (s3Head.contentType && s3Head.contentType !== file.mime_type) {
|
|
395
393
|
// Mark upload_request as rejected
|
|
396
394
|
await txClient.query({
|
|
397
395
|
text: `UPDATE ${storageConfig.uploadRequestsQualifiedName}
|
|
@@ -399,7 +397,7 @@ function createPresignedUrlPlugin(options) {
|
|
|
399
397
|
WHERE file_id = $1 AND status = 'issued'`,
|
|
400
398
|
values: [fileId],
|
|
401
399
|
});
|
|
402
|
-
throw new Error(`CONTENT_TYPE_MISMATCH: expected ${file.
|
|
400
|
+
throw new Error(`CONTENT_TYPE_MISMATCH: expected ${file.mime_type}, got ${s3Head.contentType}`);
|
|
403
401
|
}
|
|
404
402
|
// --- Transition file to 'ready' ---
|
|
405
403
|
await txClient.query({
|
package/storage-module-cache.js
CHANGED
|
@@ -254,7 +254,7 @@ async function resolveStorageModuleByFileId(pgClient, databaseId, fileId) {
|
|
|
254
254
|
// Probe each module's files table for the fileId
|
|
255
255
|
for (const config of allConfigs) {
|
|
256
256
|
const fileResult = await pgClient.query({
|
|
257
|
-
text: `SELECT id, key,
|
|
257
|
+
text: `SELECT id, key, mime_type, status, bucket_id
|
|
258
258
|
FROM ${config.filesQualifiedName}
|
|
259
259
|
WHERE id = $1
|
|
260
260
|
LIMIT 1`,
|