@rebasepro/server-postgresql 0.1.2 → 0.2.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.
- package/LICENSE +22 -6
- package/dist/common/src/util/entities.d.ts +2 -2
- package/dist/common/src/util/relations.d.ts +1 -1
- package/dist/index.es.js +1160 -612
- package/dist/index.es.js.map +1 -1
- package/dist/index.umd.js +1158 -610
- package/dist/index.umd.js.map +1 -1
- package/dist/server-postgresql/src/PostgresAdapter.d.ts +6 -0
- package/dist/server-postgresql/src/PostgresBackendDriver.d.ts +2 -1
- package/dist/server-postgresql/src/PostgresBootstrapper.d.ts +0 -5
- package/dist/server-postgresql/src/auth/ensure-tables.d.ts +2 -1
- package/dist/server-postgresql/src/auth/services.d.ts +37 -15
- package/dist/server-postgresql/src/index.d.ts +1 -0
- package/dist/server-postgresql/src/schema/auth-schema.d.ts +43 -856
- package/dist/server-postgresql/src/schema/default-collections.d.ts +2 -0
- package/dist/server-postgresql/src/schema/doctor.d.ts +10 -1
- package/dist/server-postgresql/src/schema/introspect-db-logic.d.ts +1 -0
- package/dist/server-postgresql/src/services/entity-helpers.d.ts +1 -1
- package/dist/server-postgresql/src/services/realtimeService.d.ts +12 -0
- package/dist/server-postgresql/src/websocket.d.ts +2 -1
- package/dist/types/src/controllers/auth.d.ts +9 -8
- package/dist/types/src/controllers/client.d.ts +3 -0
- package/dist/types/src/types/auth_adapter.d.ts +356 -0
- package/dist/types/src/types/collections.d.ts +67 -2
- package/dist/types/src/types/database_adapter.d.ts +94 -0
- package/dist/types/src/types/entity_actions.d.ts +7 -1
- package/dist/types/src/types/entity_callbacks.d.ts +1 -1
- package/dist/types/src/types/entity_views.d.ts +36 -1
- package/dist/types/src/types/index.d.ts +2 -0
- package/dist/types/src/types/plugins.d.ts +1 -1
- package/dist/types/src/types/properties.d.ts +24 -5
- package/dist/types/src/types/property_config.d.ts +6 -2
- package/dist/types/src/types/relations.d.ts +1 -1
- package/dist/types/src/types/translations.d.ts +8 -0
- package/dist/types/src/users/user.d.ts +5 -0
- package/package.json +21 -15
- package/src/PostgresAdapter.ts +59 -0
- package/src/PostgresBackendDriver.ts +57 -8
- package/src/PostgresBootstrapper.ts +35 -15
- package/src/auth/ensure-tables.ts +82 -189
- package/src/auth/services.ts +421 -170
- package/src/cli.ts +44 -13
- package/src/data-transformer.ts +78 -8
- package/src/history/HistoryService.ts +25 -2
- package/src/index.ts +1 -0
- package/src/schema/auth-schema.ts +130 -98
- package/src/schema/default-collections.ts +68 -0
- package/src/schema/doctor-cli.ts +5 -1
- package/src/schema/doctor.ts +85 -8
- package/src/schema/generate-drizzle-schema-logic.ts +74 -27
- package/src/schema/generate-drizzle-schema.ts +13 -3
- package/src/schema/introspect-db-inference.ts +5 -5
- package/src/schema/introspect-db-logic.ts +9 -2
- package/src/schema/introspect-db.ts +14 -3
- package/src/services/EntityFetchService.ts +5 -5
- package/src/services/RelationService.ts +2 -2
- package/src/services/entity-helpers.ts +1 -1
- package/src/services/realtimeService.ts +145 -136
- package/src/utils/drizzle-conditions.ts +16 -2
- package/src/websocket.ts +113 -37
- package/test/auth-services.test.ts +163 -74
- package/test/data-transformer-hardening.test.ts +57 -0
- package/test/data-transformer.test.ts +43 -0
- package/test/generate-drizzle-schema.test.ts +7 -5
- package/test/introspect-db-utils.test.ts +4 -1
- package/test/postgresDataDriver.test.ts +17 -0
- package/test/realtimeService.test.ts +7 -7
- package/test/websocket.test.ts +139 -0
package/dist/index.umd.js
CHANGED
|
@@ -59,6 +59,12 @@
|
|
|
59
59
|
connectionString
|
|
60
60
|
};
|
|
61
61
|
}
|
|
62
|
+
class Vector {
|
|
63
|
+
value;
|
|
64
|
+
constructor(value) {
|
|
65
|
+
this.value = value;
|
|
66
|
+
}
|
|
67
|
+
}
|
|
62
68
|
function isPostgresCollection(collection) {
|
|
63
69
|
return !collection.driver || collection.driver === "postgres";
|
|
64
70
|
}
|
|
@@ -135,16 +141,25 @@
|
|
|
135
141
|
const tokenizeRegex = /[A-Z]{2,}(?=[A-Z][a-z]|\b)|[A-Z]?[a-z]+|[0-9]+(?:[a-z](?![a-z]))?|[A-Z]/g;
|
|
136
142
|
const snakeCaseRegex = tokenizeRegex;
|
|
137
143
|
const toSnakeCase = (str) => {
|
|
144
|
+
if (!str || typeof str !== "string") return "";
|
|
138
145
|
const regExpMatchArray = str.match(snakeCaseRegex);
|
|
139
146
|
if (!regExpMatchArray) return "";
|
|
140
147
|
return regExpMatchArray.map((x) => x.toLowerCase()).join("_");
|
|
141
148
|
};
|
|
149
|
+
function camelCase(str) {
|
|
150
|
+
if (!str) return "";
|
|
151
|
+
if (str.length === 1) return str.toLowerCase();
|
|
152
|
+
const parts = str.split(/[-_ ]+/).filter(Boolean);
|
|
153
|
+
if (parts.length === 0) return "";
|
|
154
|
+
return parts[0].toLowerCase() + // Transform remaining parts to have first letter uppercase
|
|
155
|
+
parts.slice(1).map((part) => part.charAt(0).toUpperCase() + part.substring(1).toLowerCase()).join("");
|
|
156
|
+
}
|
|
142
157
|
var commonjsGlobal = typeof globalThis !== "undefined" ? globalThis : typeof window !== "undefined" ? window : typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : {};
|
|
143
158
|
function commonjsRequire(path2) {
|
|
144
159
|
throw new Error('Could not dynamically require "' + path2 + '". Please configure the dynamicRequireTargets or/and ignoreDynamicRequires option of @rollup/plugin-commonjs appropriately for this require call to work.');
|
|
145
160
|
}
|
|
146
161
|
var object_hash = { exports: {} };
|
|
147
|
-
(function(module2,
|
|
162
|
+
(function(module2, exports3) {
|
|
148
163
|
!function(e) {
|
|
149
164
|
module2.exports = e();
|
|
150
165
|
}(function() {
|
|
@@ -1263,14 +1278,56 @@
|
|
|
1263
1278
|
}
|
|
1264
1279
|
return Object.keys(propertyCallbacks).length > 0 ? propertyCallbacks : void 0;
|
|
1265
1280
|
};
|
|
1266
|
-
function sanitizeRelation(relation, sourceCollection) {
|
|
1281
|
+
function sanitizeRelation(relation, sourceCollection, resolveCollection) {
|
|
1267
1282
|
if (!relation.target) {
|
|
1268
1283
|
throw new Error("Relation is missing a `target` collection.");
|
|
1269
1284
|
}
|
|
1270
|
-
const
|
|
1285
|
+
const rawTarget = relation.target;
|
|
1286
|
+
let targetCollection;
|
|
1287
|
+
if (typeof rawTarget === "string") {
|
|
1288
|
+
if (resolveCollection) {
|
|
1289
|
+
targetCollection = resolveCollection(rawTarget);
|
|
1290
|
+
}
|
|
1291
|
+
if (!targetCollection) {
|
|
1292
|
+
targetCollection = {
|
|
1293
|
+
slug: rawTarget,
|
|
1294
|
+
name: rawTarget
|
|
1295
|
+
};
|
|
1296
|
+
}
|
|
1297
|
+
} else if (typeof rawTarget === "function") {
|
|
1298
|
+
const evaluated = rawTarget();
|
|
1299
|
+
if (typeof evaluated === "string") {
|
|
1300
|
+
if (resolveCollection) {
|
|
1301
|
+
targetCollection = resolveCollection(evaluated);
|
|
1302
|
+
}
|
|
1303
|
+
if (!targetCollection) {
|
|
1304
|
+
targetCollection = {
|
|
1305
|
+
slug: evaluated,
|
|
1306
|
+
name: evaluated
|
|
1307
|
+
};
|
|
1308
|
+
}
|
|
1309
|
+
} else {
|
|
1310
|
+
targetCollection = evaluated;
|
|
1311
|
+
}
|
|
1312
|
+
}
|
|
1313
|
+
if (!targetCollection) {
|
|
1314
|
+
throw new Error("Relation is missing a valid `target` collection.");
|
|
1315
|
+
}
|
|
1271
1316
|
const newRelation = {
|
|
1272
1317
|
...relation
|
|
1273
1318
|
};
|
|
1319
|
+
newRelation.target = () => {
|
|
1320
|
+
if (typeof rawTarget === "string") {
|
|
1321
|
+
return resolveCollection && resolveCollection(rawTarget) || targetCollection;
|
|
1322
|
+
} else if (typeof rawTarget === "function") {
|
|
1323
|
+
const evaluated = rawTarget();
|
|
1324
|
+
if (typeof evaluated === "string") {
|
|
1325
|
+
return resolveCollection && resolveCollection(evaluated) || targetCollection;
|
|
1326
|
+
}
|
|
1327
|
+
return evaluated;
|
|
1328
|
+
}
|
|
1329
|
+
return targetCollection;
|
|
1330
|
+
};
|
|
1274
1331
|
if (!newRelation.relationName) {
|
|
1275
1332
|
newRelation.relationName = toSnakeCase(targetCollection.slug);
|
|
1276
1333
|
}
|
|
@@ -1323,6 +1380,17 @@
|
|
|
1323
1380
|
break;
|
|
1324
1381
|
}
|
|
1325
1382
|
}
|
|
1383
|
+
if (!isManyToManyInverse && targetCollection.properties) {
|
|
1384
|
+
for (const [propKey, prop] of Object.entries(targetCollection.properties)) {
|
|
1385
|
+
if (prop.type !== "relation") continue;
|
|
1386
|
+
const relProp = prop;
|
|
1387
|
+
const relName = relProp.relationName || propKey;
|
|
1388
|
+
if (relName === newRelation.inverseRelationName && relProp.cardinality === "many" && (relProp.direction === "owning" || !relProp.direction)) {
|
|
1389
|
+
isManyToManyInverse = true;
|
|
1390
|
+
break;
|
|
1391
|
+
}
|
|
1392
|
+
}
|
|
1393
|
+
}
|
|
1326
1394
|
} catch (e) {
|
|
1327
1395
|
}
|
|
1328
1396
|
}
|
|
@@ -1445,7 +1513,7 @@
|
|
|
1445
1513
|
return void 0;
|
|
1446
1514
|
}
|
|
1447
1515
|
var logic = { exports: {} };
|
|
1448
|
-
(function(module2,
|
|
1516
|
+
(function(module2, exports3) {
|
|
1449
1517
|
(function(root, factory) {
|
|
1450
1518
|
{
|
|
1451
1519
|
module2.exports = factory();
|
|
@@ -2202,7 +2270,7 @@
|
|
|
2202
2270
|
"[object Uint32Array]": areTypedArraysEqual2
|
|
2203
2271
|
};
|
|
2204
2272
|
}
|
|
2205
|
-
const deepEqual = createCustomEqual();
|
|
2273
|
+
const deepEqual$1 = createCustomEqual();
|
|
2206
2274
|
createCustomEqual({ strict: true });
|
|
2207
2275
|
createCustomEqual({ circular: true });
|
|
2208
2276
|
createCustomEqual({
|
|
@@ -2271,10 +2339,16 @@
|
|
|
2271
2339
|
*/
|
|
2272
2340
|
registerMultiple(collections) {
|
|
2273
2341
|
const rawSnapshot = collections.map((c) => removeFunctions(c));
|
|
2274
|
-
if (this.lastRawInputSnapshot && deepEqual(this.lastRawInputSnapshot, rawSnapshot)) {
|
|
2342
|
+
if (this.lastRawInputSnapshot && deepEqual$1(this.lastRawInputSnapshot, rawSnapshot)) {
|
|
2275
2343
|
return false;
|
|
2276
2344
|
}
|
|
2277
2345
|
this.reset();
|
|
2346
|
+
collections.forEach((c) => {
|
|
2347
|
+
if (c.slug) {
|
|
2348
|
+
this.collectionsBySlug.set(c.slug, c);
|
|
2349
|
+
}
|
|
2350
|
+
this.collectionsByTableName.set(getTableName(c), c);
|
|
2351
|
+
});
|
|
2278
2352
|
const normalizedCollections = collections.map((c) => this.normalizeCollection({
|
|
2279
2353
|
...c
|
|
2280
2354
|
}));
|
|
@@ -2353,7 +2427,7 @@
|
|
|
2353
2427
|
if (getDataSourceCapabilities(result.driver).supportsRelations) {
|
|
2354
2428
|
mergedRelations = mergedRelationsRaw.map((r) => {
|
|
2355
2429
|
try {
|
|
2356
|
-
return sanitizeRelation(r, result);
|
|
2430
|
+
return sanitizeRelation(r, result, (slug) => this.get(slug));
|
|
2357
2431
|
} catch {
|
|
2358
2432
|
return r;
|
|
2359
2433
|
}
|
|
@@ -2792,8 +2866,14 @@
|
|
|
2792
2866
|
static buildSingleFilterCondition(column, op, value) {
|
|
2793
2867
|
switch (op) {
|
|
2794
2868
|
case "==":
|
|
2869
|
+
if (value === null || value === void 0) {
|
|
2870
|
+
return drizzleOrm.sql`${column} IS NULL`;
|
|
2871
|
+
}
|
|
2795
2872
|
return drizzleOrm.eq(column, value);
|
|
2796
2873
|
case "!=":
|
|
2874
|
+
if (value === null || value === void 0) {
|
|
2875
|
+
return drizzleOrm.sql`${column} IS NOT NULL`;
|
|
2876
|
+
}
|
|
2797
2877
|
return drizzleOrm.sql`${column} != ${value}`;
|
|
2798
2878
|
case ">":
|
|
2799
2879
|
return drizzleOrm.sql`${column} > ${value}`;
|
|
@@ -3124,7 +3204,10 @@
|
|
|
3124
3204
|
if (p.type === "string" && !p.enum && p.isId !== "uuid") {
|
|
3125
3205
|
const fieldColumn = table[key];
|
|
3126
3206
|
if (fieldColumn) {
|
|
3127
|
-
|
|
3207
|
+
const supportsILike = fieldColumn instanceof pgCore.PgVarchar || fieldColumn instanceof pgCore.PgText || fieldColumn instanceof pgCore.PgChar || fieldColumn && typeof fieldColumn === "object" && !("columnType" in fieldColumn);
|
|
3208
|
+
if (supportsILike) {
|
|
3209
|
+
searchConditions.push(drizzleOrm.ilike(fieldColumn, `%${searchString}%`));
|
|
3210
|
+
}
|
|
3128
3211
|
}
|
|
3129
3212
|
}
|
|
3130
3213
|
}
|
|
@@ -3597,6 +3680,31 @@
|
|
|
3597
3680
|
return result;
|
|
3598
3681
|
}
|
|
3599
3682
|
return value;
|
|
3683
|
+
case "vector": {
|
|
3684
|
+
if (value instanceof Vector) {
|
|
3685
|
+
return value.value;
|
|
3686
|
+
}
|
|
3687
|
+
if (value && typeof value === "object" && "value" in value && Array.isArray(value.value)) {
|
|
3688
|
+
return value.value.map(Number);
|
|
3689
|
+
}
|
|
3690
|
+
if (Array.isArray(value)) {
|
|
3691
|
+
return value.map(Number);
|
|
3692
|
+
}
|
|
3693
|
+
return value;
|
|
3694
|
+
}
|
|
3695
|
+
case "binary":
|
|
3696
|
+
if (typeof value === "string") {
|
|
3697
|
+
if (value.startsWith("data:application/octet-stream;base64,")) {
|
|
3698
|
+
const base64Data = value.split(",")[1];
|
|
3699
|
+
if (base64Data) {
|
|
3700
|
+
return Buffer.from(base64Data, "base64");
|
|
3701
|
+
}
|
|
3702
|
+
}
|
|
3703
|
+
}
|
|
3704
|
+
if (Buffer.isBuffer(value)) {
|
|
3705
|
+
return value;
|
|
3706
|
+
}
|
|
3707
|
+
return value;
|
|
3600
3708
|
case "string":
|
|
3601
3709
|
if (typeof value === "string") {
|
|
3602
3710
|
if (value.startsWith("data:application/octet-stream;base64,")) {
|
|
@@ -3741,6 +3849,21 @@
|
|
|
3741
3849
|
return value;
|
|
3742
3850
|
}
|
|
3743
3851
|
switch (property.type) {
|
|
3852
|
+
case "binary": {
|
|
3853
|
+
let buf = null;
|
|
3854
|
+
if (Buffer.isBuffer(value)) {
|
|
3855
|
+
buf = value;
|
|
3856
|
+
} else if (typeof value === "object" && value !== null) {
|
|
3857
|
+
const rawVal = value;
|
|
3858
|
+
if (rawVal.type === "Buffer" && Array.isArray(rawVal.data)) {
|
|
3859
|
+
buf = Buffer.from(rawVal.data);
|
|
3860
|
+
}
|
|
3861
|
+
}
|
|
3862
|
+
if (buf) {
|
|
3863
|
+
return `data:application/octet-stream;base64,${buf.toString("base64")}`;
|
|
3864
|
+
}
|
|
3865
|
+
return value;
|
|
3866
|
+
}
|
|
3744
3867
|
case "string": {
|
|
3745
3868
|
if (typeof value === "string") return value;
|
|
3746
3869
|
let isBuffer = false;
|
|
@@ -3748,9 +3871,12 @@
|
|
|
3748
3871
|
if (Buffer.isBuffer(value)) {
|
|
3749
3872
|
isBuffer = true;
|
|
3750
3873
|
buf = value;
|
|
3751
|
-
} else if (typeof value === "object" && value !== null
|
|
3752
|
-
|
|
3753
|
-
|
|
3874
|
+
} else if (typeof value === "object" && value !== null) {
|
|
3875
|
+
const rawVal = value;
|
|
3876
|
+
if (rawVal.type === "Buffer" && Array.isArray(rawVal.data)) {
|
|
3877
|
+
isBuffer = true;
|
|
3878
|
+
buf = Buffer.from(rawVal.data);
|
|
3879
|
+
}
|
|
3754
3880
|
}
|
|
3755
3881
|
if (isBuffer && buf) {
|
|
3756
3882
|
let isPrintable = true;
|
|
@@ -3837,6 +3963,25 @@
|
|
|
3837
3963
|
return isNaN(parsed) ? null : parsed;
|
|
3838
3964
|
}
|
|
3839
3965
|
return value;
|
|
3966
|
+
case "vector": {
|
|
3967
|
+
let nums = [];
|
|
3968
|
+
if (typeof value === "string") {
|
|
3969
|
+
nums = value.slice(1, -1).split(",").map(Number);
|
|
3970
|
+
} else if (Array.isArray(value)) {
|
|
3971
|
+
nums = value.map(Number);
|
|
3972
|
+
} else if (value instanceof Vector) {
|
|
3973
|
+
nums = value.value;
|
|
3974
|
+
} else if (typeof value === "object" && value !== null && "value" in value) {
|
|
3975
|
+
const valObj = value;
|
|
3976
|
+
if (Array.isArray(valObj.value)) {
|
|
3977
|
+
nums = valObj.value.map(Number);
|
|
3978
|
+
}
|
|
3979
|
+
}
|
|
3980
|
+
return {
|
|
3981
|
+
__type: "Vector",
|
|
3982
|
+
value: nums
|
|
3983
|
+
};
|
|
3984
|
+
}
|
|
3840
3985
|
case "date": {
|
|
3841
3986
|
let date;
|
|
3842
3987
|
if (value instanceof Date) {
|
|
@@ -3861,9 +4006,12 @@
|
|
|
3861
4006
|
if (Buffer.isBuffer(value)) {
|
|
3862
4007
|
isBuffer = true;
|
|
3863
4008
|
buf = value;
|
|
3864
|
-
} else if (typeof value === "object" && value !== null
|
|
3865
|
-
|
|
3866
|
-
|
|
4009
|
+
} else if (typeof value === "object" && value !== null) {
|
|
4010
|
+
const rawVal = value;
|
|
4011
|
+
if (rawVal.type === "Buffer" && Array.isArray(rawVal.data)) {
|
|
4012
|
+
isBuffer = true;
|
|
4013
|
+
buf = Buffer.from(rawVal.data);
|
|
4014
|
+
}
|
|
3867
4015
|
}
|
|
3868
4016
|
if (isBuffer && buf) {
|
|
3869
4017
|
let isPrintable = true;
|
|
@@ -4653,7 +4801,7 @@
|
|
|
4653
4801
|
if (parentFKValue !== null && parentFKValue !== void 0) {
|
|
4654
4802
|
await tx.update(targetTable).set({
|
|
4655
4803
|
[targetFKColName]: null
|
|
4656
|
-
}).where(drizzleOrm.eq(targetFKCol, parentFKValue));
|
|
4804
|
+
}).where(drizzleOrm.eq(targetFKCol, String(parentFKValue)));
|
|
4657
4805
|
}
|
|
4658
4806
|
continue;
|
|
4659
4807
|
}
|
|
@@ -4662,7 +4810,7 @@
|
|
|
4662
4810
|
if (parentFKValue !== null && parentFKValue !== void 0) {
|
|
4663
4811
|
await tx.update(targetTable).set({
|
|
4664
4812
|
[targetFKColName]: null
|
|
4665
|
-
}).where(drizzleOrm.eq(targetFKCol, parentFKValue));
|
|
4813
|
+
}).where(drizzleOrm.eq(targetFKCol, String(parentFKValue)));
|
|
4666
4814
|
} else {
|
|
4667
4815
|
console.warn(`Cannot set joinPath relation '${relation.relationName}' because parent FK value is null/undefined`);
|
|
4668
4816
|
continue;
|
|
@@ -6739,23 +6887,33 @@
|
|
|
6739
6887
|
client: this.client
|
|
6740
6888
|
};
|
|
6741
6889
|
if (callbacks?.beforeDelete || propertyCallbacks?.beforeDelete) {
|
|
6890
|
+
let preventDefault = false;
|
|
6742
6891
|
if (callbacks?.beforeDelete) {
|
|
6743
|
-
await callbacks.beforeDelete({
|
|
6892
|
+
const result = await callbacks.beforeDelete({
|
|
6744
6893
|
collection: resolvedCollection,
|
|
6745
6894
|
path: entity.path,
|
|
6746
6895
|
entityId: entity.id,
|
|
6747
6896
|
entity,
|
|
6748
6897
|
context: contextForCallback
|
|
6749
6898
|
});
|
|
6899
|
+
if (result === false) {
|
|
6900
|
+
preventDefault = true;
|
|
6901
|
+
}
|
|
6750
6902
|
}
|
|
6751
6903
|
if (propertyCallbacks?.beforeDelete) {
|
|
6752
|
-
await propertyCallbacks.beforeDelete({
|
|
6904
|
+
const result = await propertyCallbacks.beforeDelete({
|
|
6753
6905
|
collection: resolvedCollection,
|
|
6754
6906
|
path: entity.path,
|
|
6755
6907
|
entityId: entity.id,
|
|
6756
6908
|
entity,
|
|
6757
6909
|
context: contextForCallback
|
|
6758
6910
|
});
|
|
6911
|
+
if (result === false) {
|
|
6912
|
+
preventDefault = true;
|
|
6913
|
+
}
|
|
6914
|
+
}
|
|
6915
|
+
if (preventDefault) {
|
|
6916
|
+
return;
|
|
6759
6917
|
}
|
|
6760
6918
|
}
|
|
6761
6919
|
await this.entityService.deleteEntity(entity.path, entity.id, entity.databaseId || resolvedCollection?.databaseId);
|
|
@@ -6828,7 +6986,17 @@
|
|
|
6828
6986
|
}
|
|
6829
6987
|
const targetDb = this.getTargetDb(options?.database);
|
|
6830
6988
|
try {
|
|
6831
|
-
|
|
6989
|
+
let needsRoleSwitch = false;
|
|
6990
|
+
if (options?.role && process.env.DISABLE_DB_ROLE_SWITCHING !== "true") {
|
|
6991
|
+
try {
|
|
6992
|
+
const currentRoleResult = await targetDb.execute(drizzleOrm.sql.raw("SELECT current_user AS role"));
|
|
6993
|
+
const currentRole = currentRoleResult.rows?.[0]?.role;
|
|
6994
|
+
needsRoleSwitch = !!currentRole && currentRole !== options.role;
|
|
6995
|
+
} catch {
|
|
6996
|
+
needsRoleSwitch = true;
|
|
6997
|
+
}
|
|
6998
|
+
}
|
|
6999
|
+
if (needsRoleSwitch && options?.role) {
|
|
6832
7000
|
const safeRole = options.role.replace(/"/g, '""');
|
|
6833
7001
|
return await targetDb.transaction(async (tx) => {
|
|
6834
7002
|
await tx.execute(drizzleOrm.sql.raw(`SET LOCAL ROLE "${safeRole}"`));
|
|
@@ -6866,7 +7034,7 @@
|
|
|
6866
7034
|
return databases;
|
|
6867
7035
|
}
|
|
6868
7036
|
async fetchAvailableRoles() {
|
|
6869
|
-
const result = await this.executeSql("SELECT rolname FROM pg_roles;");
|
|
7037
|
+
const result = await this.executeSql("SELECT rolname FROM pg_roles WHERE pg_has_role(current_user, rolname, 'member') ORDER BY rolname;");
|
|
6870
7038
|
return result.map((r) => r.rolname);
|
|
6871
7039
|
}
|
|
6872
7040
|
async fetchCurrentDatabase() {
|
|
@@ -7035,6 +7203,21 @@
|
|
|
7035
7203
|
* Typed admin capabilities — delegates to the base driver.
|
|
7036
7204
|
*/
|
|
7037
7205
|
admin;
|
|
7206
|
+
get restFetchService() {
|
|
7207
|
+
if (!this.delegate.restFetchService) return void 0;
|
|
7208
|
+
return {
|
|
7209
|
+
fetchCollectionForRest: async (collectionPath, options, include) => {
|
|
7210
|
+
return this.withTransaction(async (delegate) => {
|
|
7211
|
+
return delegate.restFetchService.fetchCollectionForRest(collectionPath, options, include);
|
|
7212
|
+
});
|
|
7213
|
+
},
|
|
7214
|
+
fetchEntityForRest: async (collectionPath, entityId, include, databaseId) => {
|
|
7215
|
+
return this.withTransaction(async (delegate) => {
|
|
7216
|
+
return delegate.restFetchService.fetchEntityForRest(collectionPath, entityId, include, databaseId);
|
|
7217
|
+
});
|
|
7218
|
+
}
|
|
7219
|
+
};
|
|
7220
|
+
}
|
|
7038
7221
|
async withTransaction(operation) {
|
|
7039
7222
|
const pendingNotifications = [];
|
|
7040
7223
|
const result = await this.delegate.db.transaction(async (tx) => {
|
|
@@ -7189,113 +7372,140 @@
|
|
|
7189
7372
|
this.pools.clear();
|
|
7190
7373
|
}
|
|
7191
7374
|
}
|
|
7192
|
-
|
|
7193
|
-
|
|
7194
|
-
|
|
7195
|
-
|
|
7196
|
-
|
|
7197
|
-
|
|
7198
|
-
|
|
7199
|
-
|
|
7200
|
-
|
|
7201
|
-
|
|
7202
|
-
|
|
7203
|
-
|
|
7204
|
-
|
|
7205
|
-
|
|
7206
|
-
|
|
7207
|
-
|
|
7208
|
-
|
|
7209
|
-
|
|
7210
|
-
|
|
7211
|
-
|
|
7212
|
-
|
|
7213
|
-
|
|
7214
|
-
|
|
7215
|
-
|
|
7216
|
-
|
|
7217
|
-
|
|
7218
|
-
|
|
7219
|
-
|
|
7220
|
-
|
|
7221
|
-
|
|
7222
|
-
|
|
7223
|
-
|
|
7224
|
-
|
|
7225
|
-
|
|
7226
|
-
|
|
7227
|
-
|
|
7228
|
-
|
|
7229
|
-
|
|
7230
|
-
|
|
7231
|
-
|
|
7232
|
-
|
|
7233
|
-
|
|
7234
|
-
|
|
7235
|
-
|
|
7236
|
-
|
|
7237
|
-
|
|
7238
|
-
|
|
7239
|
-
|
|
7240
|
-
|
|
7241
|
-
|
|
7242
|
-
|
|
7243
|
-
|
|
7244
|
-
|
|
7245
|
-
|
|
7246
|
-
|
|
7247
|
-
})
|
|
7248
|
-
|
|
7249
|
-
|
|
7250
|
-
|
|
7251
|
-
|
|
7252
|
-
|
|
7253
|
-
|
|
7254
|
-
|
|
7255
|
-
|
|
7256
|
-
|
|
7257
|
-
|
|
7258
|
-
|
|
7259
|
-
|
|
7260
|
-
|
|
7261
|
-
|
|
7262
|
-
|
|
7263
|
-
|
|
7264
|
-
|
|
7265
|
-
|
|
7266
|
-
})
|
|
7267
|
-
|
|
7268
|
-
|
|
7269
|
-
|
|
7270
|
-
|
|
7271
|
-
|
|
7272
|
-
|
|
7273
|
-
|
|
7274
|
-
|
|
7275
|
-
|
|
7276
|
-
|
|
7277
|
-
|
|
7278
|
-
|
|
7279
|
-
|
|
7280
|
-
|
|
7281
|
-
|
|
7282
|
-
|
|
7283
|
-
|
|
7284
|
-
|
|
7285
|
-
})
|
|
7286
|
-
|
|
7287
|
-
|
|
7288
|
-
|
|
7289
|
-
|
|
7290
|
-
|
|
7291
|
-
|
|
7292
|
-
|
|
7293
|
-
|
|
7294
|
-
|
|
7295
|
-
|
|
7296
|
-
|
|
7297
|
-
|
|
7298
|
-
|
|
7375
|
+
function createAuthSchema(rolesSchemaName = "rebase", usersSchemaName = "rebase") {
|
|
7376
|
+
const rolesSchema = rolesSchemaName === "public" ? null : pgCore.pgSchema(rolesSchemaName);
|
|
7377
|
+
const usersSchema2 = usersSchemaName === "public" ? null : pgCore.pgSchema(usersSchemaName);
|
|
7378
|
+
const rolesTableCreator = rolesSchema ? rolesSchema.table.bind(rolesSchema) : pgCore.pgTable;
|
|
7379
|
+
const usersTableCreator = usersSchema2 ? usersSchema2.table.bind(usersSchema2) : pgCore.pgTable;
|
|
7380
|
+
const users2 = usersTableCreator("users", {
|
|
7381
|
+
id: pgCore.uuid("id").defaultRandom().primaryKey(),
|
|
7382
|
+
email: pgCore.varchar("email", {
|
|
7383
|
+
length: 255
|
|
7384
|
+
}).notNull().unique(),
|
|
7385
|
+
passwordHash: pgCore.varchar("password_hash", {
|
|
7386
|
+
length: 255
|
|
7387
|
+
}),
|
|
7388
|
+
// NULL for OAuth-only users
|
|
7389
|
+
displayName: pgCore.varchar("display_name", {
|
|
7390
|
+
length: 255
|
|
7391
|
+
}),
|
|
7392
|
+
photoUrl: pgCore.varchar("photo_url", {
|
|
7393
|
+
length: 500
|
|
7394
|
+
}),
|
|
7395
|
+
emailVerified: pgCore.boolean("email_verified").default(false).notNull(),
|
|
7396
|
+
emailVerificationToken: pgCore.varchar("email_verification_token", {
|
|
7397
|
+
length: 255
|
|
7398
|
+
}),
|
|
7399
|
+
emailVerificationSentAt: pgCore.timestamp("email_verification_sent_at"),
|
|
7400
|
+
metadata: pgCore.jsonb("metadata").$type().default({}).notNull(),
|
|
7401
|
+
createdAt: pgCore.timestamp("created_at").defaultNow().notNull(),
|
|
7402
|
+
updatedAt: pgCore.timestamp("updated_at").defaultNow().notNull()
|
|
7403
|
+
});
|
|
7404
|
+
const roles2 = rolesTableCreator("roles", {
|
|
7405
|
+
id: pgCore.varchar("id", {
|
|
7406
|
+
length: 50
|
|
7407
|
+
}).primaryKey(),
|
|
7408
|
+
// 'admin', 'editor', 'viewer'
|
|
7409
|
+
name: pgCore.varchar("name", {
|
|
7410
|
+
length: 100
|
|
7411
|
+
}).notNull(),
|
|
7412
|
+
isAdmin: pgCore.boolean("is_admin").default(false).notNull(),
|
|
7413
|
+
defaultPermissions: pgCore.jsonb("default_permissions").$type(),
|
|
7414
|
+
collectionPermissions: pgCore.jsonb("collection_permissions").$type(),
|
|
7415
|
+
config: pgCore.jsonb("config").$type()
|
|
7416
|
+
});
|
|
7417
|
+
const userRoles2 = rolesTableCreator("user_roles", {
|
|
7418
|
+
userId: pgCore.uuid("user_id").notNull().references(() => users2.id, {
|
|
7419
|
+
onDelete: "cascade"
|
|
7420
|
+
}),
|
|
7421
|
+
roleId: pgCore.varchar("role_id", {
|
|
7422
|
+
length: 50
|
|
7423
|
+
}).notNull().references(() => roles2.id, {
|
|
7424
|
+
onDelete: "cascade"
|
|
7425
|
+
})
|
|
7426
|
+
}, (table) => ({
|
|
7427
|
+
pk: pgCore.primaryKey({
|
|
7428
|
+
columns: [table.userId, table.roleId]
|
|
7429
|
+
})
|
|
7430
|
+
}));
|
|
7431
|
+
const refreshTokens2 = rolesTableCreator("refresh_tokens", {
|
|
7432
|
+
id: pgCore.uuid("id").defaultRandom().primaryKey(),
|
|
7433
|
+
userId: pgCore.uuid("user_id").notNull().references(() => users2.id, {
|
|
7434
|
+
onDelete: "cascade"
|
|
7435
|
+
}),
|
|
7436
|
+
tokenHash: pgCore.varchar("token_hash", {
|
|
7437
|
+
length: 255
|
|
7438
|
+
}).notNull().unique(),
|
|
7439
|
+
expiresAt: pgCore.timestamp("expires_at").notNull(),
|
|
7440
|
+
userAgent: pgCore.varchar("user_agent", {
|
|
7441
|
+
length: 500
|
|
7442
|
+
}),
|
|
7443
|
+
ipAddress: pgCore.varchar("ip_address", {
|
|
7444
|
+
length: 45
|
|
7445
|
+
}),
|
|
7446
|
+
createdAt: pgCore.timestamp("created_at").defaultNow().notNull()
|
|
7447
|
+
}, (table) => ({
|
|
7448
|
+
uniqueDeviceSession: pgCore.unique("unique_device_session").on(table.userId, table.userAgent, table.ipAddress)
|
|
7449
|
+
}));
|
|
7450
|
+
const passwordResetTokens2 = rolesTableCreator("password_reset_tokens", {
|
|
7451
|
+
id: pgCore.uuid("id").defaultRandom().primaryKey(),
|
|
7452
|
+
userId: pgCore.uuid("user_id").notNull().references(() => users2.id, {
|
|
7453
|
+
onDelete: "cascade"
|
|
7454
|
+
}),
|
|
7455
|
+
tokenHash: pgCore.varchar("token_hash", {
|
|
7456
|
+
length: 255
|
|
7457
|
+
}).notNull().unique(),
|
|
7458
|
+
expiresAt: pgCore.timestamp("expires_at").notNull(),
|
|
7459
|
+
usedAt: pgCore.timestamp("used_at"),
|
|
7460
|
+
createdAt: pgCore.timestamp("created_at").defaultNow().notNull()
|
|
7461
|
+
});
|
|
7462
|
+
const appConfig2 = rolesTableCreator("app_config", {
|
|
7463
|
+
key: pgCore.varchar("key", {
|
|
7464
|
+
length: 100
|
|
7465
|
+
}).primaryKey(),
|
|
7466
|
+
value: pgCore.jsonb("value").notNull(),
|
|
7467
|
+
updatedAt: pgCore.timestamp("updated_at").defaultNow().notNull()
|
|
7468
|
+
});
|
|
7469
|
+
const userIdentities2 = rolesTableCreator("user_identities", {
|
|
7470
|
+
id: pgCore.uuid("id").defaultRandom().primaryKey(),
|
|
7471
|
+
userId: pgCore.uuid("user_id").notNull().references(() => users2.id, {
|
|
7472
|
+
onDelete: "cascade"
|
|
7473
|
+
}),
|
|
7474
|
+
provider: pgCore.varchar("provider", {
|
|
7475
|
+
length: 50
|
|
7476
|
+
}).notNull(),
|
|
7477
|
+
// e.g. 'google', 'linkedin'
|
|
7478
|
+
providerId: pgCore.varchar("provider_id", {
|
|
7479
|
+
length: 255
|
|
7480
|
+
}).notNull(),
|
|
7481
|
+
profileData: pgCore.jsonb("profile_data"),
|
|
7482
|
+
createdAt: pgCore.timestamp("created_at").defaultNow().notNull(),
|
|
7483
|
+
updatedAt: pgCore.timestamp("updated_at").defaultNow().notNull()
|
|
7484
|
+
}, (table) => ({
|
|
7485
|
+
uniqueProviderId: pgCore.unique("unique_provider_id").on(table.provider, table.providerId)
|
|
7486
|
+
}));
|
|
7487
|
+
return {
|
|
7488
|
+
rolesSchema,
|
|
7489
|
+
usersSchema: usersSchema2,
|
|
7490
|
+
users: users2,
|
|
7491
|
+
roles: roles2,
|
|
7492
|
+
userRoles: userRoles2,
|
|
7493
|
+
refreshTokens: refreshTokens2,
|
|
7494
|
+
passwordResetTokens: passwordResetTokens2,
|
|
7495
|
+
appConfig: appConfig2,
|
|
7496
|
+
userIdentities: userIdentities2
|
|
7497
|
+
};
|
|
7498
|
+
}
|
|
7499
|
+
const defaultAuthSchema = createAuthSchema("rebase", "rebase");
|
|
7500
|
+
const rebaseSchema = defaultAuthSchema.rolesSchema;
|
|
7501
|
+
const usersSchema = defaultAuthSchema.usersSchema;
|
|
7502
|
+
const users = defaultAuthSchema.users;
|
|
7503
|
+
const roles = defaultAuthSchema.roles;
|
|
7504
|
+
const userRoles = defaultAuthSchema.userRoles;
|
|
7505
|
+
const refreshTokens = defaultAuthSchema.refreshTokens;
|
|
7506
|
+
const passwordResetTokens = defaultAuthSchema.passwordResetTokens;
|
|
7507
|
+
const appConfig = defaultAuthSchema.appConfig;
|
|
7508
|
+
const userIdentities = defaultAuthSchema.userIdentities;
|
|
7299
7509
|
const usersRelations = drizzleOrm.relations(users, ({
|
|
7300
7510
|
many
|
|
7301
7511
|
}) => ({
|
|
@@ -7478,6 +7688,15 @@
|
|
|
7478
7688
|
}
|
|
7479
7689
|
break;
|
|
7480
7690
|
}
|
|
7691
|
+
case "vector": {
|
|
7692
|
+
const vp = prop;
|
|
7693
|
+
columnDefinition = `vector("${colName}", { dimensions: ${vp.dimensions} })`;
|
|
7694
|
+
break;
|
|
7695
|
+
}
|
|
7696
|
+
case "binary": {
|
|
7697
|
+
columnDefinition = `customType({ dataType() { return 'bytea'; } })("${colName}")`;
|
|
7698
|
+
break;
|
|
7699
|
+
}
|
|
7481
7700
|
case "relation": {
|
|
7482
7701
|
const refProp = prop;
|
|
7483
7702
|
const resolvedRelations = resolveCollectionRelations(collection);
|
|
@@ -7544,7 +7763,7 @@
|
|
|
7544
7763
|
return ` ${propName}: ${columnDefinition}`;
|
|
7545
7764
|
};
|
|
7546
7765
|
const resolveRawSql = (expression) => {
|
|
7547
|
-
const resolved = expression.replace(/\{(\w+)\}/g, (_, col) =>
|
|
7766
|
+
const resolved = expression.replace(/\{(\w+)\}/g, (_, col) => col);
|
|
7548
7767
|
return `sql\`${resolved}\``;
|
|
7549
7768
|
};
|
|
7550
7769
|
const wrapWithRoleCheck = (clause, roles2) => {
|
|
@@ -7556,7 +7775,7 @@
|
|
|
7556
7775
|
const match = sqlExpr.match(/^sql`(.*)`$/s);
|
|
7557
7776
|
return match ? match[1] : sqlExpr;
|
|
7558
7777
|
};
|
|
7559
|
-
const buildUsingClause = (rule) => {
|
|
7778
|
+
const buildUsingClause = (rule, collection) => {
|
|
7560
7779
|
if (rule.using) {
|
|
7561
7780
|
return resolveRawSql(rule.using);
|
|
7562
7781
|
}
|
|
@@ -7564,15 +7783,17 @@
|
|
|
7564
7783
|
return "sql`true`";
|
|
7565
7784
|
}
|
|
7566
7785
|
if (rule.ownerField) {
|
|
7567
|
-
|
|
7786
|
+
const prop = collection.properties?.[rule.ownerField];
|
|
7787
|
+
const colName = resolveColumnName(rule.ownerField, prop);
|
|
7788
|
+
return `sql\`${colName} = auth.uid()\``;
|
|
7568
7789
|
}
|
|
7569
7790
|
return null;
|
|
7570
7791
|
};
|
|
7571
|
-
const buildWithCheckClause = (rule) => {
|
|
7792
|
+
const buildWithCheckClause = (rule, collection) => {
|
|
7572
7793
|
if (rule.withCheck) {
|
|
7573
7794
|
return resolveRawSql(rule.withCheck);
|
|
7574
7795
|
}
|
|
7575
|
-
return buildUsingClause(rule);
|
|
7796
|
+
return buildUsingClause(rule, collection);
|
|
7576
7797
|
};
|
|
7577
7798
|
const getPolicyNameHash = (rule) => {
|
|
7578
7799
|
const data = JSON.stringify({
|
|
@@ -7588,21 +7809,22 @@
|
|
|
7588
7809
|
});
|
|
7589
7810
|
return crypto.createHash("sha1").update(data).digest("hex").substring(0, 7);
|
|
7590
7811
|
};
|
|
7591
|
-
const generatePolicyCode = (
|
|
7812
|
+
const generatePolicyCode = (collection, rule, index) => {
|
|
7813
|
+
const tableName = getTableName(collection);
|
|
7592
7814
|
const ops = rule.operations && rule.operations.length > 0 ? rule.operations : [rule.operation ?? "all"];
|
|
7593
7815
|
const ruleHash = getPolicyNameHash(rule);
|
|
7594
7816
|
return ops.map((op, opIdx) => {
|
|
7595
7817
|
const policyName = rule.name ? ops.length > 1 ? `${rule.name}_${op}` : rule.name : `${tableName}_${op}_${ruleHash}${ops.length > 1 ? `_${opIdx}` : ""}`;
|
|
7596
|
-
return generateSinglePolicyCode(
|
|
7818
|
+
return generateSinglePolicyCode(collection, rule, op, policyName);
|
|
7597
7819
|
}).join("");
|
|
7598
7820
|
};
|
|
7599
|
-
const generateSinglePolicyCode = (
|
|
7821
|
+
const generateSinglePolicyCode = (collection, rule, operation, policyName) => {
|
|
7600
7822
|
const mode = rule.mode ?? "permissive";
|
|
7601
7823
|
const roles2 = rule.roles ? [...rule.roles].sort() : void 0;
|
|
7602
7824
|
const needsUsing = operation !== "insert";
|
|
7603
7825
|
const needsWithCheck = operation !== "select" && operation !== "delete";
|
|
7604
|
-
let usingClause = needsUsing ? buildUsingClause(rule) : null;
|
|
7605
|
-
let withCheckClause = needsWithCheck ? buildWithCheckClause(rule) : null;
|
|
7826
|
+
let usingClause = needsUsing ? buildUsingClause(rule, collection) : null;
|
|
7827
|
+
let withCheckClause = needsWithCheck ? buildWithCheckClause(rule, collection) : null;
|
|
7606
7828
|
if (roles2 && roles2.length > 0) {
|
|
7607
7829
|
if (usingClause) {
|
|
7608
7830
|
usingClause = wrapWithRoleCheck(usingClause, roles2);
|
|
@@ -7671,12 +7893,26 @@
|
|
|
7671
7893
|
const generateSchema = async (collections, stripPolicies = false) => {
|
|
7672
7894
|
let schemaContent = "// This file is auto-generated by the Rebase Drizzle generator. Do not edit manually.\n\n";
|
|
7673
7895
|
const hasUuid = collections.some((c) => c.properties && Object.values(c.properties).some((p) => p.type === "string" && (p.autoValue === "uuid" || p.isId === "uuid")));
|
|
7674
|
-
collections.some((c) => c.properties && Object.values(c.properties).some((p) =>
|
|
7896
|
+
const hasVector = collections.some((c) => c.properties && Object.values(c.properties).some((p) => p.type === "vector"));
|
|
7897
|
+
const hasBinary = collections.some((c) => c.properties && Object.values(c.properties).some((p) => p.type === "binary"));
|
|
7675
7898
|
const pgCoreImports = ["primaryKey", "pgTable", "integer", "varchar", "text", "char", "boolean", "timestamp", "date", "time", "jsonb", "json", "pgEnum", "numeric", "real", "doublePrecision", "bigint", "serial", "bigserial", "pgPolicy"];
|
|
7676
7899
|
if (hasUuid) pgCoreImports.push("uuid");
|
|
7900
|
+
if (hasVector) pgCoreImports.push("vector");
|
|
7901
|
+
if (hasBinary) pgCoreImports.push("customType");
|
|
7902
|
+
const uniqueSchemas = Array.from(new Set(collections.map((c) => isPostgresCollection(c) ? c.schema : void 0).filter(Boolean)));
|
|
7903
|
+
if (uniqueSchemas.length > 0) {
|
|
7904
|
+
pgCoreImports.push("pgSchema");
|
|
7905
|
+
}
|
|
7677
7906
|
schemaContent += `import { ${pgCoreImports.join(", ")} } from 'drizzle-orm/pg-core';
|
|
7678
7907
|
`;
|
|
7679
7908
|
schemaContent += "import { relations as drizzleRelations, sql } from 'drizzle-orm';\n\n";
|
|
7909
|
+
uniqueSchemas.forEach((schema) => {
|
|
7910
|
+
schemaContent += `export const ${schema}Schema = pgSchema("${schema}");
|
|
7911
|
+
`;
|
|
7912
|
+
});
|
|
7913
|
+
if (uniqueSchemas.length > 0) {
|
|
7914
|
+
schemaContent += "\n";
|
|
7915
|
+
}
|
|
7680
7916
|
const exportedTableVars = [];
|
|
7681
7917
|
const exportedEnumVars = [];
|
|
7682
7918
|
const exportedRelationVars = [];
|
|
@@ -7731,6 +7967,9 @@
|
|
|
7731
7967
|
const tableVarName = getTableVarName(tableName);
|
|
7732
7968
|
if (isJunction && relation && sourceCollection && relation.through) {
|
|
7733
7969
|
const targetCollection = relation.target();
|
|
7970
|
+
const schema = (isPostgresCollection(targetCollection) ? targetCollection.schema : void 0) || (isPostgresCollection(sourceCollection) ? sourceCollection.schema : void 0);
|
|
7971
|
+
const tableCreator = schema ? `${schema}Schema.table` : "pgTable";
|
|
7972
|
+
const baseTableName = tableName.includes(".") ? tableName.split(".").pop() : tableName;
|
|
7734
7973
|
const {
|
|
7735
7974
|
sourceColumn,
|
|
7736
7975
|
targetColumn
|
|
@@ -7741,7 +7980,7 @@
|
|
|
7741
7980
|
const targetColType = isNumericId(targetCollection) ? "integer" : getPrimaryKeyProp(targetCollection).isUuid ? "uuid" : "varchar";
|
|
7742
7981
|
const sourceId = getPrimaryKeyName(sourceCollection);
|
|
7743
7982
|
const targetId = getPrimaryKeyName(targetCollection);
|
|
7744
|
-
schemaContent += `export const ${tableVarName} =
|
|
7983
|
+
schemaContent += `export const ${tableVarName} = ${tableCreator}("${baseTableName}", {
|
|
7745
7984
|
`;
|
|
7746
7985
|
schemaContent += ` ${sourceColumn}: ${sourceColType}("${sourceColumn}").notNull().references(() => ${getTableVarName(getTableName(sourceCollection))}.${sourceId}, ${refOptions}),
|
|
7747
7986
|
`;
|
|
@@ -7752,7 +7991,10 @@
|
|
|
7752
7991
|
`;
|
|
7753
7992
|
schemaContent += "}));\n\n";
|
|
7754
7993
|
} else if (!isJunction) {
|
|
7755
|
-
|
|
7994
|
+
const schema = isPostgresCollection(collection) ? collection.schema : void 0;
|
|
7995
|
+
const tableCreator = schema ? `${schema}Schema.table` : "pgTable";
|
|
7996
|
+
const baseTableName = tableName.includes(".") ? tableName.split(".").pop() : tableName;
|
|
7997
|
+
schemaContent += `export const ${tableVarName} = ${tableCreator}("${baseTableName}", {
|
|
7756
7998
|
`;
|
|
7757
7999
|
const columns = /* @__PURE__ */ new Set();
|
|
7758
8000
|
Object.entries(collection.properties ?? {}).forEach(([propName, prop]) => {
|
|
@@ -7768,7 +8010,7 @@
|
|
|
7768
8010
|
if (!stripPolicies && securityRules && securityRules.length > 0) {
|
|
7769
8011
|
schemaContent += "\n}, (table) => ([\n";
|
|
7770
8012
|
securityRules.forEach((rule, idx) => {
|
|
7771
|
-
schemaContent += generatePolicyCode(
|
|
8013
|
+
schemaContent += generatePolicyCode(collection, rule);
|
|
7772
8014
|
});
|
|
7773
8015
|
schemaContent += "])).enableRLS();\n\n";
|
|
7774
8016
|
} else {
|
|
@@ -7813,11 +8055,11 @@
|
|
|
7813
8055
|
references: [${sourceTableVar}.${sourceId}],
|
|
7814
8056
|
relationName: "${owningRelationName}"
|
|
7815
8057
|
})`);
|
|
7816
|
-
const
|
|
8058
|
+
const targetRelationName = inverseRelationName ? inverseRelationName : `${tableName}_${relation.through.targetColumn}`;
|
|
7817
8059
|
tableRelations.push(` "${relation.through.targetColumn}": one(${targetTableVar}, {
|
|
7818
8060
|
fields: [${tableVarName}.${relation.through.targetColumn}],
|
|
7819
8061
|
references: [${targetTableVar}.${targetId}],
|
|
7820
|
-
relationName: "${
|
|
8062
|
+
relationName: "${targetRelationName}"
|
|
7821
8063
|
})`);
|
|
7822
8064
|
}
|
|
7823
8065
|
} else {
|
|
@@ -7916,6 +8158,89 @@ ${tableRelations.join(",\n")}
|
|
|
7916
8158
|
schemaContent += tablesExport + enumsExport + relationsExport;
|
|
7917
8159
|
return schemaContent;
|
|
7918
8160
|
};
|
|
8161
|
+
const defaultUsersCollection = {
|
|
8162
|
+
name: "Users",
|
|
8163
|
+
singularName: "User",
|
|
8164
|
+
slug: "users",
|
|
8165
|
+
table: "users",
|
|
8166
|
+
icon: "Users",
|
|
8167
|
+
group: "Settings",
|
|
8168
|
+
properties: {
|
|
8169
|
+
id: {
|
|
8170
|
+
name: "ID",
|
|
8171
|
+
type: "string",
|
|
8172
|
+
isId: "uuid"
|
|
8173
|
+
},
|
|
8174
|
+
email: {
|
|
8175
|
+
name: "Email",
|
|
8176
|
+
type: "string",
|
|
8177
|
+
validation: {
|
|
8178
|
+
required: true,
|
|
8179
|
+
unique: true
|
|
8180
|
+
}
|
|
8181
|
+
},
|
|
8182
|
+
password_hash: {
|
|
8183
|
+
name: "Password Hash",
|
|
8184
|
+
type: "string",
|
|
8185
|
+
ui: {
|
|
8186
|
+
hideFromCollection: true
|
|
8187
|
+
}
|
|
8188
|
+
},
|
|
8189
|
+
display_name: {
|
|
8190
|
+
name: "Display Name",
|
|
8191
|
+
type: "string"
|
|
8192
|
+
},
|
|
8193
|
+
photo_url: {
|
|
8194
|
+
name: "Photo URL",
|
|
8195
|
+
type: "string"
|
|
8196
|
+
},
|
|
8197
|
+
email_verified: {
|
|
8198
|
+
name: "Email Verified",
|
|
8199
|
+
type: "boolean",
|
|
8200
|
+
defaultValue: false
|
|
8201
|
+
},
|
|
8202
|
+
email_verification_token: {
|
|
8203
|
+
name: "Email Verification Token",
|
|
8204
|
+
type: "string",
|
|
8205
|
+
ui: {
|
|
8206
|
+
hideFromCollection: true
|
|
8207
|
+
}
|
|
8208
|
+
},
|
|
8209
|
+
email_verification_sent_at: {
|
|
8210
|
+
name: "Email Verification Sent At",
|
|
8211
|
+
type: "date",
|
|
8212
|
+
ui: {
|
|
8213
|
+
hideFromCollection: true
|
|
8214
|
+
}
|
|
8215
|
+
},
|
|
8216
|
+
metadata: {
|
|
8217
|
+
name: "Metadata",
|
|
8218
|
+
type: "map",
|
|
8219
|
+
defaultValue: {},
|
|
8220
|
+
ui: {
|
|
8221
|
+
hideFromCollection: true
|
|
8222
|
+
}
|
|
8223
|
+
},
|
|
8224
|
+
created_at: {
|
|
8225
|
+
name: "Created At",
|
|
8226
|
+
type: "date",
|
|
8227
|
+
autoValue: "on_create",
|
|
8228
|
+
ui: {
|
|
8229
|
+
readOnly: true,
|
|
8230
|
+
hideFromCollection: true
|
|
8231
|
+
}
|
|
8232
|
+
},
|
|
8233
|
+
updated_at: {
|
|
8234
|
+
name: "Updated At",
|
|
8235
|
+
type: "date",
|
|
8236
|
+
autoValue: "on_update",
|
|
8237
|
+
ui: {
|
|
8238
|
+
readOnly: true,
|
|
8239
|
+
hideFromCollection: true
|
|
8240
|
+
}
|
|
8241
|
+
}
|
|
8242
|
+
}
|
|
8243
|
+
};
|
|
7919
8244
|
const formatTerminalText = (text, options = {}) => {
|
|
7920
8245
|
let codes = "";
|
|
7921
8246
|
if (options.bold) codes += "\x1B[1m";
|
|
@@ -7978,10 +8303,14 @@ ${tableRelations.join(",\n")}
|
|
|
7978
8303
|
const imported = await dynamicImport(fileUrl);
|
|
7979
8304
|
collections = imported.backendCollections || imported.collections;
|
|
7980
8305
|
}
|
|
7981
|
-
if (!collections || !Array.isArray(collections)
|
|
7982
|
-
|
|
7983
|
-
|
|
8306
|
+
if (!collections || !Array.isArray(collections)) {
|
|
8307
|
+
collections = [];
|
|
8308
|
+
}
|
|
8309
|
+
const hasUsersCollection = collections.some((c) => c.slug === "users");
|
|
8310
|
+
if (!hasUsersCollection) {
|
|
8311
|
+
collections.push(defaultUsersCollection);
|
|
7984
8312
|
}
|
|
8313
|
+
collections.sort((a, b) => a.slug.localeCompare(b.slug));
|
|
7985
8314
|
const schemaContent = await generateSchema(collections);
|
|
7986
8315
|
if (outputPath) {
|
|
7987
8316
|
const outputDir = path.dirname(outputPath);
|
|
@@ -8209,29 +8538,14 @@ ${tableRelations.join(",\n")}
|
|
|
8209
8538
|
},
|
|
8210
8539
|
authContext
|
|
8211
8540
|
});
|
|
8212
|
-
|
|
8213
|
-
|
|
8214
|
-
|
|
8215
|
-
|
|
8216
|
-
|
|
8217
|
-
|
|
8218
|
-
|
|
8219
|
-
|
|
8220
|
-
limit: request.limit,
|
|
8221
|
-
startAfter: request.startAfter,
|
|
8222
|
-
searchString: request.searchString
|
|
8223
|
-
});
|
|
8224
|
-
} else {
|
|
8225
|
-
entities = await this.entityService.fetchCollection(request.path, {
|
|
8226
|
-
filter: request.filter,
|
|
8227
|
-
orderBy: request.orderBy,
|
|
8228
|
-
order: request.order,
|
|
8229
|
-
limit: request.limit,
|
|
8230
|
-
startAfter: request.startAfter,
|
|
8231
|
-
databaseId: request.collection?.databaseId,
|
|
8232
|
-
searchString: request.searchString
|
|
8233
|
-
});
|
|
8234
|
-
}
|
|
8541
|
+
const entities = await this.fetchCollectionWithAuth(request.path, {
|
|
8542
|
+
filter: request.filter,
|
|
8543
|
+
orderBy: request.orderBy,
|
|
8544
|
+
order: request.order,
|
|
8545
|
+
limit: request.limit,
|
|
8546
|
+
startAfter: request.startAfter,
|
|
8547
|
+
searchString: request.searchString
|
|
8548
|
+
}, authContext);
|
|
8235
8549
|
this.sendCollectionUpdate(clientId, subscriptionId, entities);
|
|
8236
8550
|
} catch (error) {
|
|
8237
8551
|
this.sendError(clientId, `Failed to subscribe to collection: ${error}`, subscriptionId);
|
|
@@ -8255,16 +8569,7 @@ ${tableRelations.join(",\n")}
|
|
|
8255
8569
|
entityId: request.entityId,
|
|
8256
8570
|
authContext
|
|
8257
8571
|
});
|
|
8258
|
-
|
|
8259
|
-
if (this.driver) {
|
|
8260
|
-
entity = await this.driver.fetchEntity({
|
|
8261
|
-
path: request.path,
|
|
8262
|
-
entityId: request.entityId,
|
|
8263
|
-
collection
|
|
8264
|
-
});
|
|
8265
|
-
} else {
|
|
8266
|
-
entity = await this.entityService.fetchEntity(request.path, request.entityId, request.collection?.databaseId);
|
|
8267
|
-
}
|
|
8572
|
+
const entity = await this.fetchEntityWithAuth(request.path, String(request.entityId), authContext);
|
|
8268
8573
|
this.sendEntityUpdate(clientId, subscriptionId, entity || null);
|
|
8269
8574
|
} catch (error) {
|
|
8270
8575
|
this.sendError(clientId, `Failed to subscribe to entity: ${request.path} ${request.entityId} ${error}`, subscriptionId);
|
|
@@ -8409,87 +8714,77 @@ ${tableRelations.join(",\n")}
|
|
|
8409
8714
|
async fetchCollectionWithAuth(notifyPath, collectionRequest, authContext) {
|
|
8410
8715
|
if (this.driver) {
|
|
8411
8716
|
const collection = this.registry.getCollectionByPath(notifyPath);
|
|
8412
|
-
const
|
|
8413
|
-
|
|
8414
|
-
|
|
8415
|
-
|
|
8416
|
-
|
|
8417
|
-
|
|
8418
|
-
|
|
8419
|
-
|
|
8420
|
-
|
|
8421
|
-
|
|
8717
|
+
const activeAuth = authContext || {
|
|
8718
|
+
userId: "anon",
|
|
8719
|
+
roles: ["anon"]
|
|
8720
|
+
};
|
|
8721
|
+
return await this.db.transaction(async (tx) => {
|
|
8722
|
+
await tx.execute(drizzleOrm.sql`SELECT set_config('app.user_id', ${activeAuth.userId}, true)`);
|
|
8723
|
+
await tx.execute(drizzleOrm.sql`SELECT set_config('app.user_roles', ${activeAuth.roles.join(",")}, true)`);
|
|
8724
|
+
await tx.execute(drizzleOrm.sql`SELECT set_config('app.jwt', ${JSON.stringify({
|
|
8725
|
+
sub: activeAuth.userId,
|
|
8726
|
+
roles: activeAuth.roles
|
|
8727
|
+
})}, true)`);
|
|
8728
|
+
const txEntityService = new EntityService(tx, this.registry);
|
|
8729
|
+
let fetchedEntities;
|
|
8730
|
+
if (collectionRequest.searchString) {
|
|
8731
|
+
fetchedEntities = await txEntityService.searchEntities(notifyPath, collectionRequest.searchString, {
|
|
8732
|
+
filter: collectionRequest.filter,
|
|
8733
|
+
orderBy: collectionRequest.orderBy,
|
|
8734
|
+
order: collectionRequest.order,
|
|
8735
|
+
limit: collectionRequest.limit,
|
|
8736
|
+
databaseId: collectionRequest.databaseId
|
|
8737
|
+
});
|
|
8738
|
+
} else {
|
|
8739
|
+
fetchedEntities = await txEntityService.fetchCollection(notifyPath, {
|
|
8740
|
+
filter: collectionRequest.filter,
|
|
8741
|
+
orderBy: collectionRequest.orderBy,
|
|
8742
|
+
order: collectionRequest.order,
|
|
8743
|
+
limit: collectionRequest.limit,
|
|
8744
|
+
offset: collectionRequest.offset,
|
|
8745
|
+
startAfter: collectionRequest.startAfter,
|
|
8746
|
+
databaseId: collectionRequest.databaseId
|
|
8747
|
+
});
|
|
8748
|
+
}
|
|
8749
|
+
const registryCollection = this.registry.getCollectionByPath(notifyPath);
|
|
8750
|
+
const resolvedCollection = collection ? {
|
|
8751
|
+
...collection,
|
|
8752
|
+
...registryCollection
|
|
8753
|
+
} : registryCollection;
|
|
8754
|
+
const callbacks = resolvedCollection?.callbacks;
|
|
8755
|
+
const propertyCallbacks = resolvedCollection?.properties ? buildPropertyCallbacks(resolvedCollection.properties) : void 0;
|
|
8756
|
+
if (callbacks?.afterRead || propertyCallbacks?.afterRead) {
|
|
8757
|
+
const contextForCallback = {
|
|
8758
|
+
user: {
|
|
8759
|
+
uid: activeAuth.userId,
|
|
8760
|
+
roles: activeAuth.roles
|
|
8761
|
+
},
|
|
8762
|
+
driver: this.driver,
|
|
8763
|
+
data: this.driver && "data" in this.driver ? this.driver.data : void 0
|
|
8764
|
+
};
|
|
8765
|
+
return await Promise.all(fetchedEntities.map(async (entity) => {
|
|
8766
|
+
let processedEntity = entity;
|
|
8767
|
+
if (callbacks?.afterRead) {
|
|
8768
|
+
processedEntity = await callbacks.afterRead({
|
|
8769
|
+
collection: resolvedCollection,
|
|
8770
|
+
path: notifyPath,
|
|
8771
|
+
entity: processedEntity,
|
|
8772
|
+
context: contextForCallback
|
|
8773
|
+
}) ?? processedEntity;
|
|
8774
|
+
}
|
|
8775
|
+
if (propertyCallbacks?.afterRead) {
|
|
8776
|
+
processedEntity = await propertyCallbacks.afterRead({
|
|
8777
|
+
collection: resolvedCollection,
|
|
8778
|
+
path: notifyPath,
|
|
8779
|
+
entity: processedEntity,
|
|
8780
|
+
context: contextForCallback
|
|
8781
|
+
}) ?? processedEntity;
|
|
8782
|
+
}
|
|
8783
|
+
return processedEntity;
|
|
8784
|
+
}));
|
|
8785
|
+
}
|
|
8786
|
+
return fetchedEntities;
|
|
8422
8787
|
});
|
|
8423
|
-
if (authContext) {
|
|
8424
|
-
return await this.db.transaction(async (tx) => {
|
|
8425
|
-
await tx.execute(drizzleOrm.sql`SELECT set_config('app.user_id', ${authContext.userId}, true)`);
|
|
8426
|
-
await tx.execute(drizzleOrm.sql`SELECT set_config('app.user_roles', ${authContext.roles.join(",")}, true)`);
|
|
8427
|
-
await tx.execute(drizzleOrm.sql`SELECT set_config('app.jwt', ${JSON.stringify({
|
|
8428
|
-
sub: authContext.userId,
|
|
8429
|
-
roles: authContext.roles
|
|
8430
|
-
})}, true)`);
|
|
8431
|
-
const txEntityService = new EntityService(tx, this.registry);
|
|
8432
|
-
let fetchedEntities;
|
|
8433
|
-
if (collectionRequest.searchString) {
|
|
8434
|
-
fetchedEntities = await txEntityService.searchEntities(notifyPath, collectionRequest.searchString, {
|
|
8435
|
-
filter: collectionRequest.filter,
|
|
8436
|
-
orderBy: collectionRequest.orderBy,
|
|
8437
|
-
order: collectionRequest.order,
|
|
8438
|
-
limit: collectionRequest.limit,
|
|
8439
|
-
databaseId: collectionRequest.databaseId
|
|
8440
|
-
});
|
|
8441
|
-
} else {
|
|
8442
|
-
fetchedEntities = await txEntityService.fetchCollection(notifyPath, {
|
|
8443
|
-
filter: collectionRequest.filter,
|
|
8444
|
-
orderBy: collectionRequest.orderBy,
|
|
8445
|
-
order: collectionRequest.order,
|
|
8446
|
-
limit: collectionRequest.limit,
|
|
8447
|
-
offset: collectionRequest.offset,
|
|
8448
|
-
startAfter: collectionRequest.startAfter,
|
|
8449
|
-
databaseId: collectionRequest.databaseId
|
|
8450
|
-
});
|
|
8451
|
-
}
|
|
8452
|
-
const registryCollection = this.registry.getCollectionByPath(notifyPath);
|
|
8453
|
-
const resolvedCollection = collection ? {
|
|
8454
|
-
...collection,
|
|
8455
|
-
...registryCollection
|
|
8456
|
-
} : registryCollection;
|
|
8457
|
-
const callbacks = resolvedCollection?.callbacks;
|
|
8458
|
-
const propertyCallbacks = resolvedCollection?.properties ? buildPropertyCallbacks(resolvedCollection.properties) : void 0;
|
|
8459
|
-
if (callbacks?.afterRead || propertyCallbacks?.afterRead) {
|
|
8460
|
-
const contextForCallback = {
|
|
8461
|
-
user: {
|
|
8462
|
-
uid: authContext.userId,
|
|
8463
|
-
roles: authContext.roles
|
|
8464
|
-
},
|
|
8465
|
-
driver: this.driver,
|
|
8466
|
-
data: this.driver ? this.driver.data : void 0
|
|
8467
|
-
};
|
|
8468
|
-
return await Promise.all(fetchedEntities.map(async (entity) => {
|
|
8469
|
-
let processedEntity = entity;
|
|
8470
|
-
if (callbacks?.afterRead) {
|
|
8471
|
-
processedEntity = await callbacks.afterRead({
|
|
8472
|
-
collection: resolvedCollection,
|
|
8473
|
-
path: notifyPath,
|
|
8474
|
-
entity: processedEntity,
|
|
8475
|
-
context: contextForCallback
|
|
8476
|
-
}) ?? processedEntity;
|
|
8477
|
-
}
|
|
8478
|
-
if (propertyCallbacks?.afterRead) {
|
|
8479
|
-
processedEntity = await propertyCallbacks.afterRead({
|
|
8480
|
-
collection: resolvedCollection,
|
|
8481
|
-
path: notifyPath,
|
|
8482
|
-
entity: processedEntity,
|
|
8483
|
-
context: contextForCallback
|
|
8484
|
-
}) ?? processedEntity;
|
|
8485
|
-
}
|
|
8486
|
-
return processedEntity;
|
|
8487
|
-
}));
|
|
8488
|
-
}
|
|
8489
|
-
return fetchedEntities;
|
|
8490
|
-
});
|
|
8491
|
-
}
|
|
8492
|
-
return fetchFn();
|
|
8493
8788
|
}
|
|
8494
8789
|
if (collectionRequest.searchString) {
|
|
8495
8790
|
return await this.entityService.searchEntities(notifyPath, collectionRequest.searchString, {
|
|
@@ -8553,60 +8848,56 @@ ${tableRelations.join(",\n")}
|
|
|
8553
8848
|
async fetchEntityWithAuth(notifyPath, entityId, authContext) {
|
|
8554
8849
|
if (this.driver) {
|
|
8555
8850
|
const collection = this.registry.getCollectionByPath(notifyPath);
|
|
8556
|
-
const
|
|
8557
|
-
|
|
8558
|
-
|
|
8559
|
-
|
|
8560
|
-
|
|
8561
|
-
|
|
8562
|
-
|
|
8563
|
-
|
|
8564
|
-
|
|
8565
|
-
|
|
8566
|
-
|
|
8567
|
-
|
|
8568
|
-
|
|
8569
|
-
|
|
8570
|
-
|
|
8571
|
-
|
|
8572
|
-
|
|
8573
|
-
|
|
8574
|
-
|
|
8575
|
-
|
|
8576
|
-
|
|
8577
|
-
|
|
8578
|
-
const
|
|
8579
|
-
|
|
8580
|
-
|
|
8581
|
-
|
|
8582
|
-
|
|
8583
|
-
|
|
8584
|
-
|
|
8585
|
-
|
|
8586
|
-
|
|
8587
|
-
|
|
8588
|
-
|
|
8589
|
-
|
|
8590
|
-
|
|
8591
|
-
|
|
8592
|
-
|
|
8593
|
-
|
|
8594
|
-
|
|
8595
|
-
|
|
8596
|
-
|
|
8597
|
-
|
|
8598
|
-
|
|
8599
|
-
|
|
8600
|
-
|
|
8601
|
-
context: contextForCallback
|
|
8602
|
-
}) ?? processedEntity;
|
|
8603
|
-
}
|
|
8851
|
+
const activeAuth = authContext || {
|
|
8852
|
+
userId: "anon",
|
|
8853
|
+
roles: ["anon"]
|
|
8854
|
+
};
|
|
8855
|
+
return await this.db.transaction(async (tx) => {
|
|
8856
|
+
await tx.execute(drizzleOrm.sql`SELECT set_config('app.user_id', ${activeAuth.userId}, true)`);
|
|
8857
|
+
await tx.execute(drizzleOrm.sql`SELECT set_config('app.user_roles', ${activeAuth.roles.join(",")}, true)`);
|
|
8858
|
+
await tx.execute(drizzleOrm.sql`SELECT set_config('app.jwt', ${JSON.stringify({
|
|
8859
|
+
sub: activeAuth.userId,
|
|
8860
|
+
roles: activeAuth.roles
|
|
8861
|
+
})}, true)`);
|
|
8862
|
+
const txEntityService = new EntityService(tx, this.registry);
|
|
8863
|
+
let processedEntity = await txEntityService.fetchEntity(notifyPath, entityId, collection?.databaseId);
|
|
8864
|
+
if (processedEntity) {
|
|
8865
|
+
const registryCollection = this.registry.getCollectionByPath(notifyPath);
|
|
8866
|
+
const resolvedCollection = collection ? {
|
|
8867
|
+
...collection,
|
|
8868
|
+
...registryCollection
|
|
8869
|
+
} : registryCollection;
|
|
8870
|
+
const callbacks = resolvedCollection?.callbacks;
|
|
8871
|
+
const propertyCallbacks = resolvedCollection?.properties ? buildPropertyCallbacks(resolvedCollection.properties) : void 0;
|
|
8872
|
+
if (callbacks?.afterRead || propertyCallbacks?.afterRead) {
|
|
8873
|
+
const contextForCallback = {
|
|
8874
|
+
user: {
|
|
8875
|
+
uid: activeAuth.userId,
|
|
8876
|
+
roles: activeAuth.roles
|
|
8877
|
+
},
|
|
8878
|
+
driver: this.driver,
|
|
8879
|
+
data: this.driver && "data" in this.driver ? this.driver.data : void 0
|
|
8880
|
+
};
|
|
8881
|
+
if (callbacks?.afterRead) {
|
|
8882
|
+
processedEntity = await callbacks.afterRead({
|
|
8883
|
+
collection: resolvedCollection,
|
|
8884
|
+
path: notifyPath,
|
|
8885
|
+
entity: processedEntity,
|
|
8886
|
+
context: contextForCallback
|
|
8887
|
+
}) ?? processedEntity;
|
|
8888
|
+
}
|
|
8889
|
+
if (propertyCallbacks?.afterRead) {
|
|
8890
|
+
processedEntity = await propertyCallbacks.afterRead({
|
|
8891
|
+
collection: resolvedCollection,
|
|
8892
|
+
path: notifyPath,
|
|
8893
|
+
entity: processedEntity,
|
|
8894
|
+
context: contextForCallback
|
|
8895
|
+
}) ?? processedEntity;
|
|
8604
8896
|
}
|
|
8605
8897
|
}
|
|
8606
|
-
|
|
8607
|
-
|
|
8608
|
-
}
|
|
8609
|
-
return fetchFn();
|
|
8898
|
+
}
|
|
8899
|
+
return processedEntity;
|
|
8900
|
+
});
|
|
8610
8901
|
}
|
|
8611
8902
|
return await this.entityService.fetchEntity(notifyPath, entityId);
|
|
8612
8903
|
}
|
|
@@ -8674,6 +8965,31 @@ ${tableRelations.join(",\n")}
|
|
|
8674
8965
|
return parentPaths;
|
|
8675
8966
|
}
|
|
8676
8967
|
// =============================================================================
|
|
8968
|
+
// Lifecycle / Cleanup
|
|
8969
|
+
// =============================================================================
|
|
8970
|
+
/**
|
|
8971
|
+
* Gracefully tear down all realtime resources.
|
|
8972
|
+
*
|
|
8973
|
+
* This MUST be called during process shutdown, **before** `pool.end()`.
|
|
8974
|
+
* It ensures:
|
|
8975
|
+
* 1. All debounced refetch timers are cancelled (prevents queries after pool closes).
|
|
8976
|
+
* 2. All subscription state and callbacks are cleared.
|
|
8977
|
+
* 3. The dedicated LISTEN client (outside the pool) is disconnected.
|
|
8978
|
+
* 4. All WebSocket clients are removed (but not forcefully closed — the
|
|
8979
|
+
* HTTP server close will handle that).
|
|
8980
|
+
*/
|
|
8981
|
+
async destroy() {
|
|
8982
|
+
for (const [key, timer] of this.refetchTimers) {
|
|
8983
|
+
clearTimeout(timer);
|
|
8984
|
+
this.refetchTimers.delete(key);
|
|
8985
|
+
}
|
|
8986
|
+
this._subscriptions.clear();
|
|
8987
|
+
this.subscriptionCallbacks.clear();
|
|
8988
|
+
await this.stopListening();
|
|
8989
|
+
this.clients.clear();
|
|
8990
|
+
this.debugLog("🧹 [RealtimeService] destroy() complete — all resources released.");
|
|
8991
|
+
}
|
|
8992
|
+
// =============================================================================
|
|
8677
8993
|
// Cross-Instance LISTEN/NOTIFY
|
|
8678
8994
|
// =============================================================================
|
|
8679
8995
|
/**
|
|
@@ -8814,8 +9130,23 @@ ${tableRelations.join(",\n")}
|
|
|
8814
9130
|
const WS_RATE_LIMIT = 2e3;
|
|
8815
9131
|
const WS_RATE_WINDOW_MS = 6e4;
|
|
8816
9132
|
const ADMIN_ONLY_TYPES = /* @__PURE__ */ new Set(["EXECUTE_SQL", "FETCH_DATABASES", "FETCH_ROLES", "FETCH_UNMAPPED_TABLES", "FETCH_TABLE_METADATA", "FETCH_CURRENT_DATABASE", "CREATE_BRANCH", "DELETE_BRANCH", "LIST_BRANCHES"]);
|
|
9133
|
+
function extractErrorMessage(error) {
|
|
9134
|
+
if (!error) return "Unknown error";
|
|
9135
|
+
if (typeof error === "object") {
|
|
9136
|
+
const err = error;
|
|
9137
|
+
if (err.cause) {
|
|
9138
|
+
return extractErrorMessage(err.cause);
|
|
9139
|
+
}
|
|
9140
|
+
if (typeof err.message === "string") {
|
|
9141
|
+
return err.message;
|
|
9142
|
+
}
|
|
9143
|
+
}
|
|
9144
|
+
return String(error);
|
|
9145
|
+
}
|
|
8817
9146
|
function isAdminSession(session) {
|
|
8818
|
-
if (!session?.user
|
|
9147
|
+
if (!session?.user) return false;
|
|
9148
|
+
if (session.user.isAdmin) return true;
|
|
9149
|
+
if (!session.user.roles) return false;
|
|
8819
9150
|
return session.user.roles.some((r) => {
|
|
8820
9151
|
if (typeof r === "string") return r === "admin";
|
|
8821
9152
|
if (r && typeof r === "object" && "isAdmin" in r) return r.isAdmin;
|
|
@@ -8823,7 +9154,7 @@ ${tableRelations.join(",\n")}
|
|
|
8823
9154
|
return false;
|
|
8824
9155
|
});
|
|
8825
9156
|
}
|
|
8826
|
-
function createPostgresWebSocket(server, realtimeService, driver, authConfig) {
|
|
9157
|
+
function createPostgresWebSocket(server, realtimeService, driver, authConfig, authAdapter) {
|
|
8827
9158
|
const isProduction = process.env.NODE_ENV === "production";
|
|
8828
9159
|
const wsDebug = (...args) => {
|
|
8829
9160
|
if (!isProduction) console.debug(...args);
|
|
@@ -8837,7 +9168,7 @@ ${tableRelations.join(",\n")}
|
|
|
8837
9168
|
}
|
|
8838
9169
|
console.error("❌ [WebSocket Server] Error:", err);
|
|
8839
9170
|
});
|
|
8840
|
-
const requireAuth = authConfig?.requireAuth !== false && authConfig?.jwtSecret;
|
|
9171
|
+
const requireAuth = authAdapter ? true : authConfig?.requireAuth !== false && !!authConfig?.jwtSecret;
|
|
8841
9172
|
wss.on("connection", (ws2) => {
|
|
8842
9173
|
const clientId = `client_${Date.now()}_${Math.random().toString(36).substring(2, 9)}`;
|
|
8843
9174
|
wsDebug(`WebSocket client connected: ${clientId}`);
|
|
@@ -8882,11 +9213,37 @@ ${tableRelations.join(",\n")}
|
|
|
8882
9213
|
sendError("AUTH_ERROR", "INVALID_INPUT", "Token is required");
|
|
8883
9214
|
return;
|
|
8884
9215
|
}
|
|
8885
|
-
|
|
8886
|
-
if (
|
|
9216
|
+
let verifiedUser = null;
|
|
9217
|
+
if (authAdapter) {
|
|
9218
|
+
try {
|
|
9219
|
+
const adapterUser = authAdapter.verifyToken ? await authAdapter.verifyToken(token) : await authAdapter.verifyRequest(new Request("http://localhost/_ws_auth", {
|
|
9220
|
+
headers: {
|
|
9221
|
+
Authorization: `Bearer ${token}`
|
|
9222
|
+
}
|
|
9223
|
+
}));
|
|
9224
|
+
if (adapterUser) {
|
|
9225
|
+
verifiedUser = {
|
|
9226
|
+
userId: adapterUser.uid,
|
|
9227
|
+
roles: adapterUser.roles,
|
|
9228
|
+
isAdmin: adapterUser.isAdmin
|
|
9229
|
+
};
|
|
9230
|
+
}
|
|
9231
|
+
} catch {
|
|
9232
|
+
}
|
|
9233
|
+
} else {
|
|
9234
|
+
const jwtPayload = serverCore.extractUserFromToken(token);
|
|
9235
|
+
if (jwtPayload) {
|
|
9236
|
+
verifiedUser = {
|
|
9237
|
+
userId: jwtPayload.userId,
|
|
9238
|
+
roles: jwtPayload.roles ?? [],
|
|
9239
|
+
isAdmin: (jwtPayload.roles ?? []).some((r) => r === "admin")
|
|
9240
|
+
};
|
|
9241
|
+
}
|
|
9242
|
+
}
|
|
9243
|
+
if (verifiedUser) {
|
|
8887
9244
|
const session = clientSessions.get(clientId);
|
|
8888
9245
|
if (session) {
|
|
8889
|
-
session.user =
|
|
9246
|
+
session.user = verifiedUser;
|
|
8890
9247
|
session.authenticated = true;
|
|
8891
9248
|
}
|
|
8892
9249
|
wsDebug(`[WS] replying AUTH_SUCCESS for requestId ${requestId}`);
|
|
@@ -8894,11 +9251,11 @@ ${tableRelations.join(",\n")}
|
|
|
8894
9251
|
type: "AUTH_SUCCESS",
|
|
8895
9252
|
requestId,
|
|
8896
9253
|
payload: {
|
|
8897
|
-
userId:
|
|
8898
|
-
roles:
|
|
9254
|
+
userId: verifiedUser.userId,
|
|
9255
|
+
roles: verifiedUser.roles
|
|
8899
9256
|
}
|
|
8900
9257
|
}));
|
|
8901
|
-
wsDebug(`🔐 [WebSocket Server] Client ${clientId} authenticated as ${
|
|
9258
|
+
wsDebug(`🔐 [WebSocket Server] Client ${clientId} authenticated as ${verifiedUser.userId}`);
|
|
8902
9259
|
} else {
|
|
8903
9260
|
wsDebug(`[WS] replying AUTH_ERROR for requestId ${requestId} (invalid token)`);
|
|
8904
9261
|
sendError("AUTH_ERROR", "INVALID_TOKEN", "Invalid or expired token");
|
|
@@ -8936,16 +9293,19 @@ ${tableRelations.join(",\n")}
|
|
|
8936
9293
|
}
|
|
8937
9294
|
const getScopedDelegate = async () => {
|
|
8938
9295
|
const session = clientSessions.get(clientId);
|
|
8939
|
-
if (
|
|
9296
|
+
if ("withAuth" in driver && typeof driver.withAuth === "function") {
|
|
8940
9297
|
try {
|
|
8941
|
-
const userForAuth = {
|
|
9298
|
+
const userForAuth = session?.user ? {
|
|
8942
9299
|
uid: session.user.userId,
|
|
8943
9300
|
roles: session.user.roles ?? []
|
|
9301
|
+
} : {
|
|
9302
|
+
uid: "anon",
|
|
9303
|
+
roles: ["anon"]
|
|
8944
9304
|
};
|
|
8945
9305
|
return await driver.withAuth(userForAuth);
|
|
8946
9306
|
} catch (e) {
|
|
8947
|
-
console.error("Failed to create
|
|
8948
|
-
|
|
9307
|
+
console.error("Failed to create RLS scoped delegate for WS request", e);
|
|
9308
|
+
throw new Error("Internal authentication error");
|
|
8949
9309
|
}
|
|
8950
9310
|
}
|
|
8951
9311
|
return driver;
|
|
@@ -9076,24 +9436,29 @@ ${tableRelations.join(",\n")}
|
|
|
9076
9436
|
sql,
|
|
9077
9437
|
options
|
|
9078
9438
|
} = payload;
|
|
9079
|
-
|
|
9080
|
-
|
|
9081
|
-
|
|
9082
|
-
|
|
9083
|
-
|
|
9084
|
-
|
|
9085
|
-
|
|
9086
|
-
|
|
9087
|
-
|
|
9439
|
+
try {
|
|
9440
|
+
const delegate = await getScopedDelegate();
|
|
9441
|
+
const admin = delegate.admin;
|
|
9442
|
+
if (!isSQLAdmin(admin)) {
|
|
9443
|
+
sendError("ERROR", "NOT_SUPPORTED", "SQL execution is not available for this driver.");
|
|
9444
|
+
break;
|
|
9445
|
+
}
|
|
9446
|
+
const result = await admin.executeSql(sql, options);
|
|
9447
|
+
if (process.env.NODE_ENV !== "production") {
|
|
9448
|
+
wsDebug(`⚡ [WebSocket Server] SQL executed. Returned ${Array.isArray(result) ? result.length : "non-array"} rows.`);
|
|
9449
|
+
}
|
|
9450
|
+
const response = {
|
|
9451
|
+
type: "EXECUTE_SQL_SUCCESS",
|
|
9452
|
+
payload: {
|
|
9453
|
+
result
|
|
9454
|
+
},
|
|
9455
|
+
requestId
|
|
9456
|
+
};
|
|
9457
|
+
ws2.send(JSON.stringify(response));
|
|
9458
|
+
} catch (sqlError) {
|
|
9459
|
+
const errMsg = extractErrorMessage(sqlError);
|
|
9460
|
+
sendError("ERROR", "SQL_ERROR", errMsg);
|
|
9088
9461
|
}
|
|
9089
|
-
const response = {
|
|
9090
|
-
type: "EXECUTE_SQL_SUCCESS",
|
|
9091
|
-
payload: {
|
|
9092
|
-
result
|
|
9093
|
-
},
|
|
9094
|
-
requestId
|
|
9095
|
-
};
|
|
9096
|
-
ws2.send(JSON.stringify(response));
|
|
9097
9462
|
}
|
|
9098
9463
|
break;
|
|
9099
9464
|
case "FETCH_DATABASES":
|
|
@@ -9272,7 +9637,10 @@ ${tableRelations.join(",\n")}
|
|
|
9272
9637
|
const authContext = session?.user ? {
|
|
9273
9638
|
userId: session.user.userId,
|
|
9274
9639
|
roles: session.user.roles ?? []
|
|
9275
|
-
} :
|
|
9640
|
+
} : {
|
|
9641
|
+
userId: "anon",
|
|
9642
|
+
roles: ["anon"]
|
|
9643
|
+
};
|
|
9276
9644
|
await realtimeService.handleClientMessage(clientId, {
|
|
9277
9645
|
type,
|
|
9278
9646
|
payload,
|
|
@@ -9416,29 +9784,58 @@ ${tableRelations.join(",\n")}
|
|
|
9416
9784
|
},
|
|
9417
9785
|
config: null
|
|
9418
9786
|
}];
|
|
9419
|
-
async function ensureAuthTablesExist(db) {
|
|
9787
|
+
async function ensureAuthTablesExist(db, registry) {
|
|
9420
9788
|
console.log("🔍 Checking auth tables...");
|
|
9421
9789
|
try {
|
|
9790
|
+
let usersTableName = '"users"';
|
|
9791
|
+
let userIdType = "TEXT";
|
|
9792
|
+
let usersSchema2 = "public";
|
|
9793
|
+
if (registry) {
|
|
9794
|
+
const usersTable = registry.getTable("users");
|
|
9795
|
+
if (usersTable) {
|
|
9796
|
+
const {
|
|
9797
|
+
getTableName: getTableName2
|
|
9798
|
+
} = await import("drizzle-orm");
|
|
9799
|
+
usersSchema2 = pgCore.getTableConfig(usersTable).schema || "public";
|
|
9800
|
+
usersTableName = usersSchema2 === "public" ? `"${getTableName2(usersTable)}"` : `"${usersSchema2}"."${getTableName2(usersTable)}"`;
|
|
9801
|
+
if (usersTable.id) {
|
|
9802
|
+
const col = usersTable.id;
|
|
9803
|
+
const meta = getColumnMeta(col);
|
|
9804
|
+
const columnType = meta.columnType;
|
|
9805
|
+
if (columnType === "PgUUID") {
|
|
9806
|
+
userIdType = "UUID";
|
|
9807
|
+
} else if (columnType === "PgSerial" || columnType === "PgInteger") {
|
|
9808
|
+
userIdType = "INTEGER";
|
|
9809
|
+
} else if (columnType === "PgBigInt" || columnType === "PgBigSerial") {
|
|
9810
|
+
userIdType = "BIGINT";
|
|
9811
|
+
}
|
|
9812
|
+
}
|
|
9813
|
+
}
|
|
9814
|
+
}
|
|
9815
|
+
let rolesSchema = "rebase";
|
|
9816
|
+
if (registry) {
|
|
9817
|
+
const rolesTable = registry.getTable("roles");
|
|
9818
|
+
if (rolesTable) {
|
|
9819
|
+
rolesSchema = pgCore.getTableConfig(rolesTable).schema || "public";
|
|
9820
|
+
}
|
|
9821
|
+
}
|
|
9822
|
+
if (usersSchema2 !== "public") {
|
|
9823
|
+
await db.execute(drizzleOrm.sql`CREATE SCHEMA IF NOT EXISTS ${drizzleOrm.sql.raw(usersSchema2)}`);
|
|
9824
|
+
}
|
|
9825
|
+
if (rolesSchema !== "public" && rolesSchema !== usersSchema2) {
|
|
9826
|
+
await db.execute(drizzleOrm.sql`CREATE SCHEMA IF NOT EXISTS ${drizzleOrm.sql.raw(rolesSchema)}`);
|
|
9827
|
+
}
|
|
9422
9828
|
await db.execute(drizzleOrm.sql`CREATE SCHEMA IF NOT EXISTS rebase`);
|
|
9829
|
+
const userIdentitiesTable = `"${rolesSchema}"."user_identities"`;
|
|
9830
|
+
const rolesTableName = `"${rolesSchema}"."roles"`;
|
|
9831
|
+
const userRolesTableName = `"${rolesSchema}"."user_roles"`;
|
|
9832
|
+
const refreshTokensTableName = `"${rolesSchema}"."refresh_tokens"`;
|
|
9833
|
+
const passwordResetTokensTableName = `"${rolesSchema}"."password_reset_tokens"`;
|
|
9834
|
+
const appConfigTableName = `"${rolesSchema}"."app_config"`;
|
|
9423
9835
|
await db.execute(drizzleOrm.sql`
|
|
9424
|
-
CREATE TABLE IF NOT EXISTS
|
|
9836
|
+
CREATE TABLE IF NOT EXISTS ${drizzleOrm.sql.raw(userIdentitiesTable)} (
|
|
9425
9837
|
id TEXT PRIMARY KEY DEFAULT gen_random_uuid()::text,
|
|
9426
|
-
|
|
9427
|
-
password_hash TEXT,
|
|
9428
|
-
display_name TEXT,
|
|
9429
|
-
photo_url TEXT,
|
|
9430
|
-
created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
|
|
9431
|
-
updated_at TIMESTAMP WITH TIME ZONE DEFAULT NOW()
|
|
9432
|
-
)
|
|
9433
|
-
`);
|
|
9434
|
-
await db.execute(drizzleOrm.sql`
|
|
9435
|
-
CREATE INDEX IF NOT EXISTS idx_users_email
|
|
9436
|
-
ON rebase.users(email)
|
|
9437
|
-
`);
|
|
9438
|
-
await db.execute(drizzleOrm.sql`
|
|
9439
|
-
CREATE TABLE IF NOT EXISTS rebase.user_identities (
|
|
9440
|
-
id TEXT PRIMARY KEY DEFAULT gen_random_uuid()::text,
|
|
9441
|
-
user_id TEXT NOT NULL REFERENCES rebase.users(id) ON DELETE CASCADE,
|
|
9838
|
+
user_id ${drizzleOrm.sql.raw(userIdType)} NOT NULL REFERENCES ${drizzleOrm.sql.raw(usersTableName)}(id) ON DELETE CASCADE,
|
|
9442
9839
|
provider TEXT NOT NULL,
|
|
9443
9840
|
provider_id TEXT NOT NULL,
|
|
9444
9841
|
profile_data JSONB,
|
|
@@ -9449,10 +9846,10 @@ ${tableRelations.join(",\n")}
|
|
|
9449
9846
|
`);
|
|
9450
9847
|
await db.execute(drizzleOrm.sql`
|
|
9451
9848
|
CREATE INDEX IF NOT EXISTS idx_user_identities_user
|
|
9452
|
-
ON
|
|
9849
|
+
ON ${drizzleOrm.sql.raw(userIdentitiesTable)}(user_id)
|
|
9453
9850
|
`);
|
|
9454
9851
|
await db.execute(drizzleOrm.sql`
|
|
9455
|
-
CREATE TABLE IF NOT EXISTS
|
|
9852
|
+
CREATE TABLE IF NOT EXISTS ${drizzleOrm.sql.raw(rolesTableName)} (
|
|
9456
9853
|
id TEXT PRIMARY KEY,
|
|
9457
9854
|
name TEXT NOT NULL,
|
|
9458
9855
|
is_admin BOOLEAN DEFAULT FALSE,
|
|
@@ -9463,20 +9860,20 @@ ${tableRelations.join(",\n")}
|
|
|
9463
9860
|
)
|
|
9464
9861
|
`);
|
|
9465
9862
|
await db.execute(drizzleOrm.sql`
|
|
9466
|
-
CREATE TABLE IF NOT EXISTS
|
|
9467
|
-
user_id
|
|
9468
|
-
role_id TEXT NOT NULL REFERENCES
|
|
9863
|
+
CREATE TABLE IF NOT EXISTS ${drizzleOrm.sql.raw(userRolesTableName)} (
|
|
9864
|
+
user_id ${drizzleOrm.sql.raw(userIdType)} NOT NULL REFERENCES ${drizzleOrm.sql.raw(usersTableName)}(id) ON DELETE CASCADE,
|
|
9865
|
+
role_id TEXT NOT NULL REFERENCES ${drizzleOrm.sql.raw(rolesTableName)}(id) ON DELETE CASCADE,
|
|
9469
9866
|
PRIMARY KEY (user_id, role_id)
|
|
9470
9867
|
)
|
|
9471
9868
|
`);
|
|
9472
9869
|
await db.execute(drizzleOrm.sql`
|
|
9473
9870
|
CREATE INDEX IF NOT EXISTS idx_user_roles_user
|
|
9474
|
-
ON
|
|
9871
|
+
ON ${drizzleOrm.sql.raw(userRolesTableName)}(user_id)
|
|
9475
9872
|
`);
|
|
9476
9873
|
await db.execute(drizzleOrm.sql`
|
|
9477
|
-
CREATE TABLE IF NOT EXISTS
|
|
9874
|
+
CREATE TABLE IF NOT EXISTS ${drizzleOrm.sql.raw(refreshTokensTableName)} (
|
|
9478
9875
|
id TEXT PRIMARY KEY DEFAULT gen_random_uuid()::text,
|
|
9479
|
-
user_id
|
|
9876
|
+
user_id ${drizzleOrm.sql.raw(userIdType)} NOT NULL REFERENCES ${drizzleOrm.sql.raw(usersTableName)}(id) ON DELETE CASCADE,
|
|
9480
9877
|
token_hash TEXT NOT NULL UNIQUE,
|
|
9481
9878
|
expires_at TIMESTAMP WITH TIME ZONE NOT NULL,
|
|
9482
9879
|
user_agent TEXT,
|
|
@@ -9487,16 +9884,16 @@ ${tableRelations.join(",\n")}
|
|
|
9487
9884
|
`);
|
|
9488
9885
|
await db.execute(drizzleOrm.sql`
|
|
9489
9886
|
CREATE INDEX IF NOT EXISTS idx_refresh_tokens_hash
|
|
9490
|
-
ON
|
|
9887
|
+
ON ${drizzleOrm.sql.raw(refreshTokensTableName)}(token_hash)
|
|
9491
9888
|
`);
|
|
9492
9889
|
await db.execute(drizzleOrm.sql`
|
|
9493
9890
|
CREATE INDEX IF NOT EXISTS idx_refresh_tokens_user
|
|
9494
|
-
ON
|
|
9891
|
+
ON ${drizzleOrm.sql.raw(refreshTokensTableName)}(user_id)
|
|
9495
9892
|
`);
|
|
9496
9893
|
await db.execute(drizzleOrm.sql`
|
|
9497
|
-
CREATE TABLE IF NOT EXISTS
|
|
9894
|
+
CREATE TABLE IF NOT EXISTS ${drizzleOrm.sql.raw(passwordResetTokensTableName)} (
|
|
9498
9895
|
id TEXT PRIMARY KEY DEFAULT gen_random_uuid()::text,
|
|
9499
|
-
user_id
|
|
9896
|
+
user_id ${drizzleOrm.sql.raw(userIdType)} NOT NULL REFERENCES ${drizzleOrm.sql.raw(usersTableName)}(id) ON DELETE CASCADE,
|
|
9500
9897
|
token_hash TEXT NOT NULL UNIQUE,
|
|
9501
9898
|
expires_at TIMESTAMP WITH TIME ZONE NOT NULL,
|
|
9502
9899
|
used_at TIMESTAMP WITH TIME ZONE,
|
|
@@ -9505,20 +9902,19 @@ ${tableRelations.join(",\n")}
|
|
|
9505
9902
|
`);
|
|
9506
9903
|
await db.execute(drizzleOrm.sql`
|
|
9507
9904
|
CREATE INDEX IF NOT EXISTS idx_password_reset_tokens_hash
|
|
9508
|
-
ON
|
|
9905
|
+
ON ${drizzleOrm.sql.raw(passwordResetTokensTableName)}(token_hash)
|
|
9509
9906
|
`);
|
|
9510
9907
|
await db.execute(drizzleOrm.sql`
|
|
9511
9908
|
CREATE INDEX IF NOT EXISTS idx_password_reset_tokens_user
|
|
9512
|
-
ON
|
|
9909
|
+
ON ${drizzleOrm.sql.raw(passwordResetTokensTableName)}(user_id)
|
|
9513
9910
|
`);
|
|
9514
9911
|
await db.execute(drizzleOrm.sql`
|
|
9515
|
-
CREATE TABLE IF NOT EXISTS
|
|
9912
|
+
CREATE TABLE IF NOT EXISTS ${drizzleOrm.sql.raw(appConfigTableName)} (
|
|
9516
9913
|
key TEXT PRIMARY KEY,
|
|
9517
9914
|
value JSONB NOT NULL,
|
|
9518
9915
|
updated_at TIMESTAMP WITH TIME ZONE DEFAULT NOW()
|
|
9519
9916
|
)
|
|
9520
9917
|
`);
|
|
9521
|
-
await applyInternalMigrations(db);
|
|
9522
9918
|
await db.execute(drizzleOrm.sql`CREATE SCHEMA IF NOT EXISTS auth`);
|
|
9523
9919
|
await db.transaction(async (tx) => {
|
|
9524
9920
|
await tx.execute(drizzleOrm.sql`SELECT pg_advisory_xact_lock(hashtext('rebase_auth_functions_init'))`);
|
|
@@ -9541,15 +9937,15 @@ ${tableRelations.join(",\n")}
|
|
|
9541
9937
|
$$ LANGUAGE sql STABLE
|
|
9542
9938
|
`);
|
|
9543
9939
|
});
|
|
9544
|
-
await seedDefaultRoles(db);
|
|
9940
|
+
await seedDefaultRoles(db, rolesTableName);
|
|
9545
9941
|
console.log("✅ Auth tables ready");
|
|
9546
9942
|
} catch (error) {
|
|
9547
9943
|
console.error("❌ Failed to create auth tables:", error);
|
|
9548
9944
|
console.warn("⚠️ Continuing without creating auth tables.");
|
|
9549
9945
|
}
|
|
9550
9946
|
}
|
|
9551
|
-
async function seedDefaultRoles(db) {
|
|
9552
|
-
const result = await db.execute(drizzleOrm.sql`SELECT COUNT(*) as count FROM
|
|
9947
|
+
async function seedDefaultRoles(db, rolesTableName) {
|
|
9948
|
+
const result = await db.execute(drizzleOrm.sql`SELECT COUNT(*) as count FROM ${drizzleOrm.sql.raw(rolesTableName)}`);
|
|
9553
9949
|
const count = parseInt(result.rows[0]?.count || "0", 10);
|
|
9554
9950
|
if (count > 0) {
|
|
9555
9951
|
console.log(`📋 Found ${count} existing roles`);
|
|
@@ -9558,7 +9954,7 @@ ${tableRelations.join(",\n")}
|
|
|
9558
9954
|
console.log("🌱 Seeding default roles...");
|
|
9559
9955
|
for (const role of DEFAULT_ROLES) {
|
|
9560
9956
|
await db.execute(drizzleOrm.sql`
|
|
9561
|
-
INSERT INTO
|
|
9957
|
+
INSERT INTO ${drizzleOrm.sql.raw(rolesTableName)} (id, name, is_admin, default_permissions, config)
|
|
9562
9958
|
VALUES (
|
|
9563
9959
|
${role.id},
|
|
9564
9960
|
${role.name},
|
|
@@ -9571,142 +9967,156 @@ ${tableRelations.join(",\n")}
|
|
|
9571
9967
|
}
|
|
9572
9968
|
console.log("✅ Default roles created: admin, editor, viewer");
|
|
9573
9969
|
}
|
|
9574
|
-
|
|
9575
|
-
|
|
9576
|
-
|
|
9577
|
-
|
|
9578
|
-
|
|
9579
|
-
|
|
9580
|
-
|
|
9581
|
-
|
|
9582
|
-
const columnsCheck = await db.execute(drizzleOrm.sql`
|
|
9583
|
-
SELECT column_name
|
|
9584
|
-
FROM information_schema.columns
|
|
9585
|
-
WHERE table_schema='rebase' AND table_name='users' AND column_name IN ('google_id', 'linkedin_id', 'provider')
|
|
9586
|
-
`);
|
|
9587
|
-
const existingColumns = columnsCheck.rows.map((r) => r.column_name);
|
|
9588
|
-
if (existingColumns.includes("google_id")) {
|
|
9589
|
-
await db.execute(drizzleOrm.sql`
|
|
9590
|
-
INSERT INTO rebase.user_identities (user_id, provider, provider_id)
|
|
9591
|
-
SELECT id, 'google', google_id
|
|
9592
|
-
FROM rebase.users
|
|
9593
|
-
WHERE google_id IS NOT NULL
|
|
9594
|
-
ON CONFLICT (provider, provider_id) DO NOTHING
|
|
9595
|
-
`);
|
|
9596
|
-
}
|
|
9597
|
-
if (existingColumns.includes("linkedin_id")) {
|
|
9598
|
-
await db.execute(drizzleOrm.sql`
|
|
9599
|
-
INSERT INTO rebase.user_identities (user_id, provider, provider_id)
|
|
9600
|
-
SELECT id, 'linkedin', linkedin_id
|
|
9601
|
-
FROM rebase.users
|
|
9602
|
-
WHERE linkedin_id IS NOT NULL
|
|
9603
|
-
ON CONFLICT (provider, provider_id) DO NOTHING
|
|
9604
|
-
`);
|
|
9605
|
-
}
|
|
9606
|
-
if (existingColumns.length > 0) {
|
|
9607
|
-
await db.execute(drizzleOrm.sql`
|
|
9608
|
-
ALTER TABLE rebase.users
|
|
9609
|
-
DROP COLUMN IF EXISTS provider,
|
|
9610
|
-
DROP COLUMN IF EXISTS google_id,
|
|
9611
|
-
DROP COLUMN IF EXISTS linkedin_id
|
|
9612
|
-
`);
|
|
9613
|
-
await db.execute(drizzleOrm.sql`DROP INDEX IF EXISTS rebase.idx_users_google_id`);
|
|
9614
|
-
await db.execute(drizzleOrm.sql`DROP INDEX IF EXISTS rebase.idx_users_linkedin_id`);
|
|
9615
|
-
console.log("✅ Migrated to user_identities and dropped legacy columns.");
|
|
9616
|
-
}
|
|
9617
|
-
await db.execute(drizzleOrm.sql`
|
|
9618
|
-
ALTER TABLE rebase.roles
|
|
9619
|
-
ADD COLUMN IF NOT EXISTS collection_permissions JSONB
|
|
9620
|
-
`);
|
|
9621
|
-
await db.execute(drizzleOrm.sql`
|
|
9622
|
-
ALTER TABLE rebase.refresh_tokens
|
|
9623
|
-
ADD COLUMN IF NOT EXISTS user_agent TEXT,
|
|
9624
|
-
ADD COLUMN IF NOT EXISTS ip_address TEXT
|
|
9625
|
-
`);
|
|
9626
|
-
const constraintCheck = await db.execute(drizzleOrm.sql`
|
|
9627
|
-
SELECT 1 FROM information_schema.table_constraints
|
|
9628
|
-
WHERE constraint_name = 'unique_device_session'
|
|
9629
|
-
AND table_schema = 'rebase'
|
|
9630
|
-
AND table_name = 'refresh_tokens'
|
|
9631
|
-
`);
|
|
9632
|
-
if (constraintCheck.rows.length === 0) {
|
|
9633
|
-
try {
|
|
9634
|
-
await db.execute(drizzleOrm.sql`
|
|
9635
|
-
ALTER TABLE rebase.refresh_tokens
|
|
9636
|
-
ADD CONSTRAINT unique_device_session UNIQUE (user_id, user_agent, ip_address)
|
|
9637
|
-
`);
|
|
9638
|
-
console.log("✅ Added unique_device_session constraint");
|
|
9639
|
-
} catch (e) {
|
|
9640
|
-
const errorMessage = e instanceof Error ? e.message : String(e);
|
|
9641
|
-
if (errorMessage.includes("could not create unique index")) {
|
|
9642
|
-
console.warn("⚠️ Duplicate sessions found, cleaning up before adding constraint...");
|
|
9643
|
-
await db.execute(drizzleOrm.sql`
|
|
9644
|
-
DELETE FROM rebase.refresh_tokens a
|
|
9645
|
-
USING rebase.refresh_tokens b
|
|
9646
|
-
WHERE a.user_id = b.user_id
|
|
9647
|
-
AND COALESCE(a.user_agent, '') = COALESCE(b.user_agent, '')
|
|
9648
|
-
AND COALESCE(a.ip_address, '') = COALESCE(b.ip_address, '')
|
|
9649
|
-
AND a.created_at < b.created_at
|
|
9650
|
-
`);
|
|
9651
|
-
await db.execute(drizzleOrm.sql`
|
|
9652
|
-
ALTER TABLE rebase.refresh_tokens
|
|
9653
|
-
ADD CONSTRAINT unique_device_session UNIQUE (user_id, user_agent, ip_address)
|
|
9654
|
-
`).catch((retryErr) => {
|
|
9655
|
-
const retryMessage = retryErr instanceof Error ? retryErr.message : String(retryErr);
|
|
9656
|
-
console.error("Failed to add unique_device_session constraint after cleanup:", retryMessage);
|
|
9657
|
-
});
|
|
9658
|
-
} else {
|
|
9659
|
-
console.error("Constraint migration issue:", errorMessage);
|
|
9660
|
-
}
|
|
9661
|
-
}
|
|
9662
|
-
}
|
|
9663
|
-
} catch (error) {
|
|
9664
|
-
console.error("❌ Failed to run internal migrations:", error);
|
|
9970
|
+
function getColumnKey(table, ...keys2) {
|
|
9971
|
+
if (!table) return void 0;
|
|
9972
|
+
for (const key of keys2) {
|
|
9973
|
+
if (key in table) return key;
|
|
9974
|
+
const snake = toSnakeCase(key);
|
|
9975
|
+
if (snake in table) return snake;
|
|
9976
|
+
const camel = camelCase(key);
|
|
9977
|
+
if (camel in table) return camel;
|
|
9665
9978
|
}
|
|
9979
|
+
return void 0;
|
|
9980
|
+
}
|
|
9981
|
+
function getColumn(table, ...keys2) {
|
|
9982
|
+
if (!table) return void 0;
|
|
9983
|
+
const key = getColumnKey(table, ...keys2);
|
|
9984
|
+
return key ? table[key] : void 0;
|
|
9666
9985
|
}
|
|
9667
9986
|
class UserService {
|
|
9668
|
-
constructor(db) {
|
|
9987
|
+
constructor(db, tableOrTables) {
|
|
9669
9988
|
this.db = db;
|
|
9989
|
+
if (tableOrTables && (tableOrTables.users || tableOrTables.roles)) {
|
|
9990
|
+
const tables = tableOrTables;
|
|
9991
|
+
this.usersTable = tables.users || users;
|
|
9992
|
+
this.userIdentitiesTable = tables.userIdentities || userIdentities;
|
|
9993
|
+
this.userRolesTable = tables.userRoles || userRoles;
|
|
9994
|
+
this.rolesTable = tables.roles || roles;
|
|
9995
|
+
} else {
|
|
9996
|
+
const table = tableOrTables;
|
|
9997
|
+
this.usersTable = table || users;
|
|
9998
|
+
this.userIdentitiesTable = userIdentities;
|
|
9999
|
+
this.userRolesTable = userRoles;
|
|
10000
|
+
this.rolesTable = roles;
|
|
10001
|
+
}
|
|
10002
|
+
}
|
|
10003
|
+
usersTable;
|
|
10004
|
+
userIdentitiesTable;
|
|
10005
|
+
userRolesTable;
|
|
10006
|
+
rolesTable;
|
|
10007
|
+
getQualifiedUsersTableName() {
|
|
10008
|
+
const name = drizzleOrm.getTableName(this.usersTable);
|
|
10009
|
+
const schema = pgCore.getTableConfig(this.usersTable).schema || "public";
|
|
10010
|
+
return `"${schema}"."${name}"`;
|
|
10011
|
+
}
|
|
10012
|
+
mapRowToUser(row) {
|
|
10013
|
+
if (!row) return row;
|
|
10014
|
+
const id = row.id ?? row.uid;
|
|
10015
|
+
const email = row.email;
|
|
10016
|
+
const passwordHash = row.password_hash ?? row.passwordHash ?? null;
|
|
10017
|
+
const displayName = row.display_name ?? row.displayName ?? null;
|
|
10018
|
+
const photoUrl = row.photo_url ?? row.photoUrl ?? row.photoURL ?? null;
|
|
10019
|
+
const emailVerified = row.email_verified ?? row.emailVerified ?? false;
|
|
10020
|
+
const emailVerificationToken = row.email_verification_token ?? row.emailVerificationToken ?? null;
|
|
10021
|
+
const emailVerificationSentAt = row.email_verification_sent_at ?? row.emailVerificationSentAt ?? null;
|
|
10022
|
+
const createdAt = row.created_at ?? row.createdAt;
|
|
10023
|
+
const updatedAt = row.updated_at ?? row.updatedAt;
|
|
10024
|
+
const metadata = {
|
|
10025
|
+
...row.metadata || {}
|
|
10026
|
+
};
|
|
10027
|
+
const knownKeys = /* @__PURE__ */ new Set(["id", "uid", "email", "password_hash", "passwordHash", "display_name", "displayName", "photo_url", "photoUrl", "photoURL", "email_verified", "emailVerified", "email_verification_token", "emailVerificationToken", "email_verification_sent_at", "emailVerificationSentAt", "created_at", "createdAt", "updated_at", "updatedAt", "metadata"]);
|
|
10028
|
+
for (const [key, val] of Object.entries(row)) {
|
|
10029
|
+
if (!knownKeys.has(key)) {
|
|
10030
|
+
const camelKey = camelCase(key);
|
|
10031
|
+
metadata[camelKey] = val;
|
|
10032
|
+
}
|
|
10033
|
+
}
|
|
10034
|
+
return {
|
|
10035
|
+
id,
|
|
10036
|
+
email,
|
|
10037
|
+
passwordHash,
|
|
10038
|
+
displayName,
|
|
10039
|
+
photoUrl,
|
|
10040
|
+
emailVerified,
|
|
10041
|
+
emailVerificationToken,
|
|
10042
|
+
emailVerificationSentAt: emailVerificationSentAt ? new Date(emailVerificationSentAt) : null,
|
|
10043
|
+
createdAt: createdAt ? new Date(createdAt) : /* @__PURE__ */ new Date(),
|
|
10044
|
+
updatedAt: updatedAt ? new Date(updatedAt) : /* @__PURE__ */ new Date(),
|
|
10045
|
+
metadata
|
|
10046
|
+
};
|
|
10047
|
+
}
|
|
10048
|
+
mapPayload(data) {
|
|
10049
|
+
if (!data) return {};
|
|
10050
|
+
const payload = {};
|
|
10051
|
+
const idKey = getColumnKey(this.usersTable, "id") || "id";
|
|
10052
|
+
const emailKey = getColumnKey(this.usersTable, "email") || "email";
|
|
10053
|
+
const passwordHashKey = getColumnKey(this.usersTable, "passwordHash", "password_hash") || "passwordHash";
|
|
10054
|
+
const displayNameKey = getColumnKey(this.usersTable, "displayName", "display_name") || "displayName";
|
|
10055
|
+
const photoUrlKey = getColumnKey(this.usersTable, "photoUrl", "photo_url") || "photoUrl";
|
|
10056
|
+
const emailVerifiedKey = getColumnKey(this.usersTable, "emailVerified", "email_verified") || "emailVerified";
|
|
10057
|
+
const emailVerificationTokenKey = getColumnKey(this.usersTable, "emailVerificationToken", "email_verification_token") || "emailVerificationToken";
|
|
10058
|
+
const emailVerificationSentAtKey = getColumnKey(this.usersTable, "emailVerificationSentAt", "email_verification_sent_at") || "emailVerificationSentAt";
|
|
10059
|
+
const createdAtKey = getColumnKey(this.usersTable, "createdAt", "created_at") || "createdAt";
|
|
10060
|
+
const updatedAtKey = getColumnKey(this.usersTable, "updatedAt", "updated_at") || "updatedAt";
|
|
10061
|
+
const metadataKey = getColumnKey(this.usersTable, "metadata") || "metadata";
|
|
10062
|
+
if ("id" in data) payload[idKey] = data.id;
|
|
10063
|
+
if ("email" in data) payload[emailKey] = data.email;
|
|
10064
|
+
if ("passwordHash" in data) payload[passwordHashKey] = data.passwordHash;
|
|
10065
|
+
if ("displayName" in data) payload[displayNameKey] = data.displayName;
|
|
10066
|
+
if ("photoUrl" in data) payload[photoUrlKey] = data.photoUrl;
|
|
10067
|
+
if ("emailVerified" in data) payload[emailVerifiedKey] = data.emailVerified;
|
|
10068
|
+
if ("emailVerificationToken" in data) payload[emailVerificationTokenKey] = data.emailVerificationToken;
|
|
10069
|
+
if ("emailVerificationSentAt" in data) payload[emailVerificationSentAtKey] = data.emailVerificationSentAt;
|
|
10070
|
+
if ("createdAt" in data) payload[createdAtKey] = data.createdAt;
|
|
10071
|
+
if ("updatedAt" in data) payload[updatedAtKey] = data.updatedAt;
|
|
10072
|
+
const metadata = {
|
|
10073
|
+
...data.metadata || {}
|
|
10074
|
+
};
|
|
10075
|
+
const remainingMetadata = {};
|
|
10076
|
+
for (const [key, val] of Object.entries(metadata)) {
|
|
10077
|
+
const tableColKey = getColumnKey(this.usersTable, key);
|
|
10078
|
+
if (tableColKey && tableColKey !== idKey && tableColKey !== emailKey && tableColKey !== passwordHashKey && tableColKey !== displayNameKey && tableColKey !== photoUrlKey && tableColKey !== emailVerifiedKey && tableColKey !== emailVerificationTokenKey && tableColKey !== emailVerificationSentAtKey && tableColKey !== createdAtKey && tableColKey !== updatedAtKey && tableColKey !== metadataKey) {
|
|
10079
|
+
payload[tableColKey] = val;
|
|
10080
|
+
} else {
|
|
10081
|
+
remainingMetadata[key] = val;
|
|
10082
|
+
}
|
|
10083
|
+
}
|
|
10084
|
+
if (metadataKey in this.usersTable) {
|
|
10085
|
+
payload[metadataKey] = remainingMetadata;
|
|
10086
|
+
}
|
|
10087
|
+
return payload;
|
|
9670
10088
|
}
|
|
9671
10089
|
async createUser(data) {
|
|
9672
|
-
const
|
|
9673
|
-
|
|
10090
|
+
const payload = this.mapPayload(data);
|
|
10091
|
+
const [row] = await this.db.insert(this.usersTable).values(payload).returning();
|
|
10092
|
+
return this.mapRowToUser(row);
|
|
9674
10093
|
}
|
|
9675
10094
|
async getUserById(id) {
|
|
9676
|
-
const
|
|
9677
|
-
|
|
10095
|
+
const idCol = getColumn(this.usersTable, "id");
|
|
10096
|
+
if (!idCol) return null;
|
|
10097
|
+
const [row] = await this.db.select().from(this.usersTable).where(drizzleOrm.eq(idCol, id));
|
|
10098
|
+
return row ? this.mapRowToUser(row) : null;
|
|
9678
10099
|
}
|
|
9679
10100
|
async getUserByEmail(email) {
|
|
9680
|
-
const
|
|
9681
|
-
|
|
10101
|
+
const emailCol = getColumn(this.usersTable, "email");
|
|
10102
|
+
if (!emailCol) return null;
|
|
10103
|
+
const [row] = await this.db.select().from(this.usersTable).where(drizzleOrm.eq(emailCol, email.toLowerCase()));
|
|
10104
|
+
return row ? this.mapRowToUser(row) : null;
|
|
9682
10105
|
}
|
|
9683
10106
|
async getUserByIdentity(provider, providerId) {
|
|
9684
|
-
const
|
|
9685
|
-
|
|
9686
|
-
|
|
9687
|
-
|
|
9688
|
-
|
|
9689
|
-
|
|
9690
|
-
|
|
9691
|
-
if (result.rows.length === 0) return null;
|
|
9692
|
-
const row = result.rows[0];
|
|
9693
|
-
return {
|
|
9694
|
-
id: row.id,
|
|
9695
|
-
email: row.email,
|
|
9696
|
-
passwordHash: row.password_hash ?? null,
|
|
9697
|
-
displayName: row.display_name ?? null,
|
|
9698
|
-
photoUrl: row.photo_url ?? null,
|
|
9699
|
-
emailVerified: row.email_verified ?? false,
|
|
9700
|
-
emailVerificationToken: row.email_verification_token ?? null,
|
|
9701
|
-
emailVerificationSentAt: row.email_verification_sent_at ?? null,
|
|
9702
|
-
createdAt: row.created_at,
|
|
9703
|
-
updatedAt: row.updated_at
|
|
9704
|
-
};
|
|
10107
|
+
const userIdCol = getColumn(this.usersTable, "id");
|
|
10108
|
+
if (!userIdCol) return null;
|
|
10109
|
+
const result = await this.db.select({
|
|
10110
|
+
user: this.usersTable
|
|
10111
|
+
}).from(this.usersTable).innerJoin(this.userIdentitiesTable, drizzleOrm.eq(userIdCol, this.userIdentitiesTable.userId)).where(drizzleOrm.sql`${this.userIdentitiesTable.provider} = ${provider} AND ${this.userIdentitiesTable.providerId} = ${providerId}`).limit(1);
|
|
10112
|
+
if (result.length === 0) return null;
|
|
10113
|
+
return this.mapRowToUser(result[0].user);
|
|
9705
10114
|
}
|
|
9706
10115
|
async getUserIdentities(userId) {
|
|
10116
|
+
const schema = pgCore.getTableConfig(this.userIdentitiesTable).schema || "public";
|
|
9707
10117
|
const result = await this.db.execute(drizzleOrm.sql`
|
|
9708
10118
|
SELECT id, user_id, provider, provider_id, profile_data, created_at, updated_at
|
|
9709
|
-
FROM
|
|
10119
|
+
FROM ${drizzleOrm.sql.raw(`"${schema}"."user_identities"`)}
|
|
9710
10120
|
WHERE user_id = ${userId}
|
|
9711
10121
|
`);
|
|
9712
10122
|
return result.rows.map((row) => ({
|
|
@@ -9720,27 +10130,32 @@ ${tableRelations.join(",\n")}
|
|
|
9720
10130
|
}));
|
|
9721
10131
|
}
|
|
9722
10132
|
async linkUserIdentity(userId, provider, providerId, profileData) {
|
|
9723
|
-
await this.db.insert(
|
|
10133
|
+
await this.db.insert(this.userIdentitiesTable).values({
|
|
9724
10134
|
userId,
|
|
9725
10135
|
provider,
|
|
9726
10136
|
providerId,
|
|
9727
10137
|
profileData: profileData || null
|
|
9728
10138
|
}).onConflictDoNothing({
|
|
9729
|
-
target: [
|
|
10139
|
+
target: [this.userIdentitiesTable.provider, this.userIdentitiesTable.providerId]
|
|
9730
10140
|
});
|
|
9731
10141
|
}
|
|
9732
10142
|
async updateUser(id, data) {
|
|
9733
|
-
const
|
|
9734
|
-
|
|
9735
|
-
|
|
9736
|
-
|
|
9737
|
-
|
|
10143
|
+
const idCol = getColumn(this.usersTable, "id");
|
|
10144
|
+
if (!idCol) return null;
|
|
10145
|
+
const payload = this.mapPayload(data);
|
|
10146
|
+
const updatedAtKey = getColumnKey(this.usersTable, "updatedAt", "updated_at") || "updatedAt";
|
|
10147
|
+
payload[updatedAtKey] = /* @__PURE__ */ new Date();
|
|
10148
|
+
const [row] = await this.db.update(this.usersTable).set(payload).where(drizzleOrm.eq(idCol, id)).returning();
|
|
10149
|
+
return row ? this.mapRowToUser(row) : null;
|
|
9738
10150
|
}
|
|
9739
10151
|
async deleteUser(id) {
|
|
9740
|
-
|
|
10152
|
+
const idCol = getColumn(this.usersTable, "id");
|
|
10153
|
+
if (!idCol) return;
|
|
10154
|
+
await this.db.delete(this.usersTable).where(drizzleOrm.eq(idCol, id));
|
|
9741
10155
|
}
|
|
9742
10156
|
async listUsers() {
|
|
9743
|
-
|
|
10157
|
+
const rows = await this.db.select().from(this.usersTable);
|
|
10158
|
+
return rows.map((row) => this.mapRowToUser(row));
|
|
9744
10159
|
}
|
|
9745
10160
|
async listUsersPaginated(options) {
|
|
9746
10161
|
const limit = options?.limit ?? 25;
|
|
@@ -9749,49 +10164,40 @@ ${tableRelations.join(",\n")}
|
|
|
9749
10164
|
const orderBy = options?.orderBy || "createdAt";
|
|
9750
10165
|
const orderDir = options?.orderDir || "desc";
|
|
9751
10166
|
const roleId = options?.roleId;
|
|
9752
|
-
const
|
|
9753
|
-
|
|
9754
|
-
displayName: "display_name",
|
|
9755
|
-
createdAt: "created_at",
|
|
9756
|
-
updatedAt: "updated_at",
|
|
9757
|
-
provider: "provider"
|
|
9758
|
-
};
|
|
9759
|
-
const orderColumn = columnMap[orderBy] || "created_at";
|
|
10167
|
+
const orderCol = getColumn(this.usersTable, orderBy);
|
|
10168
|
+
const orderColumn = orderCol ? orderCol.name : "created_at";
|
|
9760
10169
|
const direction = orderDir === "asc" ? drizzleOrm.sql`ASC` : drizzleOrm.sql`DESC`;
|
|
10170
|
+
const emailCol = getColumn(this.usersTable, "email");
|
|
10171
|
+
const emailColumn = emailCol ? emailCol.name : "email";
|
|
10172
|
+
const displayNameCol = getColumn(this.usersTable, "displayName", "display_name");
|
|
10173
|
+
const displayNameColumn = displayNameCol ? displayNameCol.name : "display_name";
|
|
10174
|
+
const idCol = getColumn(this.usersTable, "id");
|
|
10175
|
+
const idColumn = idCol ? idCol.name : "id";
|
|
10176
|
+
const usersTableName = this.getQualifiedUsersTableName();
|
|
10177
|
+
const rolesSchema = pgCore.getTableConfig(this.userRolesTable).schema || "public";
|
|
9761
10178
|
const conditions = [];
|
|
9762
10179
|
if (roleId) {
|
|
9763
|
-
conditions.push(drizzleOrm.sql`EXISTS (SELECT 1 FROM
|
|
10180
|
+
conditions.push(drizzleOrm.sql`EXISTS (SELECT 1 FROM ${drizzleOrm.sql.raw(`"${rolesSchema}"."user_roles"`)} ur WHERE ur.user_id = ${drizzleOrm.sql.raw(usersTableName)}.${drizzleOrm.sql.raw(idColumn)} AND ur.role_id = ${roleId})`);
|
|
9764
10181
|
}
|
|
9765
10182
|
if (search) {
|
|
9766
10183
|
const pattern = `%${search}%`;
|
|
9767
|
-
conditions.push(drizzleOrm.sql`(
|
|
10184
|
+
conditions.push(drizzleOrm.sql`(${drizzleOrm.sql.raw(usersTableName)}.${drizzleOrm.sql.raw(emailColumn)} ILIKE ${pattern} OR ${drizzleOrm.sql.raw(usersTableName)}.${drizzleOrm.sql.raw(displayNameColumn)} ILIKE ${pattern})`);
|
|
9768
10185
|
}
|
|
9769
10186
|
const whereClause = conditions.length > 0 ? drizzleOrm.sql`WHERE ${drizzleOrm.sql.join(conditions, drizzleOrm.sql` AND `)}` : drizzleOrm.sql``;
|
|
9770
|
-
const orderByClause = roleId ? drizzleOrm.sql`ORDER BY ${drizzleOrm.sql.raw(orderColumn)} ${direction}` : drizzleOrm.sql`ORDER BY (SELECT count(*) FROM
|
|
10187
|
+
const orderByClause = roleId ? drizzleOrm.sql`ORDER BY ${drizzleOrm.sql.raw(usersTableName)}.${drizzleOrm.sql.raw(orderColumn)} ${direction}` : drizzleOrm.sql`ORDER BY (SELECT count(*) FROM ${drizzleOrm.sql.raw(`"${rolesSchema}"."user_roles"`)} ur WHERE ur.user_id = ${drizzleOrm.sql.raw(usersTableName)}.${drizzleOrm.sql.raw(idColumn)}) DESC, ${drizzleOrm.sql.raw(usersTableName)}.${drizzleOrm.sql.raw(orderColumn)} ${direction}`;
|
|
9771
10188
|
const countResult = await this.db.execute(drizzleOrm.sql`
|
|
9772
|
-
SELECT count(*)::int as total FROM
|
|
10189
|
+
SELECT count(*)::int as total FROM ${drizzleOrm.sql.raw(usersTableName)}
|
|
9773
10190
|
${whereClause}
|
|
9774
10191
|
`);
|
|
9775
10192
|
const total = countResult.rows[0].total;
|
|
9776
10193
|
const dataResult = await this.db.execute(drizzleOrm.sql`
|
|
9777
|
-
SELECT * FROM
|
|
10194
|
+
SELECT * FROM ${drizzleOrm.sql.raw(usersTableName)}
|
|
9778
10195
|
${whereClause}
|
|
9779
10196
|
${orderByClause}
|
|
9780
10197
|
LIMIT ${limit} OFFSET ${offset}
|
|
9781
10198
|
`);
|
|
9782
10199
|
const rows = dataResult.rows;
|
|
9783
|
-
const mappedUsers = rows.map((row) => (
|
|
9784
|
-
id: row.id,
|
|
9785
|
-
email: row.email,
|
|
9786
|
-
passwordHash: row.password_hash ?? row.passwordHash ?? null,
|
|
9787
|
-
displayName: row.display_name ?? row.displayName ?? null,
|
|
9788
|
-
photoUrl: row.photo_url ?? row.photoUrl ?? null,
|
|
9789
|
-
emailVerified: row.email_verified ?? row.emailVerified ?? false,
|
|
9790
|
-
emailVerificationToken: row.email_verification_token ?? row.emailVerificationToken ?? null,
|
|
9791
|
-
emailVerificationSentAt: row.email_verification_sent_at ?? row.emailVerificationSentAt ?? null,
|
|
9792
|
-
createdAt: row.created_at ?? row.createdAt,
|
|
9793
|
-
updatedAt: row.updated_at ?? row.updatedAt
|
|
9794
|
-
}));
|
|
10200
|
+
const mappedUsers = rows.map((row) => this.mapRowToUser(row));
|
|
9795
10201
|
return {
|
|
9796
10202
|
users: mappedUsers,
|
|
9797
10203
|
total,
|
|
@@ -9803,46 +10209,63 @@ ${tableRelations.join(",\n")}
|
|
|
9803
10209
|
* Update user's password hash
|
|
9804
10210
|
*/
|
|
9805
10211
|
async updatePassword(id, passwordHash) {
|
|
9806
|
-
|
|
9807
|
-
|
|
9808
|
-
|
|
9809
|
-
|
|
10212
|
+
const idCol = getColumn(this.usersTable, "id");
|
|
10213
|
+
if (!idCol) return;
|
|
10214
|
+
const passwordHashColKey = getColumnKey(this.usersTable, "passwordHash", "password_hash") || "passwordHash";
|
|
10215
|
+
const updatedAtColKey = getColumnKey(this.usersTable, "updatedAt", "updated_at") || "updatedAt";
|
|
10216
|
+
await this.db.update(this.usersTable).set({
|
|
10217
|
+
[passwordHashColKey]: passwordHash,
|
|
10218
|
+
[updatedAtColKey]: /* @__PURE__ */ new Date()
|
|
10219
|
+
}).where(drizzleOrm.eq(idCol, id));
|
|
9810
10220
|
}
|
|
9811
10221
|
/**
|
|
9812
10222
|
* Set email verification status
|
|
9813
10223
|
*/
|
|
9814
10224
|
async setEmailVerified(id, verified) {
|
|
9815
|
-
|
|
9816
|
-
|
|
9817
|
-
|
|
9818
|
-
|
|
9819
|
-
|
|
10225
|
+
const idCol = getColumn(this.usersTable, "id");
|
|
10226
|
+
if (!idCol) return;
|
|
10227
|
+
const emailVerifiedColKey = getColumnKey(this.usersTable, "emailVerified", "email_verified") || "emailVerified";
|
|
10228
|
+
const emailVerificationTokenColKey = getColumnKey(this.usersTable, "emailVerificationToken", "email_verification_token") || "emailVerificationToken";
|
|
10229
|
+
const updatedAtColKey = getColumnKey(this.usersTable, "updatedAt", "updated_at") || "updatedAt";
|
|
10230
|
+
await this.db.update(this.usersTable).set({
|
|
10231
|
+
[emailVerifiedColKey]: verified,
|
|
10232
|
+
[emailVerificationTokenColKey]: null,
|
|
10233
|
+
[updatedAtColKey]: /* @__PURE__ */ new Date()
|
|
10234
|
+
}).where(drizzleOrm.eq(idCol, id));
|
|
9820
10235
|
}
|
|
9821
10236
|
/**
|
|
9822
10237
|
* Set email verification token
|
|
9823
10238
|
*/
|
|
9824
10239
|
async setVerificationToken(id, token) {
|
|
9825
|
-
|
|
9826
|
-
|
|
9827
|
-
|
|
9828
|
-
|
|
9829
|
-
|
|
10240
|
+
const idCol = getColumn(this.usersTable, "id");
|
|
10241
|
+
if (!idCol) return;
|
|
10242
|
+
const emailVerificationTokenColKey = getColumnKey(this.usersTable, "emailVerificationToken", "email_verification_token") || "emailVerificationToken";
|
|
10243
|
+
const emailVerificationSentAtColKey = getColumnKey(this.usersTable, "emailVerificationSentAt", "email_verification_sent_at") || "emailVerificationSentAt";
|
|
10244
|
+
const updatedAtColKey = getColumnKey(this.usersTable, "updatedAt", "updated_at") || "updatedAt";
|
|
10245
|
+
await this.db.update(this.usersTable).set({
|
|
10246
|
+
[emailVerificationTokenColKey]: token,
|
|
10247
|
+
[emailVerificationSentAtColKey]: token ? /* @__PURE__ */ new Date() : null,
|
|
10248
|
+
[updatedAtColKey]: /* @__PURE__ */ new Date()
|
|
10249
|
+
}).where(drizzleOrm.eq(idCol, id));
|
|
9830
10250
|
}
|
|
9831
10251
|
/**
|
|
9832
10252
|
* Find user by email verification token
|
|
9833
10253
|
*/
|
|
9834
10254
|
async getUserByVerificationToken(token) {
|
|
9835
|
-
const
|
|
9836
|
-
|
|
10255
|
+
const tokenCol = getColumn(this.usersTable, "emailVerificationToken", "email_verification_token");
|
|
10256
|
+
if (!tokenCol) return null;
|
|
10257
|
+
const [row] = await this.db.select().from(this.usersTable).where(drizzleOrm.eq(tokenCol, token));
|
|
10258
|
+
return row ? this.mapRowToUser(row) : null;
|
|
9837
10259
|
}
|
|
9838
10260
|
/**
|
|
9839
10261
|
* Get roles for a user from database
|
|
9840
10262
|
*/
|
|
9841
10263
|
async getUserRoles(userId) {
|
|
10264
|
+
const rolesSchema = pgCore.getTableConfig(this.rolesTable).schema || "public";
|
|
9842
10265
|
const result = await this.db.execute(drizzleOrm.sql`
|
|
9843
10266
|
SELECT r.id, r.name, r.is_admin, r.default_permissions, r.collection_permissions, r.config
|
|
9844
|
-
FROM
|
|
9845
|
-
INNER JOIN
|
|
10267
|
+
FROM ${drizzleOrm.sql.raw(`"${rolesSchema}"."roles"`)} r
|
|
10268
|
+
INNER JOIN ${drizzleOrm.sql.raw(`"${rolesSchema}"."user_roles"`)} ur ON r.id = ur.role_id
|
|
9846
10269
|
WHERE ur.user_id = ${userId}
|
|
9847
10270
|
`);
|
|
9848
10271
|
return result.rows.map((row) => ({
|
|
@@ -9865,10 +10288,11 @@ ${tableRelations.join(",\n")}
|
|
|
9865
10288
|
* Set roles for a user
|
|
9866
10289
|
*/
|
|
9867
10290
|
async setUserRoles(userId, roleIds) {
|
|
9868
|
-
|
|
10291
|
+
const rolesSchema = pgCore.getTableConfig(this.userRolesTable).schema || "public";
|
|
10292
|
+
await this.db.execute(drizzleOrm.sql`DELETE FROM ${drizzleOrm.sql.raw(`"${rolesSchema}"."user_roles"`)} WHERE user_id = ${userId}`);
|
|
9869
10293
|
for (const roleId of roleIds) {
|
|
9870
10294
|
await this.db.execute(drizzleOrm.sql`
|
|
9871
|
-
INSERT INTO
|
|
10295
|
+
INSERT INTO ${drizzleOrm.sql.raw(`"${rolesSchema}"."user_roles"`)} (user_id, role_id)
|
|
9872
10296
|
VALUES (${userId}, ${roleId})
|
|
9873
10297
|
ON CONFLICT DO NOTHING
|
|
9874
10298
|
`);
|
|
@@ -9878,8 +10302,9 @@ ${tableRelations.join(",\n")}
|
|
|
9878
10302
|
* Assign a specific role to new user
|
|
9879
10303
|
*/
|
|
9880
10304
|
async assignDefaultRole(userId, roleId) {
|
|
10305
|
+
const rolesSchema = pgCore.getTableConfig(this.userRolesTable).schema || "public";
|
|
9881
10306
|
await this.db.execute(drizzleOrm.sql`
|
|
9882
|
-
INSERT INTO
|
|
10307
|
+
INSERT INTO ${drizzleOrm.sql.raw(`"${rolesSchema}"."user_roles"`)} (user_id, role_id)
|
|
9883
10308
|
VALUES (${userId}, ${roleId})
|
|
9884
10309
|
ON CONFLICT DO NOTHING
|
|
9885
10310
|
`);
|
|
@@ -9898,13 +10323,25 @@ ${tableRelations.join(",\n")}
|
|
|
9898
10323
|
}
|
|
9899
10324
|
}
|
|
9900
10325
|
class RoleService {
|
|
9901
|
-
constructor(db) {
|
|
10326
|
+
constructor(db, tableOrTables) {
|
|
9902
10327
|
this.db = db;
|
|
10328
|
+
if (tableOrTables && (tableOrTables.roles || tableOrTables.users)) {
|
|
10329
|
+
this.rolesTable = tableOrTables.roles || roles;
|
|
10330
|
+
} else {
|
|
10331
|
+
this.rolesTable = tableOrTables || roles;
|
|
10332
|
+
}
|
|
10333
|
+
}
|
|
10334
|
+
rolesTable;
|
|
10335
|
+
getQualifiedRolesTableName() {
|
|
10336
|
+
const name = drizzleOrm.getTableName(this.rolesTable);
|
|
10337
|
+
const schema = pgCore.getTableConfig(this.rolesTable).schema || "public";
|
|
10338
|
+
return `"${schema}"."${name}"`;
|
|
9903
10339
|
}
|
|
9904
10340
|
async getRoleById(id) {
|
|
10341
|
+
const tableName = this.getQualifiedRolesTableName();
|
|
9905
10342
|
const result = await this.db.execute(drizzleOrm.sql`
|
|
9906
10343
|
SELECT id, name, is_admin, default_permissions, collection_permissions, config
|
|
9907
|
-
FROM
|
|
10344
|
+
FROM ${drizzleOrm.sql.raw(tableName)}
|
|
9908
10345
|
WHERE id = ${id}
|
|
9909
10346
|
`);
|
|
9910
10347
|
if (result.rows.length === 0) return null;
|
|
@@ -9919,9 +10356,10 @@ ${tableRelations.join(",\n")}
|
|
|
9919
10356
|
};
|
|
9920
10357
|
}
|
|
9921
10358
|
async listRoles() {
|
|
10359
|
+
const tableName = this.getQualifiedRolesTableName();
|
|
9922
10360
|
const result = await this.db.execute(drizzleOrm.sql`
|
|
9923
10361
|
SELECT id, name, is_admin, default_permissions, collection_permissions, config
|
|
9924
|
-
FROM
|
|
10362
|
+
FROM ${drizzleOrm.sql.raw(tableName)}
|
|
9925
10363
|
ORDER BY name
|
|
9926
10364
|
`);
|
|
9927
10365
|
return result.rows.map((row) => ({
|
|
@@ -9934,8 +10372,9 @@ ${tableRelations.join(",\n")}
|
|
|
9934
10372
|
}));
|
|
9935
10373
|
}
|
|
9936
10374
|
async createRole(data) {
|
|
10375
|
+
const tableName = this.getQualifiedRolesTableName();
|
|
9937
10376
|
const result = await this.db.execute(drizzleOrm.sql`
|
|
9938
|
-
INSERT INTO
|
|
10377
|
+
INSERT INTO ${drizzleOrm.sql.raw(tableName)} (id, name, is_admin, default_permissions, collection_permissions, config)
|
|
9939
10378
|
VALUES (
|
|
9940
10379
|
${data.id},
|
|
9941
10380
|
${data.name},
|
|
@@ -9959,8 +10398,9 @@ ${tableRelations.join(",\n")}
|
|
|
9959
10398
|
async updateRole(id, data) {
|
|
9960
10399
|
const existing = await this.getRoleById(id);
|
|
9961
10400
|
if (!existing) return null;
|
|
10401
|
+
const tableName = this.getQualifiedRolesTableName();
|
|
9962
10402
|
await this.db.execute(drizzleOrm.sql`
|
|
9963
|
-
UPDATE
|
|
10403
|
+
UPDATE ${drizzleOrm.sql.raw(tableName)}
|
|
9964
10404
|
SET
|
|
9965
10405
|
name = ${data.name ?? existing.name},
|
|
9966
10406
|
is_admin = ${data.isAdmin ?? existing.isAdmin},
|
|
@@ -9972,23 +10412,36 @@ ${tableRelations.join(",\n")}
|
|
|
9972
10412
|
return this.getRoleById(id);
|
|
9973
10413
|
}
|
|
9974
10414
|
async deleteRole(id) {
|
|
9975
|
-
|
|
10415
|
+
const tableName = this.getQualifiedRolesTableName();
|
|
10416
|
+
await this.db.execute(drizzleOrm.sql`DELETE FROM ${drizzleOrm.sql.raw(tableName)} WHERE id = ${id}`);
|
|
9976
10417
|
}
|
|
9977
10418
|
}
|
|
9978
10419
|
class RefreshTokenService {
|
|
9979
|
-
constructor(db) {
|
|
10420
|
+
constructor(db, tableOrTables) {
|
|
9980
10421
|
this.db = db;
|
|
10422
|
+
if (tableOrTables && (tableOrTables.refreshTokens || tableOrTables.users)) {
|
|
10423
|
+
this.refreshTokensTable = tableOrTables.refreshTokens || refreshTokens;
|
|
10424
|
+
} else {
|
|
10425
|
+
this.refreshTokensTable = tableOrTables || refreshTokens;
|
|
10426
|
+
}
|
|
10427
|
+
}
|
|
10428
|
+
refreshTokensTable;
|
|
10429
|
+
getQualifiedRefreshTokensTableName() {
|
|
10430
|
+
const name = drizzleOrm.getTableName(this.refreshTokensTable);
|
|
10431
|
+
const schema = pgCore.getTableConfig(this.refreshTokensTable).schema || "public";
|
|
10432
|
+
return `"${schema}"."${name}"`;
|
|
9981
10433
|
}
|
|
9982
10434
|
async createToken(userId, tokenHash, expiresAt, userAgent, ipAddress) {
|
|
9983
10435
|
const safeUserAgent = userAgent || "";
|
|
9984
10436
|
const safeIpAddress = ipAddress || "";
|
|
10437
|
+
const tableName = this.getQualifiedRefreshTokensTableName();
|
|
9985
10438
|
await this.db.execute(drizzleOrm.sql`
|
|
9986
|
-
DELETE FROM
|
|
10439
|
+
DELETE FROM ${drizzleOrm.sql.raw(tableName)}
|
|
9987
10440
|
WHERE user_id = ${userId}
|
|
9988
10441
|
AND user_agent = ${safeUserAgent}
|
|
9989
10442
|
AND ip_address = ${safeIpAddress}
|
|
9990
10443
|
`);
|
|
9991
|
-
await this.db.insert(
|
|
10444
|
+
await this.db.insert(this.refreshTokensTable).values({
|
|
9992
10445
|
userId,
|
|
9993
10446
|
tokenHash,
|
|
9994
10447
|
expiresAt,
|
|
@@ -9998,51 +10451,63 @@ ${tableRelations.join(",\n")}
|
|
|
9998
10451
|
}
|
|
9999
10452
|
async findByHash(tokenHash) {
|
|
10000
10453
|
const [token] = await this.db.select({
|
|
10001
|
-
id:
|
|
10002
|
-
userId:
|
|
10003
|
-
tokenHash:
|
|
10004
|
-
expiresAt:
|
|
10005
|
-
createdAt:
|
|
10006
|
-
userAgent:
|
|
10007
|
-
ipAddress:
|
|
10008
|
-
}).from(
|
|
10454
|
+
id: this.refreshTokensTable.id,
|
|
10455
|
+
userId: this.refreshTokensTable.userId,
|
|
10456
|
+
tokenHash: this.refreshTokensTable.tokenHash,
|
|
10457
|
+
expiresAt: this.refreshTokensTable.expiresAt,
|
|
10458
|
+
createdAt: this.refreshTokensTable.createdAt,
|
|
10459
|
+
userAgent: this.refreshTokensTable.userAgent,
|
|
10460
|
+
ipAddress: this.refreshTokensTable.ipAddress
|
|
10461
|
+
}).from(this.refreshTokensTable).where(drizzleOrm.eq(this.refreshTokensTable.tokenHash, tokenHash));
|
|
10009
10462
|
return token || null;
|
|
10010
10463
|
}
|
|
10011
10464
|
async deleteByHash(tokenHash) {
|
|
10012
|
-
await this.db.delete(
|
|
10465
|
+
await this.db.delete(this.refreshTokensTable).where(drizzleOrm.eq(this.refreshTokensTable.tokenHash, tokenHash));
|
|
10013
10466
|
}
|
|
10014
10467
|
async deleteAllForUser(userId) {
|
|
10015
|
-
await this.db.delete(
|
|
10468
|
+
await this.db.delete(this.refreshTokensTable).where(drizzleOrm.eq(this.refreshTokensTable.userId, userId));
|
|
10016
10469
|
}
|
|
10017
10470
|
async listForUser(userId) {
|
|
10018
10471
|
const tokens = await this.db.select({
|
|
10019
|
-
id:
|
|
10020
|
-
userId:
|
|
10021
|
-
tokenHash:
|
|
10022
|
-
expiresAt:
|
|
10023
|
-
createdAt:
|
|
10024
|
-
userAgent:
|
|
10025
|
-
ipAddress:
|
|
10026
|
-
}).from(
|
|
10472
|
+
id: this.refreshTokensTable.id,
|
|
10473
|
+
userId: this.refreshTokensTable.userId,
|
|
10474
|
+
tokenHash: this.refreshTokensTable.tokenHash,
|
|
10475
|
+
expiresAt: this.refreshTokensTable.expiresAt,
|
|
10476
|
+
createdAt: this.refreshTokensTable.createdAt,
|
|
10477
|
+
userAgent: this.refreshTokensTable.userAgent,
|
|
10478
|
+
ipAddress: this.refreshTokensTable.ipAddress
|
|
10479
|
+
}).from(this.refreshTokensTable).where(drizzleOrm.eq(this.refreshTokensTable.userId, userId)).orderBy(this.refreshTokensTable.createdAt);
|
|
10027
10480
|
return tokens;
|
|
10028
10481
|
}
|
|
10029
10482
|
async deleteById(id, userId) {
|
|
10030
|
-
await this.db.delete(
|
|
10483
|
+
await this.db.delete(this.refreshTokensTable).where(drizzleOrm.sql`${this.refreshTokensTable.id} = ${id} AND ${this.refreshTokensTable.userId} = ${userId}`);
|
|
10031
10484
|
}
|
|
10032
10485
|
}
|
|
10033
10486
|
class PasswordResetTokenService {
|
|
10034
|
-
constructor(db) {
|
|
10487
|
+
constructor(db, tableOrTables) {
|
|
10035
10488
|
this.db = db;
|
|
10489
|
+
if (tableOrTables && (tableOrTables.passwordResetTokens || tableOrTables.users)) {
|
|
10490
|
+
this.passwordResetTokensTable = tableOrTables.passwordResetTokens || passwordResetTokens;
|
|
10491
|
+
} else {
|
|
10492
|
+
this.passwordResetTokensTable = tableOrTables || passwordResetTokens;
|
|
10493
|
+
}
|
|
10494
|
+
}
|
|
10495
|
+
passwordResetTokensTable;
|
|
10496
|
+
getQualifiedPasswordResetTokensTableName() {
|
|
10497
|
+
const name = drizzleOrm.getTableName(this.passwordResetTokensTable);
|
|
10498
|
+
const schema = pgCore.getTableConfig(this.passwordResetTokensTable).schema || "public";
|
|
10499
|
+
return `"${schema}"."${name}"`;
|
|
10036
10500
|
}
|
|
10037
10501
|
/**
|
|
10038
10502
|
* Create a password reset token
|
|
10039
10503
|
*/
|
|
10040
10504
|
async createToken(userId, tokenHash, expiresAt) {
|
|
10505
|
+
const tableName = this.getQualifiedPasswordResetTokensTableName();
|
|
10041
10506
|
await this.db.execute(drizzleOrm.sql`
|
|
10042
|
-
DELETE FROM
|
|
10507
|
+
DELETE FROM ${drizzleOrm.sql.raw(tableName)}
|
|
10043
10508
|
WHERE user_id = ${userId} AND used_at IS NULL
|
|
10044
10509
|
`);
|
|
10045
|
-
await this.db.insert(
|
|
10510
|
+
await this.db.insert(this.passwordResetTokensTable).values({
|
|
10046
10511
|
userId,
|
|
10047
10512
|
tokenHash,
|
|
10048
10513
|
expiresAt
|
|
@@ -10053,13 +10518,14 @@ ${tableRelations.join(",\n")}
|
|
|
10053
10518
|
*/
|
|
10054
10519
|
async findValidByHash(tokenHash) {
|
|
10055
10520
|
const [token] = await this.db.select({
|
|
10056
|
-
userId:
|
|
10057
|
-
expiresAt:
|
|
10058
|
-
}).from(
|
|
10521
|
+
userId: this.passwordResetTokensTable.userId,
|
|
10522
|
+
expiresAt: this.passwordResetTokensTable.expiresAt
|
|
10523
|
+
}).from(this.passwordResetTokensTable).where(drizzleOrm.eq(this.passwordResetTokensTable.tokenHash, tokenHash));
|
|
10059
10524
|
if (!token) return null;
|
|
10525
|
+
const tableName = this.getQualifiedPasswordResetTokensTableName();
|
|
10060
10526
|
const result = await this.db.execute(drizzleOrm.sql`
|
|
10061
10527
|
SELECT user_id, expires_at
|
|
10062
|
-
FROM
|
|
10528
|
+
FROM ${drizzleOrm.sql.raw(tableName)}
|
|
10063
10529
|
WHERE token_hash = ${tokenHash}
|
|
10064
10530
|
AND used_at IS NULL
|
|
10065
10531
|
AND expires_at > NOW()
|
|
@@ -10075,31 +10541,32 @@ ${tableRelations.join(",\n")}
|
|
|
10075
10541
|
* Mark token as used
|
|
10076
10542
|
*/
|
|
10077
10543
|
async markAsUsed(tokenHash) {
|
|
10078
|
-
await this.db.update(
|
|
10544
|
+
await this.db.update(this.passwordResetTokensTable).set({
|
|
10079
10545
|
usedAt: /* @__PURE__ */ new Date()
|
|
10080
|
-
}).where(drizzleOrm.eq(
|
|
10546
|
+
}).where(drizzleOrm.eq(this.passwordResetTokensTable.tokenHash, tokenHash));
|
|
10081
10547
|
}
|
|
10082
10548
|
/**
|
|
10083
10549
|
* Delete all tokens for a user
|
|
10084
10550
|
*/
|
|
10085
10551
|
async deleteAllForUser(userId) {
|
|
10086
|
-
await this.db.delete(
|
|
10552
|
+
await this.db.delete(this.passwordResetTokensTable).where(drizzleOrm.eq(this.passwordResetTokensTable.userId, userId));
|
|
10087
10553
|
}
|
|
10088
10554
|
/**
|
|
10089
10555
|
* Clean up expired tokens
|
|
10090
10556
|
*/
|
|
10091
10557
|
async deleteExpired() {
|
|
10558
|
+
const tableName = this.getQualifiedPasswordResetTokensTableName();
|
|
10092
10559
|
await this.db.execute(drizzleOrm.sql`
|
|
10093
|
-
DELETE FROM
|
|
10560
|
+
DELETE FROM ${drizzleOrm.sql.raw(tableName)}
|
|
10094
10561
|
WHERE expires_at < NOW()
|
|
10095
10562
|
`);
|
|
10096
10563
|
}
|
|
10097
10564
|
}
|
|
10098
10565
|
class PostgresTokenRepository {
|
|
10099
|
-
constructor(db) {
|
|
10566
|
+
constructor(db, tableOrTables) {
|
|
10100
10567
|
this.db = db;
|
|
10101
|
-
this.refreshTokenService = new RefreshTokenService(db);
|
|
10102
|
-
this.passwordResetTokenService = new PasswordResetTokenService(db);
|
|
10568
|
+
this.refreshTokenService = new RefreshTokenService(db, tableOrTables);
|
|
10569
|
+
this.passwordResetTokenService = new PasswordResetTokenService(db, tableOrTables);
|
|
10103
10570
|
}
|
|
10104
10571
|
refreshTokenService;
|
|
10105
10572
|
passwordResetTokenService;
|
|
@@ -10140,11 +10607,11 @@ ${tableRelations.join(",\n")}
|
|
|
10140
10607
|
}
|
|
10141
10608
|
}
|
|
10142
10609
|
class PostgresAuthRepository {
|
|
10143
|
-
constructor(db) {
|
|
10610
|
+
constructor(db, tableOrTables) {
|
|
10144
10611
|
this.db = db;
|
|
10145
|
-
this.userService = new UserService(db);
|
|
10146
|
-
this.roleService = new RoleService(db);
|
|
10147
|
-
this.tokenRepository = new PostgresTokenRepository(db);
|
|
10612
|
+
this.userService = new UserService(db, tableOrTables);
|
|
10613
|
+
this.roleService = new RoleService(db, tableOrTables);
|
|
10614
|
+
this.tokenRepository = new PostgresTokenRepository(db, tableOrTables);
|
|
10148
10615
|
}
|
|
10149
10616
|
userService;
|
|
10150
10617
|
roleService;
|
|
@@ -10205,8 +10672,7 @@ ${tableRelations.join(",\n")}
|
|
|
10205
10672
|
await this.userService.assignDefaultRole(userId, roleId);
|
|
10206
10673
|
}
|
|
10207
10674
|
async getUserWithRoles(userId) {
|
|
10208
|
-
|
|
10209
|
-
return result;
|
|
10675
|
+
return this.userService.getUserWithRoles(userId);
|
|
10210
10676
|
}
|
|
10211
10677
|
// Role operations (delegate to RoleService)
|
|
10212
10678
|
async getRoleById(id) {
|
|
@@ -10394,6 +10860,24 @@ ${tableRelations.join(",\n")}
|
|
|
10394
10860
|
return result.rowCount ?? 0;
|
|
10395
10861
|
}
|
|
10396
10862
|
}
|
|
10863
|
+
function deepEqual(a, b) {
|
|
10864
|
+
if (a === b) return true;
|
|
10865
|
+
if (a == null || b == null) return false;
|
|
10866
|
+
if (a instanceof Date && b instanceof Date) return a.getTime() === b.getTime();
|
|
10867
|
+
if (Array.isArray(a) && Array.isArray(b)) {
|
|
10868
|
+
if (a.length !== b.length) return false;
|
|
10869
|
+
return a.every((v, i) => deepEqual(v, b[i]));
|
|
10870
|
+
}
|
|
10871
|
+
if (typeof a === "object" && typeof b === "object") {
|
|
10872
|
+
const aObj = a;
|
|
10873
|
+
const bObj = b;
|
|
10874
|
+
const aKeys = Object.keys(aObj);
|
|
10875
|
+
const bKeys = Object.keys(bObj);
|
|
10876
|
+
if (aKeys.length !== bKeys.length) return false;
|
|
10877
|
+
return aKeys.every((k) => deepEqual(aObj[k], bObj[k]));
|
|
10878
|
+
}
|
|
10879
|
+
return false;
|
|
10880
|
+
}
|
|
10397
10881
|
function findChangedFields(oldValues, newValues) {
|
|
10398
10882
|
const changed = [];
|
|
10399
10883
|
const allKeys = /* @__PURE__ */ new Set([...Object.keys(oldValues), ...Object.keys(newValues)]);
|
|
@@ -10403,7 +10887,7 @@ ${tableRelations.join(",\n")}
|
|
|
10403
10887
|
if (key.startsWith("__")) continue;
|
|
10404
10888
|
if (oldVal !== newVal) {
|
|
10405
10889
|
if (typeof oldVal === "object" && oldVal !== null && typeof newVal === "object" && newVal !== null) {
|
|
10406
|
-
if (
|
|
10890
|
+
if (!deepEqual(oldVal, newVal)) {
|
|
10407
10891
|
changed.push(key);
|
|
10408
10892
|
}
|
|
10409
10893
|
} else {
|
|
@@ -10521,14 +11005,32 @@ ${tableRelations.join(",\n")}
|
|
|
10521
11005
|
if (!authConfig) return void 0;
|
|
10522
11006
|
const internals = driverResult.internals;
|
|
10523
11007
|
const db = internals.db;
|
|
10524
|
-
|
|
11008
|
+
const registry = internals.registry;
|
|
11009
|
+
await ensureAuthTablesExist(db, registry);
|
|
10525
11010
|
let emailService;
|
|
10526
11011
|
if (authConfig.email) {
|
|
10527
11012
|
emailService = serverCore.createEmailService(authConfig.email);
|
|
10528
11013
|
}
|
|
10529
|
-
const
|
|
10530
|
-
const
|
|
10531
|
-
|
|
11014
|
+
const customUsersTable = registry?.getTable("users");
|
|
11015
|
+
const customRolesTable = registry?.getTable("roles");
|
|
11016
|
+
let usersSchemaName = "rebase";
|
|
11017
|
+
let rolesSchemaName = "rebase";
|
|
11018
|
+
if (customUsersTable) {
|
|
11019
|
+
usersSchemaName = pgCore.getTableConfig(customUsersTable).schema || "public";
|
|
11020
|
+
}
|
|
11021
|
+
if (customRolesTable) {
|
|
11022
|
+
rolesSchemaName = pgCore.getTableConfig(customRolesTable).schema || "public";
|
|
11023
|
+
}
|
|
11024
|
+
const authTables = createAuthSchema(rolesSchemaName, usersSchemaName);
|
|
11025
|
+
if (customUsersTable) {
|
|
11026
|
+
authTables.users = customUsersTable;
|
|
11027
|
+
}
|
|
11028
|
+
if (customRolesTable) {
|
|
11029
|
+
authTables.roles = customRolesTable;
|
|
11030
|
+
}
|
|
11031
|
+
const userService = new UserService(db, authTables);
|
|
11032
|
+
const roleService = new RoleService(db, authTables);
|
|
11033
|
+
const authRepository = new PostgresAuthRepository(db, authTables);
|
|
10532
11034
|
return {
|
|
10533
11035
|
userService,
|
|
10534
11036
|
roleService,
|
|
@@ -10560,11 +11062,54 @@ ${tableRelations.join(",\n")}
|
|
|
10560
11062
|
},
|
|
10561
11063
|
mountRoutes(app, basePath, driverResult) {
|
|
10562
11064
|
},
|
|
10563
|
-
async initializeWebsockets(server, realtimeService, driver, config) {
|
|
11065
|
+
async initializeWebsockets(server, realtimeService, driver, config, adapter) {
|
|
10564
11066
|
const {
|
|
10565
11067
|
createPostgresWebSocket: createPostgresWebSocket2
|
|
10566
11068
|
} = await Promise.resolve().then(() => websocket);
|
|
10567
|
-
createPostgresWebSocket2(server, realtimeService, driver, config);
|
|
11069
|
+
createPostgresWebSocket2(server, realtimeService, driver, config, adapter);
|
|
11070
|
+
}
|
|
11071
|
+
};
|
|
11072
|
+
}
|
|
11073
|
+
function createPostgresAdapter(pgConfig) {
|
|
11074
|
+
const bootstrapper = createPostgresBootstrapper(pgConfig);
|
|
11075
|
+
return {
|
|
11076
|
+
type: bootstrapper.type,
|
|
11077
|
+
async initializeDriver(config) {
|
|
11078
|
+
return bootstrapper.initializeDriver(config);
|
|
11079
|
+
},
|
|
11080
|
+
async initializeRealtime(driverResult) {
|
|
11081
|
+
if (bootstrapper.initializeRealtime) {
|
|
11082
|
+
return bootstrapper.initializeRealtime({}, driverResult);
|
|
11083
|
+
}
|
|
11084
|
+
return void 0;
|
|
11085
|
+
},
|
|
11086
|
+
async initializeAuth(config, driverResult) {
|
|
11087
|
+
if (bootstrapper.initializeAuth) {
|
|
11088
|
+
return bootstrapper.initializeAuth(config, driverResult);
|
|
11089
|
+
}
|
|
11090
|
+
return void 0;
|
|
11091
|
+
},
|
|
11092
|
+
async initializeHistory(config, driverResult) {
|
|
11093
|
+
if (bootstrapper.initializeHistory) {
|
|
11094
|
+
return bootstrapper.initializeHistory(config, driverResult);
|
|
11095
|
+
}
|
|
11096
|
+
return void 0;
|
|
11097
|
+
},
|
|
11098
|
+
initializeWebsockets(server, realtimeService, driver, config) {
|
|
11099
|
+
if (bootstrapper.initializeWebsockets) {
|
|
11100
|
+
return bootstrapper.initializeWebsockets(server, realtimeService, driver, config);
|
|
11101
|
+
}
|
|
11102
|
+
},
|
|
11103
|
+
getAdmin(driverResult) {
|
|
11104
|
+
if (bootstrapper.getAdmin) {
|
|
11105
|
+
return bootstrapper.getAdmin(driverResult);
|
|
11106
|
+
}
|
|
11107
|
+
return void 0;
|
|
11108
|
+
},
|
|
11109
|
+
mountRoutes(app, basePath, driverResult) {
|
|
11110
|
+
if (bootstrapper.mountRoutes) {
|
|
11111
|
+
bootstrapper.mountRoutes(app, basePath, driverResult);
|
|
11112
|
+
}
|
|
10568
11113
|
}
|
|
10569
11114
|
};
|
|
10570
11115
|
}
|
|
@@ -10578,6 +11123,8 @@ ${tableRelations.join(",\n")}
|
|
|
10578
11123
|
exports2.PostgresRealtimeProvider = PostgresRealtimeProvider;
|
|
10579
11124
|
exports2.RealtimeService = RealtimeService;
|
|
10580
11125
|
exports2.appConfig = appConfig;
|
|
11126
|
+
exports2.createAuthSchema = createAuthSchema;
|
|
11127
|
+
exports2.createPostgresAdapter = createPostgresAdapter;
|
|
10581
11128
|
exports2.createPostgresBootstrapper = createPostgresBootstrapper;
|
|
10582
11129
|
exports2.createPostgresDatabaseConnection = createPostgresDatabaseConnection;
|
|
10583
11130
|
exports2.createPostgresWebSocket = createPostgresWebSocket;
|
|
@@ -10595,6 +11142,7 @@ ${tableRelations.join(",\n")}
|
|
|
10595
11142
|
exports2.userRolesRelations = userRolesRelations;
|
|
10596
11143
|
exports2.users = users;
|
|
10597
11144
|
exports2.usersRelations = usersRelations;
|
|
11145
|
+
exports2.usersSchema = usersSchema;
|
|
10598
11146
|
Object.defineProperty(exports2, Symbol.toStringTag, { value: "Module" });
|
|
10599
11147
|
});
|
|
10600
11148
|
//# sourceMappingURL=index.umd.js.map
|