@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.
- package/.pinejs-cache.json +1 -1
- package/.versionbot/CHANGELOG.yml +3 -11
- package/CHANGELOG.md +1 -2
- package/out/config-loader/env.d.ts +0 -4
- package/out/config-loader/env.js +0 -4
- package/out/config-loader/env.js.map +1 -1
- package/out/sbvr-api/actions.d.ts +32 -0
- package/out/sbvr-api/actions.js +68 -0
- package/out/sbvr-api/actions.js.map +1 -0
- package/out/sbvr-api/sbvr-utils.d.ts +0 -1
- package/out/sbvr-api/sbvr-utils.js +8 -1
- package/out/sbvr-api/sbvr-utils.js.map +1 -1
- package/out/server-glue/module.d.ts +1 -0
- package/out/server-glue/module.js +1 -2
- package/out/server-glue/module.js.map +1 -1
- package/out/webresource-handler/index.d.ts +0 -9
- package/out/webresource-handler/index.js +1 -25
- package/out/webresource-handler/index.js.map +1 -1
- package/package.json +6 -6
- package/src/config-loader/env.ts +0 -11
- package/src/sbvr-api/actions.ts +141 -0
- package/src/sbvr-api/sbvr-utils.ts +10 -1
- package/src/server-glue/module.ts +1 -2
- package/src/webresource-handler/index.ts +0 -45
- package/out/webresource-handler/multipartUpload.d.ts +0 -12
- package/out/webresource-handler/multipartUpload.js +0 -297
- package/out/webresource-handler/multipartUpload.js.map +0 -1
- package/out/webresource-handler/webresource.d.ts +0 -42
- package/out/webresource-handler/webresource.js +0 -2
- package/out/webresource-handler/webresource.js.map +0 -1
- package/out/webresource-handler/webresource.sbvr +0 -60
- package/src/webresource-handler/multipartUpload.ts +0 -424
- package/src/webresource-handler/webresource.sbvr +0 -60
- 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-
|
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
|
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 &&
|
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": "
|
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":
|
148
|
+
"timeout": 60000,
|
149
149
|
"recursive": true
|
150
150
|
},
|
151
151
|
"versionist": {
|
152
|
-
"publishedAt": "2025-04-
|
152
|
+
"publishedAt": "2025-04-30T22:49:25.738Z"
|
153
153
|
}
|
154
154
|
}
|
package/src/config-loader/env.ts
CHANGED
@@ -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
|
-
|
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
|
-
|
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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"}
|