@balena/pinejs 21.4.0-build-large-file-uploads-2-56700d672d31db4406ba01ca349d69af5c8611e7-1 → 21.4.0-build-add-odata-actions-8b84ad1317d46475b2646504916f92426f33f78a-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.
Files changed (34) hide show
  1. package/.pinejs-cache.json +1 -1
  2. package/.versionbot/CHANGELOG.yml +3 -11
  3. package/CHANGELOG.md +1 -2
  4. package/out/config-loader/env.d.ts +0 -4
  5. package/out/config-loader/env.js +0 -4
  6. package/out/config-loader/env.js.map +1 -1
  7. package/out/sbvr-api/actions.d.ts +32 -0
  8. package/out/sbvr-api/actions.js +68 -0
  9. package/out/sbvr-api/actions.js.map +1 -0
  10. package/out/sbvr-api/sbvr-utils.d.ts +0 -1
  11. package/out/sbvr-api/sbvr-utils.js +8 -1
  12. package/out/sbvr-api/sbvr-utils.js.map +1 -1
  13. package/out/server-glue/module.d.ts +1 -0
  14. package/out/server-glue/module.js +1 -2
  15. package/out/server-glue/module.js.map +1 -1
  16. package/out/webresource-handler/index.d.ts +0 -9
  17. package/out/webresource-handler/index.js +1 -25
  18. package/out/webresource-handler/index.js.map +1 -1
  19. package/package.json +6 -6
  20. package/src/config-loader/env.ts +0 -11
  21. package/src/sbvr-api/actions.ts +141 -0
  22. package/src/sbvr-api/sbvr-utils.ts +10 -1
  23. package/src/server-glue/module.ts +1 -2
  24. package/src/webresource-handler/index.ts +0 -45
  25. package/out/webresource-handler/multipartUpload.d.ts +0 -12
  26. package/out/webresource-handler/multipartUpload.js +0 -297
  27. package/out/webresource-handler/multipartUpload.js.map +0 -1
  28. package/out/webresource-handler/webresource.d.ts +0 -42
  29. package/out/webresource-handler/webresource.js +0 -2
  30. package/out/webresource-handler/webresource.js.map +0 -1
  31. package/out/webresource-handler/webresource.sbvr +0 -60
  32. package/src/webresource-handler/multipartUpload.ts +0 -424
  33. package/src/webresource-handler/webresource.sbvr +0 -60
  34. package/src/webresource-handler/webresource.ts +0 -48
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@balena/pinejs",
3
- "version": "21.4.0-build-large-file-uploads-2-56700d672d31db4406ba01ca349d69af5c8611e7-1",
3
+ "version": "21.4.0-build-add-odata-actions-8b84ad1317d46475b2646504916f92426f33f78a-1",
4
4
  "main": "out/server-glue/module.js",
5
5
  "type": "module",
6
6
  "repository": "git@github.com:balena-io/pinejs.git",
@@ -20,10 +20,10 @@
20
20
  "webpack-build": "npm run webpack-browser && npm run webpack-module && npm run webpack-server",
21
21
  "lint": "balena-lint -t tsconfig.dev.json -e js -e ts src test build typings Gruntfile.cts && npx tsc --project tsconfig.dev.json --noEmit",
22
22
  "test": "npm run build && npm run lint && npm run webpack-build && npm run test:compose && npm run test:generated-types",
