@balena/pinejs 15.0.1 → 15.1.0-build-web-resource-4-528904929ba5aa3ec2cf3e80bd7800775c7b60a3-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.
Files changed (30) hide show
  1. package/.pinejs-cache.json +1 -1
  2. package/.versionbot/CHANGELOG.yml +24 -1
  3. package/CHANGELOG.md +7 -1
  4. package/README.md +10 -0
  5. package/VERSION +1 -1
  6. package/docker-compose.npm-test.yml +22 -1
  7. package/out/config-loader/config-loader.d.ts +2 -0
  8. package/out/config-loader/config-loader.js +13 -2
  9. package/out/config-loader/config-loader.js.map +1 -1
  10. package/out/sbvr-api/sbvr-utils.d.ts +6 -2
  11. package/out/sbvr-api/sbvr-utils.js +11 -2
  12. package/out/sbvr-api/sbvr-utils.js.map +1 -1
  13. package/out/server-glue/server.js +0 -2
  14. package/out/server-glue/server.js.map +1 -1
  15. package/out/server-glue/webresource-handler.d.ts +21 -0
  16. package/out/server-glue/webresource-handler.js +217 -0
  17. package/out/server-glue/webresource-handler.js.map +1 -0
  18. package/out/server-glue/webresource-handlers/NoopHandler.d.ts +5 -0
  19. package/out/server-glue/webresource-handlers/NoopHandler.js +17 -0
  20. package/out/server-glue/webresource-handlers/NoopHandler.js.map +1 -0
  21. package/out/server-glue/webresource-handlers/S3Handler.d.ts +11 -0
  22. package/out/server-glue/webresource-handlers/S3Handler.js +58 -0
  23. package/out/server-glue/webresource-handlers/S3Handler.js.map +1 -0
  24. package/package.json +17 -12
  25. package/src/config-loader/config-loader.ts +27 -2
  26. package/src/sbvr-api/sbvr-utils.ts +10 -1
  27. package/src/server-glue/server.ts +0 -3
  28. package/src/server-glue/webresource-handler.ts +317 -0
  29. package/src/server-glue/webresource-handlers/NoopHandler.ts +20 -0
  30. package/src/server-glue/webresource-handlers/S3Handler.ts +82 -0
