@balena/pinejs 21.4.0-build-add-actions-example-00cabf6eb1629a826638b8c81dcf6d45a534872b-1 → 21.4.0-build-add-actions-example-6ac2b3e43d47ee50b22cbf3985c736f454ad73bf-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/.pinejs-cache.json +1 -1
- package/.versionbot/CHANGELOG.yml +36 -4
- package/CHANGELOG.md +5 -1
- package/out/config-loader/config-loader.js +2 -2
- package/out/config-loader/config-loader.js.map +1 -1
- package/out/tasks/index.d.ts +1 -0
- package/out/tasks/index.js +6 -1
- package/out/tasks/index.js.map +1 -1
- package/out/tasks/pine-tasks.d.ts +1 -0
- package/out/tasks/pine-tasks.js +7 -0
- package/out/tasks/pine-tasks.js.map +1 -0
- package/out/webresource-handler/delete-file-task.d.ts +1 -0
- package/out/webresource-handler/delete-file-task.js +37 -0
- package/out/webresource-handler/delete-file-task.js.map +1 -0
- package/out/webresource-handler/index.d.ts +2 -2
- package/out/webresource-handler/index.js +54 -46
- package/out/webresource-handler/index.js.map +1 -1
- package/out/webresource-handler/webresource.d.ts +2 -2
- package/out/webresource-handler/webresource.sbvr +1 -1
- package/package.json +2 -2
- package/src/config-loader/config-loader.ts +2 -2
- package/src/tasks/index.ts +9 -1
- package/src/tasks/pine-tasks.ts +7 -0
- package/src/webresource-handler/delete-file-task.ts +42 -0
- package/src/webresource-handler/index.ts +69 -89
- package/src/webresource-handler/webresource.sbvr +1 -1
- package/src/webresource-handler/webresource.ts +2 -2
@@ -0,0 +1,42 @@
|
|
1
|
+
import { addTaskHandler } from '../tasks/index.js';
|
2
|
+
import { getWebresourceHandler } from './index.js';
|
3
|
+
|
4
|
+
const deleteFileSchema = {
|
5
|
+
type: 'object',
|
6
|
+
properties: {
|
7
|
+
fileKey: {
|
8
|
+
type: 'string',
|
9
|
+
},
|
10
|
+
},
|
11
|
+
required: ['fileKey'],
|
12
|
+
additionalProperties: false,
|
13
|
+
} as const;
|
14
|
+
|
15
|
+
export const addDeleteFileTaskHandler = () => {
|
16
|
+
addTaskHandler(
|
17
|
+
'delete_webresource_file',
|
18
|
+
async (task) => {
|
19
|
+
const handler = getWebresourceHandler();
|
20
|
+
if (!handler) {
|
21
|
+
return {
|
22
|
+
error: 'Webresource handler not available',
|
23
|
+
status: 'failed',
|
24
|
+
};
|
25
|
+
}
|
26
|
+
|
27
|
+
try {
|
28
|
+
await handler.removeFile(task.params.fileKey);
|
29
|
+
return {
|
30
|
+
status: 'succeeded',
|
31
|
+
};
|
32
|
+
} catch (error) {
|
33
|
+
console.error('Error deleting file:', error);
|
34
|
+
return {
|
35
|
+
error: `${error}`,
|
36
|
+
status: 'failed',
|
37
|
+
};
|
38
|
+
}
|
39
|
+
},
|
40
|
+
deleteFileSchema,
|
41
|
+
);
|
42
|
+
};
|
@@ -16,6 +16,8 @@ import { errors, permissions } from '../server-glue/module.js';
|
|
16
16
|
import type { WebResourceType as WebResource } from '@balena/sbvr-types';
|
17
17
|
import { TypedError } from 'typed-error';
|
18
18
|
import type { Resolvable } from '../sbvr-api/common-types.js';
|
19
|
+
import type { Tx } from '../database-layer/db.js';
|
20
|
+
import { isPineTasksAvailable } from '../tasks/index.js';
|
19
21
|
import type WebresourceModel from './webresource.js';
|
20
22
|
import { importSBVR } from '../server-glue/sbvr-loader.js';
|
21
23
|
import { beginUploadAction } from './actions/beginUpload.js';
|
@@ -324,12 +326,34 @@ export const getWebResourceFields = (
|
|
324
326
|
.map((f) => sqlNameToODataName(f.fieldName));
|
325
327
|
};
|
326
328
|
|
327
|
-
const
|
328
|
-
|
329
|
-
|
330
|
-
) => {
|
331
|
-
|
332
|
-
|
329
|
+
const deleteOnStorage = async (keysToDelete: string[], tx?: Tx) => {
|
330
|
+
if (isPineTasksAvailable()) {
|
331
|
+
await Promise.all(
|
332
|
+
keysToDelete.map(async (fileKey) => {
|
333
|
+
return await sbvrUtils.api.tasks.post({
|
334
|
+
resource: 'task',
|
335
|
+
passthrough: {
|
336
|
+
req: permissions.root,
|
337
|
+
tx,
|
338
|
+
},
|
339
|
+
body: {
|
340
|
+
key: crypto.randomUUID(),
|
341
|
+
is_executed_by__handler: 'delete_webresource_file',
|
342
|
+
is_executed_with__parameter_set: {
|
343
|
+
fileKey: fileKey,
|
344
|
+
},
|
345
|
+
attempt_limit: 2 ** 31 - 1,
|
346
|
+
},
|
347
|
+
});
|
348
|
+
}),
|
349
|
+
);
|
350
|
+
} else {
|
351
|
+
await Promise.all(
|
352
|
+
keysToDelete.map(async (fileKey) => {
|
353
|
+
return await configuredWebResourceHandler?.removeFile(fileKey);
|
354
|
+
}),
|
355
|
+
);
|
356
|
+
}
|
333
357
|
};
|
334
358
|
|
335
359
|
const throwIfWebresourceNotInMultipart = (
|
@@ -353,18 +377,22 @@ const throwIfWebresourceNotInMultipart = (
|
|
353
377
|
}
|
354
378
|
};
|
355
379
|
|
356
|
-
const getCreateWebResourceHooks = (
|
357
|
-
webResourceHandler: WebResourceHandler,
|
358
|
-
): sbvrUtils.Hooks => {
|
380
|
+
const getCreateWebResourceHooks = (): sbvrUtils.Hooks => {
|
359
381
|
return {
|
360
382
|
PRERUN: (hookArgs) => {
|
361
383
|
const webResourceFields = getWebResourceFields(hookArgs.request);
|
362
384
|
throwIfWebresourceNotInMultipart(webResourceFields, hookArgs);
|
363
385
|
},
|
364
|
-
'POSTRUN-ERROR': ({
|
365
|
-
|
366
|
-
|
367
|
-
|
386
|
+
'POSTRUN-ERROR': async ({ request }) => {
|
387
|
+
const fields = getWebResourceFields(request);
|
388
|
+
|
389
|
+
if (fields.length === 0) {
|
390
|
+
return;
|
391
|
+
}
|
392
|
+
|
393
|
+
const keysToDelete = getWebResourcesKeysFromRequest(fields, request);
|
394
|
+
// Explicitely not passing tx as it will be rolledback as it is an error hook
|
395
|
+
await deleteOnStorage(keysToDelete);
|
368
396
|
},
|
369
397
|
};
|
370
398
|
};
|
@@ -393,21 +421,12 @@ const getWebResourcesKeysFromRequest = (
|
|
393
421
|
.filter((href) => href != null);
|
394
422
|
};
|
395
423
|
|
396
|
-
const getRemoveWebResourceHooks = (
|
397
|
-
webResourceHandler: WebResourceHandler,
|
398
|
-
): sbvrUtils.Hooks => {
|
424
|
+
const getRemoveWebResourceHooks = (): sbvrUtils.Hooks => {
|
399
425
|
return {
|
400
426
|
PRERUN: async (args) => {
|
401
427
|
const { api, request, tx } = args;
|
402
428
|
let webResourceFields = getWebResourceFields(request);
|
403
429
|
|
404
|
-
throwIfWebresourceNotInMultipart(webResourceFields, args);
|
405
|
-
|
406
|
-
// Request failed on DB roundtrip (e.g. DB constraint) and pending files need to be deleted
|
407
|
-
tx.on('rollback', () => {
|
408
|
-
void deleteRollbackPendingFields(request, webResourceHandler);
|
409
|
-
});
|
410
|
-
|
411
430
|
if (request.method === 'PATCH') {
|
412
431
|
webResourceFields = Object.entries(request.values)
|
413
432
|
.filter(
|
@@ -416,6 +435,8 @@ const getRemoveWebResourceHooks = (
|
|
416
435
|
)
|
417
436
|
.map(([key]) => key);
|
418
437
|
}
|
438
|
+
request.custom.webResourceFields = webResourceFields;
|
439
|
+
throwIfWebresourceNotInMultipart(webResourceFields, args);
|
419
440
|
|
420
441
|
if (webResourceFields.length === 0) {
|
421
442
|
// No need to delete anything as no file is in the wire
|
@@ -426,43 +447,32 @@ const getRemoveWebResourceHooks = (
|
|
426
447
|
// This can only be validated here because we need to first ensure the
|
427
448
|
// request is actually modifying a webresource before erroring out
|
428
449
|
if (request.method === 'PATCH' && request.odataQuery?.key == null) {
|
429
|
-
// When we get here, files have already been uploaded. We need to mark them for deletion.
|
430
|
-
const keysToDelete = getWebResourcesKeysFromRequest(
|
431
|
-
webResourceFields,
|
432
|
-
request,
|
433
|
-
);
|
434
|
-
|
435
|
-
// Set deletion of files on the wire as request will throw
|
436
|
-
tx.on('end', () => {
|
437
|
-
deletePendingFiles(keysToDelete, request, webResourceHandler);
|
438
|
-
});
|
439
|
-
|
440
450
|
throw new errors.BadRequestError(
|
441
451
|
'WebResources can only be updated when providing a resource key.',
|
442
452
|
);
|
443
453
|
}
|
444
454
|
|
445
|
-
// This can be > 1 in both DELETE requests or PATCH requests to not accessible IDs.
|
446
455
|
const ids = await sbvrUtils.getAffectedIds(args);
|
447
456
|
if (ids.length === 0) {
|
448
457
|
// Set deletion of files on the wire as no resource was affected
|
449
|
-
|
450
|
-
const keysToDelete = getWebResourcesKeysFromRequest(
|
458
|
+
request.custom.onPostRunDelete = getWebResourcesKeysFromRequest(
|
451
459
|
webResourceFields,
|
452
460
|
request,
|
453
461
|
);
|
454
|
-
deletePendingFiles(keysToDelete, request, webResourceHandler);
|
455
462
|
return;
|
456
463
|
}
|
457
464
|
|
465
|
+
// If it reaches here, it means that it will try to patch/delete the webresource
|
466
|
+
// So we need (before postrun) get what are the current keys to be deleted
|
467
|
+
// if post run succeeds
|
458
468
|
const webResources = (await api.get({
|
459
469
|
resource: request.resourceName,
|
460
470
|
passthrough: {
|
461
|
-
tx
|
471
|
+
tx,
|
462
472
|
req: permissions.root,
|
463
473
|
},
|
464
474
|
options: {
|
465
|
-
$select: webResourceFields,
|
475
|
+
$select: request.custom.webResourceFields,
|
466
476
|
$filter: {
|
467
477
|
id: {
|
468
478
|
$in: ids,
|
@@ -470,60 +480,34 @@ const getRemoveWebResourceHooks = (
|
|
470
480
|
},
|
471
481
|
},
|
472
482
|
})) as WebResourcesDbResponse[] | undefined | null;
|
483
|
+
request.custom.onPostRunDelete = getWebResourcesHrefs(webResources);
|
484
|
+
},
|
473
485
|
|
474
|
-
|
475
|
-
|
476
|
-
|
477
|
-
|
478
|
-
|
479
|
-
|
480
|
-
|
481
|
-
|
486
|
+
POSTRUN: async ({ request, tx }) => {
|
487
|
+
// Either patch or delete worked. In either case, schedule the previous existing files to delete
|
488
|
+
await deleteOnStorage(request.custom.onPostRunDelete ?? [], tx);
|
489
|
+
},
|
490
|
+
'POSTRUN-ERROR': async ({ request }) => {
|
491
|
+
const keysToDelete = getWebResourcesKeysFromRequest(
|
492
|
+
request.custom.webResourceFields,
|
493
|
+
request,
|
494
|
+
);
|
495
|
+
// Explicitely not passing tx as it will be rolledback as it is an error hook
|
496
|
+
await deleteOnStorage(keysToDelete);
|
482
497
|
},
|
483
498
|
};
|
484
499
|
};
|
485
500
|
|
486
|
-
const deleteRollbackPendingFields = async (
|
487
|
-
request: uriParser.ODataRequest,
|
488
|
-
webResourceHandler: WebResourceHandler,
|
489
|
-
) => {
|
490
|
-
const fields = getWebResourceFields(request);
|
491
|
-
|
492
|
-
if (fields.length === 0) {
|
493
|
-
return;
|
494
|
-
}
|
495
|
-
|
496
|
-
const keysToDelete = getWebResourcesKeysFromRequest(fields, request);
|
497
|
-
await deleteFiles(keysToDelete, webResourceHandler);
|
498
|
-
};
|
499
|
-
|
500
|
-
const deletePendingFiles = (
|
501
|
-
keysToDelete: string[],
|
502
|
-
request: uriParser.ODataRequest,
|
503
|
-
webResourceHandler: WebResourceHandler,
|
504
|
-
): void => {
|
505
|
-
// on purpose does not await for this promise to resolve
|
506
|
-
try {
|
507
|
-
void deleteFiles(keysToDelete, webResourceHandler);
|
508
|
-
} catch (err) {
|
509
|
-
getLogger(request.vocabulary).error(`Failed to delete pending files`, err);
|
510
|
-
}
|
511
|
-
};
|
512
|
-
|
513
501
|
export const getDefaultHandler = (): WebResourceHandler => {
|
514
502
|
return new NoopHandler();
|
515
503
|
};
|
516
504
|
|
517
|
-
export const setupUploadHooks = (
|
518
|
-
handler: WebResourceHandler,
|
519
|
-
apiRoot: string,
|
520
|
-
resourceName: string,
|
521
|
-
) => {
|
505
|
+
export const setupUploadHooks = (apiRoot: string, resourceName: string) => {
|
522
506
|
sbvrUtils.addPureHook(
|
523
507
|
'DELETE',
|
524
508
|
apiRoot,
|
525
509
|
resourceName,
|
526
|
-
getRemoveWebResourceHooks(
|
510
|
+
getRemoveWebResourceHooks(),
|
527
511
|
);
|
528
512
|
|
529
513
|
sbvrUtils.addPureHook(
|
@@ -531,24 +515,20 @@ export const setupUploadHooks = (
|
|
531
515
|
apiRoot,
|
532
516
|
resourceName,
|
533
517
|
// PATCH also needs to remove the old resource in case a webresource was modified
|
534
|
-
getRemoveWebResourceHooks(
|
518
|
+
getRemoveWebResourceHooks(),
|
535
519
|
);
|
536
520
|
|
537
521
|
sbvrUtils.addPureHook(
|
538
522
|
'POST',
|
539
523
|
apiRoot,
|
540
524
|
resourceName,
|
541
|
-
getCreateWebResourceHooks(
|
525
|
+
getCreateWebResourceHooks(),
|
542
526
|
);
|
543
527
|
};
|
544
528
|
|
545
|
-
export const setupUploadActions = (
|
546
|
-
handler: WebResourceHandler,
|
547
|
-
apiRoot: string,
|
548
|
-
resourceName: string,
|
549
|
-
) => {
|
529
|
+
export const setupUploadActions = (apiRoot: string, resourceName: string) => {
|
550
530
|
const resource = sqlNameToODataName(resourceName);
|
551
|
-
if (isMultipartUploadAvailable(
|
531
|
+
if (isMultipartUploadAvailable(configuredWebResourceHandler)) {
|
552
532
|
addAction(apiRoot, resource, 'beginUpload', beginUploadAction);
|
553
533
|
|
554
534
|
addAction(apiRoot, resource, 'commitUpload', commitUploadAction);
|
@@ -16,7 +16,7 @@ export interface MultipartUpload {
|
|
16
16
|
status: 'pending' | 'completed' | 'cancelled';
|
17
17
|
filename: Types['Short Text']['Read'];
|
18
18
|
content_type: Types['Short Text']['Read'];
|
19
|
-
size: Types['Integer']['Read'];
|
19
|
+
size: Types['Big Integer']['Read'];
|
20
20
|
chunk_size: Types['Integer']['Read'];
|
21
21
|
expiry_date: Types['Date Time']['Read'];
|
22
22
|
is_created_by__actor: Types['Integer']['Read'] | null;
|
@@ -34,7 +34,7 @@ export interface MultipartUpload {
|
|
34
34
|
status: 'pending' | 'completed' | 'cancelled';
|
35
35
|
filename: Types['Short Text']['Write'];
|
36
36
|
content_type: Types['Short Text']['Write'];
|
37
|
-
size: Types['Integer']['Write'];
|
37
|
+
size: Types['Big Integer']['Write'];
|
38
38
|
chunk_size: Types['Integer']['Write'];
|
39
39
|
expiry_date: Types['Date Time']['Write'];
|
40
40
|
is_created_by__actor: Types['Integer']['Write'] | null;
|