23
- "test:compose": "trap 'docker compose -f docker-compose.npm-test.yml down ; echo Stopped ; exit 0' INT; docker compose -f docker-compose.npm-test.yml up -d && sleep 2 && DATABASE_URL=postgres://docker:docker@localhost:5431/postgres PINEJS_WEBRESOURCE_MAXFILESIZE=1000000000 S3_ENDPOINT=http://localhost:43680 S3_ACCESS_KEY=USERNAME S3_SECRET_KEY=PASSWORD S3_STORAGE_ADAPTER_BUCKET=balena-pine-web-resources S3_REGION=us-east-1 PINEJS_QUEUE_CONCURRENCY=1 TZ=UTC PINEJS_WEBRESOURCE_MULTIPART_ENABLED=true npx mocha",
23
+ "test:compose": "trap 'docker compose -f docker-compose.npm-test.yml down ; echo Stopped ; exit 0' INT; docker compose -f docker-compose.npm-test.yml up -d && sleep 2 && DATABASE_URL=postgres://docker:docker@localhost:5431/postgres PINEJS_WEBRESOURCE_MAXFILESIZE=1000000000 S3_ENDPOINT=http://localhost:43680 S3_ACCESS_KEY=USERNAME S3_SECRET_KEY=PASSWORD S3_STORAGE_ADAPTER_BUCKET=balena-pine-web-resources S3_REGION=us-east-1 PINEJS_QUEUE_CONCURRENCY=1 TZ=UTC npx mocha",
24
24
  "test:generated-types": "npm run generate-types && git diff --exit-code ./src/sbvr-api/user.ts ./src/migrator/migrations.ts ./src/sbvr-api/dev.ts",
25
25
  "lint-fix": "balena-lint -t tsconfig.dev.json -e js -e ts --fix src test build typings Gruntfile.cts",
26
- "generate-types": "node ./bin/sbvr-compiler.js generate-types ./src/sbvr-api/user.sbvr ./src/sbvr-api/user.ts && node ./bin/sbvr-compiler.js generate-types ./src/migrator/migrations.sbvr ./src/migrator/migrations.ts && node ./bin/sbvr-compiler.js generate-types ./src/sbvr-api/dev.sbvr ./src/sbvr-api/dev.ts && node ./bin/sbvr-compiler.js generate-types ./src/tasks/tasks.sbvr ./src/tasks/tasks.ts && node ./bin/sbvr-compiler.js generate-types ./src/webresource-handler/webresource.sbvr ./src/webresource-handler/webresource.ts && balena-lint -t tsconfig.dev.json --fix ./src/sbvr-api/user.ts ./src/migrator/migrations.ts ./src/sbvr-api/dev.ts"
26
+ "generate-types": "node ./bin/sbvr-compiler.js generate-types ./src/sbvr-api/user.sbvr ./src/sbvr-api/user.ts && node ./bin/sbvr-compiler.js generate-types ./src/migrator/migrations.sbvr ./src/migrator/migrations.ts && node ./bin/sbvr-compiler.js generate-types ./src/sbvr-api/dev.sbvr ./src/sbvr-api/dev.ts && node ./bin/sbvr-compiler.js generate-types ./src/tasks/tasks.sbvr ./src/tasks/tasks.ts && balena-lint -t tsconfig.dev.json --fix ./src/sbvr-api/user.ts ./src/migrator/migrations.ts ./src/sbvr-api/dev.ts"
27
27
  },
28
28
  "dependencies": {
29
29
  "@balena/abstract-sql-compiler": "^10.2.3",
@@ -70,7 +70,7 @@
70
70
  "devDependencies": {
71
71
  "@balena/lint": "^9.1.6",
72
72
  "@balena/pinejs": "file:./",
73
- "@balena/pinejs-webresource-s3": "2.0.0-build-new-multiparthandle-interface-66b03581234eaa7ce15c6f389e39b5b7ed3d1bc5-1",
73
+ "@balena/pinejs-webresource-s3": "^1.0.4",
74
74
  "@faker-js/faker": "^9.6.0",
75
75
  "@types/busboy": "^1.5.4",
76
76
  "@types/chai": "^5.2.1",
@@ -145,10 +145,10 @@
145
145
  ],
146
146
  "loader": "ts-node/esm/transpile-only",
147
147
  "exit": true,
148
- "timeout": 6000000,
148
+ "timeout": 60000,
149
149
  "recursive": true
150
150
  },
151
151
  "versionist": {
152
- "publishedAt": "2025-04-30T01:26:55.916Z"
152
+ "publishedAt": "2025-04-30T22:49:25.738Z"
153
153
  }
154
154
  }
@@ -159,17 +159,6 @@ export const tasks = {
159
159
  queueIntervalMS: intVar('PINEJS_QUEUE_INTERVAL_MS', 1000),
160
160
  };
161
161
 