@@ -0,0 +1,217 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.setupUploadHooks = exports.getDefaultHandler = exports.getUploaderMiddlware = void 0;
4
+ const busboy = require("busboy");
5
+ const is = require("type-is");
6
+ const uriParser = require("../sbvr-api/uri-parser");
7
+ const sbvr_utils_1 = require("../sbvr-api/sbvr-utils");
8
+ const permissions_1 = require("../sbvr-api/permissions");
9
+ const sbvrUtils = require("../sbvr-api/sbvr-utils");
10
+ const S3Handler_1 = require("./webresource-handlers/S3Handler");
11
+ const NoopHandler_1 = require("./webresource-handlers/NoopHandler");
12
+ const odata_to_abstract_sql_1 = require("@balena/odata-to-abstract-sql");
13
+ const module_1 = require("./module");
14
+ const ifFileInValidPath = async (fieldname, req) => {
15
+ if (req.method !== 'POST' && req.method !== 'PATCH') {
16
+ return false;
17
+ }
18
+ const apiRoot = (0, sbvr_utils_1.getApiRoot)(req);
19
+ if (apiRoot == null) {
20
+ return false;
21
+ }
22
+ const model = (0, sbvr_utils_1.getModel)(apiRoot);
23
+ const { resourceName } = await uriParser.parseOData({
24
+ url: req.url,
25
+ method: req.method,
26
+ });
27
+ const permission = req.method === 'POST' ? 'create' : 'update';
28
+ const vocab = model.versions[model.versions.length - 1];
29
+ const hasPermissions = await (0, permissions_1.checkPermissions)(req, permission, resourceName, vocab);
30
+ if (!hasPermissions) {
31
+ return false;
32
+ }
33
+ const fields = model.abstractSql.tables[resourceName].fields;
34
+ const dbFieldName = (0, odata_to_abstract_sql_1.odataNameToSqlName)(fieldname);
35
+ for (const field of fields) {
36
+ if (field.fieldName === dbFieldName && field.dataType === 'WebResource') {
37
+ return true;
38
+ }
39
+ }
40
+ return false;
41
+ };
42
+ const getUploaderMiddlware = (handler) => {
43
+ const completeUploads = [];
44
+ const filesUploaded = [];
45
+ return async (req, _res, next) => {
46
+ if (!is(req, ['multipart'])) {
47
+ return next();
48
+ }
49
+ const bb = busboy({ headers: req.headers });
50
+ let isAborting = false;
51
+ const done = () => {
52
+ req.unpipe(bb);
53
+ req.on('readable', req.read.bind(req));
54
+ bb.removeAllListeners();
55
+ };
56
+ const clearFiles = () => {
57
+ isAborting = true;
58
+ const deletions = filesUploaded.map((file) => handler.removeFile(file));
59
+ return Promise.all(deletions).catch((err) => console.error('Error deleting file', err));
60
+ };
61
+ bb.on('file', async (fieldname, filestream, info) => {
62
+ if (!isAborting && (await ifFileInValidPath(fieldname, req))) {
63
+ const file = {
64
+ originalname: info.filename,
65
+ encoding: info.encoding,
66
+ mimetype: info.mimeType,
67
+ stream: filestream,
68
+ fieldname,
69
+ };
70
+ const promise = handler.handleFile(file).then((result) => {
71
+ req.body[fieldname] = {
72
+ filename: info.filename,
73
+ contentType: info.mimeType,
74
+ contentDisposition: undefined,
75
+ size: result.size,
76
+ href: result.filename,
77
+ };
78
+ filesUploaded.push(result.filename);
79
+ });
80
+ completeUploads.push(promise);
81
+ }
82
+ else {
83
+ filestream.resume();
84
+ }
85
+ });
86
+ bb.on('field', (name, val, _info) => {
87
+ req.body[name] = val;
88
+ });
89
+ bb.on('finish', async () => {
90
+ try {
91
+ await Promise.all(completeUploads);
92
+ done();
93
+ next();
94
+ }
95
+ catch (err) {
96
+ console.error('Error uploading file', err);
97
+ await clearFiles();
98
+ next(err);
99
+ }
100
+ });
101
+ bb.on('error', async (err) => {
102
+ await clearFiles();
103
+ done();
104
+ next(err);
105
+ });
106
+ req.pipe(bb);
107
+ };
108
+ };
109
+ exports.getUploaderMiddlware = getUploaderMiddlware;
110
+ const getWebResourceFields = (request) => {
111
+ const fields = request.abstractSqlModel?.tables[request.resourceName]?.modifyFields ??
112
+ request.abstractSqlModel?.tables[request.resourceName]?.fields;
113
+ if (fields == null) {
114
+ return [];
115
+ }
116
+ return fields
117
+ .filter((f) => f.dataType === 'WebResource')
118
+ .map((f) => (0, odata_to_abstract_sql_1.sqlNameToODataName)(f.fieldName));
119
+ };
120
+ const getModifiedFields = (request) => {
121
+ return Object.entries(request.values)
122
+ .filter(([_key, value]) => value !== undefined)
123
+ .map(([key, _value]) => key);
124
+ };
125
+ const deleteFiles = async (keysToDelete, webResourceHandler) => {
126
+ const promises = keysToDelete.map((r) => webResourceHandler.removeFile(r));
127
+ await Promise.all(promises);
128
+ };
129
+ const getCreateWebResourceHook = (webResourceHandler) => {
130
+ return {
131
+ 'POSTRUN-ERROR': async ({ tx, request }) => {
132
+ tx?.on('rollback', async () => {
133
+ const fields = getWebResourceFields(request);
134
+ if (fields.length === 0) {
135
+ return;
136
+ }
137
+ const keysToDelete = fields
138
+ .filter((f) => isDefined(request.values[f]))
139
+ .map((f) => request.values[f].href);
140
+ await deleteFiles(keysToDelete, webResourceHandler);
141
+ });
142
+ },
143
+ };
144
+ };
145
+ const isDefined = (x) => x != null;
146
+ const getWebResourcesHrefs = (webResources) => {
147
+ const hrefs = Object.values(webResources ?? {})
148
+ .filter(isDefined)
149
+ .map((resourceKey) => resourceKey.href);
150
+ return hrefs;
151
+ };
152
+ const getRemoveWebResourceHook = (webResourceHandler) => {
153
+ return {
154
+ PRERUN: async (args) => {
155
+ const { api, request } = args;
156
+ let fields = getWebResourceFields(request);
157
+ if (fields.length === 0) {
158
+ return;
159
+ }
160
+ if (request.method === 'PATCH') {
161
+ if (request.odataQuery?.key == null) {
162
+ throw new module_1.errors.BadRequestError('WebResources can only be updated when providing a resource key.');
163
+ }
164
+ const allFields = getModifiedFields(request);
165
+ fields = fields.filter((f) => allFields.includes(f));
166
+ }
167
+ if (fields.length === 0) {
168
+ return;
169
+ }
170
+ const ids = await sbvrUtils.getAffectedIds(args);
171
+ if (ids.length === 0) {
172
+ return;
173
+ }
174
+ if (ids.length !== 1) {
175
+ throw new module_1.errors.BadRequestError('Resources containing webresources can only be updated/deleted one at a time.');
176
+ }
177
+ const webResources = (await api.get({
178
+ resource: request.resourceName,
179
+ passthrough: {
180
+ tx: args.tx,
181
+ req: module_1.permissions.root,
182
+ },
183
+ id: ids[0],
184
+ options: {
185
+ $select: fields,
186
+ },
187
+ }));
188
+ request.custom.$pineWebResourcesToDelete =
189
+ getWebResourcesHrefs(webResources);
190
+ },
191
+ POSTRUN: ({ tx, request }) => {
192
+ tx.on('end', () => {
193
+ const keysToDelete = request.custom.$pineWebResourcesToDelete || [];
194
+ deleteFiles(keysToDelete, webResourceHandler);
195
+ });
196
+ },
197
+ };
198
+ };
199
+ const getDefaultHandler = () => {
200
+ let handler;
201
+ try {
202
+ handler = new S3Handler_1.S3Handler();
203
+ }
204
+ catch (e) {
205
+ console.warn(`Failed to initialize S3 handler, using noop ${e}`);
206
+ handler = new NoopHandler_1.NoopHandler();
207
+ }
208
+ return handler;
209
+ };
210
+ exports.getDefaultHandler = getDefaultHandler;
211
+ const setupUploadHooks = (handler, apiRoot, resourceName) => {
212
+ sbvrUtils.addPureHook('PATCH', apiRoot, resourceName, getRemoveWebResourceHook(handler));
213
+ sbvrUtils.addPureHook('DELETE', apiRoot, resourceName, getRemoveWebResourceHook(handler));
214
+ sbvrUtils.addPureHook('POST', apiRoot, resourceName, getCreateWebResourceHook(handler));
215
+ };
216
+ exports.setupUploadHooks = setupUploadHooks;
217
+ //# sourceMappingURL=webresource-handler.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"webresource-handler.js","sourceRoot":"","sources":["../../src/server-glue/webresource-handler.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,5 @@
1
+ import { IncomingFile, UploadResponse, WebResourceHandler } from '../webresource-handler';
2
+ export declare class NoopHandler implements WebResourceHandler {
3
+ handleFile(resource: IncomingFile): Promise<UploadResponse>;
4
+ removeFile(_fileReference: string): Promise<void>;
5
+ }
@@ -0,0 +1,17 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.NoopHandler = void 0;
4
+ class NoopHandler {
5
+ async handleFile(resource) {
6
+ resource.stream.resume();
7
+ return {
8
+ filename: 'noop',
9
+ size: 0,
10
+ };
11
+ }
12
+ async removeFile(_fileReference) {
13
+ return;
14
+ }
15
+ }
16
+ exports.NoopHandler = NoopHandler;
17
+ //# sourceMappingURL=NoopHandler.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"NoopHandler.js","sourceRoot":"","sources":["../../../src/server-glue/webresource-handlers/NoopHandler.ts"],"names":[],"mappings":";;;AAMA,MAAa,WAAW;IAChB,KAAK,CAAC,UAAU,CAAC,QAAsB;QAE7C,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,CAAC;QACzB,OAAO;YACN,QAAQ,EAAE,MAAM;YAChB,IAAI,EAAE,CAAC;SACP,CAAC;IACH,CAAC;IAEM,KAAK,CAAC,UAAU,CAAC,cAAsB;QAC7C,OAAO;IACR,CAAC;CACD;AAbD,kCAaC"}
@@ -0,0 +1,11 @@
1
+ import { IncomingFile, UploadResponse, WebResourceHandler } from '../webresource-handler';
2
+ export declare class S3Handler implements WebResourceHandler {
3
+ private readonly config;
4
+ private readonly bucket;
5
+ private readonly endpoint;
6
+ private client;
7
+ constructor();
8
+ handleFile(resource: IncomingFile): Promise<UploadResponse>;
9
+ removeFile(fileReference: string): Promise<void>;
10
+ private getS3URL;
11
+ }
@@ -0,0 +1,58 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.S3Handler = void 0;
4
+ const env_parsing_1 = require("@balena/env-parsing");
5
+ const client_s3_1 = require("@aws-sdk/client-s3");
6
+ const lib_storage_1 = require("@aws-sdk/lib-storage");
7
+ const crypto_1 = require("crypto");
8
+ class S3Handler {
9
+ constructor() {
10
+ this.endpoint = (0, env_parsing_1.requiredVar)('S3_ENDPOINT');
11
+ this.config = {
12
+ region: (0, env_parsing_1.optionalVar)('S3_REGION', 'us-east-1'),
13
+ credentials: {
14
+ accessKeyId: (0, env_parsing_1.requiredVar)('S3_ACCESS_KEY'),
15
+ secretAccessKey: (0, env_parsing_1.requiredVar)('S3_SECRET_KEY'),
16
+ },
17
+ endpoint: this.endpoint,
18
+ forcePathStyle: true,
19
+ };
20
+ this.bucket = (0, env_parsing_1.optionalVar)('S3_STORAGE_ADAPTER_BUCKET', 'balena-pine-web-resources');
21
+ this.client = new client_s3_1.S3Client(this.config);
22
+ }
23
+ async handleFile(resource) {
24
+ let size = 0;
25
+ const key = `${resource.fieldname}_${(0, crypto_1.randomUUID)()}_${resource.originalname}`;
26
+ const params = {
27
+ Bucket: this.bucket,
28
+ ACL: 'public-read',
29
+ StorageClass: 'STANDARD',
30
+ Key: key,
31
+ Body: resource.stream,
32
+ ContentType: resource.mimetype,
33
+ };
34
+ const upload = new lib_storage_1.Upload({ client: this.client, params });
35
+ upload.on('httpUploadProgress', (ev) => {
36
+ size = ev.total ? ev.total : ev.loaded;
37
+ });
38
+ await upload.done();
39
+ const filename = this.getS3URL(key);
40
+ return { size, filename };
41
+ }
42
+ async removeFile(fileReference) {
43
+ const fileReferences = fileReference.split('/');
44
+ const fileKey = fileReferences[fileReferences.length - 1];
45
+ const command = new client_s3_1.DeleteObjectCommand({
46
+ Bucket: this.bucket,
47
+ Key: fileKey,
48
+ });
49
+ await this.client.send(command);
50
+ }
51
+ getS3URL(key) {
52
+ return this.endpoint.includes(this.bucket)
53
+ ? `${this.endpoint}/${key}`
54
+ : `${this.endpoint}/${this.bucket}/${key}`;
55
+ }
56
+ }
57
+ exports.S3Handler = S3Handler;
58
+ //# sourceMappingURL=S3Handler.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"S3Handler.js","sourceRoot":"","sources":["../../../src/server-glue/webresource-handlers/S3Handler.ts"],"names":[],"mappings":";;;AAAA,qDAA+D;AAM/D,kDAI4B;AAC5B,sDAA8C;AAE9C,mCAAoC;AAEpC,MAAa,SAAS;IAMrB;QACC,IAAI,CAAC,QAAQ,GAAG,IAAA,yBAAW,EAAC,aAAa,CAAC,CAAC;QAC3C,IAAI,CAAC,MAAM,GAAG;YACb,MAAM,EAAE,IAAA,yBAAW,EAAC,WAAW,EAAE,WAAW,CAAC;YAC7C,WAAW,EAAE;gBACZ,WAAW,EAAE,IAAA,yBAAW,EAAC,eAAe,CAAC;gBACzC,eAAe,EAAE,IAAA,yBAAW,EAAC,eAAe,CAAC;aAC7C;YACD,QAAQ,EAAE,IAAI,CAAC,QAAQ;YACvB,cAAc,EAAE,IAAI;SACpB,CAAC;QAEF,IAAI,CAAC,MAAM,GAAG,IAAA,yBAAW,EACxB,2BAA2B,EAC3B,2BAA2B,CAC3B,CAAC;QACF,IAAI,CAAC,MAAM,GAAG,IAAI,oBAAQ,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC;IACzC,CAAC;IAEM,KAAK,CAAC,UAAU,CAAC,QAAsB;QAC7C,IAAI,IAAI,GAAG,CAAC,CAAC;QACb,MAAM,GAAG,GAAG,GAAG,QAAQ,CAAC,SAAS,IAAI,IAAA,mBAAU,GAAE,IAChD,QAAQ,CAAC,YACV,EAAE,CAAC;QACH,MAAM,MAAM,GAAG;YACd,MAAM,EAAE,IAAI,CAAC,MAAM;YACnB,GAAG,EAAE,aAAa;YAClB,YAAY,EAAE,UAAU;YACxB,GAAG,EAAE,GAAG;YACR,IAAI,EAAE,QAAQ,CAAC,MAAM;YACrB,WAAW,EAAE,QAAQ,CAAC,QAAQ;SAC9B,CAAC;QACF,MAAM,MAAM,GAAG,IAAI,oBAAM,CAAC,EAAE,MAAM,EAAE,IAAI,CAAC,MAAM,EAAE,MAAM,EAAE,CAAC,CAAC;QAE3D,MAAM,CAAC,EAAE,CAAC,oBAAoB,EAAE,CAAC,EAAE,EAAE,EAAE;YACtC,IAAI,GAAG,EAAE,CAAC,KAAK,CAAC,CAAC,CAAC,EAAE,CAAC,KAAK,CAAC,CAAC,CAAC,EAAE,CAAC,MAAO,CAAC;QACzC,CAAC,CAAC,CAAC;QAEH,MAAM,MAAM,CAAC,IAAI,EAAE,CAAC;QACpB,MAAM,QAAQ,GAAG,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,CAAC;QACpC,OAAO,EAAE,IAAI,EAAE,QAAQ,EAAE,CAAC;IAC3B,CAAC;IAEM,KAAK,CAAC,UAAU,CAAC,aAAqB;QAC5C,MAAM,cAAc,GAAG,aAAa,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC;QAChD,MAAM,OAAO,GAAG,cAAc,CAAC,cAAc,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC;QAE1D,MAAM,OAAO,GAAG,IAAI,+BAAmB,CAAC;YACvC,MAAM,EAAE,IAAI,CAAC,MAAM;YACnB,GAAG,EAAE,OAAO;SACZ,CAAC,CAAC;QAEH,MAAM,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC;IACjC,CAAC;IAEO,QAAQ,CAAC,GAAW;QAC3B,OAAO,IAAI,CAAC,QAAQ,CAAC,QAAQ,CAAC,IAAI,CAAC,MAAM,CAAC;YACzC,CAAC,CAAC,GAAG,IAAI,CAAC,QAAQ,IAAI,GAAG,EAAE;YAC3B,CAAC,CAAC,GAAG,IAAI,CAAC,QAAQ,IAAI,IAAI,CAAC,MAAM,IAAI,GAAG,EAAE,CAAC;IAC7C,CAAC;CACD;AAlED,8BAkEC"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@balena/pinejs",
3
- "version": "15.0.1",
3
+ "version": "15.1.0-build-web-resource-4-528904929ba5aa3ec2cf3e80bd7800775c7b60a3-2",
4
4
  "main": "out/server-glue/module",
