@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.
Files changed (62) hide show
  1. package/.pinejs-cache.json +1 -1
  2. package/.versionbot/CHANGELOG.yml +362 -5
  3. package/CHANGELOG.md +78 -2
  4. package/Dockerfile +1 -1
  5. package/build/browser.ts +0 -1
  6. package/out/config-loader/config-loader.js +3 -6
  7. package/out/config-loader/config-loader.js.map +1 -1
  8. package/out/config-loader/env.js +4 -4
  9. package/out/config-loader/env.js.map +1 -1
  10. package/out/data-server/sbvr-server.d.ts +1 -13
  11. package/out/data-server/sbvr-server.js +17 -1
  12. package/out/data-server/sbvr-server.js.map +1 -1
  13. package/out/database-layer/db.js +12 -15
  14. package/out/database-layer/db.js.map +1 -1
  15. package/out/express-emulator/express.js +4 -4
  16. package/out/express-emulator/express.js.map +1 -1
  17. package/out/http-transactions/transactions.d.ts +1 -12
  18. package/out/http-transactions/transactions.js +18 -0
  19. package/out/http-transactions/transactions.js.map +1 -1
  20. package/out/migrator/async.js +14 -22
  21. package/out/migrator/async.js.map +1 -1
  22. package/out/migrator/sync.js +6 -12
  23. package/out/migrator/sync.js.map +1 -1
  24. package/out/migrator/utils.d.ts +7 -9
  25. package/out/migrator/utils.js +44 -20
  26. package/out/migrator/utils.js.map +1 -1
  27. package/out/pinejs-session-store/pinejs-session-store.js +18 -3
  28. package/out/pinejs-session-store/pinejs-session-store.js.map +1 -1
  29. package/out/sbvr-api/abstract-sql.d.ts +2 -1
  30. package/out/sbvr-api/abstract-sql.js +2 -3
  31. package/out/sbvr-api/abstract-sql.js.map +1 -1
  32. package/out/sbvr-api/cached-compile.js +1 -1
  33. package/out/sbvr-api/cached-compile.js.map +1 -1
  34. package/out/sbvr-api/hooks.d.ts +0 -3
  35. package/out/sbvr-api/hooks.js +4 -5
  36. package/out/sbvr-api/hooks.js.map +1 -1
  37. package/out/sbvr-api/odata-response.js +4 -5
  38. package/out/sbvr-api/odata-response.js.map +1 -1
  39. package/out/sbvr-api/permissions.js +23 -64
  40. package/out/sbvr-api/permissions.js.map +1 -1
  41. package/out/sbvr-api/sbvr-utils.d.ts +4 -4
  42. package/out/sbvr-api/sbvr-utils.js +47 -32
  43. package/out/sbvr-api/sbvr-utils.js.map +1 -1
  44. package/out/sbvr-api/translations.js +3 -4
  45. package/out/sbvr-api/translations.js.map +1 -1
  46. package/out/sbvr-api/uri-parser.d.ts +2 -1
  47. package/out/sbvr-api/uri-parser.js +4 -9
  48. package/out/sbvr-api/uri-parser.js.map +1 -1
  49. package/package.json +10 -10
  50. package/src/config-loader/env.ts +3 -7
  51. package/src/data-server/sbvr-server.js +17 -1
  52. package/src/database-layer/db.ts +1 -1
  53. package/src/express-emulator/express.js +4 -4
  54. package/src/http-transactions/transactions.js +18 -0
  55. package/src/migrator/async.ts +1 -5
  56. package/src/migrator/utils.ts +48 -33
  57. package/src/pinejs-session-store/pinejs-session-store.ts +15 -0
  58. package/src/sbvr-api/hooks.ts +0 -2
  59. package/src/sbvr-api/odata-response.ts +1 -2
  60. package/src/sbvr-api/permissions.ts +17 -59
  61. package/src/sbvr-api/sbvr-utils.ts +41 -19
  62. 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-renovate-node-20-x-74a72cbe8a4965e32762ac3eb5a13a9720d8740c-1",
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": "^8.5.0",
28
- "@balena/abstract-sql-to-typescript": "^1.5.1",
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": "^2.5.0",
32
- "@balena/odata-to-abstract-sql": "^5.9.6",
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": "^3.5.0",
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": ">=12.0.0",
117
- "npm": ">=6.0.0"
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-27T16:06:39.592Z"
137
+ "publishedAt": "2023-04-28T12:30:55.297Z"
138
138
  }
139
139
  }
@@ -1,12 +1,8 @@
1
- // TODO-MAJOR: Drop the support for the global `DEBUG` env var
2
- const { DEBUG: globalDebug, PINEJS_DEBUG } = process.env;
1
+ const { PINEJS_DEBUG } = process.env;
3
2
  if (![undefined, '', '0', '1'].includes(PINEJS_DEBUG)) {
4
- // TODO-MAJOR: Throw on invalid value
5
- console.warn(`Invalid value for PINEJS_DEBUG '${PINEJS_DEBUG}'`);
3
+ throw new Error(`Invalid value for PINEJS_DEBUG '${PINEJS_DEBUG}'`);
6
4
  }
7
- // Setting PINEJS_DEBUG to explicitly '0' will disable debug even if global debug is truthy
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);
@@ -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='ANSI_QUOTES';");
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(/** @type undefined | number */ statusCode) {
110
- statusCode ??= this.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
  ],
@@ -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
- const result = await migration.asyncFn(
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(
@@ -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<Result>;
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
- return JSON.parse(data.executed_migrations) as string[];
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'] ? 1 : 0,
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'] ? 1 : 0,
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: data['is backing off'] === 1,
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
  ],
@@ -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
- // TODO-MAJOR: This should also include ConceptType fields
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
- // this should be a boolean expression, but needs to be a subquery in case it
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
- let permissions: string[];
1453
- try {
1454
- permissions = await getApiKeyPermissions(apiKey, tx);
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 [guestPermissions] = await Promise.all([
1639
- (async () => {
1640
- if (
1641
- guestPermissionsInitialized === false &&
1642
- (req.user === root.user || req.user === rootRead.user)
1643
- ) {
1644
- // In the case that guest permissions are not initialized yet and the query is being made with root permissions
1645
- // then we need to bypass `getGuestPermissions` as it will cause an infinite loop back to here.
1646
- // Therefore to break that loop we just ignore guest permissions.
1647
- return [];
1648
- }
1649
- return await getGuestPermissions();
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?: number;
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: model[modelType],
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
- returningIdField?: string,
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
- // TODO-MAJOR: Omit the returning clause altogether if `affectedIds` has already been populated
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
- getIdField(request),
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, idField));
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, idField));
1811
+ ({ rowsAffected } = await runQuery(tx, request, 0, true));
1810
1812
  }
1811
1813
  } else {
1812
- ({ rowsAffected } = await runQuery(tx, request, undefined, idField));
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);