@takaro/db 0.2.1 → 0.3.1

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.
Files changed (35) hide show
  1. package/dist/TakaroModel.d.ts.map +1 -1
  2. package/dist/TakaroModel.js +2 -0
  3. package/dist/TakaroModel.js.map +1 -1
  4. package/dist/migrations/sql/20250708095623-command-required-permissions.d.ts +4 -0
  5. package/dist/migrations/sql/20250708095623-command-required-permissions.d.ts.map +1 -0
  6. package/dist/migrations/sql/20250708095623-command-required-permissions.js +11 -0
  7. package/dist/migrations/sql/20250708095623-command-required-permissions.js.map +1 -0
  8. package/dist/migrations/sql/20250710131445-add-shop-categories.d.ts +4 -0
  9. package/dist/migrations/sql/20250710131445-add-shop-categories.d.ts.map +1 -0
  10. package/dist/migrations/sql/20250710131445-add-shop-categories.js +80 -0
  11. package/dist/migrations/sql/20250710131445-add-shop-categories.js.map +1 -0
  12. package/dist/migrations/sql/20250718-fix-player-inventory-history-pk.d.ts +4 -0
  13. package/dist/migrations/sql/20250718-fix-player-inventory-history-pk.d.ts.map +1 -0
  14. package/dist/migrations/sql/20250718-fix-player-inventory-history-pk.js +150 -0
  15. package/dist/migrations/sql/20250718-fix-player-inventory-history-pk.js.map +1 -0
  16. package/dist/migrations/sql/20250719160409-add-module-author-supportedgames.d.ts +8 -0
  17. package/dist/migrations/sql/20250719160409-add-module-author-supportedgames.d.ts.map +1 -0
  18. package/dist/migrations/sql/20250719160409-add-module-author-supportedgames.js +26 -0
  19. package/dist/migrations/sql/20250719160409-add-module-author-supportedgames.js.map +1 -0
  20. package/dist/migrations/sql/20250719175542-add-discord-permissions.d.ts +4 -0
  21. package/dist/migrations/sql/20250719175542-add-discord-permissions.d.ts.map +1 -0
  22. package/dist/migrations/sql/20250719175542-add-discord-permissions.js +18 -0
  23. package/dist/migrations/sql/20250719175542-add-discord-permissions.js.map +1 -0
  24. package/dist/queryBuilder.d.ts +1 -0
  25. package/dist/queryBuilder.d.ts.map +1 -1
  26. package/dist/queryBuilder.js +32 -3
  27. package/dist/queryBuilder.js.map +1 -1
  28. package/package.json +1 -1
  29. package/src/TakaroModel.ts +1 -0
  30. package/src/migrations/sql/20250708095623-command-required-permissions.ts +13 -0
  31. package/src/migrations/sql/20250710131445-add-shop-categories.ts +98 -0
  32. package/src/migrations/sql/20250718-fix-player-inventory-history-pk.ts +161 -0
  33. package/src/migrations/sql/20250719160409-add-module-author-supportedgames.ts +31 -0
  34. package/src/migrations/sql/20250719175542-add-discord-permissions.ts +20 -0
  35. package/src/queryBuilder.ts +37 -3