5
5
  "repository": "git@github.com:balena-io/pinejs.git",
6
6
  "license": "Apache-2.0",
@@ -18,20 +18,20 @@
18
18
  "webpack-server": "grunt server",
19
19
  "webpack-build": "npm run webpack-browser && npm run webpack-module && npm run webpack-server",
20
20
  "lint": "balena-lint -e js -e ts src build typings Gruntfile.ts && npx tsc --project tsconfig.dev.json --noEmit",
21
- "test": "npm run lint && npm run build && npm run webpack-build && npm run test:compose",
22
- "test:compose": "trap 'docker-compose -f docker-compose.npm-test.yml down ; echo Stopped ; exit 0' SIGINT; docker-compose -f docker-compose.npm-test.yml up -d && sleep 2 && DATABASE_URL=postgres://docker:docker@localhost:5431/postgres npm run mocha",
21
+ "test": "npm run lint & npm run build && npm run webpack-build && npm run test:compose",
22
+ "test:compose": "trap 'docker-compose -f docker-compose.npm-test.yml down ; echo Stopped ; exit 0' SIGINT; docker-compose -f docker-compose.npm-test.yml up -d && sleep 2 && DATABASE_URL=postgres://docker:docker@localhost:5431/postgres S3_ENDPOINT=http://localhost:43680 S3_ACCESS_KEY=USERNAME S3_SECRET_KEY=PASSWORD npm run mocha",
23
23
  "mocha": "TS_NODE_FILES=true mocha",
