@takaro/db 0.0.9 → 0.0.11

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/knex.d.ts CHANGED
@@ -15,5 +15,6 @@ export declare function getKnexOptions(extra?: Record<string, unknown>): {
15
15
  };
16
16
  };
17
17
  export declare function getKnex(): Promise<KnexClient>;
18
+ export declare function isDbAvailable(): Promise<boolean>;
18
19
  export declare function disconnectKnex(): Promise<void>;
19
20
  export {};
package/dist/knex.js CHANGED
@@ -25,18 +25,20 @@ export async function getKnex() {
25
25
  log.debug('Missed knex cache, creating new client');
26
26
  const knex = createKnex(getKnexOptions());
27
27
  cachedKnex = knex;
28
- health.registerHook('db', async () => {
29
- try {
30
- await knex.raw('SELECT 1');
31
- return true;
32
- }
33
- catch (error) {
34
- log.error(error);
35
- return false;
36
- }
37
- });
28
+ health.registerHook('db', isDbAvailable);
38
29
  return cachedKnex;
39
30
  }
31
+ export async function isDbAvailable() {
32
+ try {
33
+ const knex = await getKnex();
34
+ await knex.raw('SELECT 1');
35
+ return true;
36
+ }
37
+ catch (error) {
38
+ log.error(error);
39
+ return false;
40
+ }
41
+ }
40
42
  export async function disconnectKnex() {
41
43
  if (!cachedKnex)
42
44
  return;
package/dist/knex.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"knex.js","sourceRoot":"","sources":["../src/knex.ts"],"names":[],"mappings":"AAAA,OAAO,OAAO,MAAM,MAAM,CAAC;AAC3B,OAAO,EAAE,MAAM,EAAE,MAAM,aAAa,CAAC;AACrC,OAAO,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,cAAc,CAAC;AAG9C,MAAM,GAAG,GAAG,MAAM,CAAC,KAAK,CAAC,CAAC;AAE1B,MAAM,EAAE,IAAI,EAAE,UAAU,EAAE,GAAG,OAAO,CAAC;AAIrC,MAAM,UAAU,cAAc,CAAC,QAAiC,EAAE;IAChE,MAAM,IAAI,GAAG;QACX,MAAM,EAAE,IAAI;QACZ,UAAU,EAAE;YACV,IAAI,EAAE,MAAM,CAAC,GAAG,CAAC,eAAe,CAAC;YACjC,IAAI,EAAE,MAAM,CAAC,GAAG,CAAC,eAAe,CAAC;YACjC,IAAI,EAAE,MAAM,CAAC,GAAG,CAAC,eAAe,CAAC;YACjC,QAAQ,EAAE,MAAM,CAAC,GAAG,CAAC,mBAAmB,CAAC;YACzC,QAAQ,EAAE,MAAM,CAAC,GAAG,CAAC,mBAAmB,CAAC;YACzC,GAAG,EAAE,MAAM,CAAC,GAAG,CAAC,cAAc,CAAC,CAAC,CAAC,CAAC,EAAE,EAAE,EAAE,MAAM,CAAC,GAAG,CAAC,aAAa,CAAC,EAAE,CAAC,CAAC,CAAC,KAAK;SAC5E;QACD,GAAG,KAAK;KACT,CAAC;IACF,OAAO,IAAI,CAAC;AACd,CAAC;AAED,IAAI,UAAU,GAAsB,IAAI,CAAC;AAEzC,MAAM,CAAC,KAAK,UAAU,OAAO;IAC3B,IAAI,UAAU;QAAE,OAAO,UAAU,CAAC;IAElC,GAAG,CAAC,KAAK,CAAC,wCAAwC,CAAC,CAAC;IACpD,MAAM,IAAI,GAAG,UAAU,CAAC,cAAc,EAAE,CAAC,CAAC;IAC1C,UAAU,GAAG,IAAI,CAAC;IAElB,MAAM,CAAC,YAAY,CAAC,IAAI,EAAE,KAAK,IAAI,EAAE;QACnC,IAAI,CAAC;YACH,MAAM,IAAI,CAAC,GAAG,CAAC,UAAU,CAAC,CAAC;YAC3B,OAAO,IAAI,CAAC;QACd,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACf,GAAG,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC;YACjB,OAAO,KAAK,CAAC;QACf,CAAC;IACH,CAAC,CAAC,CAAC;IAEH,OAAO,UAAU,CAAC;AACpB,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,cAAc;IAClC,IAAI,CAAC,UAAU;QAAE,OAAO;IACxB,MAAM,UAAU,CAAC,OAAO,EAAE,CAAC;IAC3B,UAAU,GAAG,IAAI,CAAC;IAClB,GAAG,CAAC,IAAI,CAAC,mBAAmB,CAAC,CAAC;AAChC,CAAC"}
1
+ {"version":3,"file":"knex.js","sourceRoot":"","sources":["../src/knex.ts"],"names":[],"mappings":"AAAA,OAAO,OAAO,MAAM,MAAM,CAAC;AAC3B,OAAO,EAAE,MAAM,EAAE,MAAM,aAAa,CAAC;AACrC,OAAO,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,cAAc,CAAC;AAG9C,MAAM,GAAG,GAAG,MAAM,CAAC,KAAK,CAAC,CAAC;AAE1B,MAAM,EAAE,IAAI,EAAE,UAAU,EAAE,GAAG,OAAO,CAAC;AAIrC,MAAM,UAAU,cAAc,CAAC,QAAiC,EAAE;IAChE,MAAM,IAAI,GAAG;QACX,MAAM,EAAE,IAAI;QACZ,UAAU,EAAE;YACV,IAAI,EAAE,MAAM,CAAC,GAAG,CAAC,eAAe,CAAC;YACjC,IAAI,EAAE,MAAM,CAAC,GAAG,CAAC,eAAe,CAAC;YACjC,IAAI,EAAE,MAAM,CAAC,GAAG,CAAC,eAAe,CAAC;YACjC,QAAQ,EAAE,MAAM,CAAC,GAAG,CAAC,mBAAmB,CAAC;YACzC,QAAQ,EAAE,MAAM,CAAC,GAAG,CAAC,mBAAmB,CAAC;YACzC,GAAG,EAAE,MAAM,CAAC,GAAG,CAAC,cAAc,CAAC,CAAC,CAAC,CAAC,EAAE,EAAE,EAAE,MAAM,CAAC,GAAG,CAAC,aAAa,CAAC,EAAE,CAAC,CAAC,CAAC,KAAK;SAC5E;QACD,GAAG,KAAK;KACT,CAAC;IACF,OAAO,IAAI,CAAC;AACd,CAAC;AAED,IAAI,UAAU,GAAsB,IAAI,CAAC;AAEzC,MAAM,CAAC,KAAK,UAAU,OAAO;IAC3B,IAAI,UAAU;QAAE,OAAO,UAAU,CAAC;IAElC,GAAG,CAAC,KAAK,CAAC,wCAAwC,CAAC,CAAC;IACpD,MAAM,IAAI,GAAG,UAAU,CAAC,cAAc,EAAE,CAAC,CAAC;IAC1C,UAAU,GAAG,IAAI,CAAC;IAElB,MAAM,CAAC,YAAY,CAAC,IAAI,EAAE,aAAa,CAAC,CAAC;IAEzC,OAAO,UAAU,CAAC;AACpB,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,aAAa;IACjC,IAAI,CAAC;QACH,MAAM,IAAI,GAAG,MAAM,OAAO,EAAE,CAAC;QAC7B,MAAM,IAAI,CAAC,GAAG,CAAC,UAAU,CAAC,CAAC;QAC3B,OAAO,IAAI,CAAC;IACd,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QACf,GAAG,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC;QACjB,OAAO,KAAK,CAAC;IACf,CAAC;AACH,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,cAAc;IAClC,IAAI,CAAC,UAAU;QAAE,OAAO;IACxB,MAAM,UAAU,CAAC,OAAO,EAAE,CAAC;IAC3B,UAAU,GAAG,IAAI,CAAC;IAClB,GAAG,CAAC,IAAI,CAAC,mBAAmB,CAAC,CAAC;AAChC,CAAC"}
@@ -0,0 +1,3 @@
1
+ import { Knex } from 'knex';
2
+ export declare function up(knex: Knex): Promise<void>;
3
+ export declare function down(knex: Knex): Promise<void>;
@@ -0,0 +1,11 @@
1
+ export async function up(knex) {
2
+ await knex.schema.alterTable('gameservers', (table) => {
3
+ table.boolean('enabled').defaultTo(true);
4
+ });
5
+ }
6
+ export async function down(knex) {
7
+ await knex.schema.alterTable('gameservers', (table) => {
8
+ table.dropColumn('enabled');
9
+ });
10
+ }
11
+ //# sourceMappingURL=20240906125101-gameserver-enable.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"20240906125101-gameserver-enable.js","sourceRoot":"","sources":["../../../src/migrations/sql/20240906125101-gameserver-enable.ts"],"names":[],"mappings":"AAGA,MAAM,CAAC,KAAK,UAAU,EAAE,CAAC,IAAU;IACjC,MAAM,IAAI,CAAC,MAAM,CAAC,UAAU,CAAC,aAAa,EAAE,CAAC,KAAK,EAAE,EAAE;QACpD,KAAK,CAAC,OAAO,CAAC,SAAS,CAAC,CAAC,SAAS,CAAC,IAAI,CAAC,CAAC;IAC3C,CAAC,CAAC,CAAC;AACL,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,IAAI,CAAC,IAAU;IACnC,MAAM,IAAI,CAAC,MAAM,CAAC,UAAU,CAAC,aAAa,EAAE,CAAC,KAAK,EAAE,EAAE;QACpD,KAAK,CAAC,UAAU,CAAC,SAAS,CAAC,CAAC;IAC9B,CAAC,CAAC,CAAC;AACL,CAAC"}
@@ -0,0 +1,3 @@
1
+ import { Knex } from 'knex';
2
+ export declare function up(knex: Knex): Promise<void>;
3
+ export declare function down(knex: Knex): Promise<void>;
@@ -0,0 +1,9 @@
1
+ export async function up(knex) {
2
+ await knex.schema.alterTable('domains', (table) => {
3
+ table.dropUnique(['name'], 'domains_name_unique');
4
+ });
5
+ }
6
+ export async function down(knex) {
7
+ // None, this is a one-way migration
8
+ }
9
+ //# sourceMappingURL=20240908185212-domain-name-not-unique.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"20240908185212-domain-name-not-unique.js","sourceRoot":"","sources":["../../../src/migrations/sql/20240908185212-domain-name-not-unique.ts"],"names":[],"mappings":"AAGA,MAAM,CAAC,KAAK,UAAU,EAAE,CAAC,IAAU;IACjC,MAAM,IAAI,CAAC,MAAM,CAAC,UAAU,CAAC,SAAS,EAAE,CAAC,KAAK,EAAE,EAAE;QAChD,KAAK,CAAC,UAAU,CAAC,CAAC,MAAM,CAAC,EAAE,qBAAqB,CAAC,CAAC;IACpD,CAAC,CAAC,CAAC;AACL,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,IAAI,CAAC,IAAU;IACnC,oCAAoC;AACtC,CAAC"}
@@ -1,10 +1,10 @@
1
1
  import { QueryBuilder as ObjectionQueryBuilder, Model as ObjectionModel, Page } from 'objection';
