@balena/pinejs 15.0.0-build-renovate-node-20-x-74a72cbe8a4965e32762ac3eb5a13a9720d8740c-1 → 15.0.0-build-15-x-cefa3a481a667c40b8e5dcacdc5699b37977b8d4-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 +362 -5
- package/CHANGELOG.md +78 -2
- package/Dockerfile +1 -1
- package/build/browser.ts +0 -1
- package/out/config-loader/config-loader.js +3 -6
- 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 +14 -22
- 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 +7 -9
- package/out/migrator/utils.js +44 -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.d.ts +2 -1
- package/out/sbvr-api/abstract-sql.js +2 -3
- 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 +0 -3
- package/out/sbvr-api/hooks.js +4 -5
- 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 +23 -64
- package/out/sbvr-api/permissions.js.map +1 -1
- package/out/sbvr-api/sbvr-utils.d.ts +4 -4
- package/out/sbvr-api/sbvr-utils.js +47 -32
- package/out/sbvr-api/sbvr-utils.js.map +1 -1
- package/out/sbvr-api/translations.js +3 -4
- package/out/sbvr-api/translations.js.map +1 -1
- package/out/sbvr-api/uri-parser.d.ts +2 -1
- package/out/sbvr-api/uri-parser.js +4 -9
- package/out/sbvr-api/uri-parser.js.map +1 -1
- package/package.json +10 -10
- 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/async.ts +1 -5
- package/src/migrator/utils.ts +48 -33
- package/src/pinejs-session-store/pinejs-session-store.ts +15 -0
- package/src/sbvr-api/hooks.ts +0 -2
- package/src/sbvr-api/odata-response.ts +1 -2
- package/src/sbvr-api/permissions.ts +17 -59
- package/src/sbvr-api/sbvr-utils.ts +41 -19
- package/tsconfig.json +1 -1
package/package.json
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
{
|
2
2
|
"name": "@balena/pinejs",
|
3
|
-
"version": "15.0.0-build-
|
3
|
+
"version": "15.0.0-build-15-x-cefa3a481a667c40b8e5dcacdc5699b37977b8d4-1",
|
4
4
|
"main": "out/server-glue/module",
|
5
5
|
"repository": "git@github.com:balena-io/pinejs.git",
|
6
6
|
"license": "Apache-2.0",
|
@@ -24,14 +24,14 @@
|
|
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": "
|
28
|
-
"@balena/abstract-sql-to-typescript": "
|
27
|
+
"@balena/abstract-sql-compiler": "9.0.0-build-8-x-374bc5bd42c3caac5c68608f802302674a511c23-1",
|
28
|
+
"@balena/abstract-sql-to-typescript": "2.0.0-build-2-x-b82285a22630389083930647580e89c60c657474-1",
|
29
29
|
"@balena/env-parsing": "^1.1.5",
|
30
30
|
"@balena/lf-to-abstract-sql": "^5.0.0",
|
31
|
-
"@balena/odata-parser": "^
|
32
|
-
"@balena/odata-to-abstract-sql": "^
|
31
|
+
"@balena/odata-parser": "^3.0.0",
|
32
|
+
"@balena/odata-to-abstract-sql": "^6.0.1",
|
33
33
|
"@balena/sbvr-parser": "^1.4.3",
|
34
|
-
"@balena/sbvr-types": "^
|
34
|
+
"@balena/sbvr-types": "^4.0.0",
|
35
35
|
"@types/body-parser": "^1.19.2",
|
36
36
|
"@types/compression": "^1.7.2",
|
37
37
|
"@types/cookie-parser": "^1.4.3",
|
@@ -71,7 +71,7 @@
|
|
71
71
|
"@types/terser-webpack-plugin": "^5.2.0",
|
72
72
|
"@types/webpack": "^5.28.1",
|
73
73
|
"chai": "^4.3.7",
|
74
|
-
"grunt": "1.6.1",
|
74
|
+
"grunt": "^1.6.1",
|
75
75
|
"grunt-check-dependencies": "^1.0.0",
|
76
76
|
"grunt-cli": "^1.4.3",
|
77
77
|
"grunt-contrib-clean": "^2.0.1",
|
@@ -113,8 +113,8 @@
|
|
113
113
|
"serve-static": "^1.15.0"
|
114
114
|
},
|
115
115
|
"engines": {
|
116
|
-
"node": ">=
|
117
|
-
"npm": ">=
|
116
|
+
"node": ">=16.13.0",
|
117
|
+
"npm": ">=8.0.0"
|
118
118
|
},
|
119
119
|
"lint-staged": {
|
120
120
|
"*.js": [
|
@@ -134,6 +134,6 @@
|
|
134
134
|
"recursive": true
|
135
135
|
},
|
136
136
|
"versionist": {
|
137
|
-
"publishedAt": "2023-04-
|
137
|
+
"publishedAt": "2023-04-28T12:30:55.297Z"
|
138
138
|
}
|
139
139
|
}
|
package/src/config-loader/env.ts
CHANGED
@@ -1,12 +1,8 @@
|
|
1
|
-
|
2
|
-
const { DEBUG: globalDebug, PINEJS_DEBUG } = process.env;
|
1
|
+
const { PINEJS_DEBUG } = process.env;
|
3
2
|
if (![undefined, '', '0', '1'].includes(PINEJS_DEBUG)) {
|
4
|
-
|
5
|
-
console.warn(`Invalid value for PINEJS_DEBUG '${PINEJS_DEBUG}'`);
|
3
|
+
throw new Error(`Invalid value for PINEJS_DEBUG '${PINEJS_DEBUG}'`);
|
6
4
|
}
|
7
|
-
|
8
|
-
export const DEBUG =
|
9
|
-
PINEJS_DEBUG === '1' || (PINEJS_DEBUG !== '0' && !!globalDebug);
|
5
|
+
export const DEBUG = PINEJS_DEBUG === '1';
|
10
6
|
|
11
7
|
type CacheFnOpts<T extends (...args: any[]) => any> =
|
12
8
|
| {
|
@@ -47,6 +47,7 @@ const serverIsOnAir = async (_req, _res, next) => {
|
|
47
47
|
}
|
48
48
|
};
|
49
49
|
|
50
|
+
/** @type {import('../config-loader/config-loader').Config} */
|
50
51
|
export let config = {
|
51
52
|
models: [
|
52
53
|
{
|
@@ -59,6 +60,21 @@ export let config = {
|
|
59
60
|
ALTER TABLE "textarea"
|
60
61
|
ADD COLUMN IF NOT EXISTS "modified at" TIMESTAMP DEFAULT CURRENT_TIMESTAMP NOT NULL;\
|
61
62
|
`,
|
63
|
+
'15.0.0-data-types': async (tx, sbvrUtils) => {
|
64
|
+
switch (sbvrUtils.db.engine) {
|
65
|
+
case 'mysql':
|
66
|
+
await tx.executeSql(`\
|
67
|
+
ALTER TABLE "textarea"
|
68
|
+
MODIFY "is disabled" BOOLEAN NOT NULL;`);
|
69
|
+
break;
|
70
|
+
case 'postgres':
|
71
|
+
await tx.executeSql(`\
|
72
|
+
ALTER TABLE "textarea"
|
73
|
+
ALTER COLUMN "is disabled" SET DATA TYPE BOOLEAN USING b::BOOLEAN;`);
|
74
|
+
break;
|
75
|
+
// No need to migrate for websql
|
76
|
+
}
|
77
|
+
},
|
62
78
|
},
|
63
79
|
},
|
64
80
|
],
|
@@ -122,7 +138,7 @@ export async function setup(app, sbvrUtils, db) {
|
|
122
138
|
const instance = result[0];
|
123
139
|
await sbvrUtils.executeModel(tx, {
|
124
140
|
apiRoot: instance.is_of__vocabulary,
|
125
|
-
modelText: instance.model_value,
|
141
|
+
modelText: instance.model_value.value,
|
126
142
|
});
|
127
143
|
});
|
128
144
|
await isServerOnAir(true);
|
package/src/database-layer/db.ts
CHANGED
@@ -718,7 +718,7 @@ if (maybeMysql != null) {
|
|
718
718
|
const MYSQL_CHECK_CONSTRAINT_VIOLATION = 'ER_CHECK_CONSTRAINT_VIOLATED';
|
719
719
|
const pool = mysql.createPool(options);
|
720
720
|
pool.on('connection', (db) => {
|
721
|
-
db.query("SET sql_mode='
|
721
|
+
db.query("SET sql_mode='ANSI';");
|
722
722
|
});
|
723
723
|
const getConnectionAsync = () =>
|
724
724
|
fromCallback<Mysql.PoolConnection>((callback) => {
|
@@ -56,9 +56,8 @@ const app = (function () {
|
|
56
56
|
/** @type string */ method,
|
57
57
|
/** @type string */ uri,
|
58
58
|
/** @type {{[key: string]: any}} */ headers,
|
59
|
-
/** @type any */ body,
|
59
|
+
/** @type any */ body = '',
|
60
60
|
) {
|
61
|
-
body ??= '';
|
62
61
|
if (!handlers[method]) {
|
63
62
|
throw [404, null, null];
|
64
63
|
}
|
@@ -106,8 +105,9 @@ const app = (function () {
|
|
106
105
|
resolve([this.statusCode, data, null]);
|
107
106
|
}
|
108
107
|
},
|
109
|
-
sendStatus(
|
110
|
-
statusCode
|
108
|
+
sendStatus(
|
109
|
+
/** @type undefined | number */ statusCode = this.statusCode,
|
110
|
+
) {
|
111
111
|
if (statusCode >= 400) {
|
112
112
|
reject([statusCode, null, null]);
|
113
113
|
} else {
|
@@ -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/async.ts
CHANGED
@@ -136,17 +136,13 @@ const $run = async (
|
|
136
136
|
migration.asyncBatchSize || migratorEnv.asyncMigrationDefaultBatchSize;
|
137
137
|
if (migration.asyncFn && typeof migration.asyncFn === 'function') {
|
138
138
|
asyncRunnerMigratorFn = async (tx: Tx) => {
|
139
|
-
|
139
|
+
return await migration.asyncFn(
|
140
140
|
tx,
|
141
141
|
{
|
142
142
|
batchSize,
|
143
143
|
},
|
144
144
|
sbvrUtils,
|
145
145
|
);
|
146
|
-
if (typeof result === 'number') {
|
147
|
-
return result;
|
148
|
-
}
|
149
|
-
return result.rowsAffected;
|
150
146
|
};
|
151
147
|
} else if (migration.asyncSql && typeof migration.asyncSql === 'string') {
|
152
148
|
const asyncMigrationSqlStatement = migration.asyncSql?.replace(
|
package/src/migrator/utils.ts
CHANGED
@@ -12,6 +12,29 @@ 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
|
+
await tx.executeSql(`\
|
23
|
+
ALTER TABLE "migration status"
|
24
|
+
MODIFY "is backing off" JSON NOT NULL;`);
|
25
|
+
break;
|
26
|
+
case 'postgres':
|
27
|
+
await tx.executeSql(`\
|
28
|
+
ALTER TABLE "migration"
|
29
|
+
ALTER COLUMN "executed migrations" SET DATA TYPE JSONB USING b::JSONB;`);
|
30
|
+
await tx.executeSql(`\
|
31
|
+
ALTER TABLE "migration status"
|
32
|
+
ALTER COLUMN "is backing off" SET DATA TYPE JSONB USING b::JSONB;`);
|
33
|
+
break;
|
34
|
+
// No need to migrate for websql
|
35
|
+
}
|
36
|
+
},
|
37
|
+
};
|
15
38
|
|
16
39
|
import * as sbvrUtils from '../sbvr-api/sbvr-utils';
|
17
40
|
export enum MigrationCategories {
|
@@ -29,22 +52,11 @@ export type MigrationFn = (tx: Tx, sbvrUtils: SbvrUtils) => Resolvable<void>;
|
|
29
52
|
export type RunnableMigrations = { [key: string]: Migration };
|
30
53
|
export type RunnableAsyncMigrations = { [key: string]: AsyncMigration };
|
31
54
|
export type Migrations = CategorizedMigrations | RunnableMigrations;
|
32
|
-
export type AsyncMigrationFn =
|
33
|
-
| ((
|
34
|
-
tx: Tx,
|
35
|
-
options: { batchSize: number },
|
36
|
-
sbvrUtils: SbvrUtils,
|
37
|
-
) => Resolvable<number>)
|
38
|
-
| DeprecatedAsyncMigrationFn;
|
39
|
-
|
40
|
-
/**
|
41
|
-
* @deprecated
|
42
|
-
*/
|
43
|
-
type DeprecatedAsyncMigrationFn = (
|
55
|
+
export type AsyncMigrationFn = (
|
44
56
|
tx: Tx,
|
45
57
|
options: { batchSize: number },
|
46
58
|
sbvrUtils: SbvrUtils,
|
47
|
-
) => Resolvable<
|
59
|
+
) => Resolvable<number>;
|
48
60
|
|
49
61
|
type AddFn<T extends {}, x extends 'sync' | 'async'> = T & {
|
50
62
|
[key in `${x}Fn`]: key extends 'syncFn' ? MigrationFn : AsyncMigrationFn;
|
@@ -89,14 +101,14 @@ export function isSyncMigration(
|
|
89
101
|
return typeof migration === 'function' || typeof migration === 'string';
|
90
102
|
}
|
91
103
|
export function areCategorizedMigrations(
|
92
|
-
migrations: Migrations,
|
93
|
-
): migrations is CategorizedMigrations {
|
104
|
+
$migrations: Migrations,
|
105
|
+
): $migrations is CategorizedMigrations {
|
94
106
|
const containsCategories = Object.keys(MigrationCategories).some(
|
95
|
-
(key) => key in migrations,
|
107
|
+
(key) => key in $migrations,
|
96
108
|
);
|
97
109
|
if (
|
98
110
|
containsCategories &&
|
99
|
-
Object.keys(migrations).some((key) => !(key in MigrationCategories))
|
111
|
+
Object.keys($migrations).some((key) => !(key in MigrationCategories))
|
100
112
|
) {
|
101
113
|
throw new Error(
|
102
114
|
'Mixing categorized and uncategorized migrations is not supported',
|
@@ -120,31 +132,31 @@ export type MigrationStatus = {
|
|
120
132
|
};
|
121
133
|
|
122
134
|
export const getRunnableAsyncMigrations = (
|
123
|
-
migrations: Migrations,
|
135
|
+
$migrations: Migrations,
|
124
136
|
): RunnableAsyncMigrations | undefined => {
|
125
|
-
if (migrations[MigrationCategories.async]) {
|
137
|
+
if ($migrations[MigrationCategories.async]) {
|
126
138
|
if (
|
127
|
-
Object.values(migrations[MigrationCategories.async]).some(
|
139
|
+
Object.values($migrations[MigrationCategories.async]).some(
|
128
140
|
(migration) => !isAsyncMigration(migration),
|
129
141
|
) ||
|
130
|
-
typeof migrations[MigrationCategories.async] !== 'object'
|
142
|
+
typeof $migrations[MigrationCategories.async] !== 'object'
|
131
143
|
) {
|
132
144
|
throw new Error(
|
133
145
|
`All loaded async migrations need to be of type: ${MigrationCategories.async}`,
|
134
146
|
);
|
135
147
|
}
|
136
|
-
return migrations[MigrationCategories.async] as RunnableAsyncMigrations;
|
148
|
+
return $migrations[MigrationCategories.async] as RunnableAsyncMigrations;
|
137
149
|
}
|
138
150
|
};
|
139
151
|
|
140
152
|
// migration loader should either get migrations from model
|
141
153
|
// or from the filepath
|
142
154
|
export const getRunnableSyncMigrations = (
|
143
|
-
migrations: Migrations,
|
155
|
+
$migrations: Migrations,
|
144
156
|
): RunnableMigrations => {
|
145
|
-
if (areCategorizedMigrations(migrations)) {
|
157
|
+
if (areCategorizedMigrations($migrations)) {
|
146
158
|
const runnableMigrations: RunnableMigrations = {};
|
147
|
-
for (const [category, categoryMigrations] of Object.entries(migrations)) {
|
159
|
+
for (const [category, categoryMigrations] of Object.entries($migrations)) {
|
148
160
|
if (category in MigrationCategories) {
|
149
161
|
for (const [key, migration] of Object.entries(
|
150
162
|
categoryMigrations as Migrations,
|
@@ -161,16 +173,16 @@ export const getRunnableSyncMigrations = (
|
|
161
173
|
}
|
162
174
|
return runnableMigrations;
|
163
175
|
}
|
164
|
-
return migrations;
|
176
|
+
return $migrations;
|
165
177
|
};
|
166
178
|
|
167
179
|
// turns {"key1": migration, "key3": migration, "key2": migration}
|
168
180
|
// into [["key1", migration], ["key2", migration], ["key3", migration]]
|
169
181
|
export const filterAndSortPendingMigrations = (
|
170
|
-
migrations: NonNullable<RunnableMigrations | RunnableAsyncMigrations>,
|
182
|
+
$migrations: NonNullable<RunnableMigrations | RunnableAsyncMigrations>,
|
171
183
|
executedMigrations: string[],
|
172
184
|
): MigrationTuple[] =>
|
173
|
-
(_(migrations).omit(executedMigrations) as _.Object<typeof migrations>)
|
185
|
+
(_($migrations).omit(executedMigrations) as _.Object<typeof $migrations>)
|
174
186
|
.toPairs()
|
175
187
|
.sortBy(([migrationKey]) => migrationKey)
|
176
188
|
.value();
|
@@ -329,8 +341,10 @@ WHERE "migration"."model name" = ${1}`,
|
|
329
341
|
if (data == null) {
|
330
342
|
return [];
|
331
343
|
}
|
332
|
-
|
333
|
-
|
344
|
+
if (typeof data.executed_migrations === 'string') {
|
345
|
+
return JSON.parse(data.executed_migrations);
|
346
|
+
}
|
347
|
+
return data.executed_migrations;
|
334
348
|
};
|
335
349
|
|
336
350
|
export const migrationTablesExist = async (tx: Tx) => {
|
@@ -354,7 +368,7 @@ WHERE NOT EXISTS (SELECT 1 FROM "migration status" WHERE "migration key" = ${5})
|
|
354
368
|
[
|
355
369
|
migrationStatus['migration_key'],
|
356
370
|
migrationStatus['start_time'],
|
357
|
-
migrationStatus['is_backing_off']
|
371
|
+
migrationStatus['is_backing_off'],
|
358
372
|
migrationStatus['run_count'],
|
359
373
|
migrationStatus['migration_key'],
|
360
374
|
],
|
@@ -388,7 +402,7 @@ WHERE "migration status"."migration key" = ${7};`,
|
|
388
402
|
migrationStatus['migrated_row_count'],
|
389
403
|
migrationStatus['error_count'],
|
390
404
|
migrationStatus['converged_time'],
|
391
|
-
migrationStatus['is_backing_off']
|
405
|
+
migrationStatus['is_backing_off'],
|
392
406
|
migrationStatus['migration_key'],
|
393
407
|
],
|
394
408
|
);
|
@@ -425,7 +439,8 @@ LIMIT 1;`,
|
|
425
439
|
migrated_row_count: data['migrated row count'],
|
426
440
|
error_count: data['error count'],
|
427
441
|
converged_time: data['converged time'],
|
428
|
-
is_backing_off:
|
442
|
+
is_backing_off:
|
443
|
+
data['is backing off'] === true || data['is backing off'] === 1,
|
429
444
|
};
|
430
445
|
} catch (err: any) {
|
431
446
|
// we report any error here, as no error should happen at all
|
@@ -172,6 +172,21 @@ export class PinejsSessionStore extends Store {
|
|
172
172
|
ALTER TABLE "session"
|
173
173
|
ADD COLUMN IF NOT EXISTS "modified at" TIMESTAMP DEFAULT CURRENT_TIMESTAMP NOT NULL;
|
174
174
|
`,
|
175
|
+
'15.0.0-data-types': async (tx, sbvrUtils) => {
|
176
|
+
switch (sbvrUtils.db.engine) {
|
177
|
+
case 'mysql':
|
178
|
+
await tx.executeSql(`\
|
179
|
+
ALTER TABLE "session"
|
180
|
+
MODIFY "data" JSON NOT NULL;`);
|
181
|
+
break;
|
182
|
+
case 'postgres':
|
183
|
+
await tx.executeSql(`\
|
184
|
+
ALTER TABLE "session"
|
185
|
+
ALTER COLUMN "data" SET DATA TYPE JSONB USING b::JSONB;`);
|
186
|
+
break;
|
187
|
+
// No need to migrate for websql
|
188
|
+
}
|
189
|
+
},
|
175
190
|
},
|
176
191
|
},
|
177
192
|
],
|
package/src/sbvr-api/hooks.ts
CHANGED
@@ -51,8 +51,6 @@ export interface Hooks {
|
|
51
51
|
result: any;
|
52
52
|
/** This can be mutated to modify the response sent to the client */
|
53
53
|
response: Response;
|
54
|
-
/** @deprecated Use the response object instead */
|
55
|
-
data?: any;
|
56
54
|
},
|
57
55
|
) => HookResponse;
|
58
56
|
/** These are run in reverse translation order from newest to oldest */
|
@@ -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
|
}
|
@@ -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 {
|
@@ -1449,26 +1447,13 @@ const checkApiKey = async (
|
|
1449
1447
|
apiKey: string,
|
1450
1448
|
tx?: Tx,
|
1451
1449
|
): Promise<PermissionReq['apiKey']> => {
|
1452
|
-
|
1453
|
-
|
1454
|
-
|
1455
|
-
} catch (err: any) {
|
1456
|
-
console.warn('Error with API key:', err);
|
1457
|
-
// Ignore errors getting the api key and just use an empty permissions object.
|
1458
|
-
permissions = [];
|
1459
|
-
}
|
1460
|
-
let actor;
|
1461
|
-
if (permissions.length > 0) {
|
1462
|
-
actor = await getApiKeyActorId(apiKey, tx);
|
1463
|
-
}
|
1464
|
-
const resolvedApiKey: PermissionReq['apiKey'] = {
|
1450
|
+
const permissions = await getApiKeyPermissions(apiKey, tx);
|
1451
|
+
const actor = await getApiKeyActorId(apiKey, tx);
|
1452
|
+
return {
|
1465
1453
|
key: apiKey,
|
1466
1454
|
permissions,
|
1455
|
+
actor,
|
1467
1456
|
};
|
1468
|
-
if (actor != null) {
|
1469
|
-
resolvedApiKey.actor = actor;
|
1470
|
-
}
|
1471
|
-
return resolvedApiKey;
|
1472
1457
|
};
|
1473
1458
|
|
1474
1459
|
export const resolveAuthHeader = async (
|
@@ -1477,11 +1462,6 @@ export const resolveAuthHeader = async (
|
|
1477
1462
|
// TODO: Consider making tx the second argument in the next major
|
1478
1463
|
tx?: Tx,
|
1479
1464
|
): Promise<PermissionReq['apiKey']> => {
|
1480
|
-
// TODO-MAJOR: remove this check
|
1481
|
-
if (req.apiKey != null) {
|
1482
|
-
return;
|
1483
|
-
}
|
1484
|
-
|
1485
1465
|
const auth = req.header('Authorization');
|
1486
1466
|
if (!auth) {
|
1487
1467
|
return;
|
@@ -1527,11 +1507,6 @@ export const resolveApiKey = async (
|
|
1527
1507
|
// TODO: Consider making tx the second argument in the next major
|
1528
1508
|
tx?: Tx,
|
1529
1509
|
): Promise<PermissionReq['apiKey']> => {
|
1530
|
-
// TODO-MAJOR: remove this check
|
1531
|
-
if (req.apiKey != null) {
|
1532
|
-
return;
|
1533
|
-
}
|
1534
|
-
|
1535
1510
|
const apiKey =
|
1536
1511
|
req.params[paramName] ?? req.body[paramName] ?? req.query[paramName];
|
1537
1512
|
if (apiKey == null) {
|
@@ -1541,9 +1516,6 @@ export const resolveApiKey = async (
|
|
1541
1516
|
};
|
1542
1517
|
|
1543
1518
|
export const customApiKeyMiddleware = (paramName = 'apikey') => {
|
1544
|
-
if (paramName == null) {
|
1545
|
-
paramName = 'apikey';
|
1546
|
-
}
|
1547
1519
|
return async (
|
1548
1520
|
req: HookReq | Express.Request,
|
1549
1521
|
_res?: Express.Response,
|
@@ -1635,32 +1607,18 @@ const getReqPermissions = async (
|
|
1635
1607
|
req: PermissionReq,
|
1636
1608
|
odataBinds: ODataBinds = [] as any as ODataBinds,
|
1637
1609
|
) => {
|
1638
|
-
const
|
1639
|
-
|
1640
|
-
|
1641
|
-
|
1642
|
-
|
1643
|
-
|
1644
|
-
|
1645
|
-
|
1646
|
-
|
1647
|
-
|
1648
|
-
|
1649
|
-
|
1650
|
-
})(),
|
1651
|
-
(async () => {
|
1652
|
-
// TODO: Remove this extra actor ID lookup making actor non-optional and updating open-balena-api.
|
1653
|
-
if (
|
1654
|
-
req.apiKey != null &&
|
1655
|
-
req.apiKey.actor == null &&
|
1656
|
-
req.apiKey.permissions != null &&
|
1657
|
-
req.apiKey.permissions.length > 0
|
1658
|
-
) {
|
1659
|
-
const actorId = await getApiKeyActorId(req.apiKey.key);
|
1660
|
-
req.apiKey!.actor = actorId;
|
1661
|
-
}
|
1662
|
-
})(),
|
1663
|
-
]);
|
1610
|
+
const guestPermissions = await (async () => {
|
1611
|
+
if (
|
1612
|
+
guestPermissionsInitialized === false &&
|
1613
|
+
(req.user === root.user || req.user === rootRead.user)
|
1614
|
+
) {
|
1615
|
+
// In the case that guest permissions are not initialized yet and the query is being made with root permissions
|
1616
|
+
// then we need to bypass `getGuestPermissions` as it will cause an infinite loop back to here.
|
1617
|
+
// Therefore to break that loop we just ignore guest permissions.
|
1618
|
+
return [];
|
1619
|
+
}
|
1620
|
+
return await getGuestPermissions();
|
1621
|
+
})();
|
1664
1622
|
|
1665
1623
|
let actorPermissions: string[] = [];
|
1666
1624
|
const addActorPermissions = (actorId: number, perms: string[]) => {
|
@@ -58,6 +58,7 @@ import {
|
|
58
58
|
UnauthorizedError,
|
59
59
|
} from './errors';
|
60
60
|
import * as uriParser from './uri-parser';
|
61
|
+
export { ODataRequest } from './uri-parser';
|
61
62
|
import {
|
62
63
|
HookReq,
|
63
64
|
HookArgs,
|
@@ -74,8 +75,6 @@ export {
|
|
74
75
|
addPureHook,
|
75
76
|
addSideEffectHook,
|
76
77
|
} from './hooks';
|
77
|
-
// TODO-MAJOR: Remove
|
78
|
-
export type HookRequest = uriParser.ODataRequest;
|
79
78
|
|
80
79
|
import memoizeWeak = require('memoizee/weak');
|
81
80
|
import * as controlFlow from './control-flow';
|
@@ -130,7 +129,7 @@ export interface User extends Actor {
|
|
130
129
|
|
131
130
|
export interface ApiKey extends Actor {
|
132
131
|
key: string;
|
133
|
-
actor
|
132
|
+
actor: number;
|
134
133
|
}
|
135
134
|
|
136
135
|
export interface Response {
|
@@ -682,7 +681,10 @@ export const executeModels = async (
|
|
682
681
|
let uri = '/dev/model';
|
683
682
|
const body: AnyObject = {
|
684
683
|
is_of__vocabulary: model.vocab,
|
685
|
-
model_value:
|
684
|
+
model_value:
|
685
|
+
typeof model[modelType] === 'string'
|
686
|
+
? { value: model[modelType] }
|
687
|
+
: model[modelType],
|
686
688
|
model_type: modelType,
|
687
689
|
};
|
688
690
|
const id = result?.[0]?.id;
|
@@ -1624,7 +1626,7 @@ const runQuery = async (
|
|
1624
1626
|
tx: Db.Tx,
|
1625
1627
|
request: uriParser.ODataRequest,
|
1626
1628
|
queryIndex?: number,
|
1627
|
-
|
1629
|
+
addReturning: boolean = false,
|
1628
1630
|
): Promise<Db.Result> => {
|
1629
1631
|
const { vocabulary } = request;
|
1630
1632
|
let { sqlQuery } = request;
|
@@ -1653,7 +1655,10 @@ const runQuery = async (
|
|
1653
1655
|
api[vocabulary].logger.log(query, values);
|
1654
1656
|
}
|
1655
1657
|
|
1656
|
-
//
|
1658
|
+
// We only add the returning clause if it's been requested and `affectedIds` hasn't been populated yet
|
1659
|
+
const returningIdField =
|
1660
|
+
addReturning && request.affectedIds == null ? getIdField(request) : false;
|
1661
|
+
|
1657
1662
|
const sqlResult = await tx.executeSql(query, values, returningIdField);
|
1658
1663
|
|
1659
1664
|
if (returningIdField) {
|
@@ -1704,7 +1709,6 @@ const respondGet = async (
|
|
1704
1709
|
request,
|
1705
1710
|
result,
|
1706
1711
|
response,
|
1707
|
-
data: d,
|
1708
1712
|
tx,
|
1709
1713
|
});
|
1710
1714
|
return response;
|
@@ -1733,7 +1737,7 @@ const runPost = async (
|
|
1733
1737
|
tx,
|
1734
1738
|
request,
|
1735
1739
|
undefined,
|
1736
|
-
|
1740
|
+
true,
|
1737
1741
|
);
|
1738
1742
|
if (rowsAffected === 0) {
|
1739
1743
|
throw new PermissionError();
|
@@ -1797,19 +1801,17 @@ const runPut = async (
|
|
1797
1801
|
request: uriParser.ODataRequest,
|
1798
1802
|
tx: Db.Tx,
|
1799
1803
|
): Promise<undefined> => {
|
1800
|
-
const idField = getIdField(request);
|
1801
|
-
|
1802
1804
|
let rowsAffected: number;
|
1803
1805
|
// If request.sqlQuery is an array it means it's an UPSERT, ie two queries: [InsertQuery, UpdateQuery]
|
1804
1806
|
if (Array.isArray(request.sqlQuery)) {
|
1805
1807
|
// Run the update query first
|
1806
|
-
({ rowsAffected } = await runQuery(tx, request, 1,
|
1808
|
+
({ rowsAffected } = await runQuery(tx, request, 1, true));
|
1807
1809
|
if (rowsAffected === 0) {
|
1808
1810
|
// Then run the insert query if nothing was updated
|
1809
|
-
({ rowsAffected } = await runQuery(tx, request, 0,
|
1811
|
+
({ rowsAffected } = await runQuery(tx, request, 0, true));
|
1810
1812
|
}
|
1811
1813
|
} else {
|
1812
|
-
({ rowsAffected } = await runQuery(tx, request, undefined,
|
1814
|
+
({ rowsAffected } = await runQuery(tx, request, undefined, true));
|
1813
1815
|
}
|
1814
1816
|
if (rowsAffected > 0) {
|
1815
1817
|
await validateModel(tx, _.last(request.translateVersions)!, request);
|
@@ -1843,12 +1845,7 @@ const runDelete = async (
|
|
1843
1845
|
request: uriParser.ODataRequest,
|
1844
1846
|
tx: Db.Tx,
|
1845
1847
|
): Promise<undefined> => {
|
1846
|
-
const { rowsAffected } = await runQuery(
|
1847
|
-
tx,
|
1848
|
-
request,
|
1849
|
-
undefined,
|
1850
|
-
getIdField(request),
|
1851
|
-
);
|
1848
|
+
const { rowsAffected } = await runQuery(tx, request, undefined, true);
|
1852
1849
|
if (rowsAffected > 0) {
|
1853
1850
|
await validateModel(tx, _.last(request.translateVersions)!, request);
|
1854
1851
|
}
|
@@ -1870,6 +1867,31 @@ export const executeStandardModels = async (tx: Db.Tx): Promise<void> => {
|
|
1870
1867
|
ALTER TABLE "model"
|
1871
1868
|
ADD COLUMN IF NOT EXISTS "modified at" TIMESTAMP DEFAULT CURRENT_TIMESTAMP NOT NULL;
|
1872
1869
|
`,
|
1870
|
+
'15.0.0-data-types': async ($tx, sbvrUtils) => {
|
1871
|
+
switch (sbvrUtils.db.engine) {
|
1872
|
+
case 'mysql':
|
1873
|
+
await $tx.executeSql(`\
|
1874
|
+
ALTER TABLE "model"
|
1875
|
+
MODIFY "model value" JSON NOT NULL;
|
1876
|
+
|
1877
|
+
UPDATE "model"
|
1878
|
+
SET "model value" = CAST('{"value":' || CAST("model value" AS CHAR) || '}' AS JSON)
|
1879
|
+
WHERE "model type" IN ('se', 'odataMetadata')
|
1880
|
+
AND CAST("model value" AS CHAR) LIKE '"%';`);
|
1881
|
+
break;
|
1882
|
+
case 'postgres':
|
1883
|
+
await $tx.executeSql(`\
|
1884
|
+
ALTER TABLE "model"
|
1885
|
+
ALTER COLUMN "model value" SET DATA TYPE JSONB USING "model value"::JSONB;
|
1886
|
+
|
1887
|
+
UPDATE "model"
|
1888
|
+
SET "model value" = CAST('{"value":' || CAST("model value" AS TEXT) || '}' AS JSON)
|
1889
|
+
WHERE "model type" IN ('se', 'odataMetadata')
|
1890
|
+
AND CAST("model value" AS TEXT) LIKE '"%';`);
|
1891
|
+
break;
|
1892
|
+
// No need to migrate for websql
|
1893
|
+
}
|
1894
|
+
},
|
1873
1895
|
},
|
1874
1896
|
});
|
1875
1897
|
await executeModels(tx, permissions.config.models);
|