24
24
  "prettify": "balena-lint -e js -e ts --fix src build typings Gruntfile.ts"
25
25
  },
26
26
  "dependencies": {
27
- "@balena/abstract-sql-compiler": "^9.0.2",
28
- "@balena/abstract-sql-to-typescript": "^2.0.0",
27
+ "@balena/abstract-sql-compiler": "9.0.3-build-bumps-sbvr-types-with-web-resource-aa97f14f560002592f9d270a6746423f38eaf5da-1",
28
+ "@balena/abstract-sql-to-typescript": "2.1.0-build-add-web-resource-f0327d04d7bccca132d040a45f91eb23c30d8f3d-1",
29
29
  "@balena/env-parsing": "^1.1.5",
30
30
  "@balena/lf-to-abstract-sql": "^5.0.0",
31
- "@balena/odata-parser": "^3.0.0",
32
- "@balena/odata-to-abstract-sql": "^6.0.1",
31
+ "@balena/odata-parser": "^2.4.6",
32
+ "@balena/odata-to-abstract-sql": "6.0.2-build-bumps-for-web-resource-fc6584414cedc9d72ae810bc2804a00d218b48a7-1",
33
33
  "@balena/sbvr-parser": "^1.4.3",
34
- "@balena/sbvr-types": "^5.0.0",
34
+ "@balena/sbvr-types": "5.1.0-build-web-resource-2-18a09938422881c4507118f81f2d5395e132e741-1",
35
35
  "@types/body-parser": "^1.19.2",
36
36
  "@types/compression": "^1.7.2",
37
37
  "@types/cookie-parser": "^1.4.3",
@@ -51,6 +51,7 @@
51
51
  "@types/randomstring": "^1.1.8",
52
52
  "@types/websql": "^0.0.27",
53
53
  "commander": "^10.0.1",
54
+ "busboy": "^1.6.0",
54
55
  "deep-freeze": "^0.0.1",
55
56
  "eventemitter3": "^5.0.0",
56
57
  "express-session": "^1.17.3",
@@ -58,18 +59,21 @@
58
59
  "memoizee": "^0.4.15",
59
60
  "pinejs-client-core": "^6.12.3",
60
61
  "randomstring": "^1.2.3",
61
- "typed-error": "^3.2.2"
62
+ "type-is": "^1.6.18",
63
+ "typed-error": "^3.2.1"
62
64
  },