162
- export const webResource = {
163
- multipartUploadEnabled: boolVar(
164
- 'PINEJS_WEBRESOURCE_MULTIPART_ENABLED',
165
- false,
166
- ),
167
- singleUploadMaxFilesize: intVar(
168
- 'PINEJS_WEBRESOURCE_MAXFILESIZE',
169
- 299 * 1024 * 1024,
170
- ),
171
- };
172
-
173
162
  export const guardTestMockOnly = () => {
174
163
  if (process.env.DEPLOYMENT !== 'TEST') {
175
164
  throw new Error('Attempting to use TEST_MOCK_ONLY outside of tests');
@@ -0,0 +1,141 @@
1
+ import type { ODataQuery } from '@balena/odata-parser';
2
+ import {
3
+ BadRequestError,
4
+ type ParsedODataRequest,
5
+ } from '../sbvr-api/uri-parser.js';
6
+ import { api, type Response } from '../sbvr-api/sbvr-utils.js';
7
+ import type { Tx } from '../database-layer/db.js';
8
+ import { sbvrUtils } from '../server-glue/module.js';
9
+ import type { AnyObject, Params } from 'pinejs-client-core';
10
+ import { UnauthorizedError } from '../sbvr-api/errors.js';
11
+
12
+ export type ODataActionRequest = Omit<ParsedODataRequest, 'odataQuery'> & {
13
+ odataQuery: Omit<ODataQuery, 'property'> & {
14
+ property: {
15
+ resource: string;
16
+ };
17
+ };
18
+ };
19
+
20
+ type ActionReq = Express.Request;
21
+
22
+ export type ODataActionArgs<Vocab extends string> = {
23
+ request: ODataActionRequest;
24
+ tx: Tx;
25
+ api: (typeof api)[Vocab];
26
+ id: Params['id'];
27
+ req: ActionReq;
28
+ };
29
+ export type ODataAction<Vocab extends string = string> = (
30
+ args: ODataActionArgs<Vocab>,
31
+ ) => Promise<Response>;
32
+
33
+ const actions = {} as {
34
+ [vocab: string]: {
35
+ [resourceName: string]: {
36
+ [actionName: string]: ODataAction;
37
+ };
38
+ };
39
+ };
40
+
41
+ export const isActionRequest = (
42
+ request: ParsedODataRequest,
43
+ ): request is ODataActionRequest => {
44
+ // OData actions must always be POST
45
+ // See: https://www.odata.org/blog/actions-in-odata/
46
+ return (
47
+ request.method === 'POST' &&
48
+ request.odataQuery.property?.resource != null &&
49
+ actions?.[request.vocabulary]?.[request.resourceName]?.[
50
+ request.odataQuery.property.resource
51
+ ] != null
52
+ );
53
+ };
54
+
55
+ export const runAction = async (
56
+ request: ODataActionRequest,
57
+ req: ActionReq,
58
+ ): Promise<Response> => {
59
+ if (api[request.vocabulary] == null) {
60
+ throw new BadRequestError();
61
+ }
62
+
63
+ const actionName = request.odataQuery.property.resource;
64
+ const action =
65
+ actions?.[request.vocabulary]?.[request.resourceName]?.[actionName];
66
+ if (!action) {
67
+ throw new BadRequestError();
68
+ }
69
+
70
+ return sbvrUtils.db.transaction(async (tx) => {
71
+ // in practice, the parser does not currently allow actions without a key
72
+ // e.g. /vocab/resource(...)/action compiles but
73
+ // /vocab/resource/action won't even compile, so (current) reality is that if canAcccess
74
+ // does not throw, there will be an id, but I kept the interface as optional id
75
+ // for when the parser has support for root level actions
76
+ const id = request.odataQuery.key
77
+ ? await canRunAction(request, req, actionName, tx)
78
+ : undefined;
79
+
80
+ const applicationApi = api[request.vocabulary].clone({
81
+ passthrough: { tx, req },
82
+ });
83
+
84
+ return await action({
85
+ request: request,
86
+ tx,
87
+ api: applicationApi,
88
+ req: req,
89
+ id,
90
+ });
91
+ });
92
+ };
93
+
94
+ export const addAction = <Vocab extends string>(
95
+ vocabulary: Vocab,
96
+ resourceName: string,
97
+ actionName: string,
98
+ action: ODataAction<Vocab>,
99
+ ) => {
100
+ actions[vocabulary] ??= {};
101
+ actions[vocabulary][resourceName] ??= {};
102
+ actions[vocabulary][resourceName][actionName] = action;
103
+ };
104
+
105
+ export const canRunAction = async (
106
+ request: ParsedODataRequest,
107
+ req: ActionReq,
108
+ actionName: string,
109
+ tx: Tx,
110
+ ) => {
111
+ const canAccessUrl = request.url
112
+ .slice(1)
113
+ .replace(new RegExp(`(${actionName})$`), 'canAccess');
114
+
115
+ const applicationApi = api[request.vocabulary];
116
+ if (!applicationApi) {
117
+ throw new BadRequestError(`Could not find model ${request.vocabulary}`);
118
+ }
119
+
120
+ const res = await api[request.vocabulary].request({
121
+ method: 'POST',
122
+ url: canAccessUrl,
123
+ body: { action: actionName },
124
+ passthrough: { tx, req },
125
+ });
126
+
127
+ return canAccessResourceId(res);
128
+ };
129
+
130
+ const canAccessResourceId = (canAccessResponse: AnyObject): Params['id'] => {
131
+ const item = canAccessResponse?.d?.[0];
132
+ if (!item || typeof item !== 'object') {
133
+ throw new UnauthorizedError();
134
+ }
135
+ const keys = Object.keys(item);
136
+ if (keys.length !== 1 || !item[keys[0]]) {
137
+ throw new UnauthorizedError();
138
+ }
139
+
140
+ return item[keys[0]];
141
+ };
@@ -111,6 +111,7 @@ import {
111
111
  type MigrationExecutionResult,
112
112
  setExecutedMigrations,
113
113
  } from '../migrator/utils.js';
114
+ import { isActionRequest, runAction } from './actions.js';
114
115
 
115
116
  const LF2AbstractSQLTranslator = LF2AbstractSQL.createTranslator(sbvrTypes);
116
117
  const LF2AbstractSQLTranslatorVersion = `${LF2AbstractSQLVersion}+${sbvrTypesVersion}`;
@@ -1140,7 +1141,7 @@ const getFinalAbstractSqlModel = (
1140
1141
  return (request.finalAbstractSqlModel ??= models[finalModel].abstractSql);
1141
1142
  };
1142
1143
 
1143
- export const getIdField = (
1144
+ const getIdField = (
1144
1145
  request: Pick<
1145
1146
  uriParser.ODataRequest,
1146
1147
  | 'translateVersions'
@@ -1386,8 +1387,16 @@ const runODataRequest = (req: Express.Request, vocabulary: string) => {
1386
1387
 
1387
1388
  let request: uriParser.ODataRequest | uriParser.ODataRequest[];
1388
1389
  if (Array.isArray(parsedRequest)) {
1390
+ if (parsedRequest.some(isActionRequest)) {
1391
+ throw new BadRequestError(
1392
+ 'Action request are not supported in $batch requests',
1393
+ );
1394
+ }
1389
1395
  request = await controlFlow.mapSeries(parsedRequest, prepareRequest);
1390
1396
  } else {
1397
+ if (isActionRequest(parsedRequest)) {
1398
+ return await runAction(parsedRequest, req);
1399
+ }
1391
1400
  request = await prepareRequest(parsedRequest);
1392
1401
  }
1393
1402
  // Run the request in its own transaction
@@ -7,7 +7,7 @@ import * as configLoader from '../config-loader/config-loader.js';
7
7
  import * as migrator from '../migrator/sync.js';
8
8
  import type * as migratorUtils from '../migrator/utils.js';
9
9
  import * as tasks from '../tasks/index.js';
10
- import * as webresource from '../webresource-handler/index.js';
10
+ export * as actions from '../sbvr-api/actions.js';
11
11
 
12
12
  import * as sbvrUtils from '../sbvr-api/sbvr-utils.js';
13
13
  import { PINEJS_ADVISORY_LOCK } from '../config-loader/env.js';
@@ -67,7 +67,6 @@ export const init = async <T extends string>(
67
67
  const cfgLoader = configLoader.setup(app);
68
68
  await cfgLoader.loadConfig(migrator.config);
69
69
  await cfgLoader.loadConfig(tasks.config);
70
- await cfgLoader.loadConfig(webresource.config);
71
70
 
72
71
  if (!process.env.CONFIG_LOADER_DISABLED) {
73
72
  await cfgLoader.loadApplicationConfig(config);
@@ -11,20 +11,12 @@ import {
11
11
  odataNameToSqlName,
12
12
  sqlNameToODataName,
13
13
  } from '@balena/odata-to-abstract-sql';
14
- import type { ConfigLoader } from '../server-glue/module.js';
15
14
  import { errors, permissions } from '../server-glue/module.js';
16
15
  import type { WebResourceType as WebResource } from '@balena/sbvr-types';
17
16
  import { TypedError } from 'typed-error';
18
17
  import type { Resolvable } from '../sbvr-api/common-types.js';
19
- import type WebresourceModel from './webresource.js';
20
- import { importSBVR } from '../server-glue/sbvr-loader.js';
21
- import {
22
- isMultipartUploadAvailable,
23
- multipartUploadHooks,
24
- } from './multipartUpload.js';
25
18
 
26
19
  export * from './handlers/index.js';
27
- export type { BeginUploadResponse } from './multipartUpload.js';
28
20
 
29
21
  export interface IncomingFile {
30
22
  fieldname: string;
@@ -337,10 +329,6 @@ const throwIfWebresourceNotInMultipart = (
337
329
  { req, request }: HookArgs,
338
330
  ) => {
339
331
  if (
340
- request.custom.isAction !== 'beginUpload' &&
341
- request.custom.isAction !== 'commitUpload' &&
342
- request.custom.isAction !== 'cancelUpload' &&
343
- req.user !== permissions.root.user &&
344
332
  !req.is?.('multipart') &&
345
333
  webResourceFields.some((field) => request.values[field] != null)
346
334
  ) {
@@ -537,37 +525,4 @@ export const setupUploadHooks = (
537
525
  resourceName,
538
526
  getCreateWebResourceHooks(handler),
539
527
  );
540
-
541
- if (isMultipartUploadAvailable(handler)) {
542
- sbvrUtils.addPureHook(
543
- 'POST',
544
- apiRoot,
545
- resourceName,
546
- multipartUploadHooks(handler),
547
- );
548
- }
549
- };
550
-
551
- const initSql = `
552
- CREATE INDEX IF NOT EXISTS idx_multipart_upload_uuid ON "multipart upload" (uuid);
553
- CREATE INDEX IF NOT EXISTS idx_multipart_upload_status ON "multipart upload" (status);
554
- `;
555
-
556
- const modelText = await importSBVR('./webresource.sbvr', import.meta);
557
-
558
- declare module '../sbvr-api/sbvr-utils.js' {
559
- export interface API {
560
- webresource: PinejsClient<WebresourceModel>;
561
- }
562
- }
563
-
564
- export const config: ConfigLoader.Config = {
565
- models: [
566
- {
567
- modelName: 'webresource',
568
- apiRoot: 'webresource',
569
- modelText,
570
- initSql,
571
- },
572
- ],
573
528
  };
@@ -1,12 +0,0 @@
1
- import type { UploadPart, WebResourceHandler } from './index.js';
2
- import { sbvrUtils } from '../server-glue/module.js';
3
- export interface BeginUploadResponse {
4
- [fieldName: string]: {
5
- uuid: string;
6
- uploadParts: UploadPart[];
7
- };
8
- }
9
- type MultipartUploadHandler = WebResourceHandler & Required<Pick<WebResourceHandler, 'multipartUpload'>>;
10
- export declare const isMultipartUploadAvailable: (webResourceHandler: WebResourceHandler) => webResourceHandler is MultipartUploadHandler;
11
- export declare const multipartUploadHooks: (webResourceHandler: MultipartUploadHandler) => sbvrUtils.Hooks;
12
- export {};
@@ -1,297 +0,0 @@
1
- import { randomUUID } from 'node:crypto';
2
- import { getWebResourceFields } from './index.js';
3
- import { api, getAffectedIds, getIdField } from '../sbvr-api/sbvr-utils.js';
4
- import { errors, sbvrUtils } from '../server-glue/module.js';
5
- import { webResource as webResourceEnv } from '../config-loader/env.js';
6
- import * as permissions from '../sbvr-api/permissions.js';
7
- import { TransactionClosedError } from '../database-layer/db.js';
8
- import { UnauthorizedError } from '../sbvr-api/errors.js';
9
- export const isMultipartUploadAvailable = (webResourceHandler) => {
10
- return (webResourceEnv.multipartUploadEnabled &&
11
- webResourceHandler.multipartUpload != null);
12
- };
13
- export const multipartUploadHooks = (webResourceHandler) => {
14
- return {
15
- POSTPARSE: async ({ request, tx, api: applicationApi }) => {
16
- if (request.odataQuery.property?.resource === 'beginUpload') {
17
- const uploadParams = await validateBeginUpload(request, applicationApi, webResourceHandler);
18
- request.method = 'GET';
19
- request.odataQuery.resource = request.resourceName;
20
- delete request.odataQuery.property;
21
- request.custom.isAction = 'beginUpload';
22
- request.custom.uploadProbeParams = uploadParams;
23
- }
24
- else if (request.odataQuery.property?.resource === 'commitUpload') {
25
- const commitPayload = await validateCommitUpload(request, applicationApi);
26
- request.method = 'PATCH';
27
- request.values = {
28
- [commitPayload.metadata.fieldName]: undefined,
29
- };
30
- request.odataQuery.resource = request.resourceName;
31
- delete request.odataQuery.property;
32
- request.custom.isAction = 'commitUpload';
33
- request.custom.commitPayload = commitPayload;
34
- request.custom.fieldName = commitPayload.metadata.fieldName;
35
- }
36
- else if (request.odataQuery.property?.resource === 'cancelUpload') {
37
- const { uuid, fileKey, uploadId } = await validateCancelPayload(request, applicationApi);
38
- await webResourceHandler.multipartUpload.cancel({ fileKey, uploadId });
39
- await api.webresource.patch({
40
- resource: 'multipart_upload',
41
- body: {
42
- status: 'cancelled',
43
- },
44
- options: {
45
- $filter: { uuid },
46
- },
47
- passthrough: {
48
- tx: tx,
49
- req: permissions.root,
50
- },
51
- });
52
- request.method = 'GET';
53
- request.odataQuery.resource = request.resourceName;
54
- delete request.odataQuery.property;
55
- request.custom.isAction = 'cancelUpload';
56
- }
57
- },
58
- PRERUN: async (args) => {
59
- const { api: applicationApi, request, tx } = args;
60
- if (request.custom.isAction === 'beginUpload') {
61
- try {
62
- await sbvrUtils.db.transaction(async (probeTx) => {
63
- const newUrl = request.url
64
- .slice(1)
65
- .replace(/(\/beginUpload|\/commitUpload|\/cancelUpload)$/, '');
66
- await applicationApi.request({
67
- method: 'PATCH',
68
- url: newUrl,
69
- body: request.custom.uploadProbeParams,
70
- passthrough: { tx: probeTx, req: permissions.root },
71
- });
72
- await probeTx.rollback();
73
- });
74
- }
75
- catch (e) {
76
- if (!(e instanceof TransactionClosedError &&
77
- e.message === 'Transaction has been rolled back.')) {
78
- throw e;
79
- }
80
- }
81
- }
82
- else if (request.custom.isAction === 'commitUpload') {
83
- args.request.url = args.request.url.replace(/(\/beginUpload|\/commitUpload|\/cancelUpload)$/, '');
84
- const ids = await getAffectedIds(args);
85
- if (ids.length !== 1) {
86
- throw new UnauthorizedError();
87
- }
88
- if (ids[0] !== request.custom.commitPayload.metadata.resourceId) {
89
- throw new UnauthorizedError();
90
- }
91
- const webresource = await webResourceHandler.multipartUpload.commit({
92
- fileKey: request.custom.commitPayload.metadata.fileKey,
93
- uploadId: request.custom.commitPayload.metadata.uploadId,
94
- filename: request.custom.commitPayload.metadata.filename,
95
- providerCommitData: request.custom.commitPayload.providerCommitData,
96
- });
97
- request.values[request.custom.fieldName] = webresource;
98
- await api.webresource.patch({
99
- resource: 'multipart_upload',
100
- body: {
101
- status: 'completed',
102
- },
103
- options: {
104
- $filter: {
105
- uuid: request.custom.commitPayload.uuid,
106
- },
107
- },
108
- passthrough: {
109
- tx: tx,
110
- req: permissions.root,
111
- },
112
- });
113
- request.custom.commitUploadPayload = webresource;
114
- }
115
- },
116
- PRERESPOND: async ({ request, response, req, tx }) => {
117
- if (request.custom.isAction === 'beginUpload') {
118
- if (!response.body || typeof response.body === 'string') {
119
- throw new UnauthorizedError();
120
- }
121
- const idField = getIdField(request);
122
- const resourceId = response.body?.d?.[0]?.[idField];
123
- if (!resourceId) {
124
- throw new UnauthorizedError();
125
- }
126
- response.statusCode = 200;
127
- response.body = await beginUpload({
128
- webResourceHandler,
129
- odataRequest: request,
130
- actorId: req.user?.actor,
131
- resourceId,
132
- }, tx);
133
- }
134
- else if (request.custom.isAction === 'commitUpload') {
135
- response.body = await webResourceHandler.onPreRespond(request.custom.commitUploadPayload);
136
- }
137
- else if (request.custom.isAction === 'cancelUpload') {
138
- response.statusCode = 204;
139
- delete response.body;
140
- }
141
- },
142
- };
143
- };
144
- const beginUpload = async ({ webResourceHandler, odataRequest, actorId, resourceId, }, tx) => {
145
- const payload = odataRequest.values;
146
- const fieldName = Object.keys(payload)[0];
147
- const metadata = payload[fieldName];
148
- const { fileKey, uploadId, uploadParts } = await webResourceHandler.multipartUpload.begin(fieldName, metadata);
149
- const uuid = randomUUID();
150
- await api.webresource.post({
151
- resource: 'multipart_upload',
152
- body: {
153
- uuid,
154
- resource_name: odataRequest.resourceName,
155
- field_name: fieldName,
156
- resource_id: resourceId,
157
- upload_id: uploadId,
158
- file_key: fileKey,
159
- status: 'pending',
160
- filename: metadata.filename,
161
- content_type: metadata.content_type,
162
- size: metadata.size,
163
- chunk_size: metadata.chunk_size,
164
- expiry_date: Date.now() + 7 * 24 * 60 * 60 * 1000,
165
- is_created_by__actor: actorId,
166
- },
167
- passthrough: {
168
- req: permissions.root,
169
- tx,
170
- },
171
- });
172
- return { [fieldName]: { uuid, uploadParts } };
173
- };
174
- const validateBeginUpload = async (request, applicationApi, webResourceHandler) => {
175
- await canAccess(request, applicationApi);
176
- const fieldNames = Object.keys(request.values);
177
- if (fieldNames.length !== 1) {
178
- throw new errors.BadRequestError('You can only get upload url for one field at a time');
179
- }
180
- const [fieldName] = fieldNames;
181
- const webResourceFields = getWebResourceFields(request);
182
- if (!webResourceFields.includes(fieldName)) {
183
- throw new errors.BadRequestError(`The provided field '${fieldName}' is not a valid webresource`);
184
- }
185
- const beginUploadPayload = parseBeginUploadPayload(request.values[fieldName], webResourceHandler);
186
- if (beginUploadPayload == null) {
187
- throw new errors.BadRequestError('Invalid file metadata');
188
- }
189
- const uploadMetadataCheck = {
190
- ...beginUploadPayload,
191
- href: 'metadata_check_probe',
192
- };
193
- return { [fieldName]: uploadMetadataCheck };
194
- };
195
- const parseBeginUploadPayload = (payload, webResourceHandler) => {
196
- if (payload == null || typeof payload !== 'object') {
197
- return null;
198
- }
199
- let { filename, content_type, size, chunk_size } = payload;
200
- if (typeof filename !== 'string' ||
201
- typeof content_type !== 'string' ||
202
- typeof size !== 'number' ||
203
- (chunk_size != null && typeof chunk_size !== 'number') ||
204
- (chunk_size != null &&
205
- chunk_size < webResourceHandler.multipartUpload.getMinimumPartSize())) {
206
- return null;
207
- }
208
- chunk_size ??= webResourceHandler.multipartUpload.getDefaultPartSize();
209
- return { filename, content_type, size, chunk_size };
210
- };
211
- const validateCommitUpload = async (request, applicationApi) => {
212
- await canAccess(request, applicationApi);
213
- const { uuid, providerCommitData } = request.values;
214
- if (typeof uuid !== 'string') {
215
- throw new errors.BadRequestError('Invalid uuid type');
216
- }
217
- const [multipartUpload] = await api.webresource.get({
218
- resource: 'multipart_upload',
219
- options: {
220
- $select: [
221
- 'id',
222
- 'file_key',
223
- 'upload_id',
224
- 'field_name',
225
- 'filename',
226
- 'resource_id',
227
- 'resource_name',
228
- ],
229
- $filter: {
230
- uuid,
231
- status: 'pending',
232
- expiry_date: { $gt: { $now: {} } },
233
- resource_name: request.resourceName,
234
- },
235
- },
236
- passthrough: {
237
- tx: request.tx,
238
- req: permissions.rootRead,
239
- },
240
- });
241
- if (multipartUpload == null) {
242
- throw new errors.BadRequestError(`Invalid upload for uuid ${uuid}`);
243
- }
244
- const metadata = {
245
- fileKey: multipartUpload.file_key,
246
- uploadId: multipartUpload.upload_id,
247
- filename: multipartUpload.filename,
248
- fieldName: multipartUpload.field_name,
249
- resourceName: multipartUpload.resource_name,
250
- resourceId: multipartUpload.resource_id,
251
- };
252
- return { uuid, providerCommitData, metadata };
253
- };
254
- const validateCancelPayload = async (request, applicationApi) => {
255
- await canAccess(request, applicationApi);
256
- const { uuid } = request.values;
257
- if (typeof uuid !== 'string') {
258
- throw new errors.BadRequestError('Invalid uuid type');
259
- }
260
- const [multipartUpload] = await api.webresource.get({
261
- resource: 'multipart_upload',
262
- options: {
263
- $select: ['id', 'file_key', 'upload_id'],
264
- $filter: {
265
- uuid,
266
- status: 'pending',
267
- expiry_date: { $gt: { $now: {} } },
268
- },
269
- },
270
- passthrough: {
271
- tx: request.tx,
272
- req: permissions.rootRead,
273
- },
274
- });
275
- if (multipartUpload == null) {
276
- throw new errors.BadRequestError(`Invalid upload for uuid ${uuid}`);
277
- }
278
- return {
279
- uuid,
280
- fileKey: multipartUpload.file_key,
281
- uploadId: multipartUpload.upload_id,
282
- };
283
- };
284
- const canAccess = async (request, applicationApi) => {
285
- if (request.odataQuery.key == null) {
286
- throw new errors.BadRequestError();
287
- }
288
- const canAccessUrl = request.url
289
- .slice(1)
290
- .replace(/(beginUpload|commitUpload|cancelUpload)$/, 'canAccess');
291
- await applicationApi.request({
292
- method: 'POST',
293
- url: canAccessUrl,
294
- body: { method: 'PATCH' },
295
- });
296
- };
297
- //# sourceMappingURL=multipartUpload.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"multipartUpload.js","sourceRoot":"","sources":["../../src/webresource-handler/multipartUpload.ts"],"names":[],"mappings":"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"}