@balena/pinejs 15.0.0-delete-state-default-user-permissions-981931563dc47b2a8b873bc787d7dacfcc6c52e3 → 15.0.0-deprecate-node12-8a99d72ae66d7708293afc56c5d7eb19b39081cd
Sign up to get free protection for your applications and to get access to all the features.
- package/.resinci.yml +0 -1
- package/.versionbot/CHANGELOG.yml +226 -8
- package/CHANGELOG.md +85 -1
- package/out/bin/utils.js +1 -1
- package/out/bin/utils.js.map +1 -1
- package/out/config-loader/config-loader.d.ts +3 -5
- package/out/config-loader/config-loader.js +35 -31
- package/out/config-loader/config-loader.js.map +1 -1
- package/out/data-server/sbvr-server.js +8 -8
- package/out/data-server/sbvr-server.js.map +1 -1
- package/out/database-layer/db.d.ts +1 -1
- package/out/database-layer/db.js +3 -3
- package/out/database-layer/db.js.map +1 -1
- package/out/express-emulator/express.js +1 -1
- package/out/express-emulator/express.js.map +1 -1
- package/out/http-transactions/transactions.js +4 -4
- package/out/http-transactions/transactions.js.map +1 -1
- package/out/migrator/sync.d.ts +9 -0
- package/out/migrator/sync.js +121 -0
- package/out/migrator/sync.js.map +1 -0
- package/out/migrator/utils.d.ts +28 -0
- package/out/migrator/utils.js +104 -0
- package/out/migrator/utils.js.map +1 -0
- package/out/odata-metadata/odata-metadata-generator.js +6 -9
- package/out/odata-metadata/odata-metadata-generator.js.map +1 -1
- package/out/passport-pinejs/passport-pinejs.js +4 -3
- package/out/passport-pinejs/passport-pinejs.js.map +1 -1
- package/out/pinejs-session-store/pinejs-session-store.js +1 -1
- package/out/pinejs-session-store/pinejs-session-store.js.map +1 -1
- package/out/sbvr-api/abstract-sql.d.ts +1 -1
- package/out/sbvr-api/abstract-sql.js.map +1 -1
- package/out/sbvr-api/control-flow.js.map +1 -1
- package/out/sbvr-api/hooks.d.ts +6 -3
- package/out/sbvr-api/hooks.js +3 -3
- package/out/sbvr-api/hooks.js.map +1 -1
- package/out/sbvr-api/odata-response.js +5 -5
- package/out/sbvr-api/odata-response.js.map +1 -1
- package/out/sbvr-api/permissions.js +25 -20
- package/out/sbvr-api/permissions.js.map +1 -1
- package/out/sbvr-api/sbvr-utils.d.ts +4 -3
- package/out/sbvr-api/sbvr-utils.js +71 -48
- package/out/sbvr-api/sbvr-utils.js.map +1 -1
- package/out/sbvr-api/uri-parser.d.ts +13 -11
- package/out/sbvr-api/uri-parser.js +4 -4
- package/out/sbvr-api/uri-parser.js.map +1 -1
- package/out/server-glue/module.d.ts +2 -2
- package/out/server-glue/module.js +2 -1
- package/out/server-glue/module.js.map +1 -1
- package/package.json +19 -19
- package/src/bin/utils.ts +1 -1
- package/src/config-loader/config-loader.ts +69 -44
- package/src/data-server/sbvr-server.js +8 -8
- package/src/database-layer/db.ts +11 -11
- package/src/express-emulator/express.js +1 -1
- package/src/http-transactions/transactions.js +4 -4
- package/src/migrator/sync.ts +169 -0
- package/src/migrator/utils.ts +154 -0
- package/src/odata-metadata/odata-metadata-generator.ts +8 -11
- package/src/passport-pinejs/passport-pinejs.ts +3 -2
- package/src/sbvr-api/abstract-sql.ts +6 -3
- package/src/sbvr-api/control-flow.ts +2 -2
- package/src/sbvr-api/hooks.ts +18 -8
- package/src/sbvr-api/odata-response.ts +4 -4
- package/src/sbvr-api/permissions.ts +42 -36
- package/src/sbvr-api/sbvr-utils.ts +121 -58
- package/src/sbvr-api/uri-parser.ts +29 -21
- package/src/server-glue/module.ts +4 -3
- package/tsconfig.json +1 -3
- package/typings/lf-to-abstract-sql.d.ts +6 -9
- package/out/migrator/migrator.d.ts +0 -17
- package/out/migrator/migrator.js +0 -185
- package/out/migrator/migrator.js.map +0 -1
- package/src/migrator/migrator.ts +0 -278
@@ -22,7 +22,7 @@ import type {
|
|
22
22
|
} from '@balena/odata-parser';
|
23
23
|
import type { Tx } from '../database-layer/db';
|
24
24
|
import type { ApiKey, User } from '../sbvr-api/sbvr-utils';
|
25
|
-
import type { AnyObject } from './common-types';
|
25
|
+
import type { AnyObject, Dictionary } from './common-types';
|
26
26
|
|
27
27
|
import {
|
28
28
|
isBindReference,
|
@@ -172,7 +172,7 @@ const parsePermissions = (
|
|
172
172
|
// array: Treated as an AND of all elements
|
173
173
|
// object: Must have only one key of either `AND` or `OR`, with an array value that will be treated according to the key.
|
174
174
|
const isAnd = <T>(x: any): x is NestedCheckAnd<T> =>
|
175
|
-
|
175
|
+
typeof x === 'object' && 'and' in x;
|
176
176
|
const isOr = <T>(x: any): x is NestedCheckOr<T> =>
|
177
177
|
typeof x === 'object' && 'or' in x;
|
178
178
|
export function nestedCheck<I, O>(
|
@@ -312,7 +312,7 @@ const namespaceRelationships = (
|
|
312
312
|
});
|
313
313
|
};
|
314
314
|
|
315
|
-
type PermissionLookup =
|
315
|
+
type PermissionLookup = Dictionary<true | string[]>;
|
316
316
|
|
317
317
|
const getPermissionsLookup = env.createCache(
|
318
318
|
'permissionsLookup',
|
@@ -420,9 +420,9 @@ const convertToLambda = (filter: AnyObject, identifier: string) => {
|
|
420
420
|
return;
|
421
421
|
}
|
422
422
|
if (Array.isArray(object)) {
|
423
|
-
|
423
|
+
for (const element of object) {
|
424
424
|
replaceObject(element);
|
425
|
-
}
|
425
|
+
}
|
426
426
|
}
|
427
427
|
|
428
428
|
if (object.hasOwnProperty('name')) {
|
@@ -445,7 +445,7 @@ const rewriteSubPermissionBindings = (filter: AnyObject, counter: number) => {
|
|
445
445
|
object.bind = counter + object.bind;
|
446
446
|
}
|
447
447
|
|
448
|
-
if (Array.isArray(object) ||
|
448
|
+
if (Array.isArray(object) || typeof object === 'object') {
|
449
449
|
_.forEach(object, (v) => {
|
450
450
|
rewrite(v);
|
451
451
|
});
|
@@ -487,7 +487,7 @@ const buildODataPermission = (
|
|
487
487
|
return {
|
488
488
|
filter: parsePermissions(permissionCheck, odata.binds),
|
489
489
|
};
|
490
|
-
} catch (e) {
|
490
|
+
} catch (e: any) {
|
491
491
|
console.warn(
|
492
492
|
'Failed to parse conditional permissions: ',
|
493
493
|
permissionCheck,
|
@@ -704,7 +704,7 @@ const onceGetter = <T, U extends keyof T>(
|
|
704
704
|
// and the delete removes that restriction
|
705
705
|
delete this[propName];
|
706
706
|
return (this[propName] = result);
|
707
|
-
} catch (e) {
|
707
|
+
} catch (e: any) {
|
708
708
|
thrownErr = e;
|
709
709
|
throw thrownErr;
|
710
710
|
} finally {
|
@@ -717,7 +717,7 @@ const onceGetter = <T, U extends keyof T>(
|
|
717
717
|
const deepFreezeExceptDefinition = (obj: AnyObject) => {
|
718
718
|
Object.freeze(obj);
|
719
719
|
|
720
|
-
Object.getOwnPropertyNames(obj)
|
720
|
+
for (const prop of Object.getOwnPropertyNames(obj)) {
|
721
721
|
// We skip the definition because we know it's a property we've defined that will throw an error in some cases
|
722
722
|
if (
|
723
723
|
prop !== 'definition' &&
|
@@ -727,7 +727,7 @@ const deepFreezeExceptDefinition = (obj: AnyObject) => {
|
|
727
727
|
) {
|
728
728
|
deepFreezeExceptDefinition(obj);
|
729
729
|
}
|
730
|
-
}
|
730
|
+
}
|
731
731
|
};
|
732
732
|
|
733
733
|
const createBypassDefinition = (definition: Definition) =>
|
@@ -880,7 +880,7 @@ const rewriteRelationship = memoizeWeak(
|
|
880
880
|
canAccess: canAccessFunction,
|
881
881
|
},
|
882
882
|
);
|
883
|
-
} catch (e) {
|
883
|
+
} catch (e: any) {
|
884
884
|
throw new ODataParser.SyntaxError(e);
|
885
885
|
}
|
886
886
|
if (foundCanAccessLink) {
|
@@ -908,7 +908,7 @@ const rewriteRelationship = memoizeWeak(
|
|
908
908
|
}
|
909
909
|
}
|
910
910
|
|
911
|
-
if (Array.isArray(object) ||
|
911
|
+
if (Array.isArray(object) || typeof object === 'object') {
|
912
912
|
_.forEach(object, (v) => {
|
913
913
|
// we want to recurse into the relationship path, but
|
914
914
|
// in case we hit a plain string, we don't need to bother
|
@@ -963,7 +963,9 @@ const getBoundConstrainedMemoizer = memoizeWeak(
|
|
963
963
|
(permissionsLookup: PermissionLookup, vocabulary: string) => {
|
964
964
|
const constrainedAbstractSqlModel = _.cloneDeep(abstractSqlModel);
|
965
965
|
|
966
|
-
const origSynonyms = Object.
|
966
|
+
const origSynonyms = Object.entries(
|
967
|
+
constrainedAbstractSqlModel.synonyms,
|
968
|
+
);
|
967
969
|
constrainedAbstractSqlModel.synonyms = new Proxy(
|
968
970
|
constrainedAbstractSqlModel.synonyms,
|
969
971
|
{
|
@@ -975,9 +977,9 @@ const getBoundConstrainedMemoizer = memoizeWeak(
|
|
975
977
|
if (!alias) {
|
976
978
|
return;
|
977
979
|
}
|
978
|
-
|
980
|
+
for (const [synonym, canonicalForm] of origSynonyms) {
|
979
981
|
synonyms[`${synonym}$${alias}`] = `${canonicalForm}$${alias}`;
|
980
|
-
}
|
982
|
+
}
|
981
983
|
return synonyms[permissionSynonym];
|
982
984
|
},
|
983
985
|
},
|
@@ -1242,7 +1244,7 @@ export const getUserPermissions = async (
|
|
1242
1244
|
}
|
1243
1245
|
try {
|
1244
1246
|
return await $getUserPermissions(userId, tx);
|
1245
|
-
} catch (err) {
|
1247
|
+
} catch (err: any) {
|
1246
1248
|
sbvrUtils.api.Auth.logger.error('Error loading user permissions', err);
|
1247
1249
|
throw err;
|
1248
1250
|
}
|
@@ -1370,7 +1372,7 @@ export const getApiKeyPermissions = async (
|
|
1370
1372
|
}
|
1371
1373
|
try {
|
1372
1374
|
return await $getApiKeyPermissions(apiKey, tx);
|
1373
|
-
} catch (err) {
|
1375
|
+
} catch (err: any) {
|
1374
1376
|
sbvrUtils.api.Auth.logger.error('Error loading api key permissions', err);
|
1375
1377
|
throw err;
|
1376
1378
|
}
|
@@ -1438,7 +1440,7 @@ const checkApiKey = async (
|
|
1438
1440
|
let permissions: string[];
|
1439
1441
|
try {
|
1440
1442
|
permissions = await getApiKeyPermissions(apiKey, tx);
|
1441
|
-
} catch (err) {
|
1443
|
+
} catch (err: any) {
|
1442
1444
|
console.warn('Error with API key:', err);
|
1443
1445
|
// Ignore errors getting the api key and just use an empty permissions object.
|
1444
1446
|
permissions = [];
|
@@ -1568,7 +1570,7 @@ export const checkPermissionsMiddleware =
|
|
1568
1570
|
'checkPermissionsMiddleware returned a conditional permission',
|
1569
1571
|
);
|
1570
1572
|
}
|
1571
|
-
} catch (err) {
|
1573
|
+
} catch (err: any) {
|
1572
1574
|
sbvrUtils.api.Auth.logger.error(
|
1573
1575
|
'Error checking permissions',
|
1574
1576
|
err,
|
@@ -1578,6 +1580,7 @@ export const checkPermissionsMiddleware =
|
|
1578
1580
|
}
|
1579
1581
|
};
|
1580
1582
|
|
1583
|
+
let guestPermissionsInitialized = false;
|
1581
1584
|
const getGuestPermissions = memoize(
|
1582
1585
|
async () => {
|
1583
1586
|
// Get guest user
|
@@ -1601,6 +1604,7 @@ const getGuestPermissions = memoize(
|
|
1601
1604
|
if (guestPermissions.some((p) => DEFAULT_ACTOR_BIND_REGEX.test(p))) {
|
1602
1605
|
throw new Error('Guest permissions cannot reference actors');
|
1603
1606
|
}
|
1607
|
+
guestPermissionsInitialized = true;
|
1604
1608
|
return guestPermissions;
|
1605
1609
|
},
|
1606
1610
|
{ promise: true },
|
@@ -1611,7 +1615,18 @@ const getReqPermissions = async (
|
|
1611
1615
|
odataBinds: ODataBinds = [],
|
1612
1616
|
) => {
|
1613
1617
|
const [guestPermissions] = await Promise.all([
|
1614
|
-
|
1618
|
+
(async () => {
|
1619
|
+
if (
|
1620
|
+
guestPermissionsInitialized === false &&
|
1621
|
+
(req.user === root.user || req.user === rootRead.user)
|
1622
|
+
) {
|
1623
|
+
// In the case that guest permissions are not initialized yet and the query is being made with root permissions
|
1624
|
+
// then we need to bypass `getGuestPermissions` as it will cause an infinite loop back to here.
|
1625
|
+
// Therefore to break that loop we just ignore guest permissions.
|
1626
|
+
return [];
|
1627
|
+
}
|
1628
|
+
return await getGuestPermissions();
|
1629
|
+
})(),
|
1615
1630
|
(async () => {
|
1616
1631
|
// TODO: Remove this extra actor ID lookup making actor non-optional and updating open-balena-api.
|
1617
1632
|
if (
|
@@ -1666,20 +1681,6 @@ export const addPermissions = async (
|
|
1666
1681
|
}
|
1667
1682
|
}
|
1668
1683
|
|
1669
|
-
// This bypasses in the root cases, needed for fetching guest permissions to work, it can almost certainly be done better though
|
1670
|
-
let permissions = req.user?.permissions ?? [];
|
1671
|
-
permissions = permissions.concat(req.apiKey?.permissions ?? []);
|
1672
|
-
if (
|
1673
|
-
permissions.length > 0 &&
|
1674
|
-
$checkPermissions(
|
1675
|
-
getPermissionsLookup(permissions),
|
1676
|
-
permissionType,
|
1677
|
-
vocabulary,
|
1678
|
-
) === true
|
1679
|
-
) {
|
1680
|
-
// We have unconditional permission to access the vocab so there's no need to intercept anything
|
1681
|
-
return;
|
1682
|
-
}
|
1683
1684
|
const permissionsLookup = await getReqPermissions(req, odataBinds);
|
1684
1685
|
// Update the request's abstract sql model to use the constrained version
|
1685
1686
|
request.abstractSqlModel = abstractSqlModel = memoizedGetConstrainedModel(
|
@@ -1799,8 +1800,13 @@ export const setup = () => {
|
|
1799
1800
|
}
|
1800
1801
|
await addPermissions(req, request);
|
1801
1802
|
},
|
1802
|
-
PRERESPOND: ({ request,
|
1803
|
-
if (
|
1803
|
+
PRERESPOND: ({ request, response }) => {
|
1804
|
+
if (
|
1805
|
+
request.custom.isAction === 'canAccess' &&
|
1806
|
+
(response.body == null ||
|
1807
|
+
typeof response.body === 'string' ||
|
1808
|
+
_.isEmpty(response.body?.d))
|
1809
|
+
) {
|
1804
1810
|
// If the caller does not have any permissions to access the
|
1805
1811
|
// resource pine will throw a PermissionError. To have the
|
1806
1812
|
// same behavior for the case that the user has permissions
|
@@ -33,7 +33,7 @@ import { PinejsClientCore, PromiseResultTypes } from 'pinejs-client-core';
|
|
33
33
|
|
34
34
|
import { ExtendedSBVRParser } from '../extended-sbvr-parser/extended-sbvr-parser';
|
35
35
|
|
36
|
-
import * as
|
36
|
+
import * as syncMigrator from '../migrator/sync';
|
37
37
|
import { generateODataMetadata } from '../odata-metadata/odata-metadata-generator';
|
38
38
|
|
39
39
|
// tslint:disable-next-line:no-var-requires
|
@@ -125,8 +125,8 @@ export interface ApiKey extends Actor {
|
|
125
125
|
actor?: number;
|
126
126
|
}
|
127
127
|
|
128
|
-
interface Response {
|
129
|
-
|
128
|
+
export interface Response {
|
129
|
+
statusCode: number;
|
130
130
|
headers?:
|
131
131
|
| {
|
132
132
|
[headerName: string]: any;
|
@@ -171,13 +171,12 @@ const memoizedResolveNavigationResource = memoizeWeak(
|
|
171
171
|
resourceName: string,
|
172
172
|
navigationName: string,
|
173
173
|
): string => {
|
174
|
-
const navigation =
|
174
|
+
const navigation = odataNameToSqlName(navigationName)
|
175
175
|
.split('-')
|
176
176
|
.flatMap((namePart) =>
|
177
177
|
memoizedResolvedSynonym(abstractSqlModel, namePart).split('-'),
|
178
|
-
)
|
179
|
-
|
180
|
-
.value();
|
178
|
+
);
|
179
|
+
navigation.push('$');
|
181
180
|
const resolvedResourceName = memoizedResolvedSynonym(
|
182
181
|
abstractSqlModel,
|
183
182
|
resourceName,
|
@@ -330,10 +329,49 @@ const prettifyConstraintError = (
|
|
330
329
|
}
|
331
330
|
};
|
332
331
|
|
332
|
+
let cachedIsModelNew: Set<string> | undefined;
|
333
|
+
|
334
|
+
/**
|
335
|
+
*
|
336
|
+
* @param tx database transaction - Needs to be executed in one contained config-loader transaction
|
337
|
+
* @param modelName name of the model to check the database for existence
|
338
|
+
* @returns true when the model was existing in database before pine config-loader is executed,
|
339
|
+
* false when the model was not existing before config-loader was executed.
|
340
|
+
*
|
341
|
+
* ATTENTION: This function needs to be executed in the same transaction as all model manipulating
|
342
|
+
* operations are executed in (as migration table operations). Otherwise it's possible that an INSERT INTO "model"
|
343
|
+
* statement executes and succeeds. Then afterwards execution fails and the isModelNew request would return `false`.
|
344
|
+
* In the case of migrations the table wouldn't have an entry and therefore it would try to run all the historical
|
345
|
+
* migrations on a new model.
|
346
|
+
*/
|
347
|
+
|
348
|
+
export const isModelNew = async (
|
349
|
+
tx: Db.Tx,
|
350
|
+
modelName: string,
|
351
|
+
): Promise<boolean> => {
|
352
|
+
const result = await tx.tableList("name = 'model'");
|
353
|
+
if (result.rows.length === 0) {
|
354
|
+
return true;
|
355
|
+
}
|
356
|
+
if (cachedIsModelNew == null) {
|
357
|
+
const { rows } = await tx.executeSql(
|
358
|
+
`SELECT "is of-vocabulary" FROM "model";`,
|
359
|
+
);
|
360
|
+
cachedIsModelNew = new Set<string>(
|
361
|
+
rows.map((row) => row['is of-vocabulary']),
|
362
|
+
);
|
363
|
+
}
|
364
|
+
|
365
|
+
return !cachedIsModelNew.has(modelName);
|
366
|
+
};
|
367
|
+
|
333
368
|
export const validateModel = async (
|
334
369
|
tx: Db.Tx,
|
335
370
|
modelName: string,
|
336
|
-
request?:
|
371
|
+
request?: Pick<
|
372
|
+
uriParser.ODataRequest,
|
373
|
+
'abstractSqlQuery' | 'modifiedFields' | 'method' | 'vocabulary'
|
374
|
+
>,
|
337
375
|
): Promise<void> => {
|
338
376
|
await Promise.all(
|
339
377
|
models[modelName].sql.rules.map(async (rule) => {
|
@@ -444,7 +482,7 @@ export const executeModels = async (
|
|
444
482
|
execModels.map(async (model) => {
|
445
483
|
const { apiRoot } = model;
|
446
484
|
|
447
|
-
await
|
485
|
+
await syncMigrator.run(tx, model);
|
448
486
|
const compiledModel = generateModels(model, db.engine);
|
449
487
|
|
450
488
|
// Create tables related to terms and fact types
|
@@ -461,7 +499,7 @@ export const executeModels = async (
|
|
461
499
|
}
|
462
500
|
await promise;
|
463
501
|
}
|
464
|
-
await
|
502
|
+
await syncMigrator.postRun(tx, model);
|
465
503
|
|
466
504
|
odataResponse.prepareModel(compiledModel.abstractSql);
|
467
505
|
deepFreeze(compiledModel.abstractSql);
|
@@ -550,7 +588,7 @@ export const executeModels = async (
|
|
550
588
|
);
|
551
589
|
}),
|
552
590
|
);
|
553
|
-
} catch (err) {
|
591
|
+
} catch (err: any) {
|
554
592
|
await Promise.all(
|
555
593
|
execModels.map(async ({ apiRoot }) => {
|
556
594
|
await cleanupModel(apiRoot);
|
@@ -772,6 +810,7 @@ export type Passthrough = AnyObject & {
|
|
772
810
|
};
|
773
811
|
|
774
812
|
export class PinejsClient extends PinejsClientCore<PinejsClient> {
|
813
|
+
// @ts-expect-error This is actually assigned by `super` so it is always declared but that isn't detected here
|
775
814
|
public passthrough: Passthrough;
|
776
815
|
public async _request({
|
777
816
|
method,
|
@@ -816,7 +855,7 @@ export const runURI = async (
|
|
816
855
|
let user: User | undefined;
|
817
856
|
let apiKey: ApiKey | undefined;
|
818
857
|
|
819
|
-
if (req != null &&
|
858
|
+
if (req != null && typeof req === 'object') {
|
820
859
|
user = req.user;
|
821
860
|
apiKey = req.apiKey;
|
822
861
|
} else {
|
@@ -858,15 +897,15 @@ export const runURI = async (
|
|
858
897
|
throw response;
|
859
898
|
}
|
860
899
|
|
861
|
-
const { body: responseBody,
|
900
|
+
const { body: responseBody, statusCode, headers } = response as Response;
|
862
901
|
|
863
|
-
if (
|
902
|
+
if (statusCode != null && statusCode >= 400) {
|
864
903
|
const ErrorClass =
|
865
|
-
statusCodeToError[
|
904
|
+
statusCodeToError[statusCode as keyof typeof statusCodeToError];
|
866
905
|
if (ErrorClass != null) {
|
867
906
|
throw new ErrorClass(undefined, responseBody, headers);
|
868
907
|
}
|
869
|
-
throw new HttpError(
|
908
|
+
throw new HttpError(statusCode, undefined, responseBody, headers);
|
870
909
|
}
|
871
910
|
|
872
911
|
return responseBody as AnyObject | undefined;
|
@@ -925,12 +964,19 @@ const $getAffectedIds = async ({
|
|
925
964
|
}
|
926
965
|
// We reparse to make sure we get a clean odataQuery, without permissions already added
|
927
966
|
// And we use the request's url rather than the req for things like batch where the req url is ../$batch
|
928
|
-
|
929
|
-
|
930
|
-
|
931
|
-
|
967
|
+
const parsedRequest: uriParser.ParsedODataRequest &
|
968
|
+
Partial<Pick<uriParser.ODataRequest, 'engine'>> =
|
969
|
+
await uriParser.parseOData({
|
970
|
+
method: request.method,
|
971
|
+
url: `/${request.vocabulary}${request.url}`,
|
972
|
+
});
|
932
973
|
|
933
|
-
|
974
|
+
parsedRequest.engine = request.engine;
|
975
|
+
// Mark that the engine is required now that we've set it
|
976
|
+
let affectedRequest: uriParser.ODataRequest = parsedRequest as RequiredField<
|
977
|
+
typeof parsedRequest,
|
978
|
+
'engine'
|
979
|
+
>;
|
934
980
|
const abstractSqlModel = getAbstractSqlModel(affectedRequest);
|
935
981
|
const resourceName = resolveSynonym(affectedRequest);
|
936
982
|
const resourceTable = abstractSqlModel.tables[resourceName];
|
@@ -1016,8 +1062,16 @@ const runODataRequest = (req: Express.Request, vocabulary: string) => {
|
|
1016
1062
|
requests = [{ method, url, data: body }];
|
1017
1063
|
}
|
1018
1064
|
|
1019
|
-
const prepareRequest = async (
|
1020
|
-
|
1065
|
+
const prepareRequest = async (
|
1066
|
+
parsedRequest: uriParser.ParsedODataRequest &
|
1067
|
+
Partial<Pick<uriParser.ODataRequest, 'engine'>>,
|
1068
|
+
): Promise<uriParser.ODataRequest> => {
|
1069
|
+
parsedRequest.engine = db.engine;
|
1070
|
+
// Mark that the engine is required now that we've set it
|
1071
|
+
const $request: uriParser.ODataRequest = parsedRequest as RequiredField<
|
1072
|
+
typeof parsedRequest,
|
1073
|
+
'engine'
|
1074
|
+
>;
|
1021
1075
|
// Get the full hooks list now that we can.
|
1022
1076
|
$request.hooks = getHooks($request);
|
1023
1077
|
// Add/check the relevant permissions
|
@@ -1029,7 +1083,7 @@ const runODataRequest = (req: Express.Request, vocabulary: string) => {
|
|
1029
1083
|
});
|
1030
1084
|
const translatedRequest = await uriParser.translateUri($request);
|
1031
1085
|
return await compileRequest(translatedRequest);
|
1032
|
-
} catch (err) {
|
1086
|
+
} catch (err: any) {
|
1033
1087
|
rollbackRequestHooks(reqHooks);
|
1034
1088
|
rollbackRequestHooks($request.hooks);
|
1035
1089
|
throw err;
|
@@ -1038,12 +1092,13 @@ const runODataRequest = (req: Express.Request, vocabulary: string) => {
|
|
1038
1092
|
|
1039
1093
|
// Parse the OData requests
|
1040
1094
|
const results = await mappingFn(requests, async (requestPart) => {
|
1041
|
-
|
1095
|
+
const parsedRequest = await uriParser.parseOData(requestPart);
|
1042
1096
|
|
1043
|
-
|
1044
|
-
|
1097
|
+
let request: uriParser.ODataRequest | uriParser.ODataRequest[];
|
1098
|
+
if (Array.isArray(parsedRequest)) {
|
1099
|
+
request = await controlFlow.mapSeries(parsedRequest, prepareRequest);
|
1045
1100
|
} else {
|
1046
|
-
request = await prepareRequest(
|
1101
|
+
request = await prepareRequest(parsedRequest);
|
1047
1102
|
}
|
1048
1103
|
// Run the request in its own transaction
|
1049
1104
|
return await runTransaction<Response | Response[]>(
|
@@ -1054,9 +1109,9 @@ const runODataRequest = (req: Express.Request, vocabulary: string) => {
|
|
1054
1109
|
tx.on('rollback', () => {
|
1055
1110
|
rollbackRequestHooks(reqHooks);
|
1056
1111
|
if (Array.isArray(request)) {
|
1057
|
-
|
1112
|
+
for (const { hooks } of request) {
|
1058
1113
|
rollbackRequestHooks(hooks);
|
1059
|
-
}
|
1114
|
+
}
|
1060
1115
|
} else {
|
1061
1116
|
rollbackRequestHooks(request.hooks);
|
1062
1117
|
}
|
@@ -1083,7 +1138,7 @@ const runODataRequest = (req: Express.Request, vocabulary: string) => {
|
|
1083
1138
|
if (
|
1084
1139
|
!Array.isArray(result) &&
|
1085
1140
|
result.body == null &&
|
1086
|
-
result.
|
1141
|
+
result.statusCode == null
|
1087
1142
|
) {
|
1088
1143
|
console.error('No status or body set', req.url, responses);
|
1089
1144
|
return new InternalRequestError();
|
@@ -1131,7 +1186,7 @@ export const handleODataRequest: Express.Handler = async (req, res, next) => {
|
|
1131
1186
|
}),
|
1132
1187
|
);
|
1133
1188
|
}
|
1134
|
-
} catch (e) {
|
1189
|
+
} catch (e: any) {
|
1135
1190
|
if (handleHttpErrors(req, res, e)) {
|
1136
1191
|
return;
|
1137
1192
|
}
|
@@ -1163,11 +1218,9 @@ export const handleHttpErrors = (
|
|
1163
1218
|
return false;
|
1164
1219
|
};
|
1165
1220
|
const handleResponse = (res: Express.Response, response: Response): void => {
|
1166
|
-
const { body, headers,
|
1221
|
+
const { body, headers, statusCode } = response as Response;
|
1167
1222
|
res.set(headers);
|
1168
|
-
|
1169
|
-
res.status(status);
|
1170
|
-
}
|
1223
|
+
res.status(statusCode);
|
1171
1224
|
if (!body) {
|
1172
1225
|
res.end();
|
1173
1226
|
} else {
|
@@ -1177,9 +1230,9 @@ const handleResponse = (res: Express.Response, response: Response): void => {
|
|
1177
1230
|
|
1178
1231
|
const httpErrorToResponse = (
|
1179
1232
|
err: HttpError,
|
1180
|
-
): RequiredField<Response, '
|
1233
|
+
): RequiredField<Response, 'statusCode'> => {
|
1181
1234
|
return {
|
1182
|
-
|
1235
|
+
statusCode: err.status,
|
1183
1236
|
body: err.getResponseBody(),
|
1184
1237
|
headers: err.headers,
|
1185
1238
|
};
|
@@ -1246,7 +1299,7 @@ const runRequest = async (
|
|
1246
1299
|
result = await runDelete(req, request, tx);
|
1247
1300
|
break;
|
1248
1301
|
}
|
1249
|
-
} catch (err) {
|
1302
|
+
} catch (err: any) {
|
1250
1303
|
if (err instanceof db.DatabaseError) {
|
1251
1304
|
prettifyConstraintError(err, request);
|
1252
1305
|
logger.error(err);
|
@@ -1270,7 +1323,7 @@ const runRequest = async (
|
|
1270
1323
|
}
|
1271
1324
|
|
1272
1325
|
await runHooks('POSTRUN', request.hooks, { req, request, result, tx });
|
1273
|
-
} catch (err) {
|
1326
|
+
} catch (err: any) {
|
1274
1327
|
await runHooks('POSTRUN-ERROR', request.hooks, {
|
1275
1328
|
req,
|
1276
1329
|
request,
|
@@ -1294,7 +1347,7 @@ const runChangeSet =
|
|
1294
1347
|
throw new Error('No request id');
|
1295
1348
|
}
|
1296
1349
|
result.headers ??= {};
|
1297
|
-
result.headers['
|
1350
|
+
result.headers['content-id'] = request.id;
|
1298
1351
|
changeSetResults.set(request.id, result);
|
1299
1352
|
};
|
1300
1353
|
|
@@ -1464,24 +1517,31 @@ const respondGet = async (
|
|
1464
1517
|
{ includeMetadata: metadata === 'full' },
|
1465
1518
|
);
|
1466
1519
|
|
1520
|
+
const response = {
|
1521
|
+
statusCode: 200,
|
1522
|
+
body: { d },
|
1523
|
+
headers: { 'content-type': 'application/json' },
|
1524
|
+
};
|
1467
1525
|
await runHooks('PRERESPOND', request.hooks, {
|
1468
1526
|
req,
|
1469
1527
|
request,
|
1470
1528
|
result,
|
1529
|
+
response,
|
1471
1530
|
data: d,
|
1472
1531
|
tx,
|
1473
1532
|
});
|
1474
|
-
return
|
1533
|
+
return response;
|
1475
1534
|
} else {
|
1476
1535
|
if (request.resourceName === '$metadata') {
|
1477
1536
|
return {
|
1537
|
+
statusCode: 200,
|
1478
1538
|
body: models[vocab].odataMetadata,
|
1479
|
-
headers: {
|
1539
|
+
headers: { 'content-type': 'xml' },
|
1480
1540
|
};
|
1481
1541
|
} else {
|
1482
1542
|
// TODO: request.resourceName can be '$serviceroot' or a resource and we should return an odata xml document based on that
|
1483
1543
|
return {
|
1484
|
-
|
1544
|
+
statusCode: 404,
|
1485
1545
|
};
|
1486
1546
|
}
|
1487
1547
|
}
|
@@ -1532,21 +1592,23 @@ const respondPost = async (
|
|
1532
1592
|
}
|
1533
1593
|
}
|
1534
1594
|
|
1595
|
+
const response = {
|
1596
|
+
statusCode: 201,
|
1597
|
+
body: result.d[0],
|
1598
|
+
headers: {
|
1599
|
+
'content-type': 'application/json',
|
1600
|
+
location,
|
1601
|
+
},
|
1602
|
+
};
|
1535
1603
|
await runHooks('PRERESPOND', request.hooks, {
|
1536
1604
|
req,
|
1537
1605
|
request,
|
1538
1606
|
result,
|
1607
|
+
response,
|
1539
1608
|
tx,
|
1540
1609
|
});
|
1541
1610
|
|
1542
|
-
return
|
1543
|
-
status: 201,
|
1544
|
-
body: result.d[0],
|
1545
|
-
headers: {
|
1546
|
-
contentType: 'application/json',
|
1547
|
-
Location: location,
|
1548
|
-
},
|
1549
|
-
};
|
1611
|
+
return response;
|
1550
1612
|
};
|
1551
1613
|
|
1552
1614
|
const runPut = async (
|
@@ -1580,16 +1642,17 @@ const respondPut = async (
|
|
1580
1642
|
result: any,
|
1581
1643
|
tx: Db.Tx,
|
1582
1644
|
): Promise<Response> => {
|
1645
|
+
const response = {
|
1646
|
+
statusCode: 200,
|
1647
|
+
};
|
1583
1648
|
await runHooks('PRERESPOND', request.hooks, {
|
1584
1649
|
req,
|
1585
1650
|
request,
|
1586
1651
|
result,
|
1652
|
+
response,
|
1587
1653
|
tx,
|
1588
1654
|
});
|
1589
|
-
return
|
1590
|
-
status: 200,
|
1591
|
-
headers: {},
|
1592
|
-
};
|
1655
|
+
return response;
|
1593
1656
|
};
|
1594
1657
|
const respondDelete = respondPut;
|
1595
1658
|
const respondOptions = respondPut;
|
@@ -1630,7 +1693,7 @@ export const executeStandardModels = async (tx: Db.Tx): Promise<void> => {
|
|
1630
1693
|
});
|
1631
1694
|
await executeModels(tx, permissions.config.models);
|
1632
1695
|
console.info('Successfully executed standard models.');
|
1633
|
-
} catch (err) {
|
1696
|
+
} catch (err: any) {
|
1634
1697
|
console.error('Failed to execute standard models.', err);
|
1635
1698
|
throw err;
|
1636
1699
|
}
|
@@ -1646,7 +1709,7 @@ export const setup = async (
|
|
1646
1709
|
await executeStandardModels(tx);
|
1647
1710
|
await permissions.setup();
|
1648
1711
|
});
|
1649
|
-
} catch (err) {
|
1712
|
+
} catch (err: any) {
|
1650
1713
|
console.error('Could not execute standard models', err);
|
1651
1714
|
process.exit(1);
|
1652
1715
|
}
|