2
2
  export declare class ITakaroQuery<T> {
3
3
  filters?: {
4
- [key in keyof T]?: unknown[] | unknown;
4
+ [key: string]: unknown[] | string[] | boolean | null | undefined;
5
5
  };
6
6
  search?: {
7
- [key in keyof T]?: unknown[] | unknown;
7
+ [key: string]: unknown[] | string[] | boolean | null | undefined;
8
8
  };
9
9
  greaterThan?: {
10
10
  [key in keyof T]?: unknown;
@@ -14,7 +14,7 @@ export declare class ITakaroQuery<T> {
14
14
  };
15
15
  page?: number;
16
16
  limit?: number;
17
- sortBy?: Extract<keyof T, string>;
17
+ sortBy?: string;
18
18
  sortDirection?: SortDirection;
19
19
  extend?: string[];
20
20
  }
@@ -22,10 +22,17 @@ export declare enum SortDirection {
22
22
  asc = "asc",
23
23
  desc = "desc"
24
24
  }
25
+ export declare function populateModelColumns(): Promise<void>;
25
26
  export declare class QueryBuilder<Model extends ObjectionModel, OutputDTO> {
26
- private readonly query;
27
- constructor(query?: ITakaroQuery<OutputDTO>);
28
- build(query: ObjectionQueryBuilder<Model, Model[]>): ObjectionQueryBuilder<Model, Page<Model>>;
27
+ private query;
28
+ constructor(rawQuery?: ITakaroQuery<OutputDTO>);
29
+ /**
30
+ * Our custom query builder can only handle columns that exist in the table
31
+ * However, we might want to do complex/relational logic in higher layers
32
+ * To make sure we don't error out here, we filter out any columns that don't exist in the current table
33
+ */
34
+ private cleanQueryObject;
35
+ build(queryBuilder: ObjectionQueryBuilder<Model, Model[]>): ObjectionQueryBuilder<Model, Page<Model>>;
29
36
  private filters;
30
37
  private greaterThan;
31
38
  private lessThan;
@@ -8,6 +8,7 @@ var __metadata = (this && this.__metadata) || function (k, v) {
8
8
  if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
9
9
  };
10
10
  import { IsEnum, IsNumber, IsOptional, IsString } from 'class-validator';
11
+ import { getKnex, isDbAvailable } from './knex.js';
11
12
  export class ITakaroQuery {
12
13
  }
13
14
  __decorate([
@@ -39,7 +40,7 @@ __decorate([
39
40
  __decorate([
40
41
  IsOptional(),
41
42
  IsString(),
42
- __metadata("design:type", Object)
43
+ __metadata("design:type", String)
43
44
  ], ITakaroQuery.prototype, "sortBy", void 0);
44
45
  __decorate([
45
46
  IsOptional(),
@@ -57,15 +58,60 @@ export var SortDirection;
57
58
  SortDirection["asc"] = "asc";
58
59
  SortDirection["desc"] = "desc";
59
60
  })(SortDirection || (SortDirection = {}));
61
+ const modelColumns = new Map();
62
+ export async function populateModelColumns() {
63
+ if (await isDbAvailable()) {
64
+ const knex = await getKnex();
65
+ // Find all tables
66
+ const tables = await knex.raw(
67
+ // eslint-disable-next-line
68
+ "SELECT table_name FROM information_schema.tables WHERE table_schema = 'public' AND table_type = 'BASE TABLE'");
69
+ // For each table, store the columns
70
+ for (const table of tables.rows) {
71
+ const tableName = table.table_name;
72
+ const columns = await knex(tableName).columnInfo();
73
+ modelColumns.set(tableName, Object.keys(columns));
74
+ }
75
+ }
76
+ }
77
+ await populateModelColumns();
60
78
  export class QueryBuilder {
61
- constructor(query = new ITakaroQuery()) {
62
- this.query = query;
79
+ constructor(rawQuery = new ITakaroQuery()) {
80
+ this.query = JSON.parse(JSON.stringify(rawQuery));
81
+ }
82
+ /**
83
+ * Our custom query builder can only handle columns that exist in the table
84
+ * However, we might want to do complex/relational logic in higher layers
85
+ * To make sure we don't error out here, we filter out any columns that don't exist in the current table
86
+ */
87
+ cleanQueryObject(tableName) {
88
+ const columns = modelColumns.get(tableName) ?? [];
89
+ for (const key in this.query.filters) {
90
+ if (Object.prototype.hasOwnProperty.call(this.query.filters, key)) {
91
+ if (!columns.includes(key)) {
92
+ if (this.query.filters) {
93
+ this.query.filters[key] = [];
94
+ }
95
+ }
96
+ }
97
+ }
98
+ for (const key in this.query.search) {
99
+ if (Object.prototype.hasOwnProperty.call(this.query.search, key)) {
100
+ if (!columns.includes(key)) {
101
+ if (this.query.search) {
102
+ this.query.search[key] = [];
103
+ }
104
+ }
105
+ }
106
+ }
107
+ return this.query;
63
108
  }
64
- build(query) {
65
- const tableName = query.modelClass().tableName;
109
+ build(queryBuilder) {
110
+ const tableName = queryBuilder.modelClass().tableName;
111
+ this.cleanQueryObject(tableName);
66
112
  const pagination = this.pagination();
67
113
  const sorting = this.sorting();
68
- let qry = query.page(pagination.page, pagination.limit).orderBy(sorting.sortBy, sorting.sortDirection);
114
+ let qry = queryBuilder.page(pagination.page, pagination.limit).orderBy(sorting.sortBy, sorting.sortDirection);
69
115
  qry = this.filters(tableName, qry);
70
116
  qry = this.greaterThan(tableName, qry);
71
117
  qry = this.lessThan(tableName, qry);
@@ -95,7 +141,7 @@ export class QueryBuilder {
95
141
  if (Object.prototype.hasOwnProperty.call(this.query.filters, filter)) {
96
142
  const searchVal = this.query.filters[filter];
97
143
  if (searchVal && Array.isArray(searchVal)) {
98
- if (searchVal.includes(null) || searchVal.includes('null')) {
144
+ if (searchVal.includes('null')) {
99
145
  query.whereNull(`${tableName}.${filter}`);
100
146
  continue;
101
147
  }
@@ -1 +1 @@
1
- {"version":3,"file":"queryBuilder.js","sourceRoot":"","sources":["../src/queryBuilder.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"queryBuilder.js","sourceRoot":"","sources":["../src/queryBuilder.ts"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@takaro/db",
3
- "version": "0.0.9",
3
+ "version": "0.0.11",
4
4
  "description": "An opinionated data layer",
5
5
  "main": "dist/main.js",
6
6
  "types": "dist/main.d.ts",
@@ -18,7 +18,6 @@
18
18
  "license": "ISC",
19
19
  "dependencies": {
20
20
  "convict": "^6.2.3",
21
- "knex": "^3.0.0",
22
21
  "objection": "^3.0.1",
23
22
  "pg": "^8.8.0"
24
23
  },
@@ -1,5 +1,5 @@
1
1
  import { disconnectKnex, getKnex } from '../knex.js';
2
- import { QueryBuilder, SortDirection } from '../queryBuilder.js';
2
+ import { populateModelColumns, QueryBuilder, SortDirection } from '../queryBuilder.js';
3
3
  import { expect } from '@takaro/test';
4
4
  import { TakaroModel } from '../TakaroModel.js';
5
5
  import { Model } from 'objection';
@@ -60,6 +60,8 @@ describe('QueryBuilder', () => {
60
60
 
61
61
  TestUserModel.knex(knex);
62
62
  TestPostModel.knex(knex);
63
+
64
+ await populateModelColumns();
63
65
  });
64
66
 
65
67
  afterEach(async () => {
package/src/knex.ts CHANGED
@@ -34,19 +34,22 @@ export async function getKnex(): Promise<KnexClient> {
34
34
  const knex = createKnex(getKnexOptions());
35
35
  cachedKnex = knex;
36
36
 
37
- health.registerHook('db', async () => {
38
- try {
39
- await knex.raw('SELECT 1');
40
- return true;
41
- } catch (error) {
42
- log.error(error);
43
- return false;
44
- }
45
- });
37
+ health.registerHook('db', isDbAvailable);
46
38
 
47
39
  return cachedKnex;
48
40
  }
49
41
 
42
+ export async function isDbAvailable(): Promise<boolean> {
43
+ try {
44
+ const knex = await getKnex();
45
+ await knex.raw('SELECT 1');
46
+ return true;
47
+ } catch (error) {
48
+ log.error(error);
49
+ return false;
50
+ }
51
+ }
52
+
50
53
  export async function disconnectKnex(): Promise<void> {
51
54
  if (!cachedKnex) return;
52
55
  await cachedKnex.destroy();
@@ -0,0 +1,14 @@
1
+ import ms from 'ms';
2
+ import { Knex } from 'knex';
3
+
4
+ export async function up(knex: Knex): Promise<void> {
5
+ await knex.schema.alterTable('gameservers', (table) => {
6
+ table.boolean('enabled').defaultTo(true);
7
+ });
8
+ }
9
+
10
+ export async function down(knex: Knex): Promise<void> {
11
+ await knex.schema.alterTable('gameservers', (table) => {
12
+ table.dropColumn('enabled');
13
+ });
14
+ }
@@ -0,0 +1,12 @@
1
+ import ms from 'ms';
2
+ import { Knex } from 'knex';
3
+
4
+ export async function up(knex: Knex): Promise<void> {
5
+ await knex.schema.alterTable('domains', (table) => {
6
+ table.dropUnique(['name'], 'domains_name_unique');
7
+ });
8
+ }
9
+
10
+ export async function down(knex: Knex): Promise<void> {
11
+ // None, this is a one-way migration
12
+ }
@@ -7,16 +7,17 @@ import {
7
7
  Expression,
8
8
  PrimitiveValue,
9
9
  } from 'objection';
10
+ import { getKnex, isDbAvailable } from './knex.js';
10
11
 
11
12
  export class ITakaroQuery<T> {
12
13
  @IsOptional()
13
14
  filters?: {
14
- [key in keyof T]?: unknown[] | unknown;
15
+ [key: string]: unknown[] | string[] | boolean | null | undefined;
15
16
  };
16
17
 
17
18
  @IsOptional()
18
19
  search?: {
19
- [key in keyof T]?: unknown[] | unknown;
20
+ [key: string]: unknown[] | string[] | boolean | null | undefined;
20
21
  };
21
22
 
22
23
  @IsOptional()
@@ -39,7 +40,7 @@ export class ITakaroQuery<T> {
39
40
 
40
41
  @IsOptional()
41
42
  @IsString()
42
- sortBy?: Extract<keyof T, string>;
43
+ sortBy?: string;
43
44
 
44
45
  @IsOptional()
45
46
  @IsString()
@@ -56,16 +57,72 @@ export enum SortDirection {
56
57
  desc = 'desc',
57
58
  }
58
59
 
60
+ const modelColumns = new Map<string, string[]>();
61
+
62
+ export async function populateModelColumns() {
63
+ if (await isDbAvailable()) {
64
+ const knex = await getKnex();
65
+ // Find all tables
66
+ const tables = await knex.raw(
67
+ // eslint-disable-next-line
68
+ "SELECT table_name FROM information_schema.tables WHERE table_schema = 'public' AND table_type = 'BASE TABLE'",
69
+ );
70
+ // For each table, store the columns
71
+ for (const table of tables.rows) {
72
+ const tableName = table.table_name;
73
+ const columns = await knex(tableName).columnInfo();
74
+ modelColumns.set(tableName, Object.keys(columns));
75
+ }
76
+ }
77
+ }
78
+
79
+ await populateModelColumns();
80
+
59
81
  export class QueryBuilder<Model extends ObjectionModel, OutputDTO> {
60
- constructor(private readonly query: ITakaroQuery<OutputDTO> = new ITakaroQuery()) {}
82
+ private query: ITakaroQuery<OutputDTO>;
83
+ constructor(rawQuery: ITakaroQuery<OutputDTO> = new ITakaroQuery()) {
84
+ this.query = JSON.parse(JSON.stringify(rawQuery));
85
+ }
86
+
87
+ /**
88
+ * Our custom query builder can only handle columns that exist in the table
89
+ * However, we might want to do complex/relational logic in higher layers
90
+ * To make sure we don't error out here, we filter out any columns that don't exist in the current table
91
+ */
92
+ private cleanQueryObject(tableName: string) {
93
+ const columns = modelColumns.get(tableName) ?? [];
94
+
95
+ for (const key in this.query.filters) {
96
+ if (Object.prototype.hasOwnProperty.call(this.query.filters, key)) {
97
+ if (!columns.includes(key)) {
98
+ if (this.query.filters) {
99
+ this.query.filters[key] = [];
100
+ }
101
+ }
102
+ }
103
+ }
104
+
105
+ for (const key in this.query.search) {
106
+ if (Object.prototype.hasOwnProperty.call(this.query.search, key)) {
107
+ if (!columns.includes(key)) {
108
+ if (this.query.search) {
109
+ this.query.search[key] = [];
110
+ }
111
+ }
112
+ }
113
+ }
114
+
115
+ return this.query;
116
+ }
61
117
 
62
- build(query: ObjectionQueryBuilder<Model, Model[]>): ObjectionQueryBuilder<Model, Page<Model>> {
63
- const tableName = query.modelClass().tableName;
118
+ build(queryBuilder: ObjectionQueryBuilder<Model, Model[]>): ObjectionQueryBuilder<Model, Page<Model>> {
119
+ const tableName = queryBuilder.modelClass().tableName;
120
+ this.cleanQueryObject(tableName);
64
121
 
65
122
  const pagination = this.pagination();
66
123
  const sorting = this.sorting();
67
124
 
68
- let qry = query.page(pagination.page, pagination.limit).orderBy(sorting.sortBy, sorting.sortDirection);
125
+ let qry = queryBuilder.page(pagination.page, pagination.limit).orderBy(sorting.sortBy, sorting.sortDirection);
69
126
 
70
127
  qry = this.filters(tableName, qry);
71
128
  qry = this.greaterThan(tableName, qry);
@@ -104,7 +161,7 @@ export class QueryBuilder<Model extends ObjectionModel, OutputDTO> {
104
161
  const searchVal = this.query.filters[filter];
105
162
 
106
163
  if (searchVal && Array.isArray(searchVal)) {
107
- if (searchVal.includes(null) || searchVal.includes('null')) {
164
+ if (searchVal.includes('null')) {
108
165
  query.whereNull(`${tableName}.${filter}`);
109
166
  continue;
110
167
  }