@@ -1 +1 @@
1
- {"version":3,"file":"TakaroModel.d.ts","sourceRoot":"","sources":["../src/TakaroModel.ts"],"names":[],"mappings":"AAAA,OAAO,SAAS,EAAE,EAAE,KAAK,EAAE,MAAM,WAAW,CAAC;AAE7C,qBAAa,6BAA8B,SAAQ,KAAK;IACtD,EAAE,EAAE,MAAM,CAAC;IACX,SAAS,EAAE,MAAM,CAAC;IAClB,SAAS,EAAE,MAAM,CAAC;IAElB,MAAM,KAAK,QAAQ,WAElB;IAED,aAAa;IAIb,aAAa;CAGd;AAED,qBAAa,WAAY,SAAQ,6BAA6B;IAC5D,MAAM,EAAE,MAAM,CAAC;IAEf,MAAM,KAAK,SAAS;4BAEI,SAAS,CAAC,YAAY,CAAC,KAAK,CAAC,YAAY,MAAM;MAKtE;CACF"}
1
+ {"version":3,"file":"TakaroModel.d.ts","sourceRoot":"","sources":["../src/TakaroModel.ts"],"names":[],"mappings":"AAAA,OAAO,SAAS,EAAE,EAAE,KAAK,EAAE,MAAM,WAAW,CAAC;AAE7C,qBAAa,6BAA8B,SAAQ,KAAK;IACtD,EAAE,EAAE,MAAM,CAAC;IACX,SAAS,EAAE,MAAM,CAAC;IAClB,SAAS,EAAE,MAAM,CAAC;IAElB,MAAM,KAAK,QAAQ,WAElB;IAED,aAAa;IAIb,aAAa;CAGd;AAED,qBAAa,WAAY,SAAQ,6BAA6B;IAC5D,MAAM,EAAE,MAAM,CAAC;IAEf,MAAM,KAAK,SAAS;4BAEI,SAAS,CAAC,YAAY,CAAC,KAAK,CAAC,YAAY,MAAM;MAMtE;CACF"}
@@ -15,6 +15,8 @@ export class TakaroModel extends NOT_DOMAIN_SCOPED_TakaroModel {
15
15
  return {
16
16
  domainScoped(query, domainId) {
17
17
  const tableName = query.modelClass().tableName;
18
+ if (!domainId)
19
+ throw new Error('Domain ID is required for domainScoped modifier');
18
20
  query.where(`${tableName}.domain`, domainId);
19
21
  },
20
22
  };
@@ -1 +1 @@
1
- {"version":3,"file":"TakaroModel.js","sourceRoot":"","sources":["../src/TakaroModel.ts"],"names":[],"mappings":"AAAA,OAAkB,EAAE,KAAK,EAAE,MAAM,WAAW,CAAC;AAE7C,MAAM,OAAO,6BAA8B,SAAQ,KAAK;IAKtD,MAAM,KAAK,QAAQ;QACjB,OAAO,IAAI,CAAC;IACd,CAAC;IAED,aAAa;QACX,IAAI,CAAC,SAAS,GAAG,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE,CAAC;IAC5C,CAAC;IAED,aAAa;QACX,IAAI,CAAC,SAAS,GAAG,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE,CAAC;IAC5C,CAAC;CACF;AAED,MAAM,OAAO,WAAY,SAAQ,6BAA6B;IAG5D,MAAM,KAAK,SAAS;QAClB,OAAO;YACL,YAAY,CAAC,KAAoC,EAAE,QAAgB;gBACjE,MAAM,SAAS,GAAG,KAAK,CAAC,UAAU,EAAE,CAAC,SAAS,CAAC;gBAC/C,KAAK,CAAC,KAAK,CAAC,GAAG,SAAS,SAAS,EAAE,QAAQ,CAAC,CAAC;YAC/C,CAAC;SACF,CAAC;IACJ,CAAC;CACF"}
1
+ {"version":3,"file":"TakaroModel.js","sourceRoot":"","sources":["../src/TakaroModel.ts"],"names":[],"mappings":"AAAA,OAAkB,EAAE,KAAK,EAAE,MAAM,WAAW,CAAC;AAE7C,MAAM,OAAO,6BAA8B,SAAQ,KAAK;IAKtD,MAAM,KAAK,QAAQ;QACjB,OAAO,IAAI,CAAC;IACd,CAAC;IAED,aAAa;QACX,IAAI,CAAC,SAAS,GAAG,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE,CAAC;IAC5C,CAAC;IAED,aAAa;QACX,IAAI,CAAC,SAAS,GAAG,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE,CAAC;IAC5C,CAAC;CACF;AAED,MAAM,OAAO,WAAY,SAAQ,6BAA6B;IAG5D,MAAM,KAAK,SAAS;QAClB,OAAO;YACL,YAAY,CAAC,KAAoC,EAAE,QAAgB;gBACjE,MAAM,SAAS,GAAG,KAAK,CAAC,UAAU,EAAE,CAAC,SAAS,CAAC;gBAC/C,IAAI,CAAC,QAAQ;oBAAE,MAAM,IAAI,KAAK,CAAC,iDAAiD,CAAC,CAAC;gBAClF,KAAK,CAAC,KAAK,CAAC,GAAG,SAAS,SAAS,EAAE,QAAQ,CAAC,CAAC;YAC/C,CAAC;SACF,CAAC;IACJ,CAAC;CACF"}
@@ -0,0 +1,4 @@
1
+ import { Knex } from 'knex';
2
+ export declare function up(knex: Knex): Promise<void>;
3
+ export declare function down(knex: Knex): Promise<void>;
4
+ //# sourceMappingURL=20250708095623-command-required-permissions.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"20250708095623-command-required-permissions.d.ts","sourceRoot":"","sources":["../../../src/migrations/sql/20250708095623-command-required-permissions.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,IAAI,EAAE,MAAM,MAAM,CAAC;AAE5B,wBAAsB,EAAE,CAAC,IAAI,EAAE,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAIlD;AAED,wBAAsB,IAAI,CAAC,IAAI,EAAE,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAIpD"}
@@ -0,0 +1,11 @@
1
+ export async function up(knex) {
2
+ await knex.schema.alterTable('commands', (table) => {
3
+ table.jsonb('requiredPermissions').defaultTo('[]').notNullable();
4
+ });
5
+ }
6
+ export async function down(knex) {
7
+ await knex.schema.alterTable('commands', (table) => {
8
+ table.dropColumn('requiredPermissions');
9
+ });
10
+ }
11
+ //# sourceMappingURL=20250708095623-command-required-permissions.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"20250708095623-command-required-permissions.js","sourceRoot":"","sources":["../../../src/migrations/sql/20250708095623-command-required-permissions.ts"],"names":[],"mappings":"AAEA,MAAM,CAAC,KAAK,UAAU,EAAE,CAAC,IAAU;IACjC,MAAM,IAAI,CAAC,MAAM,CAAC,UAAU,CAAC,UAAU,EAAE,CAAC,KAAK,EAAE,EAAE;QACjD,KAAK,CAAC,KAAK,CAAC,qBAAqB,CAAC,CAAC,SAAS,CAAC,IAAI,CAAC,CAAC,WAAW,EAAE,CAAC;IACnE,CAAC,CAAC,CAAC;AACL,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,IAAI,CAAC,IAAU;IACnC,MAAM,IAAI,CAAC,MAAM,CAAC,UAAU,CAAC,UAAU,EAAE,CAAC,KAAK,EAAE,EAAE;QACjD,KAAK,CAAC,UAAU,CAAC,qBAAqB,CAAC,CAAC;IAC1C,CAAC,CAAC,CAAC;AACL,CAAC"}
@@ -0,0 +1,4 @@
1
+ import { Knex } from 'knex';
2
+ export declare function up(knex: Knex): Promise<void>;
3
+ export declare function down(knex: Knex): Promise<void>;
4
+ //# sourceMappingURL=20250710131445-add-shop-categories.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"20250710131445-add-shop-categories.d.ts","sourceRoot":"","sources":["../../../src/migrations/sql/20250710131445-add-shop-categories.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,IAAI,EAAE,MAAM,MAAM,CAAC;AAgB5B,wBAAsB,EAAE,CAAC,IAAI,EAAE,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAkElD;AAED,wBAAsB,IAAI,CAAC,IAAI,EAAE,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAapD"}
@@ -0,0 +1,80 @@
1
+ const SHOP_CATEGORY_TABLE_NAME = 'shopCategory';
2
+ const SHOP_LISTING_CATEGORY_TABLE_NAME = 'shopListingCategory';
3
+ const DEFAULT_CATEGORIES = [
4
+ { name: 'Weapons', emoji: '⚔️' },
5
+ { name: 'Armor', emoji: '🛡️' },
6
+ { name: 'Building', emoji: '🏗️' },
7
+ { name: 'Tools', emoji: '🔧' },
8
+ { name: 'Consumables', emoji: '💊' },
9
+ { name: 'Resources', emoji: '📦' },
10
+ { name: 'Base', emoji: '🏠' },
11
+ { name: 'Vehicles', emoji: '🚗' },
12
+ ];
13
+ export async function up(knex) {
14
+ // Create shopCategory table (hierarchical categories)
15
+ await knex.schema.createTable(SHOP_CATEGORY_TABLE_NAME, (table) => {
16
+ table.timestamps(true, true, true);
17
+ table.uuid('id').primary().defaultTo(knex.raw('gen_random_uuid ()'));
18
+ table.string('domain').references('domains.id').onDelete('CASCADE').notNullable();
19
+ table.string('name', 50).notNullable();
20
+ table.string('emoji', 10).notNullable();
21
+ table.uuid('parentId').nullable();
22
+ // Add self-referencing foreign key for parent category
23
+ table.foreign('parentId').references('id').inTable(SHOP_CATEGORY_TABLE_NAME).onDelete('SET NULL');
24
+ // Indexes for performance
25
+ table.index('domain');
26
+ table.index('parentId');
27
+ table.index(['domain', 'name']);
28
+ });
29
+ // Add case-insensitive unique constraint on domain + name
30
+ await knex.raw(`
31
+ CREATE UNIQUE INDEX shopCategory_domain_name_unique
32
+ ON "${SHOP_CATEGORY_TABLE_NAME}" ("domain", LOWER("name"))
33
+ `);
34
+ // Create shopListingCategory junction table (many-to-many)
35
+ await knex.schema.createTable(SHOP_LISTING_CATEGORY_TABLE_NAME, (table) => {
36
+ table.timestamps(true, true, true);
37
+ table.uuid('id').primary().defaultTo(knex.raw('gen_random_uuid ()'));
38
+ table.string('domain').references('domains.id').onDelete('CASCADE').notNullable();
39
+ table.uuid('shopListingId').references('shopListing.id').onDelete('CASCADE').notNullable();
40
+ table.uuid('shopCategoryId').references('shopCategory.id').onDelete('CASCADE').notNullable();
41
+ // Indexes for performance
42
+ table.index('domain');
43
+ table.index('shopListingId');
44
+ table.index('shopCategoryId');
45
+ table.index(['shopListingId', 'shopCategoryId']);
46
+ // Ensure a listing can only be in a category once
47
+ table.unique(['shopListingId', 'shopCategoryId']);
48
+ });
49
+ // Insert default categories for all existing domains
50
+ const domains = await knex('domains').select('id');
51
+ for (const domain of domains) {
52
+ const categoriesToInsert = DEFAULT_CATEGORIES.map((cat) => ({
53
+ ...cat,
54
+ domain: domain.id,
55
+ parentId: null,
56
+ }));
57
+ // Insert categories, ignoring duplicates (in case some domains already have categories with these names)
58
+ for (const category of categoriesToInsert) {
59
+ const existing = await knex(SHOP_CATEGORY_TABLE_NAME)
60
+ .where('domain', category.domain)
61
+ .where('name', category.name)
62
+ .whereNull('parentId')
63
+ .first();
64
+ if (!existing) {
65
+ await knex(SHOP_CATEGORY_TABLE_NAME).insert(category);
66
+ }
67
+ }
68
+ }
69
+ }
70
+ export async function down(knex) {
71
+ // Remove default categories from all domains
72
+ await knex(SHOP_CATEGORY_TABLE_NAME)
73
+ .whereIn('name', DEFAULT_CATEGORIES.map((c) => c.name))
74
+ .whereNull('parentId')
75
+ .delete();
76
+ // Drop tables in reverse dependency order
77
+ await knex.schema.dropTable(SHOP_LISTING_CATEGORY_TABLE_NAME);
78
+ await knex.schema.dropTable(SHOP_CATEGORY_TABLE_NAME);
79
+ }
80
+ //# sourceMappingURL=20250710131445-add-shop-categories.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"20250710131445-add-shop-categories.js","sourceRoot":"","sources":["../../../src/migrations/sql/20250710131445-add-shop-categories.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,4 @@
1
+ import { Knex } from 'knex';
2
+ export declare function up(knex: Knex): Promise<void>;
3
+ export declare function down(knex: Knex): Promise<void>;
4
+ //# sourceMappingURL=20250718-fix-player-inventory-history-pk.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"20250718-fix-player-inventory-history-pk.d.ts","sourceRoot":"","sources":["../../../src/migrations/sql/20250718-fix-player-inventory-history-pk.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,IAAI,EAAE,MAAM,MAAM,CAAC;AAE5B,wBAAsB,EAAE,CAAC,IAAI,EAAE,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CA8ElD;AAED,wBAAsB,IAAI,CAAC,IAAI,EAAE,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CA8EpD"}
@@ -0,0 +1,150 @@
1
+ export async function up(knex) {
2
+ // First, drop the primary key constraint from the parent table
3
+ await knex.raw(`
4
+ ALTER TABLE "playerInventoryHistory"
5
+ DROP CONSTRAINT IF EXISTS "playerInventoryHistory_pkey"
6
+ `);
7
+ // Get all existing partitions and drop their primary key constraints
8
+ const partitions = await knex.raw(`
9
+ SELECT
10
+ inhrelid::regclass AS partition_name
11
+ FROM pg_inherits
12
+ WHERE inhparent = '"playerInventoryHistory"'::regclass
13
+ `);
14
+ for (const partition of partitions.rows) {
15
+ // partition_name already includes schema, need to extract just the table name
16
+ const fullName = partition.partition_name;
17
+ const partitionName = fullName.includes('.')
18
+ ? fullName.split('.').pop().replace(/"/g, '')
19
+ : fullName.replace(/"/g, '');
20
+ await knex.raw(`
21
+ ALTER TABLE "${partitionName}"
22
+ DROP CONSTRAINT IF EXISTS "${partitionName}_pkey"
23
+ `);
24
+ }
25
+ // Create a regular (non-unique) composite index for query performance
26
+ // This replaces the primary key constraint functionality for lookups
27
+ await knex.raw(`
28
+ CREATE INDEX IF NOT EXISTS "playerInventoryHistory_createdAt_playerId_itemId_idx"
29
+ ON "playerInventoryHistory" ("createdAt", "playerId", "itemId")
30
+ `);
31
+ // Update the partition creation function to not include the composite primary key
32
+ await knex.raw(`
33
+ CREATE OR REPLACE FUNCTION ensure_player_inventory_history_partition(date_param VARCHAR DEFAULT NULL)
34
+ RETURNS VOID AS $$
35
+ DECLARE
36
+ current_day_start DATE;
37
+ next_day_start DATE;
38
+ partition_name TEXT;
39
+ partition_exists BOOLEAN;
40
+ target_date DATE;
41
+ BEGIN
42
+ IF date_param IS NOT NULL THEN
43
+ target_date := DATE(date_param::TIMESTAMP);
44
+ ELSE
45
+ target_date := CURRENT_DATE;
46
+ END IF;
47
+
48
+ current_day_start := DATE_TRUNC('day', target_date);
49
+ next_day_start := current_day_start + INTERVAL '1 day';
50
+
51
+ partition_name := 'playerInventoryHistory_' || TO_CHAR(current_day_start, 'YYYY_MM_DD');
52
+
53
+ SELECT EXISTS (
54
+ SELECT 1 FROM pg_class c
55
+ JOIN pg_namespace n ON n.oid = c.relnamespace
56
+ WHERE c.relname = partition_name
57
+ AND n.nspname = 'public'
58
+ ) INTO partition_exists;
59
+
60
+ IF NOT partition_exists THEN
61
+ EXECUTE format(
62
+ 'CREATE TABLE %I PARTITION OF "playerInventoryHistory"
63
+ FOR VALUES FROM (%L) TO (%L)',
64
+ partition_name,
65
+ current_day_start,
66
+ next_day_start
67
+ );
68
+
69
+ RAISE NOTICE 'Created partition: % for date: %',
70
+ partition_name, current_day_start;
71
+ END IF;
72
+ END;
73
+ $$ LANGUAGE plpgsql;
74
+ `);
75
+ }
76
+ export async function down(knex) {
77
+ // Drop the new index
78
+ await knex.raw(`
79
+ DROP INDEX IF EXISTS "playerInventoryHistory_createdAt_playerId_itemId_idx"
80
+ `);
81
+ // No need to drop any primary key since we didn't add one in the up migration
82
+ // Restore the original composite primary key on the parent table
83
+ await knex.raw(`
84
+ ALTER TABLE "playerInventoryHistory"
85
+ ADD CONSTRAINT "playerInventoryHistory_pkey" PRIMARY KEY ("createdAt", "playerId", "itemId")
86
+ `);
87
+ // Get all existing partitions and restore their primary key constraints
88
+ const partitions = await knex.raw(`
89
+ SELECT
90
+ inhrelid::regclass AS partition_name
91
+ FROM pg_inherits
92
+ WHERE inhparent = '"playerInventoryHistory"'::regclass
93
+ `);
94
+ for (const partition of partitions.rows) {
95
+ // partition_name already includes schema, need to extract just the table name
96
+ const fullName = partition.partition_name;
97
+ const partitionName = fullName.includes('.')
98
+ ? fullName.split('.').pop().replace(/"/g, '')
99
+ : fullName.replace(/"/g, '');
100
+ await knex.raw(`
101
+ ALTER TABLE "${partitionName}"
102
+ ADD CONSTRAINT "${partitionName}_pkey" PRIMARY KEY ("createdAt", "playerId", "itemId")
103
+ `);
104
+ }
105
+ // Restore the original partition creation function with the composite primary key
106
+ await knex.raw(`
107
+ CREATE OR REPLACE FUNCTION ensure_player_inventory_history_partition(date_param VARCHAR DEFAULT NULL)
108
+ RETURNS VOID AS $$
109
+ DECLARE
110
+ current_day_start DATE;
111
+ next_day_start DATE;
112
+ partition_name TEXT;
113
+ partition_exists BOOLEAN;
114
+ target_date DATE;
115
+ BEGIN
116
+ IF date_param IS NOT NULL THEN
117
+ target_date := DATE(date_param::TIMESTAMP);
118
+ ELSE
119
+ target_date := CURRENT_DATE;
120
+ END IF;
121
+
122
+ current_day_start := DATE_TRUNC('day', target_date);
123
+ next_day_start := current_day_start + INTERVAL '1 day';
124
+
125
+ partition_name := 'playerInventoryHistory_' || TO_CHAR(current_day_start, 'YYYY_MM_DD');
126
+
127
+ SELECT EXISTS (
128
+ SELECT 1 FROM pg_class c
129
+ JOIN pg_namespace n ON n.oid = c.relnamespace
130
+ WHERE c.relname = partition_name
131
+ AND n.nspname = 'public'
132
+ ) INTO partition_exists;
133
+
134
+ IF NOT partition_exists THEN
135
+ EXECUTE format(
136
+ 'CREATE TABLE %I PARTITION OF "playerInventoryHistory"
137
+ FOR VALUES FROM (%L) TO (%L)',
138
+ partition_name,
139
+ current_day_start,
140
+ next_day_start
141
+ );
142
+
143
+ RAISE NOTICE 'Created partition: % for date: %',
144
+ partition_name, current_day_start;
145
+ END IF;
146
+ END;
147
+ $$ LANGUAGE plpgsql;
148
+ `);
149
+ }
150
+ //# sourceMappingURL=20250718-fix-player-inventory-history-pk.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"20250718-fix-player-inventory-history-pk.js","sourceRoot":"","sources":["../../../src/migrations/sql/20250718-fix-player-inventory-history-pk.ts"],"names":[],"mappings":"AAEA,MAAM,CAAC,KAAK,UAAU,EAAE,CAAC,IAAU;IACjC,+DAA+D;IAC/D,MAAM,IAAI,CAAC,GAAG,CAAC;;;GAGd,CAAC,CAAC;IAEH,qEAAqE;IACrE,MAAM,UAAU,GAAG,MAAM,IAAI,CAAC,GAAG,CAAC;;;;;GAKjC,CAAC,CAAC;IAEH,KAAK,MAAM,SAAS,IAAI,UAAU,CAAC,IAAI,EAAE,CAAC;QACxC,8EAA8E;QAC9E,MAAM,QAAQ,GAAG,SAAS,CAAC,cAAc,CAAC;QAC1C,MAAM,aAAa,GAAG,QAAQ,CAAC,QAAQ,CAAC,GAAG,CAAC;YAC1C,CAAC,CAAC,QAAQ,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,GAAG,EAAE,CAAC,OAAO,CAAC,IAAI,EAAE,EAAE,CAAC;YAC7C,CAAC,CAAC,QAAQ,CAAC,OAAO,CAAC,IAAI,EAAE,EAAE,CAAC,CAAC;QAC/B,MAAM,IAAI,CAAC,GAAG,CAAC;qBACE,aAAa;mCACC,aAAa;KAC3C,CAAC,CAAC;IACL,CAAC;IAED,sEAAsE;IACtE,qEAAqE;IACrE,MAAM,IAAI,CAAC,GAAG,CAAC;;;GAGd,CAAC,CAAC;IAEH,kFAAkF;IAClF,MAAM,IAAI,CAAC,GAAG,CAAC;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA0Cd,CAAC,CAAC;AACL,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,IAAI,CAAC,IAAU;IACnC,qBAAqB;IACrB,MAAM,IAAI,CAAC,GAAG,CAAC;;GAEd,CAAC,CAAC;IAEH,8EAA8E;IAE9E,iEAAiE;IACjE,MAAM,IAAI,CAAC,GAAG,CAAC;;;GAGd,CAAC,CAAC;IAEH,wEAAwE;IACxE,MAAM,UAAU,GAAG,MAAM,IAAI,CAAC,GAAG,CAAC;;;;;GAKjC,CAAC,CAAC;IAEH,KAAK,MAAM,SAAS,IAAI,UAAU,CAAC,IAAI,EAAE,CAAC;QACxC,8EAA8E;QAC9E,MAAM,QAAQ,GAAG,SAAS,CAAC,cAAc,CAAC;QAC1C,MAAM,aAAa,GAAG,QAAQ,CAAC,QAAQ,CAAC,GAAG,CAAC;YAC1C,CAAC,CAAC,QAAQ,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,GAAG,EAAE,CAAC,OAAO,CAAC,IAAI,EAAE,EAAE,CAAC;YAC7C,CAAC,CAAC,QAAQ,CAAC,OAAO,CAAC,IAAI,EAAE,EAAE,CAAC,CAAC;QAC/B,MAAM,IAAI,CAAC,GAAG,CAAC;qBACE,aAAa;wBACV,aAAa;KAChC,CAAC,CAAC;IACL,CAAC;IAED,kFAAkF;IAClF,MAAM,IAAI,CAAC,GAAG,CAAC;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA0Cd,CAAC,CAAC;AACL,CAAC"}
@@ -0,0 +1,8 @@
1
+ import { Knex } from 'knex';
2
+ /**
3
+ * This migration adds author and supportedGames fields to the modules table
4
+ * @param knex
5
+ */
6
+ export declare function up(knex: Knex): Promise<void>;
7
+ export declare function down(knex: Knex): Promise<void>;
8
+ //# sourceMappingURL=20250719160409-add-module-author-supportedgames.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"20250719160409-add-module-author-supportedgames.d.ts","sourceRoot":"","sources":["../../../src/migrations/sql/20250719160409-add-module-author-supportedgames.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,IAAI,EAAE,MAAM,MAAM,CAAC;AAE5B;;;GAGG;AAEH,wBAAsB,EAAE,CAAC,IAAI,EAAE,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAgBlD;AAED,wBAAsB,IAAI,CAAC,IAAI,EAAE,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAKpD"}
@@ -0,0 +1,26 @@
1
+ /**
2
+ * This migration adds author and supportedGames fields to the modules table
3
+ * @param knex
4
+ */
5
+ export async function up(knex) {
6
+ // Add author column
7
+ await knex.schema.alterTable('modules', (table) => {
8
+ table.string('author').nullable();
9
+ });
10
+ // Add supportedGames column as JSON array
11
+ await knex.schema.alterTable('modules', (table) => {
12
+ table.json('supportedGames').defaultTo('["all"]');
13
+ });
14
+ // Update existing modules to have default values
15
+ await knex('modules').update({
16
+ author: 'Unknown',
17
+ supportedGames: JSON.stringify(['all']),
18
+ });
19
+ }
20
+ export async function down(knex) {
21
+ await knex.schema.alterTable('modules', (table) => {
22
+ table.dropColumn('author');
23
+ table.dropColumn('supportedGames');
24
+ });
25
+ }
26
+ //# sourceMappingURL=20250719160409-add-module-author-supportedgames.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"20250719160409-add-module-author-supportedgames.js","sourceRoot":"","sources":["../../../src/migrations/sql/20250719160409-add-module-author-supportedgames.ts"],"names":[],"mappings":"AAEA;;;GAGG;AAEH,MAAM,CAAC,KAAK,UAAU,EAAE,CAAC,IAAU;IACjC,oBAAoB;IACpB,MAAM,IAAI,CAAC,MAAM,CAAC,UAAU,CAAC,SAAS,EAAE,CAAC,KAAK,EAAE,EAAE;QAChD,KAAK,CAAC,MAAM,CAAC,QAAQ,CAAC,CAAC,QAAQ,EAAE,CAAC;IACpC,CAAC,CAAC,CAAC;IAEH,0CAA0C;IAC1C,MAAM,IAAI,CAAC,MAAM,CAAC,UAAU,CAAC,SAAS,EAAE,CAAC,KAAK,EAAE,EAAE;QAChD,KAAK,CAAC,IAAI,CAAC,gBAAgB,CAAC,CAAC,SAAS,CAAC,SAAS,CAAC,CAAC;IACpD,CAAC,CAAC,CAAC;IAEH,iDAAiD;IACjD,MAAM,IAAI,CAAC,SAAS,CAAC,CAAC,MAAM,CAAC;QAC3B,MAAM,EAAE,SAAS;QACjB,cAAc,EAAE,IAAI,CAAC,SAAS,CAAC,CAAC,KAAK,CAAC,CAAC;KACxC,CAAC,CAAC;AACL,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,IAAI,CAAC,IAAU;IACnC,MAAM,IAAI,CAAC,MAAM,CAAC,UAAU,CAAC,SAAS,EAAE,CAAC,KAAK,EAAE,EAAE;QAChD,KAAK,CAAC,UAAU,CAAC,QAAQ,CAAC,CAAC;QAC3B,KAAK,CAAC,UAAU,CAAC,gBAAgB,CAAC,CAAC;IACrC,CAAC,CAAC,CAAC;AACL,CAAC"}
@@ -0,0 +1,4 @@
1
+ import { Knex } from 'knex';
2
+ export declare function up(knex: Knex): Promise<void>;
3
+ export declare function down(knex: Knex): Promise<void>;
4
+ //# sourceMappingURL=20250719175542-add-discord-permissions.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"20250719175542-add-discord-permissions.d.ts","sourceRoot":"","sources":["../../../src/migrations/sql/20250719175542-add-discord-permissions.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,IAAI,EAAE,MAAM,MAAM,CAAC;AAE5B,wBAAsB,EAAE,CAAC,IAAI,EAAE,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAalD;AAED,wBAAsB,IAAI,CAAC,IAAI,EAAE,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAEpD"}
@@ -0,0 +1,18 @@
1
+ export async function up(knex) {
2
+ await knex('permission').insert([
3
+ {
4
+ permission: 'VIEW_DISCORD_INFO',
5
+ description: 'Can view Discord guild information including channels and roles',
6
+ friendlyName: 'View Discord Info',
7
+ },
8
+ {
9
+ permission: 'SEND_DISCORD_MESSAGE',
10
+ description: 'Can send messages to Discord channels',
11
+ friendlyName: 'Send Discord Messages',
12
+ },
13
+ ]);
14
+ }
15
+ export async function down(knex) {
16
+ await knex('permission').whereIn('permission', ['VIEW_DISCORD_INFO', 'SEND_DISCORD_MESSAGE']).del();
17
+ }
18
+ //# sourceMappingURL=20250719175542-add-discord-permissions.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"20250719175542-add-discord-permissions.js","sourceRoot":"","sources":["../../../src/migrations/sql/20250719175542-add-discord-permissions.ts"],"names":[],"mappings":"AAEA,MAAM,CAAC,KAAK,UAAU,EAAE,CAAC,IAAU;IACjC,MAAM,IAAI,CAAC,YAAY,CAAC,CAAC,MAAM,CAAC;QAC9B;YACE,UAAU,EAAE,mBAAmB;YAC/B,WAAW,EAAE,iEAAiE;YAC9E,YAAY,EAAE,mBAAmB;SAClC;QACD;YACE,UAAU,EAAE,sBAAsB;YAClC,WAAW,EAAE,uCAAuC;YACpD,YAAY,EAAE,uBAAuB;SACtC;KACF,CAAC,CAAC;AACL,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,IAAI,CAAC,IAAU;IACnC,MAAM,IAAI,CAAC,YAAY,CAAC,CAAC,OAAO,CAAC,YAAY,EAAE,CAAC,mBAAmB,EAAE,sBAAsB,CAAC,CAAC,CAAC,GAAG,EAAE,CAAC;AACtG,CAAC"}
@@ -26,6 +26,7 @@ export declare function populateModelColumns(): Promise<void>;
26
26
  export declare class QueryBuilder<Model extends ObjectionModel, OutputDTO> {
27
27
  private query;
28
28
  constructor(rawQuery?: ITakaroQuery<OutputDTO>);
29
+ private validateArrayFilters;
29
30
  /**
30
31
  * Our custom query builder can only handle columns that exist in the table
31
32
  * However, we might want to do complex/relational logic in higher layers
@@ -1 +1 @@
1
- {"version":3,"file":"queryBuilder.d.ts","sourceRoot":"","sources":["../src/queryBuilder.ts"],"names":[],"mappings":"AACA,OAAO,EACL,YAAY,IAAI,qBAAqB,EACrC,KAAK,IAAI,cAAc,EACvB,IAAI,EAIL,MAAM,WAAW,CAAC;AAGnB,qBAAa,YAAY,CAAC,CAAC;IAEzB,OAAO,CAAC,EAAE;QACR,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,EAAE,GAAG,MAAM,EAAE,GAAG,OAAO,GAAG,IAAI,GAAG,SAAS,CAAC;KAClE,CAAC;IAGF,MAAM,CAAC,EAAE;QACP,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,EAAE,GAAG,MAAM,EAAE,GAAG,OAAO,GAAG,IAAI,GAAG,SAAS,CAAC;KAClE,CAAC;IAGF,WAAW,CAAC,EAAE;SACX,GAAG,IAAI,MAAM,CAAC,CAAC,CAAC,EAAE,OAAO;KAC3B,CAAC;IAGF,QAAQ,CAAC,EAAE;SACR,GAAG,IAAI,MAAM,CAAC,CAAC,CAAC,EAAE,OAAO;KAC3B,CAAC;IAIF,IAAI,CAAC,EAAE,MAAM,CAAC;IAId,KAAK,CAAC,EAAE,MAAM,CAAC;IAIf,MAAM,CAAC,EAAE,MAAM,CAAC;IAKhB,aAAa,CAAC,EAAE,aAAa,CAAC;IAI9B,MAAM,CAAC,EAAE,MAAM,EAAE,CAAC;CACnB;AAED,oBAAY,aAAa;IACvB,GAAG,QAAQ;IACX,IAAI,SAAS;CACd;AAID,wBAAsB,oBAAoB,kBAczC;AAID,qBAAa,YAAY,CAAC,KAAK,SAAS,cAAc,EAAE,SAAS;IAC/D,OAAO,CAAC,KAAK,CAA0B;gBAC3B,QAAQ,GAAE,YAAY,CAAC,SAAS,CAAsB;IAIlE;;;;OAIG;IACH,OAAO,CAAC,gBAAgB;IA0BxB,KAAK,CAAC,YAAY,EAAE,qBAAqB,CAAC,KAAK,EAAE,KAAK,EAAE,CAAC,GAAG,qBAAqB,CAAC,KAAK,EAAE,IAAI,CAAC,KAAK,CAAC,CAAC;IAqCrG,OAAO,CAAC,OAAO;IAwBf,OAAO,CAAC,WAAW;IAgBnB,OAAO,CAAC,QAAQ;IAgBhB,OAAO,CAAC,OAAO;IAaf,OAAO,CAAC,UAAU;CAMnB"}
1
+ {"version":3,"file":"queryBuilder.d.ts","sourceRoot":"","sources":["../src/queryBuilder.ts"],"names":[],"mappings":"AAEA,OAAO,EACL,YAAY,IAAI,qBAAqB,EACrC,KAAK,IAAI,cAAc,EACvB,IAAI,EAIL,MAAM,WAAW,CAAC;AAGnB,qBAAa,YAAY,CAAC,CAAC;IAEzB,OAAO,CAAC,EAAE;QACR,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,EAAE,GAAG,MAAM,EAAE,GAAG,OAAO,GAAG,IAAI,GAAG,SAAS,CAAC;KAClE,CAAC;IAGF,MAAM,CAAC,EAAE;QACP,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,EAAE,GAAG,MAAM,EAAE,GAAG,OAAO,GAAG,IAAI,GAAG,SAAS,CAAC;KAClE,CAAC;IAGF,WAAW,CAAC,EAAE;SACX,GAAG,IAAI,MAAM,CAAC,CAAC,CAAC,EAAE,OAAO;KAC3B,CAAC;IAGF,QAAQ,CAAC,EAAE;SACR,GAAG,IAAI,MAAM,CAAC,CAAC,CAAC,EAAE,OAAO;KAC3B,CAAC;IAIF,IAAI,CAAC,EAAE,MAAM,CAAC;IAId,KAAK,CAAC,EAAE,MAAM,CAAC;IAIf,MAAM,CAAC,EAAE,MAAM,CAAC;IAKhB,aAAa,CAAC,EAAE,aAAa,CAAC;IAI9B,MAAM,CAAC,EAAE,MAAM,EAAE,CAAC;CACnB;AAED,oBAAY,aAAa;IACvB,GAAG,QAAQ;IACX,IAAI,SAAS;CACd;AAID,wBAAsB,oBAAoB,kBAczC;AAID,qBAAa,YAAY,CAAC,KAAK,SAAS,cAAc,EAAE,SAAS;IAC/D,OAAO,CAAC,KAAK,CAA0B;gBAC3B,QAAQ,GAAE,YAAY,CAAC,SAAS,CAAsB;IAKlE,OAAO,CAAC,oBAAoB;IAwB5B;;;;OAIG;IACH,OAAO,CAAC,gBAAgB;IAgCxB,KAAK,CAAC,YAAY,EAAE,qBAAqB,CAAC,KAAK,EAAE,KAAK,EAAE,CAAC,GAAG,qBAAqB,CAAC,KAAK,EAAE,IAAI,CAAC,KAAK,CAAC,CAAC;IAqCrG,OAAO,CAAC,OAAO;IA0Bf,OAAO,CAAC,WAAW;IAgBnB,OAAO,CAAC,QAAQ;IAgBhB,OAAO,CAAC,OAAO;IAaf,OAAO,CAAC,UAAU;CAMnB"}
@@ -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 { errors } from '@takaro/util';
11
12
  import { getKnex, isDbAvailable } from './knex.js';
12
13
  export class ITakaroQuery {
13
14
  }
@@ -76,6 +77,25 @@ await populateModelColumns();
76
77
  export class QueryBuilder {
77
78
  constructor(rawQuery = new ITakaroQuery()) {
78
79
  this.query = JSON.parse(JSON.stringify(rawQuery));
80
+ this.validateArrayFilters();
81
+ }
82
+ validateArrayFilters() {
83
+ // Validate filters
84
+ if (this.query.filters) {
85
+ for (const [key, value] of Object.entries(this.query.filters)) {
86
+ if (value !== null && value !== undefined && !Array.isArray(value) && typeof value !== 'boolean') {
87
+ throw new errors.BadRequestError(`Filter values must be arrays or boolean. Found invalid value for '${key}': '${value}'. Example: { ${key}: ["${value}"] }`);
88
+ }
89
+ }
90
+ }
91
+ // Validate search
92
+ if (this.query.search) {
93
+ for (const [key, value] of Object.entries(this.query.search)) {
94
+ if (value !== null && value !== undefined && !Array.isArray(value) && typeof value !== 'boolean') {
95
+ throw new errors.BadRequestError(`Search values must be arrays or boolean. Found invalid value for '${key}': '${value}'. Example: { ${key}: ["${value}"] }`);
96
+ }
97
+ }
98
+ }
79
99
  }
80
100
  /**
81
101
  * Our custom query builder can only handle columns that exist in the table
@@ -88,7 +108,10 @@ export class QueryBuilder {
88
108
  if (Object.prototype.hasOwnProperty.call(this.query.filters, key)) {
89
109
  if (!columns.includes(key)) {
90
110
  if (this.query.filters) {
91
- this.query.filters[key] = [];
111
+ // Preserve boolean values, only clear arrays
112
+ if (typeof this.query.filters[key] !== 'boolean') {
113
+ this.query.filters[key] = [];
114
+ }
92
115
  }
93
116
  }
94
117
  }
@@ -97,7 +120,10 @@ export class QueryBuilder {
97
120
  if (Object.prototype.hasOwnProperty.call(this.query.search, key)) {
98
121
  if (!columns.includes(key)) {
99
122
  if (this.query.search) {
100
- this.query.search[key] = [];
123
+ // Preserve boolean values, only clear arrays
124
+ if (typeof this.query.search[key] !== 'boolean') {
125
+ this.query.search[key] = [];
126
+ }
101
127
  }
102
128
  }
103
129
  }
@@ -138,7 +164,10 @@ export class QueryBuilder {
138
164
  for (const filter in this.query.filters) {
139
165
  if (Object.prototype.hasOwnProperty.call(this.query.filters, filter)) {
140
166
  const searchVal = this.query.filters[filter];
141
- if (searchVal && Array.isArray(searchVal)) {
167
+ if (typeof searchVal === 'boolean') {
168
+ query.where(`${tableName}.${filter}`, searchVal);
169
+ }
170
+ else if (searchVal && Array.isArray(searchVal)) {
142
171
  if (searchVal.includes('null')) {
143
172
  query.whereNull(`${tableName}.${filter}`);
144
173
  continue;
@@ -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.2.1",
3
+ "version": "0.3.1",
4
4
  "description": "An opinionated data layer",
5
5
  "main": "dist/main.js",
6
6
  "types": "dist/main.d.ts",
@@ -25,6 +25,7 @@ export class TakaroModel extends NOT_DOMAIN_SCOPED_TakaroModel {
25
25
  return {
26
26
  domainScoped(query: Objection.QueryBuilder<Model>, domainId: string) {
27
27
  const tableName = query.modelClass().tableName;
28
+ if (!domainId) throw new Error('Domain ID is required for domainScoped modifier');
28
29
  query.where(`${tableName}.domain`, domainId);
29
30
  },
30
31
  };
@@ -0,0 +1,13 @@
1
+ import { Knex } from 'knex';
2
+
3
+ export async function up(knex: Knex): Promise<void> {
4
+ await knex.schema.alterTable('commands', (table) => {
5
+ table.jsonb('requiredPermissions').defaultTo('[]').notNullable();
6
+ });
7
+ }
8
+
9
+ export async function down(knex: Knex): Promise<void> {
10
+ await knex.schema.alterTable('commands', (table) => {
11
+ table.dropColumn('requiredPermissions');
12
+ });
13
+ }
@@ -0,0 +1,98 @@
1
+ import { Knex } from 'knex';
2
+
3
+ const SHOP_CATEGORY_TABLE_NAME = 'shopCategory';
4
+ const SHOP_LISTING_CATEGORY_TABLE_NAME = 'shopListingCategory';
5
+
6
+ const DEFAULT_CATEGORIES = [
7
+ { name: 'Weapons', emoji: '⚔️' },
8
+ { name: 'Armor', emoji: '🛡️' },
9
+ { name: 'Building', emoji: '🏗️' },
10
+ { name: 'Tools', emoji: '🔧' },
11
+ { name: 'Consumables', emoji: '💊' },
12
+ { name: 'Resources', emoji: '📦' },
13
+ { name: 'Base', emoji: '🏠' },
14
+ { name: 'Vehicles', emoji: '🚗' },
15
+ ];
16
+
17
+ export async function up(knex: Knex): Promise<void> {
18
+ // Create shopCategory table (hierarchical categories)
19
+ await knex.schema.createTable(SHOP_CATEGORY_TABLE_NAME, (table) => {
20
+ table.timestamps(true, true, true);
21
+ table.uuid('id').primary().defaultTo(knex.raw('gen_random_uuid ()'));
22
+ table.string('domain').references('domains.id').onDelete('CASCADE').notNullable();
23
+ table.string('name', 50).notNullable();
24
+ table.string('emoji', 10).notNullable();
25
+ table.uuid('parentId').nullable();
26
+
27
+ // Add self-referencing foreign key for parent category
28
+ table.foreign('parentId').references('id').inTable(SHOP_CATEGORY_TABLE_NAME).onDelete('SET NULL');
29
+
30
+ // Indexes for performance
31
+ table.index('domain');
32
+ table.index('parentId');
33
+ table.index(['domain', 'name']);
34
+ });
35
+
36
+ // Add case-insensitive unique constraint on domain + name
37
+ await knex.raw(`
38
+ CREATE UNIQUE INDEX shopCategory_domain_name_unique
39
+ ON "${SHOP_CATEGORY_TABLE_NAME}" ("domain", LOWER("name"))
40
+ `);
41
+
42
+ // Create shopListingCategory junction table (many-to-many)
43
+ await knex.schema.createTable(SHOP_LISTING_CATEGORY_TABLE_NAME, (table) => {
44
+ table.timestamps(true, true, true);
45
+ table.uuid('id').primary().defaultTo(knex.raw('gen_random_uuid ()'));
46
+ table.string('domain').references('domains.id').onDelete('CASCADE').notNullable();
47
+ table.uuid('shopListingId').references('shopListing.id').onDelete('CASCADE').notNullable();
48
+ table.uuid('shopCategoryId').references('shopCategory.id').onDelete('CASCADE').notNullable();
49
+
50
+ // Indexes for performance
51
+ table.index('domain');
52
+ table.index('shopListingId');
53
+ table.index('shopCategoryId');
54
+ table.index(['shopListingId', 'shopCategoryId']);
55
+
56
+ // Ensure a listing can only be in a category once
57
+ table.unique(['shopListingId', 'shopCategoryId']);
58
+ });
59
+
60
+ // Insert default categories for all existing domains
61
+ const domains = await knex('domains').select('id');
62
+
63
+ for (const domain of domains) {
64
+ const categoriesToInsert = DEFAULT_CATEGORIES.map((cat) => ({
65
+ ...cat,
66
+ domain: domain.id,
67
+ parentId: null,
68
+ }));
69
+
70
+ // Insert categories, ignoring duplicates (in case some domains already have categories with these names)
71
+ for (const category of categoriesToInsert) {
72
+ const existing = await knex(SHOP_CATEGORY_TABLE_NAME)
73
+ .where('domain', category.domain)
74
+ .where('name', category.name)
75
+ .whereNull('parentId')
76
+ .first();
77
+
78
+ if (!existing) {
79
+ await knex(SHOP_CATEGORY_TABLE_NAME).insert(category);
80
+ }
81
+ }
82
+ }
83
+ }
84
+
85
+ export async function down(knex: Knex): Promise<void> {
86
+ // Remove default categories from all domains
87
+ await knex(SHOP_CATEGORY_TABLE_NAME)
88
+ .whereIn(
89
+ 'name',
90
+ DEFAULT_CATEGORIES.map((c) => c.name),
91
+ )
92
+ .whereNull('parentId')
93
+ .delete();
94
+
95
+ // Drop tables in reverse dependency order
96
+ await knex.schema.dropTable(SHOP_LISTING_CATEGORY_TABLE_NAME);
97
+ await knex.schema.dropTable(SHOP_CATEGORY_TABLE_NAME);
98
+ }
@@ -0,0 +1,161 @@
1
+ import { Knex } from 'knex';
2
+
3
+ export async function up(knex: Knex): Promise<void> {
4
+ // First, drop the primary key constraint from the parent table
5
+ await knex.raw(`
6
+ ALTER TABLE "playerInventoryHistory"
7
+ DROP CONSTRAINT IF EXISTS "playerInventoryHistory_pkey"
8
+ `);
9
+
10
+ // Get all existing partitions and drop their primary key constraints
11
+ const partitions = await knex.raw(`
12
+ SELECT
13
+ inhrelid::regclass AS partition_name
14
+ FROM pg_inherits
15
+ WHERE inhparent = '"playerInventoryHistory"'::regclass
16
+ `);
17
+
18
+ for (const partition of partitions.rows) {
19
+ // partition_name already includes schema, need to extract just the table name
20
+ const fullName = partition.partition_name;
21
+ const partitionName = fullName.includes('.')
22
+ ? fullName.split('.').pop().replace(/"/g, '')
23
+ : fullName.replace(/"/g, '');
24
+ await knex.raw(`
25
+ ALTER TABLE "${partitionName}"
26
+ DROP CONSTRAINT IF EXISTS "${partitionName}_pkey"
27
+ `);
28
+ }
29
+
30
+ // Create a regular (non-unique) composite index for query performance
31
+ // This replaces the primary key constraint functionality for lookups
32
+ await knex.raw(`
33
+ CREATE INDEX IF NOT EXISTS "playerInventoryHistory_createdAt_playerId_itemId_idx"
34
+ ON "playerInventoryHistory" ("createdAt", "playerId", "itemId")
35
+ `);
36
+
37
+ // Update the partition creation function to not include the composite primary key
38
+ await knex.raw(`
39
+ CREATE OR REPLACE FUNCTION ensure_player_inventory_history_partition(date_param VARCHAR DEFAULT NULL)
40
+ RETURNS VOID AS $$
41
+ DECLARE
42
+ current_day_start DATE;
43
+ next_day_start DATE;
44
+ partition_name TEXT;
45
+ partition_exists BOOLEAN;
46
+ target_date DATE;
47
+ BEGIN
48
+ IF date_param IS NOT NULL THEN
49
+ target_date := DATE(date_param::TIMESTAMP);
50
+ ELSE
51
+ target_date := CURRENT_DATE;
52
+ END IF;
53
+
54
+ current_day_start := DATE_TRUNC('day', target_date);
55
+ next_day_start := current_day_start + INTERVAL '1 day';
56
+
57
+ partition_name := 'playerInventoryHistory_' || TO_CHAR(current_day_start, 'YYYY_MM_DD');
58
+
59
+ SELECT EXISTS (
60
+ SELECT 1 FROM pg_class c
61
+ JOIN pg_namespace n ON n.oid = c.relnamespace
62
+ WHERE c.relname = partition_name
63
+ AND n.nspname = 'public'
64
+ ) INTO partition_exists;
65
+
66
+ IF NOT partition_exists THEN
67
+ EXECUTE format(
68
+ 'CREATE TABLE %I PARTITION OF "playerInventoryHistory"
69
+ FOR VALUES FROM (%L) TO (%L)',
70
+ partition_name,
71
+ current_day_start,
72
+ next_day_start
73
+ );
74
+
75
+ RAISE NOTICE 'Created partition: % for date: %',
76
+ partition_name, current_day_start;
77
+ END IF;
78
+ END;
79
+ $$ LANGUAGE plpgsql;
80
+ `);
81
+ }
82
+
83
+ export async function down(knex: Knex): Promise<void> {
84
+ // Drop the new index
85
+ await knex.raw(`
86
+ DROP INDEX IF EXISTS "playerInventoryHistory_createdAt_playerId_itemId_idx"
87
+ `);
88
+
89
+ // No need to drop any primary key since we didn't add one in the up migration
90
+
91
+ // Restore the original composite primary key on the parent table
92
+ await knex.raw(`
93
+ ALTER TABLE "playerInventoryHistory"
94
+ ADD CONSTRAINT "playerInventoryHistory_pkey" PRIMARY KEY ("createdAt", "playerId", "itemId")
95
+ `);
96
+
97
+ // Get all existing partitions and restore their primary key constraints
98
+ const partitions = await knex.raw(`
99
+ SELECT
100
+ inhrelid::regclass AS partition_name
101
+ FROM pg_inherits
102
+ WHERE inhparent = '"playerInventoryHistory"'::regclass
103
+ `);
104
+
105
+ for (const partition of partitions.rows) {
106
+ // partition_name already includes schema, need to extract just the table name
107
+ const fullName = partition.partition_name;
108
+ const partitionName = fullName.includes('.')
109
+ ? fullName.split('.').pop().replace(/"/g, '')
110
+ : fullName.replace(/"/g, '');
111
+ await knex.raw(`
112
+ ALTER TABLE "${partitionName}"
113
+ ADD CONSTRAINT "${partitionName}_pkey" PRIMARY KEY ("createdAt", "playerId", "itemId")
114
+ `);
115
+ }
116
+
117
+ // Restore the original partition creation function with the composite primary key
118
+ await knex.raw(`
119
+ CREATE OR REPLACE FUNCTION ensure_player_inventory_history_partition(date_param VARCHAR DEFAULT NULL)
120
+ RETURNS VOID AS $$
121
+ DECLARE
122
+ current_day_start DATE;
123
+ next_day_start DATE;
124
+ partition_name TEXT;
125
+ partition_exists BOOLEAN;
126
+ target_date DATE;
127
+ BEGIN
128
+ IF date_param IS NOT NULL THEN
129
+ target_date := DATE(date_param::TIMESTAMP);
130
+ ELSE
131
+ target_date := CURRENT_DATE;
132
+ END IF;
133
+
134
+ current_day_start := DATE_TRUNC('day', target_date);
135
+ next_day_start := current_day_start + INTERVAL '1 day';
136
+
137
+ partition_name := 'playerInventoryHistory_' || TO_CHAR(current_day_start, 'YYYY_MM_DD');
138
+
139
+ SELECT EXISTS (
140
+ SELECT 1 FROM pg_class c
141
+ JOIN pg_namespace n ON n.oid = c.relnamespace
142
+ WHERE c.relname = partition_name
143
+ AND n.nspname = 'public'
144
+ ) INTO partition_exists;
145
+
146
+ IF NOT partition_exists THEN
147
+ EXECUTE format(
148
+ 'CREATE TABLE %I PARTITION OF "playerInventoryHistory"
149
+ FOR VALUES FROM (%L) TO (%L)',
150
+ partition_name,
151
+ current_day_start,
152
+ next_day_start
153
+ );
154
+
155
+ RAISE NOTICE 'Created partition: % for date: %',
156
+ partition_name, current_day_start;
157
+ END IF;
158
+ END;
159
+ $$ LANGUAGE plpgsql;
160
+ `);
161
+ }
@@ -0,0 +1,31 @@
1
+ import { Knex } from 'knex';
2
+
3
+ /**
4
+ * This migration adds author and supportedGames fields to the modules table
5
+ * @param knex
6
+ */
7
+
8
+ export async function up(knex: Knex): Promise<void> {
9
+ // Add author column
10
+ await knex.schema.alterTable('modules', (table) => {
11
+ table.string('author').nullable();
12
+ });
13
+
14
+ // Add supportedGames column as JSON array
15
+ await knex.schema.alterTable('modules', (table) => {
16
+ table.json('supportedGames').defaultTo('["all"]');
17
+ });
18
+
19
+ // Update existing modules to have default values
20
+ await knex('modules').update({
21
+ author: 'Unknown',
22
+ supportedGames: JSON.stringify(['all']),
23
+ });
24
+ }
25
+
26
+ export async function down(knex: Knex): Promise<void> {
27
+ await knex.schema.alterTable('modules', (table) => {
28
+ table.dropColumn('author');
29
+ table.dropColumn('supportedGames');
30
+ });
31
+ }
@@ -0,0 +1,20 @@
1
+ import { Knex } from 'knex';
2
+
3
+ export async function up(knex: Knex): Promise<void> {
4
+ await knex('permission').insert([
5
+ {
6
+ permission: 'VIEW_DISCORD_INFO',
7
+ description: 'Can view Discord guild information including channels and roles',
8
+ friendlyName: 'View Discord Info',
9
+ },
10
+ {
11
+ permission: 'SEND_DISCORD_MESSAGE',
12
+ description: 'Can send messages to Discord channels',
13
+ friendlyName: 'Send Discord Messages',
14
+ },
15
+ ]);
16
+ }
17
+
18
+ export async function down(knex: Knex): Promise<void> {
19
+ await knex('permission').whereIn('permission', ['VIEW_DISCORD_INFO', 'SEND_DISCORD_MESSAGE']).del();
20
+ }
@@ -1,4 +1,5 @@
1
1
  import { IsEnum, IsNumber, IsOptional, IsString } from 'class-validator';
2
+ import { errors } from '@takaro/util';
2
3
  import {
3
4
  QueryBuilder as ObjectionQueryBuilder,
4
5
  Model as ObjectionModel,
@@ -81,6 +82,31 @@ export class QueryBuilder<Model extends ObjectionModel, OutputDTO> {
81
82
  private query: ITakaroQuery<OutputDTO>;
82
83
  constructor(rawQuery: ITakaroQuery<OutputDTO> = new ITakaroQuery()) {
83
84
  this.query = JSON.parse(JSON.stringify(rawQuery));
85
+ this.validateArrayFilters();
86
+ }
87
+
88
+ private validateArrayFilters() {
89
+ // Validate filters
90
+ if (this.query.filters) {
91
+ for (const [key, value] of Object.entries(this.query.filters)) {
92
+ if (value !== null && value !== undefined && !Array.isArray(value) && typeof value !== 'boolean') {
93
+ throw new errors.BadRequestError(
94
+ `Filter values must be arrays or boolean. Found invalid value for '${key}': '${value}'. Example: { ${key}: ["${value}"] }`,
95
+ );
96
+ }
97
+ }
98
+ }
99
+
100
+ // Validate search
101
+ if (this.query.search) {
102
+ for (const [key, value] of Object.entries(this.query.search)) {
103
+ if (value !== null && value !== undefined && !Array.isArray(value) && typeof value !== 'boolean') {
104
+ throw new errors.BadRequestError(
105
+ `Search values must be arrays or boolean. Found invalid value for '${key}': '${value}'. Example: { ${key}: ["${value}"] }`,
106
+ );
107
+ }
108
+ }
109
+ }
84
110
  }
85
111
 
86
112
  /**
@@ -95,7 +121,10 @@ export class QueryBuilder<Model extends ObjectionModel, OutputDTO> {
95
121
  if (Object.prototype.hasOwnProperty.call(this.query.filters, key)) {
96
122
  if (!columns.includes(key)) {
97
123
  if (this.query.filters) {
98
- this.query.filters[key] = [];
124
+ // Preserve boolean values, only clear arrays
125
+ if (typeof this.query.filters[key] !== 'boolean') {
126
+ this.query.filters[key] = [];
127
+ }
99
128
  }
100
129
  }
101
130
  }
@@ -105,7 +134,10 @@ export class QueryBuilder<Model extends ObjectionModel, OutputDTO> {
105
134
  if (Object.prototype.hasOwnProperty.call(this.query.search, key)) {
106
135
  if (!columns.includes(key)) {
107
136
  if (this.query.search) {
108
- this.query.search[key] = [];
137
+ // Preserve boolean values, only clear arrays
138
+ if (typeof this.query.search[key] !== 'boolean') {
139
+ this.query.search[key] = [];
140
+ }
109
141
  }
110
142
  }
111
143
  }
@@ -159,7 +191,9 @@ export class QueryBuilder<Model extends ObjectionModel, OutputDTO> {
159
191
  if (Object.prototype.hasOwnProperty.call(this.query.filters, filter)) {
160
192
  const searchVal = this.query.filters[filter];
161
193
 
162
- if (searchVal && Array.isArray(searchVal)) {
194
+ if (typeof searchVal === 'boolean') {
195
+ query.where(`${tableName}.${filter}`, searchVal);
196
+ } else if (searchVal && Array.isArray(searchVal)) {
163
197
  if (searchVal.includes('null')) {
164
198
  query.whereNull(`${tableName}.${filter}`);
165
199
  continue;