63
65
  "devDependencies": {
64
66
  "@balena/lint": "^6.2.2",
65
67
  "@faker-js/faker": "^7.6.0",
68
+ "@types/busboy": "^1.5.0",
66
69
  "@types/chai": "^4.3.4",
67
70
  "@types/chai-as-promised": "^7.1.5",
68
71
  "@types/grunt": "^0.4.27",
69
72
  "@types/mocha": "^10.0.1",
70
73
  "@types/supertest": "^2.0.12",
71
74
  "@types/terser-webpack-plugin": "^5.2.0",
72
- "@types/webpack": "^5.28.1",
75
+ "@types/type-is": "^1.6.3",
76
+ "@types/webpack": "^5.28.0",
73
77
  "chai": "^4.3.7",
74
78
  "grunt": "^1.6.1",
75
79
  "grunt-check-dependencies": "^1.0.0",
@@ -98,13 +102,14 @@
98
102
  "webpack-dev-server": "^4.13.3"
99
103
  },
100
104
  "optionalDependencies": {
105
+ "@aws-sdk/client-s3": "^3.200.0",
106
+ "@aws-sdk/lib-storage": "^3.200.0",
101
107
  "bcrypt": "^5.1.0",
102
108
  "body-parser": "^1.20.2",
103
109
  "compression": "^1.7.4",
104
110
  "cookie-parser": "^1.4.6",
105
111
  "express": "^4.18.2",
106
112
  "method-override": "^3.0.0",
107
- "multer": "1.4.5-lts.1",
108
113
  "mysql": "^2.18.1",
109
114
  "passport": "^0.6.0",
110
115
  "passport-local": "^1.0.0",
@@ -134,6 +139,6 @@
134
139
  "recursive": true
135
140
  },
