@balena/pinejs 14.60.0-build-translated-models-85baf731130de328e2067a26c40b37309cdb1ac5-1 → 15.0.0-build-15-x-9f7b9bfe63741d7ae5f446e013251758d9d6ca26-1
Sign up to get free protection for your applications and to get access to all the features.
- package/.pinejs-cache.json +1 -1
- package/.versionbot/CHANGELOG.yml +103 -6
- package/CHANGELOG.md +14 -2
- package/VERSION +1 -1
- package/build/browser.ts +0 -1
- package/out/bin/abstract-sql-compiler.js +0 -0
- package/out/bin/abstract-sql-compiler.js.map +1 -1
- package/out/bin/odata-compiler.js +1 -4
- package/out/bin/odata-compiler.js.map +1 -1
- package/out/bin/sbvr-compiler.js +0 -0
- package/out/bin/sbvr-compiler.js.map +1 -1
- package/out/config-loader/config-loader.d.ts +2 -5
- package/out/config-loader/config-loader.js +19 -38
- package/out/config-loader/config-loader.js.map +1 -1
- package/out/config-loader/env.js +4 -4
- package/out/config-loader/env.js.map +1 -1
- package/out/data-server/sbvr-server.d.ts +1 -13
- package/out/data-server/sbvr-server.js +17 -1
- package/out/data-server/sbvr-server.js.map +1 -1
- package/out/database-layer/db.js +12 -15
- package/out/database-layer/db.js.map +1 -1
- package/out/express-emulator/express.js +4 -4
- package/out/express-emulator/express.js.map +1 -1
- package/out/http-transactions/transactions.d.ts +1 -12
- package/out/http-transactions/transactions.js +18 -0
- package/out/http-transactions/transactions.js.map +1 -1
- package/out/migrator/async.js +12 -16
- package/out/migrator/async.js.map +1 -1
- package/out/migrator/sync.js +6 -12
- package/out/migrator/sync.js.map +1 -1
- package/out/migrator/utils.d.ts +5 -4
- package/out/migrator/utils.js +38 -20
- package/out/migrator/utils.js.map +1 -1
- package/out/pinejs-session-store/pinejs-session-store.js +18 -3
- package/out/pinejs-session-store/pinejs-session-store.js.map +1 -1
- package/out/sbvr-api/abstract-sql.js +2 -4
- package/out/sbvr-api/abstract-sql.js.map +1 -1
- package/out/sbvr-api/cached-compile.js +1 -1
- package/out/sbvr-api/cached-compile.js.map +1 -1
- package/out/sbvr-api/hooks.d.ts +3 -3
- package/out/sbvr-api/hooks.js +32 -48
- package/out/sbvr-api/hooks.js.map +1 -1
- package/out/sbvr-api/odata-response.js +4 -5
- package/out/sbvr-api/odata-response.js.map +1 -1
- package/out/sbvr-api/permissions.js +25 -65
- package/out/sbvr-api/permissions.js.map +1 -1
- package/out/sbvr-api/sbvr-utils.d.ts +5 -11
- package/out/sbvr-api/sbvr-utils.js +94 -145
- package/out/sbvr-api/sbvr-utils.js.map +1 -1
- package/out/sbvr-api/uri-parser.d.ts +1 -4
- package/out/sbvr-api/uri-parser.js +4 -11
- package/out/sbvr-api/uri-parser.js.map +1 -1
- package/package.json +7 -7
- package/src/bin/abstract-sql-compiler.ts +2 -1
- package/src/bin/odata-compiler.ts +2 -4
- package/src/bin/sbvr-compiler.ts +2 -1
- package/src/config-loader/config-loader.ts +24 -62
- package/src/config-loader/env.ts +3 -7
- package/src/data-server/sbvr-server.js +17 -1
- package/src/database-layer/db.ts +1 -1
- package/src/express-emulator/express.js +4 -4
- package/src/http-transactions/transactions.js +18 -0
- package/src/migrator/utils.ts +40 -20
- package/src/pinejs-session-store/pinejs-session-store.ts +15 -0
- package/src/sbvr-api/abstract-sql.ts +1 -2
- package/src/sbvr-api/hooks.ts +33 -80
- package/src/sbvr-api/odata-response.ts +1 -2
- package/src/sbvr-api/permissions.ts +20 -68
- package/src/sbvr-api/sbvr-utils.ts +107 -195
- package/src/sbvr-api/uri-parser.ts +5 -8
- package/tsconfig.json +1 -1
- package/out/sbvr-api/translations.d.ts +0 -6
- package/out/sbvr-api/translations.js +0 -136
- package/out/sbvr-api/translations.js.map +0 -1
- package/src/sbvr-api/translations.ts +0 -219
@@ -3,6 +3,7 @@ import { odataNameToSqlName } from '@balena/odata-to-abstract-sql';
|
|
3
3
|
// @ts-ignore
|
4
4
|
const transactionModel = require('./transaction.sbvr');
|
5
5
|
|
6
|
+
/** @type {import('../config-loader/config-loader').Config} */
|
6
7
|
export let config = {
|
7
8
|
models: [
|
8
9
|
{
|
@@ -24,6 +25,23 @@ ADD COLUMN IF NOT EXISTS "modified at" TIMESTAMP DEFAULT CURRENT_TIMESTAMP NOT N
|
|
24
25
|
ALTER TABLE "transaction"
|
25
26
|
ADD COLUMN IF NOT EXISTS "modified at" TIMESTAMP DEFAULT CURRENT_TIMESTAMP NOT NULL;\
|
26
27
|
`,
|
28
|
+
'15.0.0-data-types': async (tx, sbvrUtils) => {
|
29
|
+
switch (sbvrUtils.db.engine) {
|
30
|
+
case 'mysql':
|
31
|
+
await tx.executeSql(`\
|
32
|
+
ALTER TABLE "lock"
|
33
|
+
MODIFY "is exclusive" BOOLEAN NOT NULL,
|
34
|
+
MODIFY "is under lock" BOOLEAN NOT NULL;`);
|
35
|
+
break;
|
36
|
+
case 'postgres':
|
37
|
+
await tx.executeSql(`\
|
38
|
+
ALTER TABLE "lock"
|
39
|
+
ALTER COLUMN "is exclusive" SET DATA TYPE BOOLEAN USING b::BOOLEAN,
|
40
|
+
ALTER COLUMN "is under lock" SET DATA TYPE BOOLEAN USING b::BOOLEAN;`);
|
41
|
+
break;
|
42
|
+
// No need to migrate for websql
|
43
|
+
}
|
44
|
+
},
|
27
45
|
},
|
28
46
|
},
|
29
47
|
],
|
package/src/migrator/utils.ts
CHANGED
@@ -12,6 +12,23 @@ import { delay } from '../sbvr-api/control-flow';
|
|
12
12
|
|
13
13
|
// tslint:disable-next-line:no-var-requires
|
14
14
|
export const modelText = require('./migrations.sbvr');
|
15
|
+
export const migrations: Migrations = {
|
16
|
+
'15.0.0-data-types': async (tx, { db }) => {
|
17
|
+
switch (db.engine) {
|
18
|
+
case 'mysql':
|
19
|
+
await tx.executeSql(`\
|
20
|
+
ALTER TABLE "migration"
|
21
|
+
MODIFY "executed migrations" JSON NOT NULL;`);
|
22
|
+
break;
|
23
|
+
case 'postgres':
|
24
|
+
await tx.executeSql(`\
|
25
|
+
ALTER TABLE "migration"
|
26
|
+
ALTER COLUMN "executed migrations" SET DATA TYPE JSONB USING b::JSONB;`);
|
27
|
+
break;
|
28
|
+
// No need to migrate for websql
|
29
|
+
}
|
30
|
+
},
|
31
|
+
};
|
15
32
|
|
16
33
|
import * as sbvrUtils from '../sbvr-api/sbvr-utils';
|
17
34
|
export enum MigrationCategories {
|
@@ -73,14 +90,14 @@ export function isSyncMigration(
|
|
73
90
|
return typeof migration === 'function' || typeof migration === 'string';
|
74
91
|
}
|
75
92
|
export function areCategorizedMigrations(
|
76
|
-
migrations: Migrations,
|
77
|
-
): migrations is CategorizedMigrations {
|
93
|
+
$migrations: Migrations,
|
94
|
+
): $migrations is CategorizedMigrations {
|
78
95
|
const containsCategories = Object.keys(MigrationCategories).some(
|
79
|
-
(key) => key in migrations,
|
96
|
+
(key) => key in $migrations,
|
80
97
|
);
|
81
98
|
if (
|
82
99
|
containsCategories &&
|
83
|
-
Object.keys(migrations).some((key) => !(key in MigrationCategories))
|
100
|
+
Object.keys($migrations).some((key) => !(key in MigrationCategories))
|
84
101
|
) {
|
85
102
|
throw new Error(
|
86
103
|
'Mixing categorized and uncategorized migrations is not supported',
|
@@ -104,31 +121,31 @@ export type MigrationStatus = {
|
|
104
121
|
};
|
105
122
|
|
106
123
|
export const getRunnableAsyncMigrations = (
|
107
|
-
migrations: Migrations,
|
124
|
+
$migrations: Migrations,
|
108
125
|
): RunnableAsyncMigrations | undefined => {
|
109
|
-
if (migrations[MigrationCategories.async]) {
|
126
|
+
if ($migrations[MigrationCategories.async]) {
|
110
127
|
if (
|
111
|
-
Object.values(migrations[MigrationCategories.async]).some(
|
128
|
+
Object.values($migrations[MigrationCategories.async]).some(
|
112
129
|
(migration) => !isAsyncMigration(migration),
|
113
130
|
) ||
|
114
|
-
typeof migrations[MigrationCategories.async] !== 'object'
|
131
|
+
typeof $migrations[MigrationCategories.async] !== 'object'
|
115
132
|
) {
|
116
133
|
throw new Error(
|
117
134
|
`All loaded async migrations need to be of type: ${MigrationCategories.async}`,
|
118
135
|
);
|
119
136
|
}
|
120
|
-
return migrations[MigrationCategories.async] as RunnableAsyncMigrations;
|
137
|
+
return $migrations[MigrationCategories.async] as RunnableAsyncMigrations;
|
121
138
|
}
|
122
139
|
};
|
123
140
|
|
124
141
|
// migration loader should either get migrations from model
|
125
142
|
// or from the filepath
|
126
143
|
export const getRunnableSyncMigrations = (
|
127
|
-
migrations: Migrations,
|
144
|
+
$migrations: Migrations,
|
128
145
|
): RunnableMigrations => {
|
129
|
-
if (areCategorizedMigrations(migrations)) {
|
146
|
+
if (areCategorizedMigrations($migrations)) {
|
130
147
|
const runnableMigrations: RunnableMigrations = {};
|
131
|
-
for (const [category, categoryMigrations] of Object.entries(migrations)) {
|
148
|
+
for (const [category, categoryMigrations] of Object.entries($migrations)) {
|
132
149
|
if (category in MigrationCategories) {
|
133
150
|
for (const [key, migration] of Object.entries(
|
134
151
|
categoryMigrations as Migrations,
|
@@ -145,16 +162,16 @@ export const getRunnableSyncMigrations = (
|
|
145
162
|
}
|
146
163
|
return runnableMigrations;
|
147
164
|
}
|
148
|
-
return migrations;
|
165
|
+
return $migrations;
|
149
166
|
};
|
150
167
|
|
151
168
|
// turns {"key1": migration, "key3": migration, "key2": migration}
|
152
169
|
// into [["key1", migration], ["key2", migration], ["key3", migration]]
|
153
170
|
export const filterAndSortPendingMigrations = (
|
154
|
-
migrations: NonNullable<RunnableMigrations | RunnableAsyncMigrations>,
|
171
|
+
$migrations: NonNullable<RunnableMigrations | RunnableAsyncMigrations>,
|
155
172
|
executedMigrations: string[],
|
156
173
|
): MigrationTuple[] =>
|
157
|
-
(_(migrations).omit(executedMigrations) as _.Object<typeof migrations>)
|
174
|
+
(_($migrations).omit(executedMigrations) as _.Object<typeof $migrations>)
|
158
175
|
.toPairs()
|
159
176
|
.sortBy(([migrationKey]) => migrationKey)
|
160
177
|
.value();
|
@@ -313,8 +330,10 @@ WHERE "migration"."model name" = ${1}`,
|
|
313
330
|
if (data == null) {
|
314
331
|
return [];
|
315
332
|
}
|
316
|
-
|
317
|
-
|
333
|
+
if (typeof data.executed_migrations === 'string') {
|
334
|
+
return JSON.parse(data.executed_migrations);
|
335
|
+
}
|
336
|
+
return data.executed_migrations;
|
318
337
|
};
|
319
338
|
|
320
339
|
export const migrationTablesExist = async (tx: Tx) => {
|
@@ -338,7 +357,7 @@ WHERE NOT EXISTS (SELECT 1 FROM "migration status" WHERE "migration key" = ${5})
|
|
338
357
|
[
|
339
358
|
migrationStatus['migration_key'],
|
340
359
|
migrationStatus['start_time'],
|
341
|
-
migrationStatus['is_backing_off']
|
360
|
+
migrationStatus['is_backing_off'],
|
342
361
|
migrationStatus['run_count'],
|
343
362
|
migrationStatus['migration_key'],
|
344
363
|
],
|
@@ -372,7 +391,7 @@ WHERE "migration status"."migration key" = ${7};`,
|
|
372
391
|
migrationStatus['migrated_row_count'],
|
373
392
|
migrationStatus['error_count'],
|
374
393
|
migrationStatus['converged_time'],
|
375
|
-
migrationStatus['is_backing_off']
|
394
|
+
migrationStatus['is_backing_off'],
|
376
395
|
migrationStatus['migration_key'],
|
377
396
|
],
|
378
397
|
);
|
@@ -409,7 +428,8 @@ LIMIT 1;`,
|
|
409
428
|
migrated_row_count: data['migrated row count'],
|
410
429
|
error_count: data['error count'],
|
411
430
|
converged_time: data['converged time'],
|
412
|
-
is_backing_off:
|
431
|
+
is_backing_off:
|
432
|
+
data['is backing off'] === true || data['is backing off'] === 1,
|
413
433
|
};
|
414
434
|
} catch (err: any) {
|
415
435
|
// we report any error here, as no error should happen at all
|
@@ -173,6 +173,21 @@ export class PinejsSessionStore extends Store {
|
|
173
173
|
ALTER TABLE "session"
|
174
174
|
ADD COLUMN IF NOT EXISTS "modified at" TIMESTAMP DEFAULT CURRENT_TIMESTAMP NOT NULL;
|
175
175
|
`,
|
176
|
+
'15.0.0-data-types': async (tx, sbvrUtils) => {
|
177
|
+
switch (sbvrUtils.db.engine) {
|
178
|
+
case 'mysql':
|
179
|
+
await tx.executeSql(`\
|
180
|
+
ALTER TABLE "session"
|
181
|
+
MODIFY "data" JSON NOT NULL;`);
|
182
|
+
break;
|
183
|
+
case 'postgres':
|
184
|
+
await tx.executeSql(`\
|
185
|
+
ALTER TABLE "session"
|
186
|
+
ALTER COLUMN "data" SET DATA TYPE JSONB USING b::JSONB;`);
|
187
|
+
break;
|
188
|
+
// No need to migrate for websql
|
189
|
+
}
|
190
|
+
},
|
176
191
|
},
|
177
192
|
},
|
178
193
|
],
|
@@ -95,8 +95,7 @@ export const getAndCheckBindValues = async (
|
|
95
95
|
|
96
96
|
const sqlTableName = odataNameToSqlName(tableName);
|
97
97
|
const sqlFieldName = odataNameToSqlName(fieldName);
|
98
|
-
const
|
99
|
-
const maybeField = (table.modifyFields ?? table.fields).find(
|
98
|
+
const maybeField = sqlModelTables[sqlTableName].fields.find(
|
100
99
|
(f) => f.fieldName === sqlFieldName,
|
101
100
|
);
|
102
101
|
if (maybeField == null) {
|
package/src/sbvr-api/hooks.ts
CHANGED
@@ -42,9 +42,7 @@ export interface Hooks {
|
|
42
42
|
PREPARSE?: (options: Omit<HookArgs, 'request' | 'api'>) => HookResponse;
|
43
43
|
POSTPARSE?: (options: HookArgs) => HookResponse;
|
44
44
|
PRERUN?: (options: HookArgs & { tx: Tx }) => HookResponse;
|
45
|
-
/** These are run in reverse translation order from newest to oldest */
|
46
45
|
POSTRUN?: (options: HookArgs & { tx: Tx; result: any }) => HookResponse;
|
47
|
-
/** These are run in reverse translation order from newest to oldest */
|
48
46
|
PRERESPOND?: (
|
49
47
|
options: HookArgs & {
|
50
48
|
tx: Tx;
|
@@ -55,7 +53,6 @@ export interface Hooks {
|
|
55
53
|
data?: any;
|
56
54
|
},
|
57
55
|
) => HookResponse;
|
58
|
-
/** These are run in reverse translation order from newest to oldest */
|
59
56
|
'POSTRUN-ERROR'?: (
|
60
57
|
options: HookArgs & { error: TypedError | any },
|
61
58
|
) => HookResponse;
|
@@ -129,13 +126,13 @@ class SideEffectHook<T extends HookFn> extends Hook<T> {
|
|
129
126
|
|
130
127
|
// The execution order of rollback actions is unspecified
|
131
128
|
export const rollbackRequestHooks = <T extends InstantiatedHooks>(
|
132
|
-
|
129
|
+
hooks: T | undefined,
|
133
130
|
): void => {
|
134
|
-
if (
|
131
|
+
if (hooks == null) {
|
135
132
|
return;
|
136
133
|
}
|
137
|
-
const sideEffectHooks =
|
138
|
-
.flatMap((
|
134
|
+
const sideEffectHooks = Object.values(hooks)
|
135
|
+
.flatMap((v): Array<Hook<HookFn>> => v)
|
139
136
|
.filter(
|
140
137
|
(hook): hook is SideEffectHook<HookFn> => hook instanceof SideEffectHook,
|
141
138
|
);
|
@@ -180,37 +177,21 @@ const getResourceHooks = (vocabHooks: VocabHooks, resourceName?: string) => {
|
|
180
177
|
const getVocabHooks = (
|
181
178
|
methodHooks: MethodHooks,
|
182
179
|
vocabulary: string,
|
183
|
-
resourceName
|
184
|
-
includeAllVocab: boolean,
|
180
|
+
resourceName?: string,
|
185
181
|
) => {
|
186
182
|
if (methodHooks == null) {
|
187
183
|
return {};
|
188
184
|
}
|
189
|
-
const vocabHooks = getResourceHooks(methodHooks[vocabulary], resourceName);
|
190
|
-
if (!includeAllVocab) {
|
191
|
-
// Do not include `vocabulary='all'` hooks, useful for translated vocabularies
|
192
|
-
return vocabHooks;
|
193
|
-
}
|
194
185
|
return mergeHooks(
|
195
|
-
|
186
|
+
getResourceHooks(methodHooks[vocabulary], resourceName),
|
196
187
|
getResourceHooks(methodHooks['all'], resourceName),
|
197
188
|
);
|
198
189
|
};
|
199
190
|
const getMethodHooks = memoize(
|
200
|
-
(
|
201
|
-
method: SupportedMethod,
|
202
|
-
vocabulary: string,
|
203
|
-
resourceName: string | undefined,
|
204
|
-
includeAllVocab: boolean,
|
205
|
-
) =>
|
191
|
+
(method: SupportedMethod, vocabulary: string, resourceName?: string) =>
|
206
192
|
mergeHooks(
|
207
|
-
getVocabHooks(
|
208
|
-
|
209
|
-
vocabulary,
|
210
|
-
resourceName,
|
211
|
-
includeAllVocab,
|
212
|
-
),
|
213
|
-
getVocabHooks(apiHooks['all'], vocabulary, resourceName, includeAllVocab),
|
193
|
+
getVocabHooks(apiHooks[method], vocabulary, resourceName),
|
194
|
+
getVocabHooks(apiHooks['all'], vocabulary, resourceName),
|
214
195
|
),
|
215
196
|
{ primitive: true },
|
216
197
|
);
|
@@ -219,7 +200,6 @@ export const getHooks = (
|
|
219
200
|
OptionalField<ParsedODataRequest, 'resourceName'>,
|
220
201
|
'resourceName' | 'method' | 'vocabulary'
|
221
202
|
>,
|
222
|
-
includeAllVocab: boolean,
|
223
203
|
): InstantiatedHooks => {
|
224
204
|
let { resourceName } = request;
|
225
205
|
if (resourceName != null) {
|
@@ -228,17 +208,10 @@ export const getHooks = (
|
|
228
208
|
ParsedODataRequest,
|
229
209
|
'resourceName' | 'method' | 'vocabulary'
|
230
210
|
>,
|
231
|
-
)
|
232
|
-
// Remove version suffixes
|
233
|
-
.replace(/\$.*$/, '');
|
211
|
+
);
|
234
212
|
}
|
235
213
|
return instantiateHooks(
|
236
|
-
getMethodHooks(
|
237
|
-
request.method,
|
238
|
-
request.vocabulary,
|
239
|
-
resourceName,
|
240
|
-
includeAllVocab,
|
241
|
-
),
|
214
|
+
getMethodHooks(request.method, request.vocabulary, resourceName),
|
242
215
|
);
|
243
216
|
};
|
244
217
|
getHooks.clear = () => getMethodHooks.clear();
|
@@ -370,11 +343,12 @@ export const addPureHook = (
|
|
370
343
|
});
|
371
344
|
};
|
372
345
|
|
373
|
-
const defineApi = (
|
374
|
-
const { req, tx } = args;
|
346
|
+
const defineApi = (args: HookArgs) => {
|
347
|
+
const { request, req, tx } = args;
|
348
|
+
const { vocabulary } = request;
|
375
349
|
Object.defineProperty(args, 'api', {
|
376
350
|
get: _.once(() =>
|
377
|
-
api[
|
351
|
+
api[vocabulary].clone({
|
378
352
|
passthrough: { req, tx },
|
379
353
|
}),
|
380
354
|
),
|
@@ -386,7 +360,6 @@ type RunHookArgs<T extends keyof Hooks> = Omit<
|
|
386
360
|
'api'
|
387
361
|
>;
|
388
362
|
const getReadOnlyArgs = <T extends keyof Hooks>(
|
389
|
-
modelName: string,
|
390
363
|
args: RunHookArgs<T>,
|
391
364
|
): RunHookArgs<T> => {
|
392
365
|
if (args.tx == null || args.tx.isReadOnly()) {
|
@@ -396,58 +369,38 @@ const getReadOnlyArgs = <T extends keyof Hooks>(
|
|
396
369
|
let readOnlyArgs: typeof args;
|
397
370
|
readOnlyArgs = { ...args, tx: args.tx.asReadOnly() };
|
398
371
|
if ((args as HookArgs).request != null) {
|
399
|
-
defineApi(
|
372
|
+
defineApi(readOnlyArgs as HookArgs);
|
400
373
|
}
|
401
374
|
return readOnlyArgs;
|
402
375
|
};
|
403
376
|
|
404
377
|
export const runHooks = async <T extends keyof Hooks>(
|
405
378
|
hookName: T,
|
406
|
-
|
407
|
-
* A list of modelName/hooks to run in order, which will be reversed for hooks after the "RUN" stage,
|
408
|
-
* ie POSTRUN/PRERESPOND/POSTRUN-ERROR
|
409
|
-
*/
|
410
|
-
hooksList: Array<[modelName: string, hooks: InstantiatedHooks]> | undefined,
|
379
|
+
hooksList: InstantiatedHooks | undefined,
|
411
380
|
args: RunHookArgs<T>,
|
412
381
|
) => {
|
413
382
|
if (hooksList == null) {
|
414
383
|
return;
|
415
384
|
}
|
416
|
-
const hooks = hooksList
|
417
|
-
|
418
|
-
modelName,
|
419
|
-
$hooks[hookName],
|
420
|
-
])
|
421
|
-
.filter(
|
422
|
-
(v): v is [string, InstantiatedHooks[T]] =>
|
423
|
-
v[1] != null && v[1].length > 0,
|
424
|
-
);
|
425
|
-
if (hooks.length === 0) {
|
385
|
+
const hooks = hooksList[hookName];
|
386
|
+
if (hooks == null || hooks.length === 0) {
|
426
387
|
return;
|
427
388
|
}
|
428
|
-
|
429
|
-
|
430
|
-
|
431
|
-
// was actually requested
|
432
|
-
hooks.reverse();
|
389
|
+
|
390
|
+
if ((args as HookArgs).request != null) {
|
391
|
+
defineApi(args as HookArgs);
|
433
392
|
}
|
434
393
|
|
435
|
-
|
436
|
-
const modelArgs = { ...args };
|
437
|
-
let modelReadOnlyArgs: typeof modelArgs;
|
438
|
-
if ((args as HookArgs).request != null) {
|
439
|
-
defineApi(modelName, modelArgs as HookArgs);
|
440
|
-
}
|
394
|
+
let readOnlyArgs: RunHookArgs<T>;
|
441
395
|
|
442
|
-
|
443
|
-
|
444
|
-
|
445
|
-
|
446
|
-
|
447
|
-
|
448
|
-
|
449
|
-
|
450
|
-
|
451
|
-
|
452
|
-
}
|
396
|
+
await Promise.all(
|
397
|
+
(hooks as Array<Hook<HookFn>>).map(async (hook) => {
|
398
|
+
if (hook.readOnlyTx) {
|
399
|
+
readOnlyArgs ??= getReadOnlyArgs(args);
|
400
|
+
await hook.run(readOnlyArgs);
|
401
|
+
} else {
|
402
|
+
await hook.run(args);
|
403
|
+
}
|
404
|
+
}),
|
405
|
+
);
|
453
406
|
};
|
@@ -90,8 +90,7 @@ const getLocalFields = (table: AbstractSqlTable) => {
|
|
90
90
|
if (table.localFields == null) {
|
91
91
|
table.localFields = {};
|
92
92
|
for (const { fieldName, dataType } of table.fields) {
|
93
|
-
|
94
|
-
if (dataType !== 'ForeignKey') {
|
93
|
+
if (!['ForeignKey', 'ConceptType'].includes(dataType)) {
|
95
94
|
const odataName = sqlNameToODataName(fieldName);
|
96
95
|
table.localFields[odataName] = true;
|
97
96
|
}
|
@@ -140,7 +140,7 @@ const $parsePermissions = env.createCache(
|
|
140
140
|
},
|
141
141
|
);
|
142
142
|
|
143
|
-
const
|
143
|
+
const rewriteBinds = (
|
144
144
|
{ tree, extraBinds }: { tree: ODataQuery; extraBinds: ODataBinds },
|
145
145
|
odataBinds: ODataBinds,
|
146
146
|
): ODataQuery => {
|
@@ -163,7 +163,7 @@ const parsePermissions = (
|
|
163
163
|
odataBinds: ODataBinds,
|
164
164
|
): ODataQuery => {
|
165
165
|
const odata = $parsePermissions(filter);
|
166
|
-
return
|
166
|
+
return rewriteBinds(odata, odataBinds);
|
167
167
|
};
|
168
168
|
|
169
169
|
// Traverses all values in `check`, actions for the following data types:
|
@@ -862,9 +862,7 @@ const rewriteRelationship = memoizeWeak(
|
|
862
862
|
foundCanAccessLink = true;
|
863
863
|
}
|
864
864
|
// return a true expression to not select the relationship, which might be virtual
|
865
|
-
|
866
|
-
// is wrapped in an `or` or `and`
|
867
|
-
return ['Equals', ['Boolean', true], ['Boolean', true]];
|
865
|
+
return ['Boolean', true];
|
868
866
|
};
|
869
867
|
|
870
868
|
try {
|
@@ -1050,7 +1048,6 @@ const getBoundConstrainedMemoizer = memoizeWeak(
|
|
1050
1048
|
sqlNameToODataName(permissionsTable.name),
|
1051
1049
|
),
|
1052
1050
|
);
|
1053
|
-
|
1054
1051
|
return permissionsTable;
|
1055
1052
|
},
|
1056
1053
|
},
|
@@ -1447,26 +1444,13 @@ const checkApiKey = async (
|
|
1447
1444
|
apiKey: string,
|
1448
1445
|
tx?: Tx,
|
1449
1446
|
): Promise<PermissionReq['apiKey']> => {
|
1450
|
-
|
1451
|
-
|
1452
|
-
|
1453
|
-
} catch (err: any) {
|
1454
|
-
console.warn('Error with API key:', err);
|
1455
|
-
// Ignore errors getting the api key and just use an empty permissions object.
|
1456
|
-
permissions = [];
|
1457
|
-
}
|
1458
|
-
let actor;
|
1459
|
-
if (permissions.length > 0) {
|
1460
|
-
actor = await getApiKeyActorId(apiKey, tx);
|
1461
|
-
}
|
1462
|
-
const resolvedApiKey: PermissionReq['apiKey'] = {
|
1447
|
+
const permissions = await getApiKeyPermissions(apiKey, tx);
|
1448
|
+
const actor = await getApiKeyActorId(apiKey, tx);
|
1449
|
+
return {
|
1463
1450
|
key: apiKey,
|
1464
1451
|
permissions,
|
1452
|
+
actor,
|
1465
1453
|
};
|
1466
|
-
if (actor != null) {
|
1467
|
-
resolvedApiKey.actor = actor;
|
1468
|
-
}
|
1469
|
-
return resolvedApiKey;
|
1470
1454
|
};
|
1471
1455
|
|
1472
1456
|
export const resolveAuthHeader = async (
|
@@ -1475,11 +1459,6 @@ export const resolveAuthHeader = async (
|
|
1475
1459
|
// TODO: Consider making tx the second argument in the next major
|
1476
1460
|
tx?: Tx,
|
1477
1461
|
): Promise<PermissionReq['apiKey']> => {
|
1478
|
-
// TODO-MAJOR: remove this check
|
1479
|
-
if (req.apiKey != null) {
|
1480
|
-
return;
|
1481
|
-
}
|
1482
|
-
|
1483
1462
|
const auth = req.header('Authorization');
|
1484
1463
|
if (!auth) {
|
1485
1464
|
return;
|
@@ -1525,11 +1504,6 @@ export const resolveApiKey = async (
|
|
1525
1504
|
// TODO: Consider making tx the second argument in the next major
|
1526
1505
|
tx?: Tx,
|
1527
1506
|
): Promise<PermissionReq['apiKey']> => {
|
1528
|
-
// TODO-MAJOR: remove this check
|
1529
|
-
if (req.apiKey != null) {
|
1530
|
-
return;
|
1531
|
-
}
|
1532
|
-
|
1533
1507
|
const apiKey =
|
1534
1508
|
req.params[paramName] ?? req.body[paramName] ?? req.query[paramName];
|
1535
1509
|
if (apiKey == null) {
|
@@ -1539,9 +1513,6 @@ export const resolveApiKey = async (
|
|
1539
1513
|
};
|
1540
1514
|
|
1541
1515
|
export const customApiKeyMiddleware = (paramName = 'apikey') => {
|
1542
|
-
if (paramName == null) {
|
1543
|
-
paramName = 'apikey';
|
1544
|
-
}
|
1545
1516
|
return async (
|
1546
1517
|
req: HookReq | Express.Request,
|
1547
1518
|
_res?: Express.Response,
|
@@ -1633,32 +1604,18 @@ const getReqPermissions = async (
|
|
1633
1604
|
req: PermissionReq,
|
1634
1605
|
odataBinds: ODataBinds = [],
|
1635
1606
|
) => {
|
1636
|
-
const
|
1637
|
-
|
1638
|
-
|
1639
|
-
|
1640
|
-
|
1641
|
-
|
1642
|
-
|
1643
|
-
|
1644
|
-
|
1645
|
-
|
1646
|
-
|
1647
|
-
|
1648
|
-
})(),
|
1649
|
-
(async () => {
|
1650
|
-
// TODO: Remove this extra actor ID lookup making actor non-optional and updating open-balena-api.
|
1651
|
-
if (
|
1652
|
-
req.apiKey != null &&
|
1653
|
-
req.apiKey.actor == null &&
|
1654
|
-
req.apiKey.permissions != null &&
|
1655
|
-
req.apiKey.permissions.length > 0
|
1656
|
-
) {
|
1657
|
-
const actorId = await getApiKeyActorId(req.apiKey.key);
|
1658
|
-
req.apiKey!.actor = actorId;
|
1659
|
-
}
|
1660
|
-
})(),
|
1661
|
-
]);
|
1607
|
+
const guestPermissions = await (async () => {
|
1608
|
+
if (
|
1609
|
+
guestPermissionsInitialized === false &&
|
1610
|
+
(req.user === root.user || req.user === rootRead.user)
|
1611
|
+
) {
|
1612
|
+
// In the case that guest permissions are not initialized yet and the query is being made with root permissions
|
1613
|
+
// then we need to bypass `getGuestPermissions` as it will cause an infinite loop back to here.
|
1614
|
+
// Therefore to break that loop we just ignore guest permissions.
|
1615
|
+
return [];
|
1616
|
+
}
|
1617
|
+
return await getGuestPermissions();
|
1618
|
+
})();
|
1662
1619
|
|
1663
1620
|
let actorPermissions: string[] = [];
|
1664
1621
|
const addActorPermissions = (actorId: number, perms: string[]) => {
|
@@ -1679,8 +1636,7 @@ export const addPermissions = async (
|
|
1679
1636
|
req: PermissionReq,
|
1680
1637
|
request: ODataRequest & { permissionType?: PermissionCheck },
|
1681
1638
|
): Promise<void> => {
|
1682
|
-
const { resourceName, odataQuery, odataBinds } = request;
|
1683
|
-
const vocabulary = _.last(request.translateVersions)!;
|
1639
|
+
const { vocabulary, resourceName, odataQuery, odataBinds } = request;
|
1684
1640
|
let abstractSqlModel = sbvrUtils.getAbstractSqlModel(request);
|
1685
1641
|
|
1686
1642
|
let { permissionType } = request;
|
@@ -1803,10 +1759,6 @@ export const setup = () => {
|
|
1803
1759
|
0,
|
1804
1760
|
-'#canAccess'.length,
|
1805
1761
|
);
|
1806
|
-
request.originalResourceName = request.originalResourceName.slice(
|
1807
|
-
0,
|
1808
|
-
-'#canAccess'.length,
|
1809
|
-
);
|
1810
1762
|
const resourceName = sbvrUtils.resolveSynonym(request);
|
1811
1763
|
const resourceTable = abstractSqlModel.tables[resourceName];
|
1812
1764
|
if (resourceTable == null) {
|