@takaro/db 0.2.1 → 0.3.0
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/TakaroModel.d.ts.map +1 -1
- package/dist/TakaroModel.js +2 -0
- package/dist/TakaroModel.js.map +1 -1
- package/dist/migrations/sql/20250708095623-command-required-permissions.d.ts +4 -0
- package/dist/migrations/sql/20250708095623-command-required-permissions.d.ts.map +1 -0
- package/dist/migrations/sql/20250708095623-command-required-permissions.js +11 -0
- package/dist/migrations/sql/20250708095623-command-required-permissions.js.map +1 -0
- package/dist/migrations/sql/20250710131445-add-shop-categories.d.ts +4 -0
- package/dist/migrations/sql/20250710131445-add-shop-categories.d.ts.map +1 -0
- package/dist/migrations/sql/20250710131445-add-shop-categories.js +80 -0
- package/dist/migrations/sql/20250710131445-add-shop-categories.js.map +1 -0
- package/dist/migrations/sql/20250718-fix-player-inventory-history-pk.d.ts +4 -0
- package/dist/migrations/sql/20250718-fix-player-inventory-history-pk.d.ts.map +1 -0
- package/dist/migrations/sql/20250718-fix-player-inventory-history-pk.js +150 -0
- package/dist/migrations/sql/20250718-fix-player-inventory-history-pk.js.map +1 -0
- package/dist/migrations/sql/20250719160409-add-module-author-supportedgames.d.ts +8 -0
- package/dist/migrations/sql/20250719160409-add-module-author-supportedgames.d.ts.map +1 -0
- package/dist/migrations/sql/20250719160409-add-module-author-supportedgames.js +26 -0
- package/dist/migrations/sql/20250719160409-add-module-author-supportedgames.js.map +1 -0
- package/dist/migrations/sql/20250719175542-add-discord-permissions.d.ts +4 -0
- package/dist/migrations/sql/20250719175542-add-discord-permissions.d.ts.map +1 -0
- package/dist/migrations/sql/20250719175542-add-discord-permissions.js +18 -0
- package/dist/migrations/sql/20250719175542-add-discord-permissions.js.map +1 -0
- package/dist/queryBuilder.d.ts +1 -0
- package/dist/queryBuilder.d.ts.map +1 -1
- package/dist/queryBuilder.js +32 -3
- package/dist/queryBuilder.js.map +1 -1
- package/package.json +1 -1
- package/src/TakaroModel.ts +1 -0
- package/src/migrations/sql/20250708095623-command-required-permissions.ts +13 -0
- package/src/migrations/sql/20250710131445-add-shop-categories.ts +98 -0
- package/src/migrations/sql/20250718-fix-player-inventory-history-pk.ts +161 -0
- package/src/migrations/sql/20250719160409-add-module-author-supportedgames.ts +31 -0
- package/src/migrations/sql/20250719175542-add-discord-permissions.ts +20 -0
- 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;
|
|
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"}
|
package/dist/TakaroModel.js
CHANGED
|
@@ -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
|
};
|
package/dist/TakaroModel.js.map
CHANGED
|
@@ -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 @@
|
|
|
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 @@
|
|
|
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 @@
|
|
|
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 @@
|
|
|
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"}
|
package/dist/queryBuilder.d.ts
CHANGED
|
@@ -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":"
|
|
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"}
|
package/dist/queryBuilder.js
CHANGED
|
@@ -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
|
-
|
|
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
|
-
|
|
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
|
|
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;
|
package/dist/queryBuilder.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"queryBuilder.js","sourceRoot":"","sources":["../src/queryBuilder.ts"],"names":[],"mappings":";;;;;;;;;AAAA,OAAO,EAAE,MAAM,EAAE,QAAQ,EAAE,UAAU,EAAE,QAAQ,EAAE,MAAM,iBAAiB,CAAC;
|
|
1
|
+
{"version":3,"file":"queryBuilder.js","sourceRoot":"","sources":["../src/queryBuilder.ts"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
package/src/TakaroModel.ts
CHANGED
|
@@ -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
|
+
}
|
package/src/queryBuilder.ts
CHANGED
|
@@ -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
|
-
|
|
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
|
-
|
|
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
|
|
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;
|