136
141
  "versionist": {
137
- "publishedAt": "2023-05-18T16:00:09.855Z"
142
+ "publishedAt": "2023-05-24T14:54:34.406Z"
138
143
  }
139
144
  }
@@ -27,6 +27,12 @@ import * as path from 'path';
27
27
  import * as sbvrUtils from '../sbvr-api/sbvr-utils';
28
28
 
29
29
  import * as permissions from '../sbvr-api/permissions';
30
+ import {
31
+ getDefaultHandler,
32
+ getUploaderMiddlware,
33
+ WebResourceHandler,
34
+ setupUploadHooks,
35
+ } from '../server-glue/webresource-handler';
30
36
  import { AliasValidNodeType } from '../sbvr-api/translations';
31
37
 
32
38
  export type SetupFunction = (
@@ -55,6 +61,7 @@ export interface Model {
55
61
  translations?: Dictionary<
56
62
  Definition | Dictionary<string | AliasValidNodeType>
57
63
  >;
64
+ webResourceHandler?: WebResourceHandler;
58
65
  }
59
66
  export interface User {
60
67
  username: string;
@@ -195,15 +202,33 @@ export const setup = (app: Express.Application) => {
195
202
  );
196
203
 
197
204
  const apiRoute = `/${model.apiRoot}/*`;
205
+ const webResourceHandler =
206
+ model.webResourceHandler ?? getDefaultHandler();
207
+
208
+ const fileUploadMiddleware =
209
+ getUploaderMiddlware(webResourceHandler);
198
210
  app
199
211
  .route(apiRoute)
200
212
  .options((_req, res) => res.status(200).end())
201
213
  .get(sbvrUtils.handleODataRequest)
202
214
  .put(sbvrUtils.handleODataRequest)
203
- .post(sbvrUtils.handleODataRequest)
204
- .patch(sbvrUtils.handleODataRequest)
215
+ .post(fileUploadMiddleware, sbvrUtils.handleODataRequest)
216
+ .patch(fileUploadMiddleware, sbvrUtils.handleODataRequest)
205
217
  .merge(sbvrUtils.handleODataRequest)
206
218
  .delete(sbvrUtils.handleODataRequest);
219
+ app.options(apiRoute, (_req, res) => res.status(200).end());
220
+
221
+ const loadedModel = sbvrUtils.getModel(model.apiRoot!);
222
+ if (translateTo == null) {
223
+ const resources = Object.entries(
224
+ loadedModel.abstractSql.tables,
225
+ ).filter(([_resourceName, table]) =>
226
+ table.fields.some((f) => f.dataType === 'WebResource'),
227
+ );
228
+ for (const [resource, _table] of resources) {
229
+ setupUploadHooks(webResourceHandler, model.apiRoot!, resource);
230
+ }
231
+ }
207
232
 
208
233
  console.info(
209
234
  'Successfully executed ' + model.modelName + ' model.',
@@ -1153,6 +1153,10 @@ const $getAffectedIds = async ({
1153
1153
  return result.rows.map((row) => row[idField]);
1154
1154
  };
1155
1155
 
1156
+ export const getModel = (vocabulary: string) => {
1157
+ return models[vocabulary];
1158
+ };
1159
+
1156
1160
  const runODataRequest = (req: Express.Request, vocabulary: string) => {
1157
1161
  if (env.DEBUG) {
1158
1162
  api[vocabulary].logger.log('Parsing', req.method, req.url);
@@ -1328,8 +1332,13 @@ const runODataRequest = (req: Express.Request, vocabulary: string) => {
1328
1332
  };
1329
1333
  };
1330
1334
 
1331
- export const handleODataRequest: Express.Handler = async (req, res, next) => {
1335
+ export const getApiRoot = (req: Express.Request): string | undefined => {
1332
1336
  const [, apiRoot] = req.url.split('/', 2);
1337
+ return apiRoot;
1338
+ };
1339
+
1340
+ export const handleODataRequest: Express.Handler = async (req, res, next) => {
1341
+ const apiRoot = getApiRoot(req);
1333
1342
  if (apiRoot == null || models[apiRoot] == null) {
1334
1343
  return next('route');
1335
1344
  }
@@ -3,7 +3,6 @@ import type * as Compression from 'compression';
3
3
  import type * as CookieParser from 'cookie-parser';
4
4
  import type * as ExpressSession from 'express-session';
5
5
  import type * as MethodOverride from 'method-override';
6
- import type * as Multer from 'multer';
7
6
  import type * as Passport from 'passport';
8
7
  import type * as Path from 'path';
9
8
  import type * as ServeStatic from 'serve-static';
@@ -35,7 +34,6 @@ if (!process.browser) {
35
34
  const serveStatic: typeof ServeStatic = require('serve-static');
36
35
  const cookieParser: typeof CookieParser = require('cookie-parser');
37
36
  const bodyParser: typeof BodyParser = require('body-parser');
38
- const multer: typeof Multer = require('multer');
39
37
  const methodOverride: typeof MethodOverride = require('method-override');
40
38
  const expressSession: typeof ExpressSession = require('express-session');
41
39
  // tslint:enable:no-var-requires
@@ -47,7 +45,6 @@ if (!process.browser) {
47
45
 
48
46
  app.use(cookieParser());
49
47
  app.use(bodyParser());
50
- app.use(multer().any());
51
48
  app.use(methodOverride());
52
49
  app.use(
53
50
  expressSession({