@balena/pinejs 16.0.0-build--batch-f2ffc3d6bcb9f3294fd4fc9de3c21bfe167e100d-1 → 16.0.0-build-fisehara-update-sbvr-types-b58e72aca3193964afac96c955fde178fe39d077-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 +2168 -11
- package/CHANGELOG.md +815 -2
- package/Gruntfile.ts +9 -6
- package/README.md +10 -0
- package/build/browser.ts +2 -2
- package/build/config.ts +1 -1
- package/build/module.ts +2 -2
- package/build/server.ts +2 -2
- package/docker-compose.npm-test.yml +21 -3
- package/out/bin/abstract-sql-compiler.js +5 -5
- package/out/bin/abstract-sql-compiler.js.map +1 -1
- package/out/bin/odata-compiler.js +10 -10
- package/out/bin/odata-compiler.js.map +1 -1
- package/out/bin/sbvr-compiler.js +34 -11
- package/out/bin/sbvr-compiler.js.map +1 -1
- package/out/bin/utils.js +25 -2
- package/out/bin/utils.js.map +1 -1
- package/out/config-loader/config-loader.d.ts +4 -2
- package/out/config-loader/config-loader.js +54 -13
- package/out/config-loader/config-loader.js.map +1 -1
- package/out/config-loader/env.d.ts +2 -1
- package/out/config-loader/env.js +5 -2
- package/out/config-loader/env.js.map +1 -1
- package/out/data-server/sbvr-server.d.ts +1 -1
- package/out/data-server/sbvr-server.js +3 -1
- package/out/data-server/sbvr-server.js.map +1 -1
- package/out/database-layer/db.js +40 -14
- package/out/database-layer/db.js.map +1 -1
- package/out/express-emulator/express.js +5 -3
- package/out/express-emulator/express.js.map +1 -1
- package/out/http-transactions/transactions.d.ts +1 -1
- package/out/http-transactions/transactions.js +10 -5
- package/out/http-transactions/transactions.js.map +1 -1
- package/out/migrator/async.js +32 -5
- package/out/migrator/async.js.map +1 -1
- package/out/migrator/sync.d.ts +2 -1
- package/out/migrator/sync.js +29 -3
- package/out/migrator/sync.js.map +1 -1
- package/out/migrator/utils.d.ts +6 -3
- package/out/migrator/utils.js +30 -4
- package/out/migrator/utils.js.map +1 -1
- package/out/odata-metadata/odata-metadata-generator.js +4 -1
- package/out/odata-metadata/odata-metadata-generator.js.map +1 -1
- package/out/passport-pinejs/mount-login-router.d.ts +3 -0
- package/out/passport-pinejs/mount-login-router.js +65 -0
- package/out/passport-pinejs/mount-login-router.js.map +1 -0
- package/out/passport-pinejs/passport-pinejs.d.ts +2 -1
- package/out/passport-pinejs/passport-pinejs.js +28 -2
- package/out/passport-pinejs/passport-pinejs.js.map +1 -1
- package/out/pinejs-session-store/pinejs-session-store.js +30 -7
- package/out/pinejs-session-store/pinejs-session-store.js.map +1 -1
- package/out/sbvr-api/abstract-sql.d.ts +2 -2
- package/out/sbvr-api/abstract-sql.js +35 -9
- package/out/sbvr-api/abstract-sql.js.map +1 -1
- package/out/sbvr-api/cached-compile.js +9 -6
- package/out/sbvr-api/cached-compile.js.map +1 -1
- package/out/sbvr-api/common-types.d.ts +1 -1
- package/out/sbvr-api/control-flow.js +5 -2
- package/out/sbvr-api/control-flow.js.map +1 -1
- package/out/sbvr-api/express-extension.d.ts +10 -7
- package/out/sbvr-api/express-extension.js +1 -0
- package/out/sbvr-api/hooks.d.ts +5 -1
- package/out/sbvr-api/hooks.js +12 -10
- package/out/sbvr-api/hooks.js.map +1 -1
- package/out/sbvr-api/odata-response.d.ts +5 -2
- package/out/sbvr-api/odata-response.js +36 -6
- package/out/sbvr-api/odata-response.js.map +1 -1
- package/out/sbvr-api/permissions.d.ts +6 -7
- package/out/sbvr-api/permissions.js +69 -38
- package/out/sbvr-api/permissions.js.map +1 -1
- package/out/sbvr-api/sbvr-utils.d.ts +20 -9
- package/out/sbvr-api/sbvr-utils.js +134 -136
- package/out/sbvr-api/sbvr-utils.js.map +1 -1
- package/out/sbvr-api/translations.d.ts +2 -2
- package/out/sbvr-api/translations.js +17 -10
- package/out/sbvr-api/translations.js.map +1 -1
- package/out/sbvr-api/uri-parser.d.ts +7 -10
- package/out/sbvr-api/uri-parser.js +46 -19
- package/out/sbvr-api/uri-parser.js.map +1 -1
- package/out/server-glue/global-ext.d.ts +2 -1
- package/out/server-glue/module.d.ts +3 -1
- package/out/server-glue/module.js +40 -13
- package/out/server-glue/module.js.map +1 -1
- package/out/server-glue/sbvr-loader.js.map +1 -1
- package/out/server-glue/server.js +31 -39
- package/out/server-glue/server.js.map +1 -1
- package/out/webresource-handler/handlers/NoopHandler.d.ts +7 -0
- package/out/webresource-handler/handlers/NoopHandler.js +20 -0
- package/out/webresource-handler/handlers/NoopHandler.js.map +1 -0
- package/out/webresource-handler/handlers/S3Handler.d.ts +28 -0
- package/out/webresource-handler/handlers/S3Handler.js +97 -0
- package/out/webresource-handler/handlers/S3Handler.js.map +1 -0
- package/out/webresource-handler/handlers/index.d.ts +2 -0
- package/out/webresource-handler/handlers/index.js +19 -0
- package/out/webresource-handler/handlers/index.js.map +1 -0
- package/out/webresource-handler/index.d.ts +34 -0
- package/out/webresource-handler/index.js +307 -0
- package/out/webresource-handler/index.js.map +1 -0
- package/package.json +68 -62
- package/src/bin/abstract-sql-compiler.ts +7 -9
- package/src/bin/odata-compiler.ts +12 -15
- package/src/bin/sbvr-compiler.ts +14 -18
- package/src/bin/utils.ts +1 -1
- package/src/config-loader/config-loader.ts +44 -10
- package/src/config-loader/env.ts +1 -1
- package/src/data-server/sbvr-server.js +3 -1
- package/src/database-layer/db.ts +23 -19
- package/src/express-emulator/express.js +5 -3
- package/src/extended-sbvr-parser/extended-sbvr-parser.ts +1 -1
- package/src/http-transactions/transactions.js +10 -5
- package/src/migrator/async.ts +7 -6
- package/src/migrator/sync.ts +10 -7
- package/src/migrator/utils.ts +11 -5
- package/src/odata-metadata/odata-metadata-generator.ts +2 -2
- package/src/passport-pinejs/mount-login-router.ts +46 -0
- package/src/passport-pinejs/passport-pinejs.ts +7 -3
- package/src/pinejs-session-store/pinejs-session-store.ts +6 -6
- package/src/sbvr-api/abstract-sql.ts +5 -5
- package/src/sbvr-api/cached-compile.ts +1 -2
- package/src/sbvr-api/common-types.ts +1 -1
- package/src/sbvr-api/control-flow.ts +1 -1
- package/src/sbvr-api/express-extension.ts +12 -8
- package/src/sbvr-api/hooks.ts +11 -11
- package/src/sbvr-api/odata-response.ts +56 -9
- package/src/sbvr-api/permissions.ts +44 -35
- package/src/sbvr-api/sbvr-utils.ts +118 -172
- package/src/sbvr-api/translations.ts +9 -6
- package/src/sbvr-api/uri-parser.ts +22 -28
- package/src/server-glue/global-ext.d.ts +2 -1
- package/src/server-glue/module.ts +8 -2
- package/src/server-glue/sbvr-loader.ts +1 -1
- package/src/server-glue/server.ts +11 -49
- package/src/webresource-handler/handlers/NoopHandler.ts +21 -0
- package/src/webresource-handler/handlers/S3Handler.ts +143 -0
- package/src/webresource-handler/handlers/index.ts +2 -0
- package/src/webresource-handler/index.ts +450 -0
- package/tsconfig.dev.json +2 -1
- package/tsconfig.json +1 -1
- package/typings/lf-to-abstract-sql.d.ts +1 -1
- package/typings/memoizee.d.ts +3 -4
@@ -3,7 +3,9 @@ import type * as Db from '../database-layer/db';
|
|
3
3
|
import type { Model } from '../config-loader/config-loader';
|
4
4
|
import type { AnyObject, RequiredField } from './common-types';
|
5
5
|
|
6
|
+
// Augment the Express typings
|
6
7
|
declare global {
|
8
|
+
// eslint-disable-next-line @typescript-eslint/no-namespace
|
7
9
|
namespace Express {
|
8
10
|
export interface Request {
|
9
11
|
tx?: Db.Tx;
|
@@ -12,9 +14,9 @@ declare global {
|
|
12
14
|
}
|
13
15
|
}
|
14
16
|
|
15
|
-
import
|
17
|
+
import _ from 'lodash';
|
16
18
|
|
17
|
-
import { TypedError } from 'typed-error';
|
19
|
+
import type { TypedError } from 'typed-error';
|
18
20
|
import { cachedCompile } from './cached-compile';
|
19
21
|
|
20
22
|
type LFModel = any[];
|
@@ -23,14 +25,14 @@ import { version as AbstractSQLCompilerVersion } from '@balena/abstract-sql-comp
|
|
23
25
|
import * as LF2AbstractSQL from '@balena/lf-to-abstract-sql';
|
24
26
|
|
25
27
|
import {
|
26
|
-
ODataBinds,
|
28
|
+
type ODataBinds,
|
27
29
|
odataNameToSqlName,
|
28
30
|
sqlNameToODataName,
|
29
|
-
SupportedMethod,
|
31
|
+
type SupportedMethod,
|
30
32
|
} from '@balena/odata-to-abstract-sql';
|
31
33
|
import sbvrTypes from '@balena/sbvr-types';
|
32
34
|
import deepFreeze = require('deep-freeze');
|
33
|
-
import { PinejsClientCore, PromiseResultTypes } from 'pinejs-client-core';
|
35
|
+
import { PinejsClientCore, type PromiseResultTypes } from 'pinejs-client-core';
|
34
36
|
|
35
37
|
import { ExtendedSBVRParser } from '../extended-sbvr-parser/extended-sbvr-parser';
|
36
38
|
|
@@ -38,7 +40,7 @@ import * as asyncMigrator from '../migrator/async';
|
|
38
40
|
import * as syncMigrator from '../migrator/sync';
|
39
41
|
import { generateODataMetadata } from '../odata-metadata/odata-metadata-generator';
|
40
42
|
|
41
|
-
//
|
43
|
+
// eslint-disable-next-line @typescript-eslint/no-var-requires
|
42
44
|
const devModel = require('./dev.sbvr');
|
43
45
|
import * as permissions from './permissions';
|
44
46
|
import {
|
@@ -58,20 +60,20 @@ import {
|
|
58
60
|
UnauthorizedError,
|
59
61
|
} from './errors';
|
60
62
|
import * as uriParser from './uri-parser';
|
61
|
-
export { ODataRequest } from './uri-parser';
|
63
|
+
export type { ODataRequest } from './uri-parser';
|
62
64
|
import {
|
63
|
-
HookReq,
|
64
|
-
HookArgs,
|
65
|
+
type HookReq,
|
66
|
+
type HookArgs,
|
65
67
|
rollbackRequestHooks,
|
66
68
|
getHooks,
|
67
69
|
runHooks,
|
68
|
-
InstantiatedHooks,
|
70
|
+
type InstantiatedHooks,
|
69
71
|
} from './hooks';
|
70
72
|
export {
|
71
|
-
HookReq,
|
72
|
-
HookArgs,
|
73
|
-
HookResponse,
|
74
|
-
Hooks,
|
73
|
+
type HookReq,
|
74
|
+
type HookArgs,
|
75
|
+
type HookResponse,
|
76
|
+
type Hooks,
|
75
77
|
addPureHook,
|
76
78
|
addSideEffectHook,
|
77
79
|
} from './hooks';
|
@@ -94,8 +96,10 @@ export { resolveOdataBind } from './abstract-sql';
|
|
94
96
|
import * as odataResponse from './odata-response';
|
95
97
|
import { env } from '../server-glue/module';
|
96
98
|
import { translateAbstractSqlModel } from './translations';
|
97
|
-
|
98
|
-
|
99
|
+
import {
|
100
|
+
type MigrationExecutionResult,
|
101
|
+
setExecutedMigrations,
|
102
|
+
} from '../migrator/utils';
|
99
103
|
|
100
104
|
const LF2AbstractSQLTranslator = LF2AbstractSQL.createTranslator(sbvrTypes);
|
101
105
|
const LF2AbstractSQLTranslatorVersion = `${LF2AbstractSQLVersion}+${sbvrTypesVersion}`;
|
@@ -117,6 +121,7 @@ interface CompiledModel {
|
|
117
121
|
const models: {
|
118
122
|
[vocabulary: string]: CompiledModel & {
|
119
123
|
versions: string[];
|
124
|
+
modelExecutionResult?: ModelExecutionResult;
|
120
125
|
};
|
121
126
|
} = {};
|
122
127
|
|
@@ -135,8 +140,7 @@ export interface ApiKey extends Actor {
|
|
135
140
|
}
|
136
141
|
|
137
142
|
export interface Response {
|
138
|
-
|
139
|
-
status: number;
|
143
|
+
statusCode: number;
|
140
144
|
headers?:
|
141
145
|
| {
|
142
146
|
[headerName: string]: any;
|
@@ -145,6 +149,12 @@ export interface Response {
|
|
145
149
|
body?: AnyObject | string;
|
146
150
|
}
|
147
151
|
|
152
|
+
export type ModelExecutionResult =
|
153
|
+
| undefined
|
154
|
+
| {
|
155
|
+
migrationExecutionResult?: MigrationExecutionResult;
|
156
|
+
};
|
157
|
+
|
148
158
|
const memoizedResolvedSynonym = memoizeWeak(
|
149
159
|
(
|
150
160
|
abstractSqlModel: AbstractSQLCompiler.AbstractSqlModel,
|
@@ -241,13 +251,14 @@ const prettifyConstraintError = (
|
|
241
251
|
err.message,
|
242
252
|
);
|
243
253
|
break;
|
244
|
-
case 'postgres':
|
254
|
+
case 'postgres': {
|
245
255
|
const resourceName = resolveSynonym(request);
|
246
256
|
const abstractSqlModel = getFinalAbstractSqlModel(request);
|
247
257
|
matches = new RegExp(
|
248
258
|
'"' + abstractSqlModel.tables[resourceName].name + '_(.*?)_key"',
|
249
259
|
).exec(err.message);
|
250
260
|
break;
|
261
|
+
}
|
251
262
|
}
|
252
263
|
// We know it's the right error type, so if matches exists just throw a generic error message, since we have failed to get the info for a more specific one.
|
253
264
|
if (matches == null) {
|
@@ -274,7 +285,7 @@ const prettifyConstraintError = (
|
|
274
285
|
err.message,
|
275
286
|
);
|
276
287
|
break;
|
277
|
-
case 'postgres':
|
288
|
+
case 'postgres': {
|
278
289
|
const resourceName = resolveSynonym(request);
|
279
290
|
const abstractSqlModel = getFinalAbstractSqlModel(request);
|
280
291
|
const tableName = abstractSqlModel.tables[resourceName].name;
|
@@ -293,6 +304,7 @@ const prettifyConstraintError = (
|
|
293
304
|
).exec(err.message);
|
294
305
|
}
|
295
306
|
break;
|
307
|
+
}
|
296
308
|
}
|
297
309
|
// We know it's the right error type, so if no matches exists just throw a generic error message,
|
298
310
|
// since we have failed to get the info for a more specific one.
|
@@ -586,7 +598,7 @@ export const executeModels = async (
|
|
586
598
|
execModels.map(async (model) => {
|
587
599
|
const { apiRoot } = model;
|
588
600
|
|
589
|
-
await syncMigrator.run(tx, model);
|
601
|
+
const migrationExecutionResult = await syncMigrator.run(tx, model);
|
590
602
|
const compiledModel = generateModels(model, db.engine);
|
591
603
|
|
592
604
|
if (compiledModel.sql) {
|
@@ -617,6 +629,9 @@ export const executeModels = async (
|
|
617
629
|
models[apiRoot] = {
|
618
630
|
...compiledModel,
|
619
631
|
versions,
|
632
|
+
modelExecutionResult: {
|
633
|
+
migrationExecutionResult,
|
634
|
+
},
|
620
635
|
};
|
621
636
|
|
622
637
|
// Validate the [empty] model according to the rules.
|
@@ -723,6 +738,23 @@ export const executeModels = async (
|
|
723
738
|
}
|
724
739
|
};
|
725
740
|
|
741
|
+
export const postExecuteModels = async (tx: Db.Tx): Promise<void> => {
|
742
|
+
// Executing the `migrations` model takes place after other models have been executed.
|
743
|
+
// Hence, skipped migrations from earlier models are not set as executed as the `migration` table is missing
|
744
|
+
// Here the skipped migrations that haven't been set properly are covered
|
745
|
+
// This is mostly an edge case when running on an empty database schema and migrations model hasn't been executed, yet.
|
746
|
+
// One specifc case are tests to run tests against migrated and unmigrated database states
|
747
|
+
|
748
|
+
for (const modelKey of Object.keys(models)) {
|
749
|
+
const pendingToSetExecutedMigrations =
|
750
|
+
models[modelKey]?.modelExecutionResult?.migrationExecutionResult
|
751
|
+
?.pendingUnsetMigrations;
|
752
|
+
if (pendingToSetExecutedMigrations != null) {
|
753
|
+
await setExecutedMigrations(tx, modelKey, pendingToSetExecutedMigrations);
|
754
|
+
}
|
755
|
+
}
|
756
|
+
};
|
757
|
+
|
726
758
|
const cleanupModel = (vocab: string) => {
|
727
759
|
delete models[vocab];
|
728
760
|
delete api[vocab];
|
@@ -955,7 +987,7 @@ export class PinejsClient extends PinejsClientCore<PinejsClient> {
|
|
955
987
|
req?: permissions.PermissionReq;
|
956
988
|
custom?: AnyObject;
|
957
989
|
}) {
|
958
|
-
return (await runURI(method, url, body, tx, req, custom)) as
|
990
|
+
return (await runURI(method, url, body, tx, req, custom)) as object;
|
959
991
|
}
|
960
992
|
}
|
961
993
|
|
@@ -1025,15 +1057,15 @@ export const runURI = async (
|
|
1025
1057
|
throw response;
|
1026
1058
|
}
|
1027
1059
|
|
1028
|
-
const { body: responseBody,
|
1060
|
+
const { body: responseBody, statusCode, headers } = response as Response;
|
1029
1061
|
|
1030
|
-
if (
|
1062
|
+
if (statusCode != null && statusCode >= 400) {
|
1031
1063
|
const ErrorClass =
|
1032
|
-
statusCodeToError[
|
1064
|
+
statusCodeToError[statusCode as keyof typeof statusCodeToError];
|
1033
1065
|
if (ErrorClass != null) {
|
1034
1066
|
throw new ErrorClass(undefined, responseBody, headers);
|
1035
1067
|
}
|
1036
|
-
throw new HttpError(
|
1068
|
+
throw new HttpError(statusCode, undefined, responseBody, headers);
|
1037
1069
|
}
|
1038
1070
|
|
1039
1071
|
return responseBody as AnyObject | undefined;
|
@@ -1110,8 +1142,7 @@ const $getAffectedIds = async ({
|
|
1110
1142
|
// And we use the request's url rather than the req for things like batch where the req url is ../$batch
|
1111
1143
|
const parsedRequest: uriParser.ParsedODataRequest &
|
1112
1144
|
Partial<Pick<uriParser.ODataRequest, 'engine' | 'translateVersions'>> =
|
1113
|
-
|
1114
|
-
id: request.batchRequestId,
|
1145
|
+
uriParser.parseOData({
|
1115
1146
|
method: request.method,
|
1116
1147
|
url: `/${request.vocabulary}${request.url}`,
|
1117
1148
|
});
|
@@ -1157,90 +1188,8 @@ const $getAffectedIds = async ({
|
|
1157
1188
|
return result.rows.map((row) => row[idField]);
|
1158
1189
|
};
|
1159
1190
|
|
1160
|
-
const
|
1161
|
-
|
1162
|
-
if (!Array.isArray(requests)) {
|
1163
|
-
throw new BadRequestError(
|
1164
|
-
'Batch requests must include an array of requests in the body via the "requests" property',
|
1165
|
-
);
|
1166
|
-
}
|
1167
|
-
if (req.headers != null && req.headers['content-type'] == null) {
|
1168
|
-
throw new BadRequestError(
|
1169
|
-
'Headers in a batch request must include a "content-type" header if they are provided',
|
1170
|
-
);
|
1171
|
-
}
|
1172
|
-
if (
|
1173
|
-
requests.find(
|
1174
|
-
(request) =>
|
1175
|
-
request.headers?.authorization != null ||
|
1176
|
-
request.url?.includes('apikey='),
|
1177
|
-
) != null
|
1178
|
-
) {
|
1179
|
-
throw new BadRequestError(
|
1180
|
-
'Authorization may only be passed to the main batch request',
|
1181
|
-
);
|
1182
|
-
}
|
1183
|
-
const ids = new Set<string>(
|
1184
|
-
requests
|
1185
|
-
.map((request) => request.id)
|
1186
|
-
.filter((id) => typeof id === 'string') as string[],
|
1187
|
-
);
|
1188
|
-
if (ids.size !== requests.length) {
|
1189
|
-
throw new BadRequestError(
|
1190
|
-
'All requests in a batch request must have unique string ids',
|
1191
|
-
);
|
1192
|
-
}
|
1193
|
-
|
1194
|
-
for (const request of requests) {
|
1195
|
-
if (
|
1196
|
-
request.headers != null &&
|
1197
|
-
request.headers['content-type'] == null &&
|
1198
|
-
(req.headers == null || req.headers['content-type'] == null)
|
1199
|
-
) {
|
1200
|
-
throw new BadRequestError(
|
1201
|
-
'Requests of a batch request that have headers must include a "content-type" header',
|
1202
|
-
);
|
1203
|
-
}
|
1204
|
-
if (request.method == null) {
|
1205
|
-
throw new BadRequestError(
|
1206
|
-
'Requests of a batch request must have a "method"',
|
1207
|
-
);
|
1208
|
-
}
|
1209
|
-
const upperCaseMethod = request.method.toUpperCase();
|
1210
|
-
if (!validBatchMethods.has(upperCaseMethod)) {
|
1211
|
-
throw new BadRequestError(
|
1212
|
-
`Requests of a batch request must have a method matching one of the following: ${Array.from(
|
1213
|
-
validBatchMethods,
|
1214
|
-
).join(', ')}`,
|
1215
|
-
);
|
1216
|
-
}
|
1217
|
-
if (
|
1218
|
-
request.body !== undefined &&
|
1219
|
-
(upperCaseMethod === 'GET' || upperCaseMethod === 'DELETE')
|
1220
|
-
) {
|
1221
|
-
throw new BadRequestError(
|
1222
|
-
'GET and DELETE requests of a batch request must not have a body',
|
1223
|
-
);
|
1224
|
-
}
|
1225
|
-
}
|
1226
|
-
|
1227
|
-
const urls = new Set<string | undefined>(
|
1228
|
-
requests.map((request) => request.url),
|
1229
|
-
);
|
1230
|
-
if (urls.has(undefined)) {
|
1231
|
-
throw new BadRequestError('Requests of a batch request must have a "url"');
|
1232
|
-
}
|
1233
|
-
if (urls.has('/university/$batch')) {
|
1234
|
-
throw new BadRequestError('Batch requests cannot contain batch requests');
|
1235
|
-
}
|
1236
|
-
const urlModels = new Set(
|
1237
|
-
Array.from(urls.values()).map((url: string) => url.split('/')[1]),
|
1238
|
-
);
|
1239
|
-
if (urlModels.size > 1) {
|
1240
|
-
throw new BadRequestError(
|
1241
|
-
'Batch requests must consist of requests for only one model',
|
1242
|
-
);
|
1243
|
-
}
|
1191
|
+
export const getModel = (vocabulary: string) => {
|
1192
|
+
return models[vocabulary];
|
1244
1193
|
};
|
1245
1194
|
|
1246
1195
|
const runODataRequest = (req: Express.Request, vocabulary: string) => {
|
@@ -1248,10 +1197,6 @@ const runODataRequest = (req: Express.Request, vocabulary: string) => {
|
|
1248
1197
|
api[vocabulary].logger.log('Parsing', req.method, req.url);
|
1249
1198
|
}
|
1250
1199
|
|
1251
|
-
if (req.url.startsWith(`/${vocabulary}/$batch`)) {
|
1252
|
-
validateBatch(req);
|
1253
|
-
}
|
1254
|
-
|
1255
1200
|
// Get the hooks for the current method/vocabulary as we know it,
|
1256
1201
|
// in order to run PREPARSE hooks, before parsing gets us more info
|
1257
1202
|
const { versions } = models[vocabulary];
|
@@ -1299,27 +1244,19 @@ const runODataRequest = (req: Express.Request, vocabulary: string) => {
|
|
1299
1244
|
await runHooks('PREPARSE', reqHooks, { req, tx: req.tx });
|
1300
1245
|
let requests: uriParser.UnparsedRequest[];
|
1301
1246
|
// Check if it is a single request or a batch
|
1302
|
-
if (req.
|
1303
|
-
|
1304
|
-
req.body.requests.map(
|
1305
|
-
async (request: HookReq) =>
|
1306
|
-
await runHooks('PREPARSE', reqHooks, {
|
1307
|
-
req: request,
|
1308
|
-
tx: req.tx,
|
1309
|
-
}),
|
1310
|
-
),
|
1311
|
-
);
|
1312
|
-
requests = req.body.requests;
|
1247
|
+
if (req.batch != null && req.batch.length > 0) {
|
1248
|
+
requests = req.batch;
|
1313
1249
|
} else {
|
1314
1250
|
const { method, url, body } = req;
|
1315
|
-
requests = [{ method, url, body }];
|
1251
|
+
requests = [{ method, url, data: body }];
|
1316
1252
|
}
|
1317
1253
|
|
1318
1254
|
const prepareRequest = async (
|
1319
1255
|
parsedRequest: uriParser.ParsedODataRequest &
|
1320
1256
|
Partial<Pick<uriParser.ODataRequest, 'engine' | 'translateVersions'>>,
|
1321
1257
|
): Promise<uriParser.ODataRequest> => {
|
1322
|
-
|
1258
|
+
const abstractSqlModel = getAbstractSqlModel(parsedRequest);
|
1259
|
+
if (abstractSqlModel == null) {
|
1323
1260
|
throw new BadRequestError(
|
1324
1261
|
'Unknown vocabulary: ' + parsedRequest.vocabulary,
|
1325
1262
|
);
|
@@ -1333,6 +1270,14 @@ const runODataRequest = (req: Express.Request, vocabulary: string) => {
|
|
1333
1270
|
>;
|
1334
1271
|
// Add/check the relevant permissions
|
1335
1272
|
try {
|
1273
|
+
const resolvedResourceName = resolveSynonym($request);
|
1274
|
+
if (
|
1275
|
+
abstractSqlModel.tables[resolvedResourceName] == null &&
|
1276
|
+
!resolvedResourceName.endsWith('#canAccess')
|
1277
|
+
) {
|
1278
|
+
throw new UnauthorizedError();
|
1279
|
+
}
|
1280
|
+
|
1336
1281
|
$request.hooks = [];
|
1337
1282
|
for (const version of versions) {
|
1338
1283
|
// We get the hooks list between each `runHooks` so that any resource renames will be used
|
@@ -1376,13 +1321,7 @@ const runODataRequest = (req: Express.Request, vocabulary: string) => {
|
|
1376
1321
|
|
1377
1322
|
// Parse the OData requests
|
1378
1323
|
const results = await mappingFn(requests, async (requestPart) => {
|
1379
|
-
const parsedRequest =
|
1380
|
-
requestPart,
|
1381
|
-
req.url.startsWith(`/${vocabulary}/$batch`) &&
|
1382
|
-
!requestPart.url.includes(`/${vocabulary}/$batch`)
|
1383
|
-
? req.headers
|
1384
|
-
: undefined,
|
1385
|
-
);
|
1324
|
+
const parsedRequest = uriParser.parseOData(requestPart);
|
1386
1325
|
|
1387
1326
|
let request: uriParser.ODataRequest | uriParser.ODataRequest[];
|
1388
1327
|
if (Array.isArray(parsedRequest)) {
|
@@ -1428,7 +1367,7 @@ const runODataRequest = (req: Express.Request, vocabulary: string) => {
|
|
1428
1367
|
if (
|
1429
1368
|
!Array.isArray(result) &&
|
1430
1369
|
result.body == null &&
|
1431
|
-
result.
|
1370
|
+
result.statusCode == null
|
1432
1371
|
) {
|
1433
1372
|
console.error('No status or body set', req.url, responses);
|
1434
1373
|
return new InternalRequestError();
|
@@ -1442,8 +1381,13 @@ const runODataRequest = (req: Express.Request, vocabulary: string) => {
|
|
1442
1381
|
};
|
1443
1382
|
};
|
1444
1383
|
|
1445
|
-
export const
|
1384
|
+
export const getApiRoot = (req: Express.Request): string | undefined => {
|
1446
1385
|
const [, apiRoot] = req.url.split('/', 2);
|
1386
|
+
return apiRoot;
|
1387
|
+
};
|
1388
|
+
|
1389
|
+
export const handleODataRequest: Express.Handler = async (req, res, next) => {
|
1390
|
+
const apiRoot = getApiRoot(req);
|
1447
1391
|
if (apiRoot == null || models[apiRoot] == null) {
|
1448
1392
|
return next('route');
|
1449
1393
|
}
|
@@ -1457,10 +1401,7 @@ export const handleODataRequest: Express.Handler = async (req, res, next) => {
|
|
1457
1401
|
|
1458
1402
|
res.set('Cache-Control', 'no-cache');
|
1459
1403
|
// If we are dealing with a single request unpack the response and respond normally
|
1460
|
-
if (
|
1461
|
-
!req.url.startsWith(`/${apiRoot}/$batch`) ||
|
1462
|
-
req.body.requests?.length === 0
|
1463
|
-
) {
|
1404
|
+
if (req.batch == null || req.batch.length === 0) {
|
1464
1405
|
let [response] = responses;
|
1465
1406
|
if (response instanceof HttpError) {
|
1466
1407
|
response = httpErrorToResponse(response);
|
@@ -1469,15 +1410,15 @@ export const handleODataRequest: Express.Handler = async (req, res, next) => {
|
|
1469
1410
|
|
1470
1411
|
// Otherwise its a multipart request and we reply with the appropriate multipart response
|
1471
1412
|
} else {
|
1472
|
-
res.status(200).
|
1473
|
-
responses
|
1413
|
+
(res.status(200) as any).sendMulti(
|
1414
|
+
responses.map((response) => {
|
1474
1415
|
if (response instanceof HttpError) {
|
1475
1416
|
return httpErrorToResponse(response);
|
1476
1417
|
} else {
|
1477
1418
|
return response;
|
1478
1419
|
}
|
1479
1420
|
}),
|
1480
|
-
|
1421
|
+
);
|
1481
1422
|
}
|
1482
1423
|
} catch (e: any) {
|
1483
1424
|
if (handleHttpErrors(req, res, e)) {
|
@@ -1504,16 +1445,16 @@ export const handleHttpErrors = (
|
|
1504
1445
|
for (const handleErrorFn of handleErrorFns) {
|
1505
1446
|
handleErrorFn(req, err);
|
1506
1447
|
}
|
1507
|
-
const response = httpErrorToResponse(err
|
1448
|
+
const response = httpErrorToResponse(err);
|
1508
1449
|
handleResponse(res, response);
|
1509
1450
|
return true;
|
1510
1451
|
}
|
1511
1452
|
return false;
|
1512
1453
|
};
|
1513
1454
|
const handleResponse = (res: Express.Response, response: Response): void => {
|
1514
|
-
const { body, headers,
|
1455
|
+
const { body, headers, statusCode } = response as Response;
|
1515
1456
|
res.set(headers);
|
1516
|
-
res.status(
|
1457
|
+
res.status(statusCode);
|
1517
1458
|
if (!body) {
|
1518
1459
|
res.end();
|
1519
1460
|
} else {
|
@@ -1523,12 +1464,10 @@ const handleResponse = (res: Express.Response, response: Response): void => {
|
|
1523
1464
|
|
1524
1465
|
const httpErrorToResponse = (
|
1525
1466
|
err: HttpError,
|
1526
|
-
|
1527
|
-
): RequiredField<Response, 'status'> => {
|
1528
|
-
const message = err.getResponseBody();
|
1467
|
+
): RequiredField<Response, 'statusCode'> => {
|
1529
1468
|
return {
|
1530
|
-
|
1531
|
-
body:
|
1469
|
+
statusCode: err.status,
|
1470
|
+
body: err.getResponseBody(),
|
1532
1471
|
headers: err.headers,
|
1533
1472
|
};
|
1534
1473
|
};
|
@@ -1642,8 +1581,7 @@ const runChangeSet =
|
|
1642
1581
|
throw new Error('No request id');
|
1643
1582
|
}
|
1644
1583
|
result.headers ??= {};
|
1645
|
-
result.headers['content-id'] = request.
|
1646
|
-
result.id = request.batchRequestId;
|
1584
|
+
result.headers['content-id'] = request.id;
|
1647
1585
|
changeSetResults.set(request.id, result);
|
1648
1586
|
};
|
1649
1587
|
|
@@ -1682,29 +1620,22 @@ const prepareResponse = async (
|
|
1682
1620
|
result: any,
|
1683
1621
|
tx: Db.Tx,
|
1684
1622
|
): Promise<Response> => {
|
1685
|
-
let response: Response;
|
1686
1623
|
switch (request.method) {
|
1687
1624
|
case 'GET':
|
1688
|
-
|
1689
|
-
break;
|
1625
|
+
return await respondGet(req, request, result, tx);
|
1690
1626
|
case 'POST':
|
1691
|
-
|
1692
|
-
break;
|
1627
|
+
return await respondPost(req, request, result, tx);
|
1693
1628
|
case 'PUT':
|
1694
1629
|
case 'PATCH':
|
1695
1630
|
case 'MERGE':
|
1696
|
-
|
1697
|
-
break;
|
1631
|
+
return await respondPut(req, request, result, tx);
|
1698
1632
|
case 'DELETE':
|
1699
|
-
|
1700
|
-
break;
|
1633
|
+
return await respondDelete(req, request, result, tx);
|
1701
1634
|
case 'OPTIONS':
|
1702
|
-
|
1703
|
-
break;
|
1635
|
+
return await respondOptions(req, request, result, tx);
|
1704
1636
|
default:
|
1705
1637
|
throw new MethodNotAllowedError();
|
1706
1638
|
}
|
1707
|
-
return { ...response, id: request.batchRequestId };
|
1708
1639
|
};
|
1709
1640
|
|
1710
1641
|
const checkReadOnlyRequests = (request: uriParser.ODataRequest) => {
|
@@ -1827,7 +1758,7 @@ const respondGet = async (
|
|
1827
1758
|
);
|
1828
1759
|
|
1829
1760
|
const response = {
|
1830
|
-
|
1761
|
+
statusCode: 200,
|
1831
1762
|
body: { d },
|
1832
1763
|
headers: { 'content-type': 'application/json' },
|
1833
1764
|
};
|
@@ -1842,14 +1773,14 @@ const respondGet = async (
|
|
1842
1773
|
} else {
|
1843
1774
|
if (request.resourceName === '$metadata') {
|
1844
1775
|
return {
|
1845
|
-
|
1776
|
+
statusCode: 200,
|
1846
1777
|
body: models[vocab].odataMetadata,
|
1847
1778
|
headers: { 'content-type': 'xml' },
|
1848
1779
|
};
|
1849
1780
|
} else {
|
1850
1781
|
// TODO: request.resourceName can be '$serviceroot' or a resource and we should return an odata xml document based on that
|
1851
1782
|
return {
|
1852
|
-
|
1783
|
+
statusCode: 404,
|
1853
1784
|
};
|
1854
1785
|
}
|
1855
1786
|
}
|
@@ -1905,7 +1836,7 @@ const respondPost = async (
|
|
1905
1836
|
}
|
1906
1837
|
|
1907
1838
|
const response = {
|
1908
|
-
|
1839
|
+
statusCode: 201,
|
1909
1840
|
body: result.d[0],
|
1910
1841
|
headers: {
|
1911
1842
|
'content-type': 'application/json',
|
@@ -1953,7 +1884,7 @@ const respondPut = async (
|
|
1953
1884
|
tx: Db.Tx,
|
1954
1885
|
): Promise<Response> => {
|
1955
1886
|
const response = {
|
1956
|
-
|
1887
|
+
statusCode: 200,
|
1957
1888
|
};
|
1958
1889
|
await runHooks('PRERESPOND', request.hooks, {
|
1959
1890
|
req,
|
@@ -2051,3 +1982,18 @@ export const setup = async (
|
|
2051
1982
|
// we can't use IF NOT EXISTS on all dbs, so we have to ignore the error raised if this index already exists
|
2052
1983
|
}
|
2053
1984
|
};
|
1985
|
+
|
1986
|
+
export const postSetup = async (
|
1987
|
+
_app: Express.Application,
|
1988
|
+
$db: Db.Database,
|
1989
|
+
): Promise<void> => {
|
1990
|
+
exports.db = db = $db;
|
1991
|
+
try {
|
1992
|
+
await db.transaction(async (tx) => {
|
1993
|
+
await postExecuteModels(tx);
|
1994
|
+
});
|
1995
|
+
} catch (err: any) {
|
1996
|
+
console.error('Could not post execute models', err);
|
1997
|
+
process.exit(1);
|
1998
|
+
}
|
1999
|
+
};
|
@@ -1,5 +1,5 @@
|
|
1
|
-
import
|
2
|
-
import {
|
1
|
+
import _ from 'lodash';
|
2
|
+
import type {
|
3
3
|
AbstractSqlModel,
|
4
4
|
Relationship,
|
5
5
|
ReferencedFieldNode,
|
@@ -14,7 +14,7 @@ import {
|
|
14
14
|
UnknownTypeNodes,
|
15
15
|
NullNode,
|
16
16
|
} from '@balena/abstract-sql-compiler';
|
17
|
-
import { Dictionary } from './common-types';
|
17
|
+
import type { Dictionary } from './common-types';
|
18
18
|
|
19
19
|
export type AliasValidNodeType =
|
20
20
|
| ReferencedFieldNode
|
@@ -160,9 +160,8 @@ export const translateAbstractSqlModel = (
|
|
160
160
|
if (synonym.includes('$')) {
|
161
161
|
fromAbstractSqlModel.synonyms[synonym] = canonicalForm;
|
162
162
|
} else {
|
163
|
-
fromAbstractSqlModel.synonyms[
|
164
|
-
`${
|
165
|
-
] = `${canonicalForm}$${toVersion}`;
|
163
|
+
fromAbstractSqlModel.synonyms[`${synonym}$${toVersion}`] =
|
164
|
+
`${canonicalForm}$${toVersion}`;
|
166
165
|
}
|
167
166
|
}
|
168
167
|
const relationships = _.cloneDeep(toAbstractSqlModel.relationships);
|
@@ -199,6 +198,10 @@ export const translateAbstractSqlModel = (
|
|
199
198
|
}
|
200
199
|
|
201
200
|
for (const key of fromResourceKeys) {
|
201
|
+
if (key.includes('$')) {
|
202
|
+
// Skip translated resources, eg `resource$v2`
|
203
|
+
continue;
|
204
|
+
}
|
202
205
|
const translationDefinition = translationDefinitions[key];
|
203
206
|
const table = fromAbstractSqlModel.tables[key];
|
204
207
|
if (translationDefinition) {
|