@rebasepro/server-postgresql 0.1.2 → 0.2.3
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/data/query_builder.d.ts +51 -0
- package/dist/common/src/index.d.ts +1 -0
- 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 +1435 -738
- package/dist/index.es.js.map +1 -1
- package/dist/index.umd.js +1433 -736
- 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/controllers/data.d.ts +21 -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 +22 -15
- package/src/PostgresAdapter.ts +59 -0
- package/src/PostgresBackendDriver.ts +66 -13
- 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 +49 -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 +69 -0
- package/src/schema/doctor-cli.ts +5 -1
- package/src/schema/doctor.ts +166 -48
- 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 +147 -1
- 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() {
|
|
@@ -1151,126 +1166,58 @@
|
|
|
1151
1166
|
});
|
|
1152
1167
|
}
|
|
1153
1168
|
}
|
|
1154
|
-
function
|
|
1155
|
-
if (
|
|
1156
|
-
|
|
1157
|
-
}
|
|
1158
|
-
if (getDataSourceCapabilities(collection.driver).supportsSubcollections && collection.subcollections) {
|
|
1159
|
-
return collection.subcollections() ?? [];
|
|
1169
|
+
function sanitizeRelation(relation, sourceCollection, resolveCollection) {
|
|
1170
|
+
if (!relation.target) {
|
|
1171
|
+
throw new Error("Relation is missing a `target` collection.");
|
|
1160
1172
|
}
|
|
1161
|
-
|
|
1162
|
-
|
|
1163
|
-
|
|
1164
|
-
|
|
1165
|
-
|
|
1166
|
-
|
|
1167
|
-
|
|
1168
|
-
|
|
1169
|
-
|
|
1170
|
-
|
|
1171
|
-
customName = prop[1].name;
|
|
1172
|
-
}
|
|
1173
|
-
}
|
|
1174
|
-
const baseOverrides = {
|
|
1175
|
-
slug: relationKey
|
|
1176
|
-
};
|
|
1177
|
-
if (customName) {
|
|
1178
|
-
baseOverrides.name = customName;
|
|
1179
|
-
baseOverrides.singularName = customName;
|
|
1180
|
-
}
|
|
1181
|
-
const targetWithOverrides = {
|
|
1182
|
-
...target,
|
|
1183
|
-
...baseOverrides
|
|
1173
|
+
const rawTarget = relation.target;
|
|
1174
|
+
let targetCollection;
|
|
1175
|
+
if (typeof rawTarget === "string") {
|
|
1176
|
+
if (resolveCollection) {
|
|
1177
|
+
targetCollection = resolveCollection(rawTarget);
|
|
1178
|
+
}
|
|
1179
|
+
if (!targetCollection) {
|
|
1180
|
+
targetCollection = {
|
|
1181
|
+
slug: rawTarget,
|
|
1182
|
+
name: rawTarget
|
|
1184
1183
|
};
|
|
1185
|
-
return r.overrides ? mergeDeep(targetWithOverrides, r.overrides) : targetWithOverrides;
|
|
1186
|
-
}).filter((c) => Boolean(c));
|
|
1187
|
-
}
|
|
1188
|
-
return [];
|
|
1189
|
-
}
|
|
1190
|
-
function hasPropertyCallbacks(properties, callbackName) {
|
|
1191
|
-
if (!properties) return false;
|
|
1192
|
-
for (const property of Object.values(properties)) {
|
|
1193
|
-
if (property.callbacks?.[callbackName]) return true;
|
|
1194
|
-
if (property.type === "map" && property.properties) {
|
|
1195
|
-
if (hasPropertyCallbacks(property.properties, callbackName)) return true;
|
|
1196
|
-
} else if (property.type === "array" && property.of) {
|
|
1197
|
-
const ofs = Array.isArray(property.of) ? property.of : [property.of];
|
|
1198
|
-
for (const of of ofs) {
|
|
1199
|
-
if (of.callbacks?.[callbackName]) return true;
|
|
1200
|
-
if (of.type === "map" && of.properties && hasPropertyCallbacks(of.properties, callbackName)) return true;
|
|
1201
|
-
}
|
|
1202
1184
|
}
|
|
1203
|
-
}
|
|
1204
|
-
|
|
1205
|
-
|
|
1206
|
-
|
|
1207
|
-
|
|
1208
|
-
const result = {
|
|
1209
|
-
...values
|
|
1210
|
-
};
|
|
1211
|
-
for (const [key, property] of Object.entries(properties)) {
|
|
1212
|
-
if (result[key] === void 0) continue;
|
|
1213
|
-
let currentValue = result[key];
|
|
1214
|
-
const previousValue = previousValues?.[key];
|
|
1215
|
-
if (property.type === "array" && Array.isArray(currentValue)) {
|
|
1216
|
-
if (property.of && !Array.isArray(property.of)) {
|
|
1217
|
-
currentValue = await Promise.all(currentValue.map(async (item, index) => {
|
|
1218
|
-
const prevItem = Array.isArray(previousValue) ? previousValue[index] : void 0;
|
|
1219
|
-
const singlePropData = {
|
|
1220
|
-
"_tmp": property.of
|
|
1221
|
-
};
|
|
1222
|
-
const res = await processProperties(singlePropData, {
|
|
1223
|
-
"_tmp": item
|
|
1224
|
-
}, {
|
|
1225
|
-
"_tmp": prevItem
|
|
1226
|
-
}, propsContext, callbackName);
|
|
1227
|
-
return res["_tmp"];
|
|
1228
|
-
}));
|
|
1185
|
+
} else if (typeof rawTarget === "function") {
|
|
1186
|
+
const evaluated = rawTarget();
|
|
1187
|
+
if (typeof evaluated === "string") {
|
|
1188
|
+
if (resolveCollection) {
|
|
1189
|
+
targetCollection = resolveCollection(evaluated);
|
|
1229
1190
|
}
|
|
1230
|
-
|
|
1231
|
-
|
|
1232
|
-
|
|
1233
|
-
|
|
1234
|
-
|
|
1235
|
-
...propsContext,
|
|
1236
|
-
value: currentValue,
|
|
1237
|
-
previousValue
|
|
1238
|
-
}));
|
|
1239
|
-
if (cbRes !== void 0) {
|
|
1240
|
-
currentValue = cbRes;
|
|
1191
|
+
if (!targetCollection) {
|
|
1192
|
+
targetCollection = {
|
|
1193
|
+
slug: evaluated,
|
|
1194
|
+
name: evaluated
|
|
1195
|
+
};
|
|
1241
1196
|
}
|
|
1197
|
+
} else {
|
|
1198
|
+
targetCollection = evaluated;
|
|
1242
1199
|
}
|
|
1243
|
-
|
|
1244
|
-
|
|
1245
|
-
return result;
|
|
1246
|
-
}
|
|
1247
|
-
const buildPropertyCallbacks = (properties) => {
|
|
1248
|
-
if (!properties) return void 0;
|
|
1249
|
-
const propertyCallbacks = {};
|
|
1250
|
-
if (hasPropertyCallbacks(properties, "afterRead")) {
|
|
1251
|
-
propertyCallbacks.afterRead = async (props) => {
|
|
1252
|
-
const processedValues = await processProperties(properties, props.entity.values, props.entity.values, props, "afterRead");
|
|
1253
|
-
return {
|
|
1254
|
-
...props.entity,
|
|
1255
|
-
values: processedValues
|
|
1256
|
-
};
|
|
1257
|
-
};
|
|
1200
|
+
} else if (rawTarget && typeof rawTarget === "object") {
|
|
1201
|
+
targetCollection = rawTarget;
|
|
1258
1202
|
}
|
|
1259
|
-
if (
|
|
1260
|
-
|
|
1261
|
-
return await processProperties(properties, props.values, props.previousValues ?? {}, props, "beforeSave");
|
|
1262
|
-
};
|
|
1263
|
-
}
|
|
1264
|
-
return Object.keys(propertyCallbacks).length > 0 ? propertyCallbacks : void 0;
|
|
1265
|
-
};
|
|
1266
|
-
function sanitizeRelation(relation, sourceCollection) {
|
|
1267
|
-
if (!relation.target) {
|
|
1268
|
-
throw new Error("Relation is missing a `target` collection.");
|
|
1203
|
+
if (!targetCollection) {
|
|
1204
|
+
throw new Error("Relation is missing a valid `target` collection.");
|
|
1269
1205
|
}
|
|
1270
|
-
const targetCollection = relation.target();
|
|
1271
1206
|
const newRelation = {
|
|
1272
1207
|
...relation
|
|
1273
1208
|
};
|
|
1209
|
+
newRelation.target = () => {
|
|
1210
|
+
if (typeof rawTarget === "string") {
|
|
1211
|
+
return resolveCollection && resolveCollection(rawTarget) || targetCollection;
|
|
1212
|
+
} else if (typeof rawTarget === "function") {
|
|
1213
|
+
const evaluated = rawTarget();
|
|
1214
|
+
if (typeof evaluated === "string") {
|
|
1215
|
+
return resolveCollection && resolveCollection(evaluated) || targetCollection;
|
|
1216
|
+
}
|
|
1217
|
+
return evaluated;
|
|
1218
|
+
}
|
|
1219
|
+
return targetCollection;
|
|
1220
|
+
};
|
|
1274
1221
|
if (!newRelation.relationName) {
|
|
1275
1222
|
newRelation.relationName = toSnakeCase(targetCollection.slug);
|
|
1276
1223
|
}
|
|
@@ -1323,6 +1270,17 @@
|
|
|
1323
1270
|
break;
|
|
1324
1271
|
}
|
|
1325
1272
|
}
|
|
1273
|
+
if (!isManyToManyInverse && targetCollection.properties) {
|
|
1274
|
+
for (const [propKey, prop] of Object.entries(targetCollection.properties)) {
|
|
1275
|
+
if (prop.type !== "relation") continue;
|
|
1276
|
+
const relProp = prop;
|
|
1277
|
+
const relName = relProp.relationName || propKey;
|
|
1278
|
+
if (relName === newRelation.inverseRelationName && relProp.cardinality === "many" && (relProp.direction === "owning" || !relProp.direction)) {
|
|
1279
|
+
isManyToManyInverse = true;
|
|
1280
|
+
break;
|
|
1281
|
+
}
|
|
1282
|
+
}
|
|
1283
|
+
}
|
|
1326
1284
|
} catch (e) {
|
|
1327
1285
|
}
|
|
1328
1286
|
}
|
|
@@ -1360,11 +1318,14 @@
|
|
|
1360
1318
|
const registeredRelationNames = /* @__PURE__ */ new Set();
|
|
1361
1319
|
if (relCollection.relations) {
|
|
1362
1320
|
relCollection.relations.forEach((relation) => {
|
|
1363
|
-
|
|
1364
|
-
|
|
1365
|
-
|
|
1366
|
-
|
|
1367
|
-
|
|
1321
|
+
try {
|
|
1322
|
+
const normalizedRelation = sanitizeRelation(relation, collection);
|
|
1323
|
+
const relationKey = normalizedRelation.relationName;
|
|
1324
|
+
if (relationKey) {
|
|
1325
|
+
relations[relationKey] = normalizedRelation;
|
|
1326
|
+
registeredRelationNames.add(relationKey);
|
|
1327
|
+
}
|
|
1328
|
+
} catch (e) {
|
|
1368
1329
|
}
|
|
1369
1330
|
});
|
|
1370
1331
|
}
|
|
@@ -1412,12 +1373,8 @@
|
|
|
1412
1373
|
overrides: relProp.overrides
|
|
1413
1374
|
};
|
|
1414
1375
|
}
|
|
1415
|
-
|
|
1416
|
-
|
|
1417
|
-
console.warn(`Unrecognized relation format for property '${propertyKey}' in collection '${sourceCollection.slug}'`);
|
|
1418
|
-
return void 0;
|
|
1419
|
-
}
|
|
1420
|
-
return relation;
|
|
1376
|
+
console.warn(`Unrecognized or missing relation target for property '${propertyKey}' in collection '${sourceCollection.slug}'`);
|
|
1377
|
+
return void 0;
|
|
1421
1378
|
}
|
|
1422
1379
|
function getTableName(collection) {
|
|
1423
1380
|
if (getDataSourceCapabilities(collection.driver).supportsRelations) {
|
|
@@ -1444,8 +1401,121 @@
|
|
|
1444
1401
|
if (snakeKey !== key && resolvedRelations[snakeKey]) return resolvedRelations[snakeKey];
|
|
1445
1402
|
return void 0;
|
|
1446
1403
|
}
|
|
1404
|
+
function getSubcollections(collection) {
|
|
1405
|
+
if (collection.childCollections) {
|
|
1406
|
+
return collection.childCollections() ?? [];
|
|
1407
|
+
}
|
|
1408
|
+
if (getDataSourceCapabilities(collection.driver).supportsSubcollections && collection.subcollections) {
|
|
1409
|
+
return collection.subcollections() ?? [];
|
|
1410
|
+
}
|
|
1411
|
+
if (getDataSourceCapabilities(collection.driver).supportsRelations) {
|
|
1412
|
+
const resolvedRelations = resolveCollectionRelations(collection);
|
|
1413
|
+
const manyRelations = Object.values(resolvedRelations).filter((r) => r.cardinality === "many");
|
|
1414
|
+
return manyRelations.map((r) => {
|
|
1415
|
+
const target = r.target();
|
|
1416
|
+
if (!target) return void 0;
|
|
1417
|
+
const relationKey = r.relationName || target.slug;
|
|
1418
|
+
let customName;
|
|
1419
|
+
if (collection.properties) {
|
|
1420
|
+
const prop = Object.entries(collection.properties).find(([_, p]) => p.type === "relation" && p.relationName === relationKey);
|
|
1421
|
+
if (prop && prop[1].name) {
|
|
1422
|
+
customName = prop[1].name;
|
|
1423
|
+
}
|
|
1424
|
+
}
|
|
1425
|
+
const baseOverrides = {
|
|
1426
|
+
slug: relationKey
|
|
1427
|
+
};
|
|
1428
|
+
if (customName) {
|
|
1429
|
+
baseOverrides.name = customName;
|
|
1430
|
+
baseOverrides.singularName = customName;
|
|
1431
|
+
}
|
|
1432
|
+
const targetWithOverrides = {
|
|
1433
|
+
...target,
|
|
1434
|
+
...baseOverrides
|
|
1435
|
+
};
|
|
1436
|
+
return r.overrides ? mergeDeep(targetWithOverrides, r.overrides) : targetWithOverrides;
|
|
1437
|
+
}).filter((c) => Boolean(c));
|
|
1438
|
+
}
|
|
1439
|
+
return [];
|
|
1440
|
+
}
|
|
1441
|
+
function hasPropertyCallbacks(properties, callbackName) {
|
|
1442
|
+
if (!properties) return false;
|
|
1443
|
+
for (const property of Object.values(properties)) {
|
|
1444
|
+
if (property.callbacks?.[callbackName]) return true;
|
|
1445
|
+
if (property.type === "map" && property.properties) {
|
|
1446
|
+
if (hasPropertyCallbacks(property.properties, callbackName)) return true;
|
|
1447
|
+
} else if (property.type === "array" && property.of) {
|
|
1448
|
+
const ofs = Array.isArray(property.of) ? property.of : [property.of];
|
|
1449
|
+
for (const of of ofs) {
|
|
1450
|
+
if (of.callbacks?.[callbackName]) return true;
|
|
1451
|
+
if (of.type === "map" && of.properties && hasPropertyCallbacks(of.properties, callbackName)) return true;
|
|
1452
|
+
}
|
|
1453
|
+
}
|
|
1454
|
+
}
|
|
1455
|
+
return false;
|
|
1456
|
+
}
|
|
1457
|
+
async function processProperties(properties, values, previousValues, propsContext, callbackName) {
|
|
1458
|
+
if (!values || typeof values !== "object") return values;
|
|
1459
|
+
const result = {
|
|
1460
|
+
...values
|
|
1461
|
+
};
|
|
1462
|
+
for (const [key, property] of Object.entries(properties)) {
|
|
1463
|
+
if (result[key] === void 0) continue;
|
|
1464
|
+
let currentValue = result[key];
|
|
1465
|
+
const previousValue = previousValues?.[key];
|
|
1466
|
+
if (property.type === "array" && Array.isArray(currentValue)) {
|
|
1467
|
+
if (property.of && !Array.isArray(property.of)) {
|
|
1468
|
+
currentValue = await Promise.all(currentValue.map(async (item, index) => {
|
|
1469
|
+
const prevItem = Array.isArray(previousValue) ? previousValue[index] : void 0;
|
|
1470
|
+
const singlePropData = {
|
|
1471
|
+
"_tmp": property.of
|
|
1472
|
+
};
|
|
1473
|
+
const res = await processProperties(singlePropData, {
|
|
1474
|
+
"_tmp": item
|
|
1475
|
+
}, {
|
|
1476
|
+
"_tmp": prevItem
|
|
1477
|
+
}, propsContext, callbackName);
|
|
1478
|
+
return res["_tmp"];
|
|
1479
|
+
}));
|
|
1480
|
+
}
|
|
1481
|
+
} else if (property.type === "map" && property.properties && typeof currentValue === "object") {
|
|
1482
|
+
currentValue = await processProperties(property.properties, currentValue, previousValue ?? {}, propsContext, callbackName);
|
|
1483
|
+
}
|
|
1484
|
+
if (property.callbacks?.[callbackName]) {
|
|
1485
|
+
const cbRes = await Promise.resolve(property.callbacks[callbackName]({
|
|
1486
|
+
...propsContext,
|
|
1487
|
+
value: currentValue,
|
|
1488
|
+
previousValue
|
|
1489
|
+
}));
|
|
1490
|
+
if (cbRes !== void 0) {
|
|
1491
|
+
currentValue = cbRes;
|
|
1492
|
+
}
|
|
1493
|
+
}
|
|
1494
|
+
result[key] = currentValue;
|
|
1495
|
+
}
|
|
1496
|
+
return result;
|
|
1497
|
+
}
|
|
1498
|
+
const buildPropertyCallbacks = (properties) => {
|
|
1499
|
+
if (!properties) return void 0;
|
|
1500
|
+
const propertyCallbacks = {};
|
|
1501
|
+
if (hasPropertyCallbacks(properties, "afterRead")) {
|
|
1502
|
+
propertyCallbacks.afterRead = async (props) => {
|
|
1503
|
+
const processedValues = await processProperties(properties, props.entity.values, props.entity.values, props, "afterRead");
|
|
1504
|
+
return {
|
|
1505
|
+
...props.entity,
|
|
1506
|
+
values: processedValues
|
|
1507
|
+
};
|
|
1508
|
+
};
|
|
1509
|
+
}
|
|
1510
|
+
if (hasPropertyCallbacks(properties, "beforeSave")) {
|
|
1511
|
+
propertyCallbacks.beforeSave = async (props) => {
|
|
1512
|
+
return await processProperties(properties, props.values, props.previousValues ?? {}, props, "beforeSave");
|
|
1513
|
+
};
|
|
1514
|
+
}
|
|
1515
|
+
return Object.keys(propertyCallbacks).length > 0 ? propertyCallbacks : void 0;
|
|
1516
|
+
};
|
|
1447
1517
|
var logic = { exports: {} };
|
|
1448
|
-
(function(module2,
|
|
1518
|
+
(function(module2, exports3) {
|
|
1449
1519
|
(function(root, factory) {
|
|
1450
1520
|
{
|
|
1451
1521
|
module2.exports = factory();
|
|
@@ -2202,7 +2272,7 @@
|
|
|
2202
2272
|
"[object Uint32Array]": areTypedArraysEqual2
|
|
2203
2273
|
};
|
|
2204
2274
|
}
|
|
2205
|
-
const deepEqual = createCustomEqual();
|
|
2275
|
+
const deepEqual$1 = createCustomEqual();
|
|
2206
2276
|
createCustomEqual({ strict: true });
|
|
2207
2277
|
createCustomEqual({ circular: true });
|
|
2208
2278
|
createCustomEqual({
|
|
@@ -2271,10 +2341,16 @@
|
|
|
2271
2341
|
*/
|
|
2272
2342
|
registerMultiple(collections) {
|
|
2273
2343
|
const rawSnapshot = collections.map((c) => removeFunctions(c));
|
|
2274
|
-
if (this.lastRawInputSnapshot && deepEqual(this.lastRawInputSnapshot, rawSnapshot)) {
|
|
2344
|
+
if (this.lastRawInputSnapshot && deepEqual$1(this.lastRawInputSnapshot, rawSnapshot)) {
|
|
2275
2345
|
return false;
|
|
2276
2346
|
}
|
|
2277
2347
|
this.reset();
|
|
2348
|
+
collections.forEach((c) => {
|
|
2349
|
+
if (c.slug) {
|
|
2350
|
+
this.collectionsBySlug.set(c.slug, c);
|
|
2351
|
+
}
|
|
2352
|
+
this.collectionsByTableName.set(getTableName(c), c);
|
|
2353
|
+
});
|
|
2278
2354
|
const normalizedCollections = collections.map((c) => this.normalizeCollection({
|
|
2279
2355
|
...c
|
|
2280
2356
|
}));
|
|
@@ -2345,15 +2421,25 @@
|
|
|
2345
2421
|
const mergedRelationsRaw = [...extractedRelations];
|
|
2346
2422
|
for (const manual of manualRelations) {
|
|
2347
2423
|
const name = manual.relationName;
|
|
2348
|
-
if (!name
|
|
2424
|
+
if (!name) {
|
|
2349
2425
|
mergedRelationsRaw.push(manual);
|
|
2426
|
+
} else {
|
|
2427
|
+
const existingIndex = mergedRelationsRaw.findIndex((r) => r.relationName === name);
|
|
2428
|
+
if (existingIndex === -1) {
|
|
2429
|
+
mergedRelationsRaw.push(manual);
|
|
2430
|
+
} else {
|
|
2431
|
+
mergedRelationsRaw[existingIndex] = {
|
|
2432
|
+
...manual,
|
|
2433
|
+
...mergedRelationsRaw[existingIndex]
|
|
2434
|
+
};
|
|
2435
|
+
}
|
|
2350
2436
|
}
|
|
2351
2437
|
}
|
|
2352
2438
|
let mergedRelations = mergedRelationsRaw;
|
|
2353
2439
|
if (getDataSourceCapabilities(result.driver).supportsRelations) {
|
|
2354
2440
|
mergedRelations = mergedRelationsRaw.map((r) => {
|
|
2355
2441
|
try {
|
|
2356
|
-
return sanitizeRelation(r, result);
|
|
2442
|
+
return sanitizeRelation(r, result, (slug) => this.get(slug));
|
|
2357
2443
|
} catch {
|
|
2358
2444
|
return r;
|
|
2359
2445
|
}
|
|
@@ -2566,6 +2652,118 @@
|
|
|
2566
2652
|
};
|
|
2567
2653
|
}
|
|
2568
2654
|
}
|
|
2655
|
+
function mapOperator(op) {
|
|
2656
|
+
switch (op) {
|
|
2657
|
+
case "==":
|
|
2658
|
+
return "eq";
|
|
2659
|
+
case "!=":
|
|
2660
|
+
return "neq";
|
|
2661
|
+
case ">":
|
|
2662
|
+
return "gt";
|
|
2663
|
+
case ">=":
|
|
2664
|
+
return "gte";
|
|
2665
|
+
case "<":
|
|
2666
|
+
return "lt";
|
|
2667
|
+
case "<=":
|
|
2668
|
+
return "lte";
|
|
2669
|
+
case "array-contains":
|
|
2670
|
+
return "cs";
|
|
2671
|
+
case "array-contains-any":
|
|
2672
|
+
return "csa";
|
|
2673
|
+
case "not-in":
|
|
2674
|
+
return "nin";
|
|
2675
|
+
default:
|
|
2676
|
+
return op;
|
|
2677
|
+
}
|
|
2678
|
+
}
|
|
2679
|
+
class QueryBuilder {
|
|
2680
|
+
constructor(collection) {
|
|
2681
|
+
this.collection = collection;
|
|
2682
|
+
}
|
|
2683
|
+
params = {
|
|
2684
|
+
where: {}
|
|
2685
|
+
};
|
|
2686
|
+
/**
|
|
2687
|
+
* Add a filter condition to your query.
|
|
2688
|
+
* @example
|
|
2689
|
+
* client.collection('users').where('age', '>=', 18).find()
|
|
2690
|
+
*/
|
|
2691
|
+
where(column, operator, value) {
|
|
2692
|
+
if (!this.params.where) {
|
|
2693
|
+
this.params.where = {};
|
|
2694
|
+
}
|
|
2695
|
+
const mappedOp = mapOperator(operator);
|
|
2696
|
+
let formattedValue = value;
|
|
2697
|
+
if (Array.isArray(value) && ["in", "nin", "cs", "csa"].includes(mappedOp)) {
|
|
2698
|
+
formattedValue = `(${value.join(",")})`;
|
|
2699
|
+
} else if (value === null) {
|
|
2700
|
+
formattedValue = "null";
|
|
2701
|
+
}
|
|
2702
|
+
this.params.where[column] = mappedOp === "eq" ? String(formattedValue) : `${mappedOp}.${formattedValue}`;
|
|
2703
|
+
return this;
|
|
2704
|
+
}
|
|
2705
|
+
/**
|
|
2706
|
+
* Order the results by a specific column.
|
|
2707
|
+
* @example
|
|
2708
|
+
* client.collection('users').orderBy('createdAt', 'desc').find()
|
|
2709
|
+
*/
|
|
2710
|
+
orderBy(column, ascending = "asc") {
|
|
2711
|
+
this.params.orderBy = `${column}:${ascending}`;
|
|
2712
|
+
return this;
|
|
2713
|
+
}
|
|
2714
|
+
/**
|
|
2715
|
+
* Limit the number of results returned.
|
|
2716
|
+
*/
|
|
2717
|
+
limit(count) {
|
|
2718
|
+
this.params.limit = count;
|
|
2719
|
+
return this;
|
|
2720
|
+
}
|
|
2721
|
+
/**
|
|
2722
|
+
* Skip the first N results.
|
|
2723
|
+
*/
|
|
2724
|
+
offset(count) {
|
|
2725
|
+
this.params.offset = count;
|
|
2726
|
+
return this;
|
|
2727
|
+
}
|
|
2728
|
+
/**
|
|
2729
|
+
* Set a free-text search string if supported by the backend.
|
|
2730
|
+
*/
|
|
2731
|
+
search(searchString) {
|
|
2732
|
+
this.params.searchString = searchString;
|
|
2733
|
+
return this;
|
|
2734
|
+
}
|
|
2735
|
+
/**
|
|
2736
|
+
* Include related entities in the response.
|
|
2737
|
+
* Relations will be populated with full entity data instead of just IDs.
|
|
2738
|
+
*
|
|
2739
|
+
* @param relations - Relation names to include, or "*" for all.
|
|
2740
|
+
* @example
|
|
2741
|
+
* // Include specific relations
|
|
2742
|
+
* client.data.posts.include("tags", "author").find()
|
|
2743
|
+
*
|
|
2744
|
+
* // Include all relations
|
|
2745
|
+
* client.data.posts.include("*").find()
|
|
2746
|
+
*/
|
|
2747
|
+
include(...relations) {
|
|
2748
|
+
this.params.include = relations;
|
|
2749
|
+
return this;
|
|
2750
|
+
}
|
|
2751
|
+
/**
|
|
2752
|
+
* Execute the find query and return the results.
|
|
2753
|
+
*/
|
|
2754
|
+
async find() {
|
|
2755
|
+
return this.collection.find(this.params);
|
|
2756
|
+
}
|
|
2757
|
+
/**
|
|
2758
|
+
* Listen to realtime updates matching this query.
|
|
2759
|
+
*/
|
|
2760
|
+
listen(onUpdate, onError) {
|
|
2761
|
+
if (!this.collection.listen) {
|
|
2762
|
+
throw new Error("Listen is only available when RebaseClient is configured with a websocketUrl.");
|
|
2763
|
+
}
|
|
2764
|
+
return this.collection.listen(this.params, onUpdate, onError);
|
|
2765
|
+
}
|
|
2766
|
+
}
|
|
2569
2767
|
function convertWhereToFilter(where) {
|
|
2570
2768
|
if (!where) return void 0;
|
|
2571
2769
|
const operatorMap = {
|
|
@@ -2645,7 +2843,7 @@
|
|
|
2645
2843
|
return [field, direction];
|
|
2646
2844
|
}
|
|
2647
2845
|
function createDriverAccessor(driver, slug) {
|
|
2648
|
-
|
|
2846
|
+
const accessor = {
|
|
2649
2847
|
async find(params) {
|
|
2650
2848
|
const orderParsed = parseOrderBy(params?.orderBy);
|
|
2651
2849
|
const entities = await driver.fetchCollection({
|
|
@@ -2739,8 +2937,28 @@
|
|
|
2739
2937
|
onUpdate: (entity) => onUpdate(entity ?? void 0),
|
|
2740
2938
|
onError
|
|
2741
2939
|
});
|
|
2742
|
-
} : void 0
|
|
2940
|
+
} : void 0,
|
|
2941
|
+
// Fluent Query Builder
|
|
2942
|
+
where(column, operator, value) {
|
|
2943
|
+
return new QueryBuilder(accessor).where(column, operator, value);
|
|
2944
|
+
},
|
|
2945
|
+
orderBy(column, ascending) {
|
|
2946
|
+
return new QueryBuilder(accessor).orderBy(column, ascending);
|
|
2947
|
+
},
|
|
2948
|
+
limit(count) {
|
|
2949
|
+
return new QueryBuilder(accessor).limit(count);
|
|
2950
|
+
},
|
|
2951
|
+
offset(count) {
|
|
2952
|
+
return new QueryBuilder(accessor).offset(count);
|
|
2953
|
+
},
|
|
2954
|
+
search(searchString) {
|
|
2955
|
+
return new QueryBuilder(accessor).search(searchString);
|
|
2956
|
+
},
|
|
2957
|
+
include(...relations) {
|
|
2958
|
+
return new QueryBuilder(accessor).include(...relations);
|
|
2959
|
+
}
|
|
2743
2960
|
};
|
|
2961
|
+
return accessor;
|
|
2744
2962
|
}
|
|
2745
2963
|
function buildRebaseData(driver) {
|
|
2746
2964
|
const cache = /* @__PURE__ */ new Map();
|
|
@@ -2792,8 +3010,14 @@
|
|
|
2792
3010
|
static buildSingleFilterCondition(column, op, value) {
|
|
2793
3011
|
switch (op) {
|
|
2794
3012
|
case "==":
|
|
3013
|
+
if (value === null || value === void 0) {
|
|
3014
|
+
return drizzleOrm.sql`${column} IS NULL`;
|
|
3015
|
+
}
|
|
2795
3016
|
return drizzleOrm.eq(column, value);
|
|
2796
3017
|
case "!=":
|
|
3018
|
+
if (value === null || value === void 0) {
|
|
3019
|
+
return drizzleOrm.sql`${column} IS NOT NULL`;
|
|
3020
|
+
}
|
|
2797
3021
|
return drizzleOrm.sql`${column} != ${value}`;
|
|
2798
3022
|
case ">":
|
|
2799
3023
|
return drizzleOrm.sql`${column} > ${value}`;
|
|
@@ -3124,7 +3348,10 @@
|
|
|
3124
3348
|
if (p.type === "string" && !p.enum && p.isId !== "uuid") {
|
|
3125
3349
|
const fieldColumn = table[key];
|
|
3126
3350
|
if (fieldColumn) {
|
|
3127
|
-
|
|
3351
|
+
const supportsILike = fieldColumn instanceof pgCore.PgVarchar || fieldColumn instanceof pgCore.PgText || fieldColumn instanceof pgCore.PgChar || fieldColumn && typeof fieldColumn === "object" && !("columnType" in fieldColumn);
|
|
3352
|
+
if (supportsILike) {
|
|
3353
|
+
searchConditions.push(drizzleOrm.ilike(fieldColumn, `%${searchString}%`));
|
|
3354
|
+
}
|
|
3128
3355
|
}
|
|
3129
3356
|
}
|
|
3130
3357
|
}
|
|
@@ -3597,6 +3824,31 @@
|
|
|
3597
3824
|
return result;
|
|
3598
3825
|
}
|
|
3599
3826
|
return value;
|
|
3827
|
+
case "vector": {
|
|
3828
|
+
if (value instanceof Vector) {
|
|
3829
|
+
return value.value;
|
|
3830
|
+
}
|
|
3831
|
+
if (value && typeof value === "object" && "value" in value && Array.isArray(value.value)) {
|
|
3832
|
+
return value.value.map(Number);
|
|
3833
|
+
}
|
|
3834
|
+
if (Array.isArray(value)) {
|
|
3835
|
+
return value.map(Number);
|
|
3836
|
+
}
|
|
3837
|
+
return value;
|
|
3838
|
+
}
|
|
3839
|
+
case "binary":
|
|
3840
|
+
if (typeof value === "string") {
|
|
3841
|
+
if (value.startsWith("data:application/octet-stream;base64,")) {
|
|
3842
|
+
const base64Data = value.split(",")[1];
|
|
3843
|
+
if (base64Data) {
|
|
3844
|
+
return Buffer.from(base64Data, "base64");
|
|
3845
|
+
}
|
|
3846
|
+
}
|
|
3847
|
+
}
|
|
3848
|
+
if (Buffer.isBuffer(value)) {
|
|
3849
|
+
return value;
|
|
3850
|
+
}
|
|
3851
|
+
return value;
|
|
3600
3852
|
case "string":
|
|
3601
3853
|
if (typeof value === "string") {
|
|
3602
3854
|
if (value.startsWith("data:application/octet-stream;base64,")) {
|
|
@@ -3741,6 +3993,21 @@
|
|
|
3741
3993
|
return value;
|
|
3742
3994
|
}
|
|
3743
3995
|
switch (property.type) {
|
|
3996
|
+
case "binary": {
|
|
3997
|
+
let buf = null;
|
|
3998
|
+
if (Buffer.isBuffer(value)) {
|
|
3999
|
+
buf = value;
|
|
4000
|
+
} else if (typeof value === "object" && value !== null) {
|
|
4001
|
+
const rawVal = value;
|
|
4002
|
+
if (rawVal.type === "Buffer" && Array.isArray(rawVal.data)) {
|
|
4003
|
+
buf = Buffer.from(rawVal.data);
|
|
4004
|
+
}
|
|
4005
|
+
}
|
|
4006
|
+
if (buf) {
|
|
4007
|
+
return `data:application/octet-stream;base64,${buf.toString("base64")}`;
|
|
4008
|
+
}
|
|
4009
|
+
return value;
|
|
4010
|
+
}
|
|
3744
4011
|
case "string": {
|
|
3745
4012
|
if (typeof value === "string") return value;
|
|
3746
4013
|
let isBuffer = false;
|
|
@@ -3748,9 +4015,12 @@
|
|
|
3748
4015
|
if (Buffer.isBuffer(value)) {
|
|
3749
4016
|
isBuffer = true;
|
|
3750
4017
|
buf = value;
|
|
3751
|
-
} else if (typeof value === "object" && value !== null
|
|
3752
|
-
|
|
3753
|
-
|
|
4018
|
+
} else if (typeof value === "object" && value !== null) {
|
|
4019
|
+
const rawVal = value;
|
|
4020
|
+
if (rawVal.type === "Buffer" && Array.isArray(rawVal.data)) {
|
|
4021
|
+
isBuffer = true;
|
|
4022
|
+
buf = Buffer.from(rawVal.data);
|
|
4023
|
+
}
|
|
3754
4024
|
}
|
|
3755
4025
|
if (isBuffer && buf) {
|
|
3756
4026
|
let isPrintable = true;
|
|
@@ -3837,6 +4107,25 @@
|
|
|
3837
4107
|
return isNaN(parsed) ? null : parsed;
|
|
3838
4108
|
}
|
|
3839
4109
|
return value;
|
|
4110
|
+
case "vector": {
|
|
4111
|
+
let nums = [];
|
|
4112
|
+
if (typeof value === "string") {
|
|
4113
|
+
nums = value.slice(1, -1).split(",").map(Number);
|
|
4114
|
+
} else if (Array.isArray(value)) {
|
|
4115
|
+
nums = value.map(Number);
|
|
4116
|
+
} else if (value instanceof Vector) {
|
|
4117
|
+
nums = value.value;
|
|
4118
|
+
} else if (typeof value === "object" && value !== null && "value" in value) {
|
|
4119
|
+
const valObj = value;
|
|
4120
|
+
if (Array.isArray(valObj.value)) {
|
|
4121
|
+
nums = valObj.value.map(Number);
|
|
4122
|
+
}
|
|
4123
|
+
}
|
|
4124
|
+
return {
|
|
4125
|
+
__type: "Vector",
|
|
4126
|
+
value: nums
|
|
4127
|
+
};
|
|
4128
|
+
}
|
|
3840
4129
|
case "date": {
|
|
3841
4130
|
let date;
|
|
3842
4131
|
if (value instanceof Date) {
|
|
@@ -3861,9 +4150,12 @@
|
|
|
3861
4150
|
if (Buffer.isBuffer(value)) {
|
|
3862
4151
|
isBuffer = true;
|
|
3863
4152
|
buf = value;
|
|
3864
|
-
} else if (typeof value === "object" && value !== null
|
|
3865
|
-
|
|
3866
|
-
|
|
4153
|
+
} else if (typeof value === "object" && value !== null) {
|
|
4154
|
+
const rawVal = value;
|
|
4155
|
+
if (rawVal.type === "Buffer" && Array.isArray(rawVal.data)) {
|
|
4156
|
+
isBuffer = true;
|
|
4157
|
+
buf = Buffer.from(rawVal.data);
|
|
4158
|
+
}
|
|
3867
4159
|
}
|
|
3868
4160
|
if (isBuffer && buf) {
|
|
3869
4161
|
let isPrintable = true;
|
|
@@ -4653,7 +4945,7 @@
|
|
|
4653
4945
|
if (parentFKValue !== null && parentFKValue !== void 0) {
|
|
4654
4946
|
await tx.update(targetTable).set({
|
|
4655
4947
|
[targetFKColName]: null
|
|
4656
|
-
}).where(drizzleOrm.eq(targetFKCol, parentFKValue));
|
|
4948
|
+
}).where(drizzleOrm.eq(targetFKCol, String(parentFKValue)));
|
|
4657
4949
|
}
|
|
4658
4950
|
continue;
|
|
4659
4951
|
}
|
|
@@ -4662,7 +4954,7 @@
|
|
|
4662
4954
|
if (parentFKValue !== null && parentFKValue !== void 0) {
|
|
4663
4955
|
await tx.update(targetTable).set({
|
|
4664
4956
|
[targetFKColName]: null
|
|
4665
|
-
}).where(drizzleOrm.eq(targetFKCol, parentFKValue));
|
|
4957
|
+
}).where(drizzleOrm.eq(targetFKCol, String(parentFKValue)));
|
|
4666
4958
|
} else {
|
|
4667
4959
|
console.warn(`Cannot set joinPath relation '${relation.relationName}' because parent FK value is null/undefined`);
|
|
4668
4960
|
continue;
|
|
@@ -6374,7 +6666,7 @@
|
|
|
6374
6666
|
callbacks: void 0,
|
|
6375
6667
|
propertyCallbacks: void 0
|
|
6376
6668
|
};
|
|
6377
|
-
const registryCollection = this.registry
|
|
6669
|
+
const registryCollection = this.registry?.getCollectionByPath(path2);
|
|
6378
6670
|
const resolvedCollection = registryCollection ? {
|
|
6379
6671
|
...collection,
|
|
6380
6672
|
...registryCollection
|
|
@@ -6422,7 +6714,8 @@
|
|
|
6422
6714
|
user: this.user,
|
|
6423
6715
|
driver: this,
|
|
6424
6716
|
data: this.data,
|
|
6425
|
-
client: this.client
|
|
6717
|
+
client: this.client,
|
|
6718
|
+
storageSource: this.client?.storage
|
|
6426
6719
|
};
|
|
6427
6720
|
return Promise.all(entities.map(async (entity) => {
|
|
6428
6721
|
let fetched = entity;
|
|
@@ -6517,7 +6810,8 @@
|
|
|
6517
6810
|
user: this.user,
|
|
6518
6811
|
driver: this,
|
|
6519
6812
|
data: this.data,
|
|
6520
|
-
client: this.client
|
|
6813
|
+
client: this.client,
|
|
6814
|
+
storageSource: this.client?.storage
|
|
6521
6815
|
};
|
|
6522
6816
|
if (callbacks?.afterRead) {
|
|
6523
6817
|
entity = await callbacks.afterRead({
|
|
@@ -6587,7 +6881,8 @@
|
|
|
6587
6881
|
user: this.user,
|
|
6588
6882
|
driver: this,
|
|
6589
6883
|
data: this.data,
|
|
6590
|
-
client: this.client
|
|
6884
|
+
client: this.client,
|
|
6885
|
+
storageSource: this.client?.storage
|
|
6591
6886
|
};
|
|
6592
6887
|
let previousValuesForHistory;
|
|
6593
6888
|
if (status === "existing" && entityId) {
|
|
@@ -6736,26 +7031,37 @@
|
|
|
6736
7031
|
user: this.user,
|
|
6737
7032
|
driver: this,
|
|
6738
7033
|
data: this.data,
|
|
6739
|
-
client: this.client
|
|
7034
|
+
client: this.client,
|
|
7035
|
+
storageSource: this.client?.storage
|
|
6740
7036
|
};
|
|
6741
7037
|
if (callbacks?.beforeDelete || propertyCallbacks?.beforeDelete) {
|
|
7038
|
+
let preventDefault = false;
|
|
6742
7039
|
if (callbacks?.beforeDelete) {
|
|
6743
|
-
await callbacks.beforeDelete({
|
|
7040
|
+
const result = await callbacks.beforeDelete({
|
|
6744
7041
|
collection: resolvedCollection,
|
|
6745
7042
|
path: entity.path,
|
|
6746
7043
|
entityId: entity.id,
|
|
6747
7044
|
entity,
|
|
6748
7045
|
context: contextForCallback
|
|
6749
7046
|
});
|
|
7047
|
+
if (result === false) {
|
|
7048
|
+
preventDefault = true;
|
|
7049
|
+
}
|
|
6750
7050
|
}
|
|
6751
7051
|
if (propertyCallbacks?.beforeDelete) {
|
|
6752
|
-
await propertyCallbacks.beforeDelete({
|
|
7052
|
+
const result = await propertyCallbacks.beforeDelete({
|
|
6753
7053
|
collection: resolvedCollection,
|
|
6754
7054
|
path: entity.path,
|
|
6755
7055
|
entityId: entity.id,
|
|
6756
7056
|
entity,
|
|
6757
7057
|
context: contextForCallback
|
|
6758
7058
|
});
|
|
7059
|
+
if (result === false) {
|
|
7060
|
+
preventDefault = true;
|
|
7061
|
+
}
|
|
7062
|
+
}
|
|
7063
|
+
if (preventDefault) {
|
|
7064
|
+
return;
|
|
6759
7065
|
}
|
|
6760
7066
|
}
|
|
6761
7067
|
await this.entityService.deleteEntity(entity.path, entity.id, entity.databaseId || resolvedCollection?.databaseId);
|
|
@@ -6828,7 +7134,17 @@
|
|
|
6828
7134
|
}
|
|
6829
7135
|
const targetDb = this.getTargetDb(options?.database);
|
|
6830
7136
|
try {
|
|
6831
|
-
|
|
7137
|
+
let needsRoleSwitch = false;
|
|
7138
|
+
if (options?.role && process.env.DISABLE_DB_ROLE_SWITCHING !== "true") {
|
|
7139
|
+
try {
|
|
7140
|
+
const currentRoleResult = await targetDb.execute(drizzleOrm.sql.raw("SELECT current_user AS role"));
|
|
7141
|
+
const currentRole = currentRoleResult.rows?.[0]?.role;
|
|
7142
|
+
needsRoleSwitch = !!currentRole && currentRole !== options.role;
|
|
7143
|
+
} catch {
|
|
7144
|
+
needsRoleSwitch = true;
|
|
7145
|
+
}
|
|
7146
|
+
}
|
|
7147
|
+
if (needsRoleSwitch && options?.role) {
|
|
6832
7148
|
const safeRole = options.role.replace(/"/g, '""');
|
|
6833
7149
|
return await targetDb.transaction(async (tx) => {
|
|
6834
7150
|
await tx.execute(drizzleOrm.sql.raw(`SET LOCAL ROLE "${safeRole}"`));
|
|
@@ -6866,7 +7182,7 @@
|
|
|
6866
7182
|
return databases;
|
|
6867
7183
|
}
|
|
6868
7184
|
async fetchAvailableRoles() {
|
|
6869
|
-
const result = await this.executeSql("SELECT rolname FROM pg_roles;");
|
|
7185
|
+
const result = await this.executeSql("SELECT rolname FROM pg_roles WHERE pg_has_role(current_user, rolname, 'member') ORDER BY rolname;");
|
|
6870
7186
|
return result.map((r) => r.rolname);
|
|
6871
7187
|
}
|
|
6872
7188
|
async fetchCurrentDatabase() {
|
|
@@ -7035,6 +7351,21 @@
|
|
|
7035
7351
|
* Typed admin capabilities — delegates to the base driver.
|
|
7036
7352
|
*/
|
|
7037
7353
|
admin;
|
|
7354
|
+
get restFetchService() {
|
|
7355
|
+
if (!this.delegate.restFetchService) return void 0;
|
|
7356
|
+
return {
|
|
7357
|
+
fetchCollectionForRest: async (collectionPath, options, include) => {
|
|
7358
|
+
return this.withTransaction(async (delegate) => {
|
|
7359
|
+
return delegate.restFetchService.fetchCollectionForRest(collectionPath, options, include);
|
|
7360
|
+
});
|
|
7361
|
+
},
|
|
7362
|
+
fetchEntityForRest: async (collectionPath, entityId, include, databaseId) => {
|
|
7363
|
+
return this.withTransaction(async (delegate) => {
|
|
7364
|
+
return delegate.restFetchService.fetchEntityForRest(collectionPath, entityId, include, databaseId);
|
|
7365
|
+
});
|
|
7366
|
+
}
|
|
7367
|
+
};
|
|
7368
|
+
}
|
|
7038
7369
|
async withTransaction(operation) {
|
|
7039
7370
|
const pendingNotifications = [];
|
|
7040
7371
|
const result = await this.delegate.db.transaction(async (tx) => {
|
|
@@ -7189,113 +7520,140 @@
|
|
|
7189
7520
|
this.pools.clear();
|
|
7190
7521
|
}
|
|
7191
7522
|
}
|
|
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
|
-
|
|
7523
|
+
function createAuthSchema(rolesSchemaName = "rebase", usersSchemaName = "rebase") {
|
|
7524
|
+
const rolesSchema = rolesSchemaName === "public" ? null : pgCore.pgSchema(rolesSchemaName);
|
|
7525
|
+
const usersSchema2 = usersSchemaName === "public" ? null : pgCore.pgSchema(usersSchemaName);
|
|
7526
|
+
const rolesTableCreator = rolesSchema ? rolesSchema.table.bind(rolesSchema) : pgCore.pgTable;
|
|
7527
|
+
const usersTableCreator = usersSchema2 ? usersSchema2.table.bind(usersSchema2) : pgCore.pgTable;
|
|
7528
|
+
const users2 = usersTableCreator("users", {
|
|
7529
|
+
id: pgCore.uuid("id").defaultRandom().primaryKey(),
|
|
7530
|
+
email: pgCore.varchar("email", {
|
|
7531
|
+
length: 255
|
|
7532
|
+
}).notNull().unique(),
|
|
7533
|
+
passwordHash: pgCore.varchar("password_hash", {
|
|
7534
|
+
length: 255
|
|
7535
|
+
}),
|
|
7536
|
+
// NULL for OAuth-only users
|
|
7537
|
+
displayName: pgCore.varchar("display_name", {
|
|
7538
|
+
length: 255
|
|
7539
|
+
}),
|
|
7540
|
+
photoUrl: pgCore.varchar("photo_url", {
|
|
7541
|
+
length: 500
|
|
7542
|
+
}),
|
|
7543
|
+
emailVerified: pgCore.boolean("email_verified").default(false).notNull(),
|
|
7544
|
+
emailVerificationToken: pgCore.varchar("email_verification_token", {
|
|
7545
|
+
length: 255
|
|
7546
|
+
}),
|
|
7547
|
+
emailVerificationSentAt: pgCore.timestamp("email_verification_sent_at"),
|
|
7548
|
+
metadata: pgCore.jsonb("metadata").$type().default({}).notNull(),
|
|
7549
|
+
createdAt: pgCore.timestamp("created_at").defaultNow().notNull(),
|
|
7550
|
+
updatedAt: pgCore.timestamp("updated_at").defaultNow().notNull()
|
|
7551
|
+
});
|
|
7552
|
+
const roles2 = rolesTableCreator("roles", {
|
|
7553
|
+
id: pgCore.varchar("id", {
|
|
7554
|
+
length: 50
|
|
7555
|
+
}).primaryKey(),
|
|
7556
|
+
// 'admin', 'editor', 'viewer'
|
|
7557
|
+
name: pgCore.varchar("name", {
|
|
7558
|
+
length: 100
|
|
7559
|
+
}).notNull(),
|
|
7560
|
+
isAdmin: pgCore.boolean("is_admin").default(false).notNull(),
|
|
7561
|
+
defaultPermissions: pgCore.jsonb("default_permissions").$type(),
|
|
7562
|
+
collectionPermissions: pgCore.jsonb("collection_permissions").$type(),
|
|
7563
|
+
config: pgCore.jsonb("config").$type()
|
|
7564
|
+
});
|
|
7565
|
+
const userRoles2 = rolesTableCreator("user_roles", {
|
|
7566
|
+
userId: pgCore.uuid("user_id").notNull().references(() => users2.id, {
|
|
7567
|
+
onDelete: "cascade"
|
|
7568
|
+
}),
|
|
7569
|
+
roleId: pgCore.varchar("role_id", {
|
|
7570
|
+
length: 50
|
|
7571
|
+
}).notNull().references(() => roles2.id, {
|
|
7572
|
+
onDelete: "cascade"
|
|
7573
|
+
})
|
|
7574
|
+
}, (table) => ({
|
|
7575
|
+
pk: pgCore.primaryKey({
|
|
7576
|
+
columns: [table.userId, table.roleId]
|
|
7577
|
+
})
|
|
7578
|
+
}));
|
|
7579
|
+
const refreshTokens2 = rolesTableCreator("refresh_tokens", {
|
|
7580
|
+
id: pgCore.uuid("id").defaultRandom().primaryKey(),
|
|
7581
|
+
userId: pgCore.uuid("user_id").notNull().references(() => users2.id, {
|
|
7582
|
+
onDelete: "cascade"
|
|
7583
|
+
}),
|
|
7584
|
+
tokenHash: pgCore.varchar("token_hash", {
|
|
7585
|
+
length: 255
|
|
7586
|
+
}).notNull().unique(),
|
|
7587
|
+
expiresAt: pgCore.timestamp("expires_at").notNull(),
|
|
7588
|
+
userAgent: pgCore.varchar("user_agent", {
|
|
7589
|
+
length: 500
|
|
7590
|
+
}),
|
|
7591
|
+
ipAddress: pgCore.varchar("ip_address", {
|
|
7592
|
+
length: 45
|
|
7593
|
+
}),
|
|
7594
|
+
createdAt: pgCore.timestamp("created_at").defaultNow().notNull()
|
|
7595
|
+
}, (table) => ({
|
|
7596
|
+
uniqueDeviceSession: pgCore.unique("unique_device_session").on(table.userId, table.userAgent, table.ipAddress)
|
|
7597
|
+
}));
|
|
7598
|
+
const passwordResetTokens2 = rolesTableCreator("password_reset_tokens", {
|
|
7599
|
+
id: pgCore.uuid("id").defaultRandom().primaryKey(),
|
|
7600
|
+
userId: pgCore.uuid("user_id").notNull().references(() => users2.id, {
|
|
7601
|
+
onDelete: "cascade"
|
|
7602
|
+
}),
|
|
7603
|
+
tokenHash: pgCore.varchar("token_hash", {
|
|
7604
|
+
length: 255
|
|
7605
|
+
}).notNull().unique(),
|
|
7606
|
+
expiresAt: pgCore.timestamp("expires_at").notNull(),
|
|
7607
|
+
usedAt: pgCore.timestamp("used_at"),
|
|
7608
|
+
createdAt: pgCore.timestamp("created_at").defaultNow().notNull()
|
|
7609
|
+
});
|
|
7610
|
+
const appConfig2 = rolesTableCreator("app_config", {
|
|
7611
|
+
key: pgCore.varchar("key", {
|
|
7612
|
+
length: 100
|
|
7613
|
+
}).primaryKey(),
|
|
7614
|
+
value: pgCore.jsonb("value").notNull(),
|
|
7615
|
+
updatedAt: pgCore.timestamp("updated_at").defaultNow().notNull()
|
|
7616
|
+
});
|
|
7617
|
+
const userIdentities2 = rolesTableCreator("user_identities", {
|
|
7618
|
+
id: pgCore.uuid("id").defaultRandom().primaryKey(),
|
|
7619
|
+
userId: pgCore.uuid("user_id").notNull().references(() => users2.id, {
|
|
7620
|
+
onDelete: "cascade"
|
|
7621
|
+
}),
|
|
7622
|
+
provider: pgCore.varchar("provider", {
|
|
7623
|
+
length: 50
|
|
7624
|
+
}).notNull(),
|
|
7625
|
+
// e.g. 'google', 'linkedin'
|
|
7626
|
+
providerId: pgCore.varchar("provider_id", {
|
|
7627
|
+
length: 255
|
|
7628
|
+
}).notNull(),
|
|
7629
|
+
profileData: pgCore.jsonb("profile_data"),
|
|
7630
|
+
createdAt: pgCore.timestamp("created_at").defaultNow().notNull(),
|
|
7631
|
+
updatedAt: pgCore.timestamp("updated_at").defaultNow().notNull()
|
|
7632
|
+
}, (table) => ({
|
|
7633
|
+
uniqueProviderId: pgCore.unique("unique_provider_id").on(table.provider, table.providerId)
|
|
7634
|
+
}));
|
|
7635
|
+
return {
|
|
7636
|
+
rolesSchema,
|
|
7637
|
+
usersSchema: usersSchema2,
|
|
7638
|
+
users: users2,
|
|
7639
|
+
roles: roles2,
|
|
7640
|
+
userRoles: userRoles2,
|
|
7641
|
+
refreshTokens: refreshTokens2,
|
|
7642
|
+
passwordResetTokens: passwordResetTokens2,
|
|
7643
|
+
appConfig: appConfig2,
|
|
7644
|
+
userIdentities: userIdentities2
|
|
7645
|
+
};
|
|
7646
|
+
}
|
|
7647
|
+
const defaultAuthSchema = createAuthSchema("rebase", "rebase");
|
|
7648
|
+
const rebaseSchema = defaultAuthSchema.rolesSchema;
|
|
7649
|
+
const usersSchema = defaultAuthSchema.usersSchema;
|
|
7650
|
+
const users = defaultAuthSchema.users;
|
|
7651
|
+
const roles = defaultAuthSchema.roles;
|
|
7652
|
+
const userRoles = defaultAuthSchema.userRoles;
|
|
7653
|
+
const refreshTokens = defaultAuthSchema.refreshTokens;
|
|
7654
|
+
const passwordResetTokens = defaultAuthSchema.passwordResetTokens;
|
|
7655
|
+
const appConfig = defaultAuthSchema.appConfig;
|
|
7656
|
+
const userIdentities = defaultAuthSchema.userIdentities;
|
|
7299
7657
|
const usersRelations = drizzleOrm.relations(users, ({
|
|
7300
7658
|
many
|
|
7301
7659
|
}) => ({
|
|
@@ -7478,6 +7836,15 @@
|
|
|
7478
7836
|
}
|
|
7479
7837
|
break;
|
|
7480
7838
|
}
|
|
7839
|
+
case "vector": {
|
|
7840
|
+
const vp = prop;
|
|
7841
|
+
columnDefinition = `vector("${colName}", { dimensions: ${vp.dimensions} })`;
|
|
7842
|
+
break;
|
|
7843
|
+
}
|
|
7844
|
+
case "binary": {
|
|
7845
|
+
columnDefinition = `customType({ dataType() { return 'bytea'; } })("${colName}")`;
|
|
7846
|
+
break;
|
|
7847
|
+
}
|
|
7481
7848
|
case "relation": {
|
|
7482
7849
|
const refProp = prop;
|
|
7483
7850
|
const resolvedRelations = resolveCollectionRelations(collection);
|
|
@@ -7544,7 +7911,7 @@
|
|
|
7544
7911
|
return ` ${propName}: ${columnDefinition}`;
|
|
7545
7912
|
};
|
|
7546
7913
|
const resolveRawSql = (expression) => {
|
|
7547
|
-
const resolved = expression.replace(/\{(\w+)\}/g, (_, col) =>
|
|
7914
|
+
const resolved = expression.replace(/\{(\w+)\}/g, (_, col) => col);
|
|
7548
7915
|
return `sql\`${resolved}\``;
|
|
7549
7916
|
};
|
|
7550
7917
|
const wrapWithRoleCheck = (clause, roles2) => {
|
|
@@ -7556,7 +7923,7 @@
|
|
|
7556
7923
|
const match = sqlExpr.match(/^sql`(.*)`$/s);
|
|
7557
7924
|
return match ? match[1] : sqlExpr;
|
|
7558
7925
|
};
|
|
7559
|
-
const buildUsingClause = (rule) => {
|
|
7926
|
+
const buildUsingClause = (rule, collection) => {
|
|
7560
7927
|
if (rule.using) {
|
|
7561
7928
|
return resolveRawSql(rule.using);
|
|
7562
7929
|
}
|
|
@@ -7564,15 +7931,17 @@
|
|
|
7564
7931
|
return "sql`true`";
|
|
7565
7932
|
}
|
|
7566
7933
|
if (rule.ownerField) {
|
|
7567
|
-
|
|
7934
|
+
const prop = collection.properties?.[rule.ownerField];
|
|
7935
|
+
const colName = resolveColumnName(rule.ownerField, prop);
|
|
7936
|
+
return `sql\`${colName} = auth.uid()\``;
|
|
7568
7937
|
}
|
|
7569
7938
|
return null;
|
|
7570
7939
|
};
|
|
7571
|
-
const buildWithCheckClause = (rule) => {
|
|
7940
|
+
const buildWithCheckClause = (rule, collection) => {
|
|
7572
7941
|
if (rule.withCheck) {
|
|
7573
7942
|
return resolveRawSql(rule.withCheck);
|
|
7574
7943
|
}
|
|
7575
|
-
return buildUsingClause(rule);
|
|
7944
|
+
return buildUsingClause(rule, collection);
|
|
7576
7945
|
};
|
|
7577
7946
|
const getPolicyNameHash = (rule) => {
|
|
7578
7947
|
const data = JSON.stringify({
|
|
@@ -7588,21 +7957,22 @@
|
|
|
7588
7957
|
});
|
|
7589
7958
|
return crypto.createHash("sha1").update(data).digest("hex").substring(0, 7);
|
|
7590
7959
|
};
|
|
7591
|
-
const generatePolicyCode = (
|
|
7960
|
+
const generatePolicyCode = (collection, rule, index) => {
|
|
7961
|
+
const tableName = getTableName(collection);
|
|
7592
7962
|
const ops = rule.operations && rule.operations.length > 0 ? rule.operations : [rule.operation ?? "all"];
|
|
7593
7963
|
const ruleHash = getPolicyNameHash(rule);
|
|
7594
7964
|
return ops.map((op, opIdx) => {
|
|
7595
7965
|
const policyName = rule.name ? ops.length > 1 ? `${rule.name}_${op}` : rule.name : `${tableName}_${op}_${ruleHash}${ops.length > 1 ? `_${opIdx}` : ""}`;
|
|
7596
|
-
return generateSinglePolicyCode(
|
|
7966
|
+
return generateSinglePolicyCode(collection, rule, op, policyName);
|
|
7597
7967
|
}).join("");
|
|
7598
7968
|
};
|
|
7599
|
-
const generateSinglePolicyCode = (
|
|
7969
|
+
const generateSinglePolicyCode = (collection, rule, operation, policyName) => {
|
|
7600
7970
|
const mode = rule.mode ?? "permissive";
|
|
7601
7971
|
const roles2 = rule.roles ? [...rule.roles].sort() : void 0;
|
|
7602
7972
|
const needsUsing = operation !== "insert";
|
|
7603
7973
|
const needsWithCheck = operation !== "select" && operation !== "delete";
|
|
7604
|
-
let usingClause = needsUsing ? buildUsingClause(rule) : null;
|
|
7605
|
-
let withCheckClause = needsWithCheck ? buildWithCheckClause(rule) : null;
|
|
7974
|
+
let usingClause = needsUsing ? buildUsingClause(rule, collection) : null;
|
|
7975
|
+
let withCheckClause = needsWithCheck ? buildWithCheckClause(rule, collection) : null;
|
|
7606
7976
|
if (roles2 && roles2.length > 0) {
|
|
7607
7977
|
if (usingClause) {
|
|
7608
7978
|
usingClause = wrapWithRoleCheck(usingClause, roles2);
|
|
@@ -7671,12 +8041,26 @@
|
|
|
7671
8041
|
const generateSchema = async (collections, stripPolicies = false) => {
|
|
7672
8042
|
let schemaContent = "// This file is auto-generated by the Rebase Drizzle generator. Do not edit manually.\n\n";
|
|
7673
8043
|
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) =>
|
|
8044
|
+
const hasVector = collections.some((c) => c.properties && Object.values(c.properties).some((p) => p.type === "vector"));
|
|
8045
|
+
const hasBinary = collections.some((c) => c.properties && Object.values(c.properties).some((p) => p.type === "binary"));
|
|
7675
8046
|
const pgCoreImports = ["primaryKey", "pgTable", "integer", "varchar", "text", "char", "boolean", "timestamp", "date", "time", "jsonb", "json", "pgEnum", "numeric", "real", "doublePrecision", "bigint", "serial", "bigserial", "pgPolicy"];
|
|
7676
8047
|
if (hasUuid) pgCoreImports.push("uuid");
|
|
8048
|
+
if (hasVector) pgCoreImports.push("vector");
|
|
8049
|
+
if (hasBinary) pgCoreImports.push("customType");
|
|
8050
|
+
const uniqueSchemas = Array.from(new Set(collections.map((c) => isPostgresCollection(c) ? c.schema : void 0).filter(Boolean)));
|
|
8051
|
+
if (uniqueSchemas.length > 0) {
|
|
8052
|
+
pgCoreImports.push("pgSchema");
|
|
8053
|
+
}
|
|
7677
8054
|
schemaContent += `import { ${pgCoreImports.join(", ")} } from 'drizzle-orm/pg-core';
|
|
7678
8055
|
`;
|
|
7679
8056
|
schemaContent += "import { relations as drizzleRelations, sql } from 'drizzle-orm';\n\n";
|
|
8057
|
+
uniqueSchemas.forEach((schema) => {
|
|
8058
|
+
schemaContent += `export const ${schema}Schema = pgSchema("${schema}");
|
|
8059
|
+
`;
|
|
8060
|
+
});
|
|
8061
|
+
if (uniqueSchemas.length > 0) {
|
|
8062
|
+
schemaContent += "\n";
|
|
8063
|
+
}
|
|
7680
8064
|
const exportedTableVars = [];
|
|
7681
8065
|
const exportedEnumVars = [];
|
|
7682
8066
|
const exportedRelationVars = [];
|
|
@@ -7731,6 +8115,9 @@
|
|
|
7731
8115
|
const tableVarName = getTableVarName(tableName);
|
|
7732
8116
|
if (isJunction && relation && sourceCollection && relation.through) {
|
|
7733
8117
|
const targetCollection = relation.target();
|
|
8118
|
+
const schema = (isPostgresCollection(targetCollection) ? targetCollection.schema : void 0) || (isPostgresCollection(sourceCollection) ? sourceCollection.schema : void 0);
|
|
8119
|
+
const tableCreator = schema ? `${schema}Schema.table` : "pgTable";
|
|
8120
|
+
const baseTableName = tableName.includes(".") ? tableName.split(".").pop() : tableName;
|
|
7734
8121
|
const {
|
|
7735
8122
|
sourceColumn,
|
|
7736
8123
|
targetColumn
|
|
@@ -7741,7 +8128,7 @@
|
|
|
7741
8128
|
const targetColType = isNumericId(targetCollection) ? "integer" : getPrimaryKeyProp(targetCollection).isUuid ? "uuid" : "varchar";
|
|
7742
8129
|
const sourceId = getPrimaryKeyName(sourceCollection);
|
|
7743
8130
|
const targetId = getPrimaryKeyName(targetCollection);
|
|
7744
|
-
schemaContent += `export const ${tableVarName} =
|
|
8131
|
+
schemaContent += `export const ${tableVarName} = ${tableCreator}("${baseTableName}", {
|
|
7745
8132
|
`;
|
|
7746
8133
|
schemaContent += ` ${sourceColumn}: ${sourceColType}("${sourceColumn}").notNull().references(() => ${getTableVarName(getTableName(sourceCollection))}.${sourceId}, ${refOptions}),
|
|
7747
8134
|
`;
|
|
@@ -7752,7 +8139,10 @@
|
|
|
7752
8139
|
`;
|
|
7753
8140
|
schemaContent += "}));\n\n";
|
|
7754
8141
|
} else if (!isJunction) {
|
|
7755
|
-
|
|
8142
|
+
const schema = isPostgresCollection(collection) ? collection.schema : void 0;
|
|
8143
|
+
const tableCreator = schema ? `${schema}Schema.table` : "pgTable";
|
|
8144
|
+
const baseTableName = tableName.includes(".") ? tableName.split(".").pop() : tableName;
|
|
8145
|
+
schemaContent += `export const ${tableVarName} = ${tableCreator}("${baseTableName}", {
|
|
7756
8146
|
`;
|
|
7757
8147
|
const columns = /* @__PURE__ */ new Set();
|
|
7758
8148
|
Object.entries(collection.properties ?? {}).forEach(([propName, prop]) => {
|
|
@@ -7768,7 +8158,7 @@
|
|
|
7768
8158
|
if (!stripPolicies && securityRules && securityRules.length > 0) {
|
|
7769
8159
|
schemaContent += "\n}, (table) => ([\n";
|
|
7770
8160
|
securityRules.forEach((rule, idx) => {
|
|
7771
|
-
schemaContent += generatePolicyCode(
|
|
8161
|
+
schemaContent += generatePolicyCode(collection, rule);
|
|
7772
8162
|
});
|
|
7773
8163
|
schemaContent += "])).enableRLS();\n\n";
|
|
7774
8164
|
} else {
|
|
@@ -7813,11 +8203,11 @@
|
|
|
7813
8203
|
references: [${sourceTableVar}.${sourceId}],
|
|
7814
8204
|
relationName: "${owningRelationName}"
|
|
7815
8205
|
})`);
|
|
7816
|
-
const
|
|
8206
|
+
const targetRelationName = inverseRelationName ? inverseRelationName : `${tableName}_${relation.through.targetColumn}`;
|
|
7817
8207
|
tableRelations.push(` "${relation.through.targetColumn}": one(${targetTableVar}, {
|
|
7818
8208
|
fields: [${tableVarName}.${relation.through.targetColumn}],
|
|
7819
8209
|
references: [${targetTableVar}.${targetId}],
|
|
7820
|
-
relationName: "${
|
|
8210
|
+
relationName: "${targetRelationName}"
|
|
7821
8211
|
})`);
|
|
7822
8212
|
}
|
|
7823
8213
|
} else {
|
|
@@ -7916,6 +8306,90 @@ ${tableRelations.join(",\n")}
|
|
|
7916
8306
|
schemaContent += tablesExport + enumsExport + relationsExport;
|
|
7917
8307
|
return schemaContent;
|
|
7918
8308
|
};
|
|
8309
|
+
const defaultUsersCollection = {
|
|
8310
|
+
name: "Users",
|
|
8311
|
+
singularName: "User",
|
|
8312
|
+
slug: "users",
|
|
8313
|
+
table: "users",
|
|
8314
|
+
schema: "rebase",
|
|
8315
|
+
icon: "Users",
|
|
8316
|
+
group: "Settings",
|
|
8317
|
+
properties: {
|
|
8318
|
+
id: {
|
|
8319
|
+
name: "ID",
|
|
8320
|
+
type: "string",
|
|
8321
|
+
isId: "uuid"
|
|
8322
|
+
},
|
|
8323
|
+
email: {
|
|
8324
|
+
name: "Email",
|
|
8325
|
+
type: "string",
|
|
8326
|
+
validation: {
|
|
8327
|
+
required: true,
|
|
8328
|
+
unique: true
|
|
8329
|
+
}
|
|
8330
|
+
},
|
|
8331
|
+
password_hash: {
|
|
8332
|
+
name: "Password Hash",
|
|
8333
|
+
type: "string",
|
|
8334
|
+
ui: {
|
|
8335
|
+
hideFromCollection: true
|
|
8336
|
+
}
|
|
8337
|
+
},
|
|
8338
|
+
display_name: {
|
|
8339
|
+
name: "Display Name",
|
|
8340
|
+
type: "string"
|
|
8341
|
+
},
|
|
8342
|
+
photo_url: {
|
|
8343
|
+
name: "Photo URL",
|
|
8344
|
+
type: "string"
|
|
8345
|
+
},
|
|
8346
|
+
email_verified: {
|
|
8347
|
+
name: "Email Verified",
|
|
8348
|
+
type: "boolean",
|
|
8349
|
+
defaultValue: false
|
|
8350
|
+
},
|
|
8351
|
+
email_verification_token: {
|
|
8352
|
+
name: "Email Verification Token",
|
|
8353
|
+
type: "string",
|
|
8354
|
+
ui: {
|
|
8355
|
+
hideFromCollection: true
|
|
8356
|
+
}
|
|
8357
|
+
},
|
|
8358
|
+
email_verification_sent_at: {
|
|
8359
|
+
name: "Email Verification Sent At",
|
|
8360
|
+
type: "date",
|
|
8361
|
+
ui: {
|
|
8362
|
+
hideFromCollection: true
|
|
8363
|
+
}
|
|
8364
|
+
},
|
|
8365
|
+
metadata: {
|
|
8366
|
+
name: "Metadata",
|
|
8367
|
+
type: "map",
|
|
8368
|
+
defaultValue: {},
|
|
8369
|
+
ui: {
|
|
8370
|
+
hideFromCollection: true
|
|
8371
|
+
}
|
|
8372
|
+
},
|
|
8373
|
+
created_at: {
|
|
8374
|
+
name: "Created At",
|
|
8375
|
+
type: "date",
|
|
8376
|
+
autoValue: "on_create",
|
|
8377
|
+
ui: {
|
|
8378
|
+
readOnly: true,
|
|
8379
|
+
hideFromCollection: true
|
|
8380
|
+
}
|
|
8381
|
+
},
|
|
8382
|
+
updated_at: {
|
|
8383
|
+
name: "Updated At",
|
|
8384
|
+
type: "date",
|
|
8385
|
+
autoValue: "on_update",
|
|
8386
|
+
ui: {
|
|
8387
|
+
readOnly: true,
|
|
8388
|
+
hideFromCollection: true
|
|
8389
|
+
}
|
|
8390
|
+
}
|
|
8391
|
+
}
|
|
8392
|
+
};
|
|
7919
8393
|
const formatTerminalText = (text, options = {}) => {
|
|
7920
8394
|
let codes = "";
|
|
7921
8395
|
if (options.bold) codes += "\x1B[1m";
|
|
@@ -7978,10 +8452,14 @@ ${tableRelations.join(",\n")}
|
|
|
7978
8452
|
const imported = await dynamicImport(fileUrl);
|
|
7979
8453
|
collections = imported.backendCollections || imported.collections;
|
|
7980
8454
|
}
|
|
7981
|
-
if (!collections || !Array.isArray(collections)
|
|
7982
|
-
|
|
7983
|
-
return;
|
|
8455
|
+
if (!collections || !Array.isArray(collections)) {
|
|
8456
|
+
collections = [];
|
|
7984
8457
|
}
|
|
8458
|
+
const hasUsersCollection = collections.some((c) => c.slug === "users");
|
|
8459
|
+
if (!hasUsersCollection) {
|
|
8460
|
+
collections.push(defaultUsersCollection);
|
|
8461
|
+
}
|
|
8462
|
+
collections.sort((a, b) => a.slug.localeCompare(b.slug));
|
|
7985
8463
|
const schemaContent = await generateSchema(collections);
|
|
7986
8464
|
if (outputPath) {
|
|
7987
8465
|
const outputDir = path.dirname(outputPath);
|
|
@@ -8209,29 +8687,14 @@ ${tableRelations.join(",\n")}
|
|
|
8209
8687
|
},
|
|
8210
8688
|
authContext
|
|
8211
8689
|
});
|
|
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
|
-
}
|
|
8690
|
+
const entities = await this.fetchCollectionWithAuth(request.path, {
|
|
8691
|
+
filter: request.filter,
|
|
8692
|
+
orderBy: request.orderBy,
|
|
8693
|
+
order: request.order,
|
|
8694
|
+
limit: request.limit,
|
|
8695
|
+
startAfter: request.startAfter,
|
|
8696
|
+
searchString: request.searchString
|
|
8697
|
+
}, authContext);
|
|
8235
8698
|
this.sendCollectionUpdate(clientId, subscriptionId, entities);
|
|
8236
8699
|
} catch (error) {
|
|
8237
8700
|
this.sendError(clientId, `Failed to subscribe to collection: ${error}`, subscriptionId);
|
|
@@ -8255,16 +8718,7 @@ ${tableRelations.join(",\n")}
|
|
|
8255
8718
|
entityId: request.entityId,
|
|
8256
8719
|
authContext
|
|
8257
8720
|
});
|
|
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
|
-
}
|
|
8721
|
+
const entity = await this.fetchEntityWithAuth(request.path, String(request.entityId), authContext);
|
|
8268
8722
|
this.sendEntityUpdate(clientId, subscriptionId, entity || null);
|
|
8269
8723
|
} catch (error) {
|
|
8270
8724
|
this.sendError(clientId, `Failed to subscribe to entity: ${request.path} ${request.entityId} ${error}`, subscriptionId);
|
|
@@ -8409,87 +8863,77 @@ ${tableRelations.join(",\n")}
|
|
|
8409
8863
|
async fetchCollectionWithAuth(notifyPath, collectionRequest, authContext) {
|
|
8410
8864
|
if (this.driver) {
|
|
8411
8865
|
const collection = this.registry.getCollectionByPath(notifyPath);
|
|
8412
|
-
const
|
|
8413
|
-
|
|
8414
|
-
|
|
8415
|
-
|
|
8416
|
-
|
|
8417
|
-
|
|
8418
|
-
|
|
8419
|
-
|
|
8420
|
-
|
|
8421
|
-
|
|
8866
|
+
const activeAuth = authContext || {
|
|
8867
|
+
userId: "anon",
|
|
8868
|
+
roles: ["anon"]
|
|
8869
|
+
};
|
|
8870
|
+
return await this.db.transaction(async (tx) => {
|
|
8871
|
+
await tx.execute(drizzleOrm.sql`SELECT set_config('app.user_id', ${activeAuth.userId}, true)`);
|
|
8872
|
+
await tx.execute(drizzleOrm.sql`SELECT set_config('app.user_roles', ${activeAuth.roles.join(",")}, true)`);
|
|
8873
|
+
await tx.execute(drizzleOrm.sql`SELECT set_config('app.jwt', ${JSON.stringify({
|
|
8874
|
+
sub: activeAuth.userId,
|
|
8875
|
+
roles: activeAuth.roles
|
|
8876
|
+
})}, true)`);
|
|
8877
|
+
const txEntityService = new EntityService(tx, this.registry);
|
|
8878
|
+
let fetchedEntities;
|
|
8879
|
+
if (collectionRequest.searchString) {
|
|
8880
|
+
fetchedEntities = await txEntityService.searchEntities(notifyPath, collectionRequest.searchString, {
|
|
8881
|
+
filter: collectionRequest.filter,
|
|
8882
|
+
orderBy: collectionRequest.orderBy,
|
|
8883
|
+
order: collectionRequest.order,
|
|
8884
|
+
limit: collectionRequest.limit,
|
|
8885
|
+
databaseId: collectionRequest.databaseId
|
|
8886
|
+
});
|
|
8887
|
+
} else {
|
|
8888
|
+
fetchedEntities = await txEntityService.fetchCollection(notifyPath, {
|
|
8889
|
+
filter: collectionRequest.filter,
|
|
8890
|
+
orderBy: collectionRequest.orderBy,
|
|
8891
|
+
order: collectionRequest.order,
|
|
8892
|
+
limit: collectionRequest.limit,
|
|
8893
|
+
offset: collectionRequest.offset,
|
|
8894
|
+
startAfter: collectionRequest.startAfter,
|
|
8895
|
+
databaseId: collectionRequest.databaseId
|
|
8896
|
+
});
|
|
8897
|
+
}
|
|
8898
|
+
const registryCollection = this.registry.getCollectionByPath(notifyPath);
|
|
8899
|
+
const resolvedCollection = collection ? {
|
|
8900
|
+
...collection,
|
|
8901
|
+
...registryCollection
|
|
8902
|
+
} : registryCollection;
|
|
8903
|
+
const callbacks = resolvedCollection?.callbacks;
|
|
8904
|
+
const propertyCallbacks = resolvedCollection?.properties ? buildPropertyCallbacks(resolvedCollection.properties) : void 0;
|
|
8905
|
+
if (callbacks?.afterRead || propertyCallbacks?.afterRead) {
|
|
8906
|
+
const contextForCallback = {
|
|
8907
|
+
user: {
|
|
8908
|
+
uid: activeAuth.userId,
|
|
8909
|
+
roles: activeAuth.roles
|
|
8910
|
+
},
|
|
8911
|
+
driver: this.driver,
|
|
8912
|
+
data: this.driver && "data" in this.driver ? this.driver.data : void 0
|
|
8913
|
+
};
|
|
8914
|
+
return await Promise.all(fetchedEntities.map(async (entity) => {
|
|
8915
|
+
let processedEntity = entity;
|
|
8916
|
+
if (callbacks?.afterRead) {
|
|
8917
|
+
processedEntity = await callbacks.afterRead({
|
|
8918
|
+
collection: resolvedCollection,
|
|
8919
|
+
path: notifyPath,
|
|
8920
|
+
entity: processedEntity,
|
|
8921
|
+
context: contextForCallback
|
|
8922
|
+
}) ?? processedEntity;
|
|
8923
|
+
}
|
|
8924
|
+
if (propertyCallbacks?.afterRead) {
|
|
8925
|
+
processedEntity = await propertyCallbacks.afterRead({
|
|
8926
|
+
collection: resolvedCollection,
|
|
8927
|
+
path: notifyPath,
|
|
8928
|
+
entity: processedEntity,
|
|
8929
|
+
context: contextForCallback
|
|
8930
|
+
}) ?? processedEntity;
|
|
8931
|
+
}
|
|
8932
|
+
return processedEntity;
|
|
8933
|
+
}));
|
|
8934
|
+
}
|
|
8935
|
+
return fetchedEntities;
|
|
8422
8936
|
});
|
|
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
8937
|
}
|
|
8494
8938
|
if (collectionRequest.searchString) {
|
|
8495
8939
|
return await this.entityService.searchEntities(notifyPath, collectionRequest.searchString, {
|
|
@@ -8553,60 +8997,56 @@ ${tableRelations.join(",\n")}
|
|
|
8553
8997
|
async fetchEntityWithAuth(notifyPath, entityId, authContext) {
|
|
8554
8998
|
if (this.driver) {
|
|
8555
8999
|
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
|
-
}
|
|
9000
|
+
const activeAuth = authContext || {
|
|
9001
|
+
userId: "anon",
|
|
9002
|
+
roles: ["anon"]
|
|
9003
|
+
};
|
|
9004
|
+
return await this.db.transaction(async (tx) => {
|
|
9005
|
+
await tx.execute(drizzleOrm.sql`SELECT set_config('app.user_id', ${activeAuth.userId}, true)`);
|
|
9006
|
+
await tx.execute(drizzleOrm.sql`SELECT set_config('app.user_roles', ${activeAuth.roles.join(",")}, true)`);
|
|
9007
|
+
await tx.execute(drizzleOrm.sql`SELECT set_config('app.jwt', ${JSON.stringify({
|
|
9008
|
+
sub: activeAuth.userId,
|
|
9009
|
+
roles: activeAuth.roles
|
|
9010
|
+
})}, true)`);
|
|
9011
|
+
const txEntityService = new EntityService(tx, this.registry);
|
|
9012
|
+
let processedEntity = await txEntityService.fetchEntity(notifyPath, entityId, collection?.databaseId);
|
|
9013
|
+
if (processedEntity) {
|
|
9014
|
+
const registryCollection = this.registry.getCollectionByPath(notifyPath);
|
|
9015
|
+
const resolvedCollection = collection ? {
|
|
9016
|
+
...collection,
|
|
9017
|
+
...registryCollection
|
|
9018
|
+
} : registryCollection;
|
|
9019
|
+
const callbacks = resolvedCollection?.callbacks;
|
|
9020
|
+
const propertyCallbacks = resolvedCollection?.properties ? buildPropertyCallbacks(resolvedCollection.properties) : void 0;
|
|
9021
|
+
if (callbacks?.afterRead || propertyCallbacks?.afterRead) {
|
|
9022
|
+
const contextForCallback = {
|
|
9023
|
+
user: {
|
|
9024
|
+
uid: activeAuth.userId,
|
|
9025
|
+
roles: activeAuth.roles
|
|
9026
|
+
},
|
|
9027
|
+
driver: this.driver,
|
|
9028
|
+
data: this.driver && "data" in this.driver ? this.driver.data : void 0
|
|
9029
|
+
};
|
|
9030
|
+
if (callbacks?.afterRead) {
|
|
9031
|
+
processedEntity = await callbacks.afterRead({
|
|
9032
|
+
collection: resolvedCollection,
|
|
9033
|
+
path: notifyPath,
|
|
9034
|
+
entity: processedEntity,
|
|
9035
|
+
context: contextForCallback
|
|
9036
|
+
}) ?? processedEntity;
|
|
9037
|
+
}
|
|
9038
|
+
if (propertyCallbacks?.afterRead) {
|
|
9039
|
+
processedEntity = await propertyCallbacks.afterRead({
|
|
9040
|
+
collection: resolvedCollection,
|
|
9041
|
+
path: notifyPath,
|
|
9042
|
+
entity: processedEntity,
|
|
9043
|
+
context: contextForCallback
|
|
9044
|
+
}) ?? processedEntity;
|
|
8604
9045
|
}
|
|
8605
9046
|
}
|
|
8606
|
-
|
|
8607
|
-
|
|
8608
|
-
}
|
|
8609
|
-
return fetchFn();
|
|
9047
|
+
}
|
|
9048
|
+
return processedEntity;
|
|
9049
|
+
});
|
|
8610
9050
|
}
|
|
8611
9051
|
return await this.entityService.fetchEntity(notifyPath, entityId);
|
|
8612
9052
|
}
|
|
@@ -8674,6 +9114,31 @@ ${tableRelations.join(",\n")}
|
|
|
8674
9114
|
return parentPaths;
|
|
8675
9115
|
}
|
|
8676
9116
|
// =============================================================================
|
|
9117
|
+
// Lifecycle / Cleanup
|
|
9118
|
+
// =============================================================================
|
|
9119
|
+
/**
|
|
9120
|
+
* Gracefully tear down all realtime resources.
|
|
9121
|
+
*
|
|
9122
|
+
* This MUST be called during process shutdown, **before** `pool.end()`.
|
|
9123
|
+
* It ensures:
|
|
9124
|
+
* 1. All debounced refetch timers are cancelled (prevents queries after pool closes).
|
|
9125
|
+
* 2. All subscription state and callbacks are cleared.
|
|
9126
|
+
* 3. The dedicated LISTEN client (outside the pool) is disconnected.
|
|
9127
|
+
* 4. All WebSocket clients are removed (but not forcefully closed — the
|
|
9128
|
+
* HTTP server close will handle that).
|
|
9129
|
+
*/
|
|
9130
|
+
async destroy() {
|
|
9131
|
+
for (const [key, timer] of this.refetchTimers) {
|
|
9132
|
+
clearTimeout(timer);
|
|
9133
|
+
this.refetchTimers.delete(key);
|
|
9134
|
+
}
|
|
9135
|
+
this._subscriptions.clear();
|
|
9136
|
+
this.subscriptionCallbacks.clear();
|
|
9137
|
+
await this.stopListening();
|
|
9138
|
+
this.clients.clear();
|
|
9139
|
+
this.debugLog("🧹 [RealtimeService] destroy() complete — all resources released.");
|
|
9140
|
+
}
|
|
9141
|
+
// =============================================================================
|
|
8677
9142
|
// Cross-Instance LISTEN/NOTIFY
|
|
8678
9143
|
// =============================================================================
|
|
8679
9144
|
/**
|
|
@@ -8814,8 +9279,23 @@ ${tableRelations.join(",\n")}
|
|
|
8814
9279
|
const WS_RATE_LIMIT = 2e3;
|
|
8815
9280
|
const WS_RATE_WINDOW_MS = 6e4;
|
|
8816
9281
|
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"]);
|
|
9282
|
+
function extractErrorMessage(error) {
|
|
9283
|
+
if (!error) return "Unknown error";
|
|
9284
|
+
if (typeof error === "object") {
|
|
9285
|
+
const err = error;
|
|
9286
|
+
if (err.cause) {
|
|
9287
|
+
return extractErrorMessage(err.cause);
|
|
9288
|
+
}
|
|
9289
|
+
if (typeof err.message === "string") {
|
|
9290
|
+
return err.message;
|
|
9291
|
+
}
|
|
9292
|
+
}
|
|
9293
|
+
return String(error);
|
|
9294
|
+
}
|
|
8817
9295
|
function isAdminSession(session) {
|
|
8818
|
-
if (!session?.user
|
|
9296
|
+
if (!session?.user) return false;
|
|
9297
|
+
if (session.user.isAdmin) return true;
|
|
9298
|
+
if (!session.user.roles) return false;
|
|
8819
9299
|
return session.user.roles.some((r) => {
|
|
8820
9300
|
if (typeof r === "string") return r === "admin";
|
|
8821
9301
|
if (r && typeof r === "object" && "isAdmin" in r) return r.isAdmin;
|
|
@@ -8823,7 +9303,7 @@ ${tableRelations.join(",\n")}
|
|
|
8823
9303
|
return false;
|
|
8824
9304
|
});
|
|
8825
9305
|
}
|
|
8826
|
-
function createPostgresWebSocket(server, realtimeService, driver, authConfig) {
|
|
9306
|
+
function createPostgresWebSocket(server, realtimeService, driver, authConfig, authAdapter) {
|
|
8827
9307
|
const isProduction = process.env.NODE_ENV === "production";
|
|
8828
9308
|
const wsDebug = (...args) => {
|
|
8829
9309
|
if (!isProduction) console.debug(...args);
|
|
@@ -8837,7 +9317,7 @@ ${tableRelations.join(",\n")}
|
|
|
8837
9317
|
}
|
|
8838
9318
|
console.error("❌ [WebSocket Server] Error:", err);
|
|
8839
9319
|
});
|
|
8840
|
-
const requireAuth = authConfig?.requireAuth !== false && authConfig?.jwtSecret;
|
|
9320
|
+
const requireAuth = authAdapter ? true : authConfig?.requireAuth !== false && !!authConfig?.jwtSecret;
|
|
8841
9321
|
wss.on("connection", (ws2) => {
|
|
8842
9322
|
const clientId = `client_${Date.now()}_${Math.random().toString(36).substring(2, 9)}`;
|
|
8843
9323
|
wsDebug(`WebSocket client connected: ${clientId}`);
|
|
@@ -8882,11 +9362,37 @@ ${tableRelations.join(",\n")}
|
|
|
8882
9362
|
sendError("AUTH_ERROR", "INVALID_INPUT", "Token is required");
|
|
8883
9363
|
return;
|
|
8884
9364
|
}
|
|
8885
|
-
|
|
8886
|
-
if (
|
|
9365
|
+
let verifiedUser = null;
|
|
9366
|
+
if (authAdapter) {
|
|
9367
|
+
try {
|
|
9368
|
+
const adapterUser = authAdapter.verifyToken ? await authAdapter.verifyToken(token) : await authAdapter.verifyRequest(new Request("http://localhost/_ws_auth", {
|
|
9369
|
+
headers: {
|
|
9370
|
+
Authorization: `Bearer ${token}`
|
|
9371
|
+
}
|
|
9372
|
+
}));
|
|
9373
|
+
if (adapterUser) {
|
|
9374
|
+
verifiedUser = {
|
|
9375
|
+
userId: adapterUser.uid,
|
|
9376
|
+
roles: adapterUser.roles,
|
|
9377
|
+
isAdmin: adapterUser.isAdmin
|
|
9378
|
+
};
|
|
9379
|
+
}
|
|
9380
|
+
} catch {
|
|
9381
|
+
}
|
|
9382
|
+
} else {
|
|
9383
|
+
const jwtPayload = serverCore.extractUserFromToken(token);
|
|
9384
|
+
if (jwtPayload) {
|
|
9385
|
+
verifiedUser = {
|
|
9386
|
+
userId: jwtPayload.userId,
|
|
9387
|
+
roles: jwtPayload.roles ?? [],
|
|
9388
|
+
isAdmin: (jwtPayload.roles ?? []).some((r) => r === "admin")
|
|
9389
|
+
};
|
|
9390
|
+
}
|
|
9391
|
+
}
|
|
9392
|
+
if (verifiedUser) {
|
|
8887
9393
|
const session = clientSessions.get(clientId);
|
|
8888
9394
|
if (session) {
|
|
8889
|
-
session.user =
|
|
9395
|
+
session.user = verifiedUser;
|
|
8890
9396
|
session.authenticated = true;
|
|
8891
9397
|
}
|
|
8892
9398
|
wsDebug(`[WS] replying AUTH_SUCCESS for requestId ${requestId}`);
|
|
@@ -8894,11 +9400,11 @@ ${tableRelations.join(",\n")}
|
|
|
8894
9400
|
type: "AUTH_SUCCESS",
|
|
8895
9401
|
requestId,
|
|
8896
9402
|
payload: {
|
|
8897
|
-
userId:
|
|
8898
|
-
roles:
|
|
9403
|
+
userId: verifiedUser.userId,
|
|
9404
|
+
roles: verifiedUser.roles
|
|
8899
9405
|
}
|
|
8900
9406
|
}));
|
|
8901
|
-
wsDebug(`🔐 [WebSocket Server] Client ${clientId} authenticated as ${
|
|
9407
|
+
wsDebug(`🔐 [WebSocket Server] Client ${clientId} authenticated as ${verifiedUser.userId}`);
|
|
8902
9408
|
} else {
|
|
8903
9409
|
wsDebug(`[WS] replying AUTH_ERROR for requestId ${requestId} (invalid token)`);
|
|
8904
9410
|
sendError("AUTH_ERROR", "INVALID_TOKEN", "Invalid or expired token");
|
|
@@ -8936,16 +9442,19 @@ ${tableRelations.join(",\n")}
|
|
|
8936
9442
|
}
|
|
8937
9443
|
const getScopedDelegate = async () => {
|
|
8938
9444
|
const session = clientSessions.get(clientId);
|
|
8939
|
-
if (
|
|
9445
|
+
if ("withAuth" in driver && typeof driver.withAuth === "function") {
|
|
8940
9446
|
try {
|
|
8941
|
-
const userForAuth = {
|
|
9447
|
+
const userForAuth = session?.user ? {
|
|
8942
9448
|
uid: session.user.userId,
|
|
8943
9449
|
roles: session.user.roles ?? []
|
|
9450
|
+
} : {
|
|
9451
|
+
uid: "anon",
|
|
9452
|
+
roles: ["anon"]
|
|
8944
9453
|
};
|
|
8945
9454
|
return await driver.withAuth(userForAuth);
|
|
8946
9455
|
} catch (e) {
|
|
8947
|
-
console.error("Failed to create
|
|
8948
|
-
|
|
9456
|
+
console.error("Failed to create RLS scoped delegate for WS request", e);
|
|
9457
|
+
throw new Error("Internal authentication error");
|
|
8949
9458
|
}
|
|
8950
9459
|
}
|
|
8951
9460
|
return driver;
|
|
@@ -9076,24 +9585,29 @@ ${tableRelations.join(",\n")}
|
|
|
9076
9585
|
sql,
|
|
9077
9586
|
options
|
|
9078
9587
|
} = payload;
|
|
9079
|
-
|
|
9080
|
-
|
|
9081
|
-
|
|
9082
|
-
|
|
9083
|
-
|
|
9084
|
-
|
|
9085
|
-
|
|
9086
|
-
|
|
9087
|
-
|
|
9588
|
+
try {
|
|
9589
|
+
const delegate = await getScopedDelegate();
|
|
9590
|
+
const admin = delegate.admin;
|
|
9591
|
+
if (!isSQLAdmin(admin)) {
|
|
9592
|
+
sendError("ERROR", "NOT_SUPPORTED", "SQL execution is not available for this driver.");
|
|
9593
|
+
break;
|
|
9594
|
+
}
|
|
9595
|
+
const result = await admin.executeSql(sql, options);
|
|
9596
|
+
if (process.env.NODE_ENV !== "production") {
|
|
9597
|
+
wsDebug(`⚡ [WebSocket Server] SQL executed. Returned ${Array.isArray(result) ? result.length : "non-array"} rows.`);
|
|
9598
|
+
}
|
|
9599
|
+
const response = {
|
|
9600
|
+
type: "EXECUTE_SQL_SUCCESS",
|
|
9601
|
+
payload: {
|
|
9602
|
+
result
|
|
9603
|
+
},
|
|
9604
|
+
requestId
|
|
9605
|
+
};
|
|
9606
|
+
ws2.send(JSON.stringify(response));
|
|
9607
|
+
} catch (sqlError) {
|
|
9608
|
+
const errMsg = extractErrorMessage(sqlError);
|
|
9609
|
+
sendError("ERROR", "SQL_ERROR", errMsg);
|
|
9088
9610
|
}
|
|
9089
|
-
const response = {
|
|
9090
|
-
type: "EXECUTE_SQL_SUCCESS",
|
|
9091
|
-
payload: {
|
|
9092
|
-
result
|
|
9093
|
-
},
|
|
9094
|
-
requestId
|
|
9095
|
-
};
|
|
9096
|
-
ws2.send(JSON.stringify(response));
|
|
9097
9611
|
}
|
|
9098
9612
|
break;
|
|
9099
9613
|
case "FETCH_DATABASES":
|
|
@@ -9272,7 +9786,10 @@ ${tableRelations.join(",\n")}
|
|
|
9272
9786
|
const authContext = session?.user ? {
|
|
9273
9787
|
userId: session.user.userId,
|
|
9274
9788
|
roles: session.user.roles ?? []
|
|
9275
|
-
} :
|
|
9789
|
+
} : {
|
|
9790
|
+
userId: "anon",
|
|
9791
|
+
roles: ["anon"]
|
|
9792
|
+
};
|
|
9276
9793
|
await realtimeService.handleClientMessage(clientId, {
|
|
9277
9794
|
type,
|
|
9278
9795
|
payload,
|
|
@@ -9416,29 +9933,58 @@ ${tableRelations.join(",\n")}
|
|
|
9416
9933
|
},
|
|
9417
9934
|
config: null
|
|
9418
9935
|
}];
|
|
9419
|
-
async function ensureAuthTablesExist(db) {
|
|
9936
|
+
async function ensureAuthTablesExist(db, registry) {
|
|
9420
9937
|
console.log("🔍 Checking auth tables...");
|
|
9421
9938
|
try {
|
|
9939
|
+
let usersTableName = '"users"';
|
|
9940
|
+
let userIdType = "TEXT";
|
|
9941
|
+
let usersSchema2 = "public";
|
|
9942
|
+
if (registry) {
|
|
9943
|
+
const usersTable = registry.getTable("users");
|
|
9944
|
+
if (usersTable) {
|
|
9945
|
+
const {
|
|
9946
|
+
getTableName: getTableName2
|
|
9947
|
+
} = await import("drizzle-orm");
|
|
9948
|
+
usersSchema2 = pgCore.getTableConfig(usersTable).schema || "public";
|
|
9949
|
+
usersTableName = usersSchema2 === "public" ? `"${getTableName2(usersTable)}"` : `"${usersSchema2}"."${getTableName2(usersTable)}"`;
|
|
9950
|
+
if (usersTable.id) {
|
|
9951
|
+
const col = usersTable.id;
|
|
9952
|
+
const meta = getColumnMeta(col);
|
|
9953
|
+
const columnType = meta.columnType;
|
|
9954
|
+
if (columnType === "PgUUID") {
|
|
9955
|
+
userIdType = "UUID";
|
|
9956
|
+
} else if (columnType === "PgSerial" || columnType === "PgInteger") {
|
|
9957
|
+
userIdType = "INTEGER";
|
|
9958
|
+
} else if (columnType === "PgBigInt" || columnType === "PgBigSerial") {
|
|
9959
|
+
userIdType = "BIGINT";
|
|
9960
|
+
}
|
|
9961
|
+
}
|
|
9962
|
+
}
|
|
9963
|
+
}
|
|
9964
|
+
let rolesSchema = "rebase";
|
|
9965
|
+
if (registry) {
|
|
9966
|
+
const rolesTable = registry.getTable("roles");
|
|
9967
|
+
if (rolesTable) {
|
|
9968
|
+
rolesSchema = pgCore.getTableConfig(rolesTable).schema || "public";
|
|
9969
|
+
}
|
|
9970
|
+
}
|
|
9971
|
+
if (usersSchema2 !== "public") {
|
|
9972
|
+
await db.execute(drizzleOrm.sql`CREATE SCHEMA IF NOT EXISTS ${drizzleOrm.sql.raw(usersSchema2)}`);
|
|
9973
|
+
}
|
|
9974
|
+
if (rolesSchema !== "public" && rolesSchema !== usersSchema2) {
|
|
9975
|
+
await db.execute(drizzleOrm.sql`CREATE SCHEMA IF NOT EXISTS ${drizzleOrm.sql.raw(rolesSchema)}`);
|
|
9976
|
+
}
|
|
9422
9977
|
await db.execute(drizzleOrm.sql`CREATE SCHEMA IF NOT EXISTS rebase`);
|
|
9978
|
+
const userIdentitiesTable = `"${rolesSchema}"."user_identities"`;
|
|
9979
|
+
const rolesTableName = `"${rolesSchema}"."roles"`;
|
|
9980
|
+
const userRolesTableName = `"${rolesSchema}"."user_roles"`;
|
|
9981
|
+
const refreshTokensTableName = `"${rolesSchema}"."refresh_tokens"`;
|
|
9982
|
+
const passwordResetTokensTableName = `"${rolesSchema}"."password_reset_tokens"`;
|
|
9983
|
+
const appConfigTableName = `"${rolesSchema}"."app_config"`;
|
|
9423
9984
|
await db.execute(drizzleOrm.sql`
|
|
9424
|
-
CREATE TABLE IF NOT EXISTS
|
|
9425
|
-
id TEXT PRIMARY KEY DEFAULT gen_random_uuid()::text,
|
|
9426
|
-
email TEXT NOT NULL UNIQUE,
|
|
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 (
|
|
9985
|
+
CREATE TABLE IF NOT EXISTS ${drizzleOrm.sql.raw(userIdentitiesTable)} (
|
|
9440
9986
|
id TEXT PRIMARY KEY DEFAULT gen_random_uuid()::text,
|
|
9441
|
-
user_id
|
|
9987
|
+
user_id ${drizzleOrm.sql.raw(userIdType)} NOT NULL REFERENCES ${drizzleOrm.sql.raw(usersTableName)}(id) ON DELETE CASCADE,
|
|
9442
9988
|
provider TEXT NOT NULL,
|
|
9443
9989
|
provider_id TEXT NOT NULL,
|
|
9444
9990
|
profile_data JSONB,
|
|
@@ -9449,10 +9995,10 @@ ${tableRelations.join(",\n")}
|
|
|
9449
9995
|
`);
|
|
9450
9996
|
await db.execute(drizzleOrm.sql`
|
|
9451
9997
|
CREATE INDEX IF NOT EXISTS idx_user_identities_user
|
|
9452
|
-
ON
|
|
9998
|
+
ON ${drizzleOrm.sql.raw(userIdentitiesTable)}(user_id)
|
|
9453
9999
|
`);
|
|
9454
10000
|
await db.execute(drizzleOrm.sql`
|
|
9455
|
-
CREATE TABLE IF NOT EXISTS
|
|
10001
|
+
CREATE TABLE IF NOT EXISTS ${drizzleOrm.sql.raw(rolesTableName)} (
|
|
9456
10002
|
id TEXT PRIMARY KEY,
|
|
9457
10003
|
name TEXT NOT NULL,
|
|
9458
10004
|
is_admin BOOLEAN DEFAULT FALSE,
|
|
@@ -9463,20 +10009,20 @@ ${tableRelations.join(",\n")}
|
|
|
9463
10009
|
)
|
|
9464
10010
|
`);
|
|
9465
10011
|
await db.execute(drizzleOrm.sql`
|
|
9466
|
-
CREATE TABLE IF NOT EXISTS
|
|
9467
|
-
user_id
|
|
9468
|
-
role_id TEXT NOT NULL REFERENCES
|
|
10012
|
+
CREATE TABLE IF NOT EXISTS ${drizzleOrm.sql.raw(userRolesTableName)} (
|
|
10013
|
+
user_id ${drizzleOrm.sql.raw(userIdType)} NOT NULL REFERENCES ${drizzleOrm.sql.raw(usersTableName)}(id) ON DELETE CASCADE,
|
|
10014
|
+
role_id TEXT NOT NULL REFERENCES ${drizzleOrm.sql.raw(rolesTableName)}(id) ON DELETE CASCADE,
|
|
9469
10015
|
PRIMARY KEY (user_id, role_id)
|
|
9470
10016
|
)
|
|
9471
10017
|
`);
|
|
9472
10018
|
await db.execute(drizzleOrm.sql`
|
|
9473
10019
|
CREATE INDEX IF NOT EXISTS idx_user_roles_user
|
|
9474
|
-
ON
|
|
10020
|
+
ON ${drizzleOrm.sql.raw(userRolesTableName)}(user_id)
|
|
9475
10021
|
`);
|
|
9476
10022
|
await db.execute(drizzleOrm.sql`
|
|
9477
|
-
CREATE TABLE IF NOT EXISTS
|
|
10023
|
+
CREATE TABLE IF NOT EXISTS ${drizzleOrm.sql.raw(refreshTokensTableName)} (
|
|
9478
10024
|
id TEXT PRIMARY KEY DEFAULT gen_random_uuid()::text,
|
|
9479
|
-
user_id
|
|
10025
|
+
user_id ${drizzleOrm.sql.raw(userIdType)} NOT NULL REFERENCES ${drizzleOrm.sql.raw(usersTableName)}(id) ON DELETE CASCADE,
|
|
9480
10026
|
token_hash TEXT NOT NULL UNIQUE,
|
|
9481
10027
|
expires_at TIMESTAMP WITH TIME ZONE NOT NULL,
|
|
9482
10028
|
user_agent TEXT,
|
|
@@ -9487,16 +10033,16 @@ ${tableRelations.join(",\n")}
|
|
|
9487
10033
|
`);
|
|
9488
10034
|
await db.execute(drizzleOrm.sql`
|
|
9489
10035
|
CREATE INDEX IF NOT EXISTS idx_refresh_tokens_hash
|
|
9490
|
-
ON
|
|
10036
|
+
ON ${drizzleOrm.sql.raw(refreshTokensTableName)}(token_hash)
|
|
9491
10037
|
`);
|
|
9492
10038
|
await db.execute(drizzleOrm.sql`
|
|
9493
10039
|
CREATE INDEX IF NOT EXISTS idx_refresh_tokens_user
|
|
9494
|
-
ON
|
|
10040
|
+
ON ${drizzleOrm.sql.raw(refreshTokensTableName)}(user_id)
|
|
9495
10041
|
`);
|
|
9496
10042
|
await db.execute(drizzleOrm.sql`
|
|
9497
|
-
CREATE TABLE IF NOT EXISTS
|
|
10043
|
+
CREATE TABLE IF NOT EXISTS ${drizzleOrm.sql.raw(passwordResetTokensTableName)} (
|
|
9498
10044
|
id TEXT PRIMARY KEY DEFAULT gen_random_uuid()::text,
|
|
9499
|
-
user_id
|
|
10045
|
+
user_id ${drizzleOrm.sql.raw(userIdType)} NOT NULL REFERENCES ${drizzleOrm.sql.raw(usersTableName)}(id) ON DELETE CASCADE,
|
|
9500
10046
|
token_hash TEXT NOT NULL UNIQUE,
|
|
9501
10047
|
expires_at TIMESTAMP WITH TIME ZONE NOT NULL,
|
|
9502
10048
|
used_at TIMESTAMP WITH TIME ZONE,
|
|
@@ -9505,20 +10051,19 @@ ${tableRelations.join(",\n")}
|
|
|
9505
10051
|
`);
|
|
9506
10052
|
await db.execute(drizzleOrm.sql`
|
|
9507
10053
|
CREATE INDEX IF NOT EXISTS idx_password_reset_tokens_hash
|
|
9508
|
-
ON
|
|
10054
|
+
ON ${drizzleOrm.sql.raw(passwordResetTokensTableName)}(token_hash)
|
|
9509
10055
|
`);
|
|
9510
10056
|
await db.execute(drizzleOrm.sql`
|
|
9511
10057
|
CREATE INDEX IF NOT EXISTS idx_password_reset_tokens_user
|
|
9512
|
-
ON
|
|
10058
|
+
ON ${drizzleOrm.sql.raw(passwordResetTokensTableName)}(user_id)
|
|
9513
10059
|
`);
|
|
9514
10060
|
await db.execute(drizzleOrm.sql`
|
|
9515
|
-
CREATE TABLE IF NOT EXISTS
|
|
10061
|
+
CREATE TABLE IF NOT EXISTS ${drizzleOrm.sql.raw(appConfigTableName)} (
|
|
9516
10062
|
key TEXT PRIMARY KEY,
|
|
9517
10063
|
value JSONB NOT NULL,
|
|
9518
10064
|
updated_at TIMESTAMP WITH TIME ZONE DEFAULT NOW()
|
|
9519
10065
|
)
|
|
9520
10066
|
`);
|
|
9521
|
-
await applyInternalMigrations(db);
|
|
9522
10067
|
await db.execute(drizzleOrm.sql`CREATE SCHEMA IF NOT EXISTS auth`);
|
|
9523
10068
|
await db.transaction(async (tx) => {
|
|
9524
10069
|
await tx.execute(drizzleOrm.sql`SELECT pg_advisory_xact_lock(hashtext('rebase_auth_functions_init'))`);
|
|
@@ -9541,15 +10086,15 @@ ${tableRelations.join(",\n")}
|
|
|
9541
10086
|
$$ LANGUAGE sql STABLE
|
|
9542
10087
|
`);
|
|
9543
10088
|
});
|
|
9544
|
-
await seedDefaultRoles(db);
|
|
10089
|
+
await seedDefaultRoles(db, rolesTableName);
|
|
9545
10090
|
console.log("✅ Auth tables ready");
|
|
9546
10091
|
} catch (error) {
|
|
9547
10092
|
console.error("❌ Failed to create auth tables:", error);
|
|
9548
10093
|
console.warn("⚠️ Continuing without creating auth tables.");
|
|
9549
10094
|
}
|
|
9550
10095
|
}
|
|
9551
|
-
async function seedDefaultRoles(db) {
|
|
9552
|
-
const result = await db.execute(drizzleOrm.sql`SELECT COUNT(*) as count FROM
|
|
10096
|
+
async function seedDefaultRoles(db, rolesTableName) {
|
|
10097
|
+
const result = await db.execute(drizzleOrm.sql`SELECT COUNT(*) as count FROM ${drizzleOrm.sql.raw(rolesTableName)}`);
|
|
9553
10098
|
const count = parseInt(result.rows[0]?.count || "0", 10);
|
|
9554
10099
|
if (count > 0) {
|
|
9555
10100
|
console.log(`📋 Found ${count} existing roles`);
|
|
@@ -9558,7 +10103,7 @@ ${tableRelations.join(",\n")}
|
|
|
9558
10103
|
console.log("🌱 Seeding default roles...");
|
|
9559
10104
|
for (const role of DEFAULT_ROLES) {
|
|
9560
10105
|
await db.execute(drizzleOrm.sql`
|
|
9561
|
-
INSERT INTO
|
|
10106
|
+
INSERT INTO ${drizzleOrm.sql.raw(rolesTableName)} (id, name, is_admin, default_permissions, config)
|
|
9562
10107
|
VALUES (
|
|
9563
10108
|
${role.id},
|
|
9564
10109
|
${role.name},
|
|
@@ -9571,142 +10116,156 @@ ${tableRelations.join(",\n")}
|
|
|
9571
10116
|
}
|
|
9572
10117
|
console.log("✅ Default roles created: admin, editor, viewer");
|
|
9573
10118
|
}
|
|
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);
|
|
10119
|
+
function getColumnKey(table, ...keys2) {
|
|
10120
|
+
if (!table) return void 0;
|
|
10121
|
+
for (const key of keys2) {
|
|
10122
|
+
if (key in table) return key;
|
|
10123
|
+
const snake = toSnakeCase(key);
|
|
10124
|
+
if (snake in table) return snake;
|
|
10125
|
+
const camel = camelCase(key);
|
|
10126
|
+
if (camel in table) return camel;
|
|
9665
10127
|
}
|
|
10128
|
+
return void 0;
|
|
10129
|
+
}
|
|
10130
|
+
function getColumn(table, ...keys2) {
|
|
10131
|
+
if (!table) return void 0;
|
|
10132
|
+
const key = getColumnKey(table, ...keys2);
|
|
10133
|
+
return key ? table[key] : void 0;
|
|
9666
10134
|
}
|
|
9667
10135
|
class UserService {
|
|
9668
|
-
constructor(db) {
|
|
10136
|
+
constructor(db, tableOrTables) {
|
|
9669
10137
|
this.db = db;
|
|
10138
|
+
if (tableOrTables && (tableOrTables.users || tableOrTables.roles)) {
|
|
10139
|
+
const tables = tableOrTables;
|
|
10140
|
+
this.usersTable = tables.users || users;
|
|
10141
|
+
this.userIdentitiesTable = tables.userIdentities || userIdentities;
|
|
10142
|
+
this.userRolesTable = tables.userRoles || userRoles;
|
|
10143
|
+
this.rolesTable = tables.roles || roles;
|
|
10144
|
+
} else {
|
|
10145
|
+
const table = tableOrTables;
|
|
10146
|
+
this.usersTable = table || users;
|
|
10147
|
+
this.userIdentitiesTable = userIdentities;
|
|
10148
|
+
this.userRolesTable = userRoles;
|
|
10149
|
+
this.rolesTable = roles;
|
|
10150
|
+
}
|
|
10151
|
+
}
|
|
10152
|
+
usersTable;
|
|
10153
|
+
userIdentitiesTable;
|
|
10154
|
+
userRolesTable;
|
|
10155
|
+
rolesTable;
|
|
10156
|
+
getQualifiedUsersTableName() {
|
|
10157
|
+
const name = drizzleOrm.getTableName(this.usersTable);
|
|
10158
|
+
const schema = pgCore.getTableConfig(this.usersTable).schema || "public";
|
|
10159
|
+
return `"${schema}"."${name}"`;
|
|
10160
|
+
}
|
|
10161
|
+
mapRowToUser(row) {
|
|
10162
|
+
if (!row) return row;
|
|
10163
|
+
const id = row.id ?? row.uid;
|
|
10164
|
+
const email = row.email;
|
|
10165
|
+
const passwordHash = row.password_hash ?? row.passwordHash ?? null;
|
|
10166
|
+
const displayName = row.display_name ?? row.displayName ?? null;
|
|
10167
|
+
const photoUrl = row.photo_url ?? row.photoUrl ?? row.photoURL ?? null;
|
|
10168
|
+
const emailVerified = row.email_verified ?? row.emailVerified ?? false;
|
|
10169
|
+
const emailVerificationToken = row.email_verification_token ?? row.emailVerificationToken ?? null;
|
|
10170
|
+
const emailVerificationSentAt = row.email_verification_sent_at ?? row.emailVerificationSentAt ?? null;
|
|
10171
|
+
const createdAt = row.created_at ?? row.createdAt;
|
|
10172
|
+
const updatedAt = row.updated_at ?? row.updatedAt;
|
|
10173
|
+
const metadata = {
|
|
10174
|
+
...row.metadata || {}
|
|
10175
|
+
};
|
|
10176
|
+
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"]);
|
|
10177
|
+
for (const [key, val] of Object.entries(row)) {
|
|
10178
|
+
if (!knownKeys.has(key)) {
|
|
10179
|
+
const camelKey = camelCase(key);
|
|
10180
|
+
metadata[camelKey] = val;
|
|
10181
|
+
}
|
|
10182
|
+
}
|
|
10183
|
+
return {
|
|
10184
|
+
id,
|
|
10185
|
+
email,
|
|
10186
|
+
passwordHash,
|
|
10187
|
+
displayName,
|
|
10188
|
+
photoUrl,
|
|
10189
|
+
emailVerified,
|
|
10190
|
+
emailVerificationToken,
|
|
10191
|
+
emailVerificationSentAt: emailVerificationSentAt ? new Date(emailVerificationSentAt) : null,
|
|
10192
|
+
createdAt: createdAt ? new Date(createdAt) : /* @__PURE__ */ new Date(),
|
|
10193
|
+
updatedAt: updatedAt ? new Date(updatedAt) : /* @__PURE__ */ new Date(),
|
|
10194
|
+
metadata
|
|
10195
|
+
};
|
|
10196
|
+
}
|
|
10197
|
+
mapPayload(data) {
|
|
10198
|
+
if (!data) return {};
|
|
10199
|
+
const payload = {};
|
|
10200
|
+
const idKey = getColumnKey(this.usersTable, "id") || "id";
|
|
10201
|
+
const emailKey = getColumnKey(this.usersTable, "email") || "email";
|
|
10202
|
+
const passwordHashKey = getColumnKey(this.usersTable, "passwordHash", "password_hash") || "passwordHash";
|
|
10203
|
+
const displayNameKey = getColumnKey(this.usersTable, "displayName", "display_name") || "displayName";
|
|
10204
|
+
const photoUrlKey = getColumnKey(this.usersTable, "photoUrl", "photo_url") || "photoUrl";
|
|
10205
|
+
const emailVerifiedKey = getColumnKey(this.usersTable, "emailVerified", "email_verified") || "emailVerified";
|
|
10206
|
+
const emailVerificationTokenKey = getColumnKey(this.usersTable, "emailVerificationToken", "email_verification_token") || "emailVerificationToken";
|
|
10207
|
+
const emailVerificationSentAtKey = getColumnKey(this.usersTable, "emailVerificationSentAt", "email_verification_sent_at") || "emailVerificationSentAt";
|
|
10208
|
+
const createdAtKey = getColumnKey(this.usersTable, "createdAt", "created_at") || "createdAt";
|
|
10209
|
+
const updatedAtKey = getColumnKey(this.usersTable, "updatedAt", "updated_at") || "updatedAt";
|
|
10210
|
+
const metadataKey = getColumnKey(this.usersTable, "metadata") || "metadata";
|
|
10211
|
+
if ("id" in data) payload[idKey] = data.id;
|
|
10212
|
+
if ("email" in data) payload[emailKey] = data.email;
|
|
10213
|
+
if ("passwordHash" in data) payload[passwordHashKey] = data.passwordHash;
|
|
10214
|
+
if ("displayName" in data) payload[displayNameKey] = data.displayName;
|
|
10215
|
+
if ("photoUrl" in data) payload[photoUrlKey] = data.photoUrl;
|
|
10216
|
+
if ("emailVerified" in data) payload[emailVerifiedKey] = data.emailVerified;
|
|
10217
|
+
if ("emailVerificationToken" in data) payload[emailVerificationTokenKey] = data.emailVerificationToken;
|
|
10218
|
+
if ("emailVerificationSentAt" in data) payload[emailVerificationSentAtKey] = data.emailVerificationSentAt;
|
|
10219
|
+
if ("createdAt" in data) payload[createdAtKey] = data.createdAt;
|
|
10220
|
+
if ("updatedAt" in data) payload[updatedAtKey] = data.updatedAt;
|
|
10221
|
+
const metadata = {
|
|
10222
|
+
...data.metadata || {}
|
|
10223
|
+
};
|
|
10224
|
+
const remainingMetadata = {};
|
|
10225
|
+
for (const [key, val] of Object.entries(metadata)) {
|
|
10226
|
+
const tableColKey = getColumnKey(this.usersTable, key);
|
|
10227
|
+
if (tableColKey && tableColKey !== idKey && tableColKey !== emailKey && tableColKey !== passwordHashKey && tableColKey !== displayNameKey && tableColKey !== photoUrlKey && tableColKey !== emailVerifiedKey && tableColKey !== emailVerificationTokenKey && tableColKey !== emailVerificationSentAtKey && tableColKey !== createdAtKey && tableColKey !== updatedAtKey && tableColKey !== metadataKey) {
|
|
10228
|
+
payload[tableColKey] = val;
|
|
10229
|
+
} else {
|
|
10230
|
+
remainingMetadata[key] = val;
|
|
10231
|
+
}
|
|
10232
|
+
}
|
|
10233
|
+
if (metadataKey in this.usersTable) {
|
|
10234
|
+
payload[metadataKey] = remainingMetadata;
|
|
10235
|
+
}
|
|
10236
|
+
return payload;
|
|
9670
10237
|
}
|
|
9671
10238
|
async createUser(data) {
|
|
9672
|
-
const
|
|
9673
|
-
|
|
10239
|
+
const payload = this.mapPayload(data);
|
|
10240
|
+
const [row] = await this.db.insert(this.usersTable).values(payload).returning();
|
|
10241
|
+
return this.mapRowToUser(row);
|
|
9674
10242
|
}
|
|
9675
10243
|
async getUserById(id) {
|
|
9676
|
-
const
|
|
9677
|
-
|
|
10244
|
+
const idCol = getColumn(this.usersTable, "id");
|
|
10245
|
+
if (!idCol) return null;
|
|
10246
|
+
const [row] = await this.db.select().from(this.usersTable).where(drizzleOrm.eq(idCol, id));
|
|
10247
|
+
return row ? this.mapRowToUser(row) : null;
|
|
9678
10248
|
}
|
|
9679
10249
|
async getUserByEmail(email) {
|
|
9680
|
-
const
|
|
9681
|
-
|
|
10250
|
+
const emailCol = getColumn(this.usersTable, "email");
|
|
10251
|
+
if (!emailCol) return null;
|
|
10252
|
+
const [row] = await this.db.select().from(this.usersTable).where(drizzleOrm.eq(emailCol, email.toLowerCase()));
|
|
10253
|
+
return row ? this.mapRowToUser(row) : null;
|
|
9682
10254
|
}
|
|
9683
10255
|
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
|
-
};
|
|
10256
|
+
const userIdCol = getColumn(this.usersTable, "id");
|
|
10257
|
+
if (!userIdCol) return null;
|
|
10258
|
+
const result = await this.db.select({
|
|
10259
|
+
user: this.usersTable
|
|
10260
|
+
}).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);
|
|
10261
|
+
if (result.length === 0) return null;
|
|
10262
|
+
return this.mapRowToUser(result[0].user);
|
|
9705
10263
|
}
|
|
9706
10264
|
async getUserIdentities(userId) {
|
|
10265
|
+
const schema = pgCore.getTableConfig(this.userIdentitiesTable).schema || "public";
|
|
9707
10266
|
const result = await this.db.execute(drizzleOrm.sql`
|
|
9708
10267
|
SELECT id, user_id, provider, provider_id, profile_data, created_at, updated_at
|
|
9709
|
-
FROM
|
|
10268
|
+
FROM ${drizzleOrm.sql.raw(`"${schema}"."user_identities"`)}
|
|
9710
10269
|
WHERE user_id = ${userId}
|
|
9711
10270
|
`);
|
|
9712
10271
|
return result.rows.map((row) => ({
|
|
@@ -9720,27 +10279,32 @@ ${tableRelations.join(",\n")}
|
|
|
9720
10279
|
}));
|
|
9721
10280
|
}
|
|
9722
10281
|
async linkUserIdentity(userId, provider, providerId, profileData) {
|
|
9723
|
-
await this.db.insert(
|
|
10282
|
+
await this.db.insert(this.userIdentitiesTable).values({
|
|
9724
10283
|
userId,
|
|
9725
10284
|
provider,
|
|
9726
10285
|
providerId,
|
|
9727
10286
|
profileData: profileData || null
|
|
9728
10287
|
}).onConflictDoNothing({
|
|
9729
|
-
target: [
|
|
10288
|
+
target: [this.userIdentitiesTable.provider, this.userIdentitiesTable.providerId]
|
|
9730
10289
|
});
|
|
9731
10290
|
}
|
|
9732
10291
|
async updateUser(id, data) {
|
|
9733
|
-
const
|
|
9734
|
-
|
|
9735
|
-
|
|
9736
|
-
|
|
9737
|
-
|
|
10292
|
+
const idCol = getColumn(this.usersTable, "id");
|
|
10293
|
+
if (!idCol) return null;
|
|
10294
|
+
const payload = this.mapPayload(data);
|
|
10295
|
+
const updatedAtKey = getColumnKey(this.usersTable, "updatedAt", "updated_at") || "updatedAt";
|
|
10296
|
+
payload[updatedAtKey] = /* @__PURE__ */ new Date();
|
|
10297
|
+
const [row] = await this.db.update(this.usersTable).set(payload).where(drizzleOrm.eq(idCol, id)).returning();
|
|
10298
|
+
return row ? this.mapRowToUser(row) : null;
|
|
9738
10299
|
}
|
|
9739
10300
|
async deleteUser(id) {
|
|
9740
|
-
|
|
10301
|
+
const idCol = getColumn(this.usersTable, "id");
|
|
10302
|
+
if (!idCol) return;
|
|
10303
|
+
await this.db.delete(this.usersTable).where(drizzleOrm.eq(idCol, id));
|
|
9741
10304
|
}
|
|
9742
10305
|
async listUsers() {
|
|
9743
|
-
|
|
10306
|
+
const rows = await this.db.select().from(this.usersTable);
|
|
10307
|
+
return rows.map((row) => this.mapRowToUser(row));
|
|
9744
10308
|
}
|
|
9745
10309
|
async listUsersPaginated(options) {
|
|
9746
10310
|
const limit = options?.limit ?? 25;
|
|
@@ -9749,49 +10313,40 @@ ${tableRelations.join(",\n")}
|
|
|
9749
10313
|
const orderBy = options?.orderBy || "createdAt";
|
|
9750
10314
|
const orderDir = options?.orderDir || "desc";
|
|
9751
10315
|
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";
|
|
10316
|
+
const orderCol = getColumn(this.usersTable, orderBy);
|
|
10317
|
+
const orderColumn = orderCol ? orderCol.name : "created_at";
|
|
9760
10318
|
const direction = orderDir === "asc" ? drizzleOrm.sql`ASC` : drizzleOrm.sql`DESC`;
|
|
10319
|
+
const emailCol = getColumn(this.usersTable, "email");
|
|
10320
|
+
const emailColumn = emailCol ? emailCol.name : "email";
|
|
10321
|
+
const displayNameCol = getColumn(this.usersTable, "displayName", "display_name");
|
|
10322
|
+
const displayNameColumn = displayNameCol ? displayNameCol.name : "display_name";
|
|
10323
|
+
const idCol = getColumn(this.usersTable, "id");
|
|
10324
|
+
const idColumn = idCol ? idCol.name : "id";
|
|
10325
|
+
const usersTableName = this.getQualifiedUsersTableName();
|
|
10326
|
+
const rolesSchema = pgCore.getTableConfig(this.userRolesTable).schema || "public";
|
|
9761
10327
|
const conditions = [];
|
|
9762
10328
|
if (roleId) {
|
|
9763
|
-
conditions.push(drizzleOrm.sql`EXISTS (SELECT 1 FROM
|
|
10329
|
+
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
10330
|
}
|
|
9765
10331
|
if (search) {
|
|
9766
10332
|
const pattern = `%${search}%`;
|
|
9767
|
-
conditions.push(drizzleOrm.sql`(
|
|
10333
|
+
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
10334
|
}
|
|
9769
10335
|
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
|
|
10336
|
+
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
10337
|
const countResult = await this.db.execute(drizzleOrm.sql`
|
|
9772
|
-
SELECT count(*)::int as total FROM
|
|
10338
|
+
SELECT count(*)::int as total FROM ${drizzleOrm.sql.raw(usersTableName)}
|
|
9773
10339
|
${whereClause}
|
|
9774
10340
|
`);
|
|
9775
10341
|
const total = countResult.rows[0].total;
|
|
9776
10342
|
const dataResult = await this.db.execute(drizzleOrm.sql`
|
|
9777
|
-
SELECT * FROM
|
|
10343
|
+
SELECT * FROM ${drizzleOrm.sql.raw(usersTableName)}
|
|
9778
10344
|
${whereClause}
|
|
9779
10345
|
${orderByClause}
|
|
9780
10346
|
LIMIT ${limit} OFFSET ${offset}
|
|
9781
10347
|
`);
|
|
9782
10348
|
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
|
-
}));
|
|
10349
|
+
const mappedUsers = rows.map((row) => this.mapRowToUser(row));
|
|
9795
10350
|
return {
|
|
9796
10351
|
users: mappedUsers,
|
|
9797
10352
|
total,
|
|
@@ -9803,46 +10358,63 @@ ${tableRelations.join(",\n")}
|
|
|
9803
10358
|
* Update user's password hash
|
|
9804
10359
|
*/
|
|
9805
10360
|
async updatePassword(id, passwordHash) {
|
|
9806
|
-
|
|
9807
|
-
|
|
9808
|
-
|
|
9809
|
-
|
|
10361
|
+
const idCol = getColumn(this.usersTable, "id");
|
|
10362
|
+
if (!idCol) return;
|
|
10363
|
+
const passwordHashColKey = getColumnKey(this.usersTable, "passwordHash", "password_hash") || "passwordHash";
|
|
10364
|
+
const updatedAtColKey = getColumnKey(this.usersTable, "updatedAt", "updated_at") || "updatedAt";
|
|
10365
|
+
await this.db.update(this.usersTable).set({
|
|
10366
|
+
[passwordHashColKey]: passwordHash,
|
|
10367
|
+
[updatedAtColKey]: /* @__PURE__ */ new Date()
|
|
10368
|
+
}).where(drizzleOrm.eq(idCol, id));
|
|
9810
10369
|
}
|
|
9811
10370
|
/**
|
|
9812
10371
|
* Set email verification status
|
|
9813
10372
|
*/
|
|
9814
10373
|
async setEmailVerified(id, verified) {
|
|
9815
|
-
|
|
9816
|
-
|
|
9817
|
-
|
|
9818
|
-
|
|
9819
|
-
|
|
10374
|
+
const idCol = getColumn(this.usersTable, "id");
|
|
10375
|
+
if (!idCol) return;
|
|
10376
|
+
const emailVerifiedColKey = getColumnKey(this.usersTable, "emailVerified", "email_verified") || "emailVerified";
|
|
10377
|
+
const emailVerificationTokenColKey = getColumnKey(this.usersTable, "emailVerificationToken", "email_verification_token") || "emailVerificationToken";
|
|
10378
|
+
const updatedAtColKey = getColumnKey(this.usersTable, "updatedAt", "updated_at") || "updatedAt";
|
|
10379
|
+
await this.db.update(this.usersTable).set({
|
|
10380
|
+
[emailVerifiedColKey]: verified,
|
|
10381
|
+
[emailVerificationTokenColKey]: null,
|
|
10382
|
+
[updatedAtColKey]: /* @__PURE__ */ new Date()
|
|
10383
|
+
}).where(drizzleOrm.eq(idCol, id));
|
|
9820
10384
|
}
|
|
9821
10385
|
/**
|
|
9822
10386
|
* Set email verification token
|
|
9823
10387
|
*/
|
|
9824
10388
|
async setVerificationToken(id, token) {
|
|
9825
|
-
|
|
9826
|
-
|
|
9827
|
-
|
|
9828
|
-
|
|
9829
|
-
|
|
10389
|
+
const idCol = getColumn(this.usersTable, "id");
|
|
10390
|
+
if (!idCol) return;
|
|
10391
|
+
const emailVerificationTokenColKey = getColumnKey(this.usersTable, "emailVerificationToken", "email_verification_token") || "emailVerificationToken";
|
|
10392
|
+
const emailVerificationSentAtColKey = getColumnKey(this.usersTable, "emailVerificationSentAt", "email_verification_sent_at") || "emailVerificationSentAt";
|
|
10393
|
+
const updatedAtColKey = getColumnKey(this.usersTable, "updatedAt", "updated_at") || "updatedAt";
|
|
10394
|
+
await this.db.update(this.usersTable).set({
|
|
10395
|
+
[emailVerificationTokenColKey]: token,
|
|
10396
|
+
[emailVerificationSentAtColKey]: token ? /* @__PURE__ */ new Date() : null,
|
|
10397
|
+
[updatedAtColKey]: /* @__PURE__ */ new Date()
|
|
10398
|
+
}).where(drizzleOrm.eq(idCol, id));
|
|
9830
10399
|
}
|
|
9831
10400
|
/**
|
|
9832
10401
|
* Find user by email verification token
|
|
9833
10402
|
*/
|
|
9834
10403
|
async getUserByVerificationToken(token) {
|
|
9835
|
-
const
|
|
9836
|
-
|
|
10404
|
+
const tokenCol = getColumn(this.usersTable, "emailVerificationToken", "email_verification_token");
|
|
10405
|
+
if (!tokenCol) return null;
|
|
10406
|
+
const [row] = await this.db.select().from(this.usersTable).where(drizzleOrm.eq(tokenCol, token));
|
|
10407
|
+
return row ? this.mapRowToUser(row) : null;
|
|
9837
10408
|
}
|
|
9838
10409
|
/**
|
|
9839
10410
|
* Get roles for a user from database
|
|
9840
10411
|
*/
|
|
9841
10412
|
async getUserRoles(userId) {
|
|
10413
|
+
const rolesSchema = pgCore.getTableConfig(this.rolesTable).schema || "public";
|
|
9842
10414
|
const result = await this.db.execute(drizzleOrm.sql`
|
|
9843
10415
|
SELECT r.id, r.name, r.is_admin, r.default_permissions, r.collection_permissions, r.config
|
|
9844
|
-
FROM
|
|
9845
|
-
INNER JOIN
|
|
10416
|
+
FROM ${drizzleOrm.sql.raw(`"${rolesSchema}"."roles"`)} r
|
|
10417
|
+
INNER JOIN ${drizzleOrm.sql.raw(`"${rolesSchema}"."user_roles"`)} ur ON r.id = ur.role_id
|
|
9846
10418
|
WHERE ur.user_id = ${userId}
|
|
9847
10419
|
`);
|
|
9848
10420
|
return result.rows.map((row) => ({
|
|
@@ -9865,10 +10437,11 @@ ${tableRelations.join(",\n")}
|
|
|
9865
10437
|
* Set roles for a user
|
|
9866
10438
|
*/
|
|
9867
10439
|
async setUserRoles(userId, roleIds) {
|
|
9868
|
-
|
|
10440
|
+
const rolesSchema = pgCore.getTableConfig(this.userRolesTable).schema || "public";
|
|
10441
|
+
await this.db.execute(drizzleOrm.sql`DELETE FROM ${drizzleOrm.sql.raw(`"${rolesSchema}"."user_roles"`)} WHERE user_id = ${userId}`);
|
|
9869
10442
|
for (const roleId of roleIds) {
|
|
9870
10443
|
await this.db.execute(drizzleOrm.sql`
|
|
9871
|
-
INSERT INTO
|
|
10444
|
+
INSERT INTO ${drizzleOrm.sql.raw(`"${rolesSchema}"."user_roles"`)} (user_id, role_id)
|
|
9872
10445
|
VALUES (${userId}, ${roleId})
|
|
9873
10446
|
ON CONFLICT DO NOTHING
|
|
9874
10447
|
`);
|
|
@@ -9878,8 +10451,9 @@ ${tableRelations.join(",\n")}
|
|
|
9878
10451
|
* Assign a specific role to new user
|
|
9879
10452
|
*/
|
|
9880
10453
|
async assignDefaultRole(userId, roleId) {
|
|
10454
|
+
const rolesSchema = pgCore.getTableConfig(this.userRolesTable).schema || "public";
|
|
9881
10455
|
await this.db.execute(drizzleOrm.sql`
|
|
9882
|
-
INSERT INTO
|
|
10456
|
+
INSERT INTO ${drizzleOrm.sql.raw(`"${rolesSchema}"."user_roles"`)} (user_id, role_id)
|
|
9883
10457
|
VALUES (${userId}, ${roleId})
|
|
9884
10458
|
ON CONFLICT DO NOTHING
|
|
9885
10459
|
`);
|
|
@@ -9898,13 +10472,25 @@ ${tableRelations.join(",\n")}
|
|
|
9898
10472
|
}
|
|
9899
10473
|
}
|
|
9900
10474
|
class RoleService {
|
|
9901
|
-
constructor(db) {
|
|
10475
|
+
constructor(db, tableOrTables) {
|
|
9902
10476
|
this.db = db;
|
|
10477
|
+
if (tableOrTables && (tableOrTables.roles || tableOrTables.users)) {
|
|
10478
|
+
this.rolesTable = tableOrTables.roles || roles;
|
|
10479
|
+
} else {
|
|
10480
|
+
this.rolesTable = tableOrTables || roles;
|
|
10481
|
+
}
|
|
10482
|
+
}
|
|
10483
|
+
rolesTable;
|
|
10484
|
+
getQualifiedRolesTableName() {
|
|
10485
|
+
const name = drizzleOrm.getTableName(this.rolesTable);
|
|
10486
|
+
const schema = pgCore.getTableConfig(this.rolesTable).schema || "public";
|
|
10487
|
+
return `"${schema}"."${name}"`;
|
|
9903
10488
|
}
|
|
9904
10489
|
async getRoleById(id) {
|
|
10490
|
+
const tableName = this.getQualifiedRolesTableName();
|
|
9905
10491
|
const result = await this.db.execute(drizzleOrm.sql`
|
|
9906
10492
|
SELECT id, name, is_admin, default_permissions, collection_permissions, config
|
|
9907
|
-
FROM
|
|
10493
|
+
FROM ${drizzleOrm.sql.raw(tableName)}
|
|
9908
10494
|
WHERE id = ${id}
|
|
9909
10495
|
`);
|
|
9910
10496
|
if (result.rows.length === 0) return null;
|
|
@@ -9919,9 +10505,10 @@ ${tableRelations.join(",\n")}
|
|
|
9919
10505
|
};
|
|
9920
10506
|
}
|
|
9921
10507
|
async listRoles() {
|
|
10508
|
+
const tableName = this.getQualifiedRolesTableName();
|
|
9922
10509
|
const result = await this.db.execute(drizzleOrm.sql`
|
|
9923
10510
|
SELECT id, name, is_admin, default_permissions, collection_permissions, config
|
|
9924
|
-
FROM
|
|
10511
|
+
FROM ${drizzleOrm.sql.raw(tableName)}
|
|
9925
10512
|
ORDER BY name
|
|
9926
10513
|
`);
|
|
9927
10514
|
return result.rows.map((row) => ({
|
|
@@ -9934,8 +10521,9 @@ ${tableRelations.join(",\n")}
|
|
|
9934
10521
|
}));
|
|
9935
10522
|
}
|
|
9936
10523
|
async createRole(data) {
|
|
10524
|
+
const tableName = this.getQualifiedRolesTableName();
|
|
9937
10525
|
const result = await this.db.execute(drizzleOrm.sql`
|
|
9938
|
-
INSERT INTO
|
|
10526
|
+
INSERT INTO ${drizzleOrm.sql.raw(tableName)} (id, name, is_admin, default_permissions, collection_permissions, config)
|
|
9939
10527
|
VALUES (
|
|
9940
10528
|
${data.id},
|
|
9941
10529
|
${data.name},
|
|
@@ -9959,8 +10547,9 @@ ${tableRelations.join(",\n")}
|
|
|
9959
10547
|
async updateRole(id, data) {
|
|
9960
10548
|
const existing = await this.getRoleById(id);
|
|
9961
10549
|
if (!existing) return null;
|
|
10550
|
+
const tableName = this.getQualifiedRolesTableName();
|
|
9962
10551
|
await this.db.execute(drizzleOrm.sql`
|
|
9963
|
-
UPDATE
|
|
10552
|
+
UPDATE ${drizzleOrm.sql.raw(tableName)}
|
|
9964
10553
|
SET
|
|
9965
10554
|
name = ${data.name ?? existing.name},
|
|
9966
10555
|
is_admin = ${data.isAdmin ?? existing.isAdmin},
|
|
@@ -9972,23 +10561,36 @@ ${tableRelations.join(",\n")}
|
|
|
9972
10561
|
return this.getRoleById(id);
|
|
9973
10562
|
}
|
|
9974
10563
|
async deleteRole(id) {
|
|
9975
|
-
|
|
10564
|
+
const tableName = this.getQualifiedRolesTableName();
|
|
10565
|
+
await this.db.execute(drizzleOrm.sql`DELETE FROM ${drizzleOrm.sql.raw(tableName)} WHERE id = ${id}`);
|
|
9976
10566
|
}
|
|
9977
10567
|
}
|
|
9978
10568
|
class RefreshTokenService {
|
|
9979
|
-
constructor(db) {
|
|
10569
|
+
constructor(db, tableOrTables) {
|
|
9980
10570
|
this.db = db;
|
|
10571
|
+
if (tableOrTables && (tableOrTables.refreshTokens || tableOrTables.users)) {
|
|
10572
|
+
this.refreshTokensTable = tableOrTables.refreshTokens || refreshTokens;
|
|
10573
|
+
} else {
|
|
10574
|
+
this.refreshTokensTable = tableOrTables || refreshTokens;
|
|
10575
|
+
}
|
|
10576
|
+
}
|
|
10577
|
+
refreshTokensTable;
|
|
10578
|
+
getQualifiedRefreshTokensTableName() {
|
|
10579
|
+
const name = drizzleOrm.getTableName(this.refreshTokensTable);
|
|
10580
|
+
const schema = pgCore.getTableConfig(this.refreshTokensTable).schema || "public";
|
|
10581
|
+
return `"${schema}"."${name}"`;
|
|
9981
10582
|
}
|
|
9982
10583
|
async createToken(userId, tokenHash, expiresAt, userAgent, ipAddress) {
|
|
9983
10584
|
const safeUserAgent = userAgent || "";
|
|
9984
10585
|
const safeIpAddress = ipAddress || "";
|
|
10586
|
+
const tableName = this.getQualifiedRefreshTokensTableName();
|
|
9985
10587
|
await this.db.execute(drizzleOrm.sql`
|
|
9986
|
-
DELETE FROM
|
|
10588
|
+
DELETE FROM ${drizzleOrm.sql.raw(tableName)}
|
|
9987
10589
|
WHERE user_id = ${userId}
|
|
9988
10590
|
AND user_agent = ${safeUserAgent}
|
|
9989
10591
|
AND ip_address = ${safeIpAddress}
|
|
9990
10592
|
`);
|
|
9991
|
-
await this.db.insert(
|
|
10593
|
+
await this.db.insert(this.refreshTokensTable).values({
|
|
9992
10594
|
userId,
|
|
9993
10595
|
tokenHash,
|
|
9994
10596
|
expiresAt,
|
|
@@ -9998,51 +10600,63 @@ ${tableRelations.join(",\n")}
|
|
|
9998
10600
|
}
|
|
9999
10601
|
async findByHash(tokenHash) {
|
|
10000
10602
|
const [token] = await this.db.select({
|
|
10001
|
-
id:
|
|
10002
|
-
userId:
|
|
10003
|
-
tokenHash:
|
|
10004
|
-
expiresAt:
|
|
10005
|
-
createdAt:
|
|
10006
|
-
userAgent:
|
|
10007
|
-
ipAddress:
|
|
10008
|
-
}).from(
|
|
10603
|
+
id: this.refreshTokensTable.id,
|
|
10604
|
+
userId: this.refreshTokensTable.userId,
|
|
10605
|
+
tokenHash: this.refreshTokensTable.tokenHash,
|
|
10606
|
+
expiresAt: this.refreshTokensTable.expiresAt,
|
|
10607
|
+
createdAt: this.refreshTokensTable.createdAt,
|
|
10608
|
+
userAgent: this.refreshTokensTable.userAgent,
|
|
10609
|
+
ipAddress: this.refreshTokensTable.ipAddress
|
|
10610
|
+
}).from(this.refreshTokensTable).where(drizzleOrm.eq(this.refreshTokensTable.tokenHash, tokenHash));
|
|
10009
10611
|
return token || null;
|
|
10010
10612
|
}
|
|
10011
10613
|
async deleteByHash(tokenHash) {
|
|
10012
|
-
await this.db.delete(
|
|
10614
|
+
await this.db.delete(this.refreshTokensTable).where(drizzleOrm.eq(this.refreshTokensTable.tokenHash, tokenHash));
|
|
10013
10615
|
}
|
|
10014
10616
|
async deleteAllForUser(userId) {
|
|
10015
|
-
await this.db.delete(
|
|
10617
|
+
await this.db.delete(this.refreshTokensTable).where(drizzleOrm.eq(this.refreshTokensTable.userId, userId));
|
|
10016
10618
|
}
|
|
10017
10619
|
async listForUser(userId) {
|
|
10018
10620
|
const tokens = await this.db.select({
|
|
10019
|
-
id:
|
|
10020
|
-
userId:
|
|
10021
|
-
tokenHash:
|
|
10022
|
-
expiresAt:
|
|
10023
|
-
createdAt:
|
|
10024
|
-
userAgent:
|
|
10025
|
-
ipAddress:
|
|
10026
|
-
}).from(
|
|
10621
|
+
id: this.refreshTokensTable.id,
|
|
10622
|
+
userId: this.refreshTokensTable.userId,
|
|
10623
|
+
tokenHash: this.refreshTokensTable.tokenHash,
|
|
10624
|
+
expiresAt: this.refreshTokensTable.expiresAt,
|
|
10625
|
+
createdAt: this.refreshTokensTable.createdAt,
|
|
10626
|
+
userAgent: this.refreshTokensTable.userAgent,
|
|
10627
|
+
ipAddress: this.refreshTokensTable.ipAddress
|
|
10628
|
+
}).from(this.refreshTokensTable).where(drizzleOrm.eq(this.refreshTokensTable.userId, userId)).orderBy(this.refreshTokensTable.createdAt);
|
|
10027
10629
|
return tokens;
|
|
10028
10630
|
}
|
|
10029
10631
|
async deleteById(id, userId) {
|
|
10030
|
-
await this.db.delete(
|
|
10632
|
+
await this.db.delete(this.refreshTokensTable).where(drizzleOrm.sql`${this.refreshTokensTable.id} = ${id} AND ${this.refreshTokensTable.userId} = ${userId}`);
|
|
10031
10633
|
}
|
|
10032
10634
|
}
|
|
10033
10635
|
class PasswordResetTokenService {
|
|
10034
|
-
constructor(db) {
|
|
10636
|
+
constructor(db, tableOrTables) {
|
|
10035
10637
|
this.db = db;
|
|
10638
|
+
if (tableOrTables && (tableOrTables.passwordResetTokens || tableOrTables.users)) {
|
|
10639
|
+
this.passwordResetTokensTable = tableOrTables.passwordResetTokens || passwordResetTokens;
|
|
10640
|
+
} else {
|
|
10641
|
+
this.passwordResetTokensTable = tableOrTables || passwordResetTokens;
|
|
10642
|
+
}
|
|
10643
|
+
}
|
|
10644
|
+
passwordResetTokensTable;
|
|
10645
|
+
getQualifiedPasswordResetTokensTableName() {
|
|
10646
|
+
const name = drizzleOrm.getTableName(this.passwordResetTokensTable);
|
|
10647
|
+
const schema = pgCore.getTableConfig(this.passwordResetTokensTable).schema || "public";
|
|
10648
|
+
return `"${schema}"."${name}"`;
|
|
10036
10649
|
}
|
|
10037
10650
|
/**
|
|
10038
10651
|
* Create a password reset token
|
|
10039
10652
|
*/
|
|
10040
10653
|
async createToken(userId, tokenHash, expiresAt) {
|
|
10654
|
+
const tableName = this.getQualifiedPasswordResetTokensTableName();
|
|
10041
10655
|
await this.db.execute(drizzleOrm.sql`
|
|
10042
|
-
DELETE FROM
|
|
10656
|
+
DELETE FROM ${drizzleOrm.sql.raw(tableName)}
|
|
10043
10657
|
WHERE user_id = ${userId} AND used_at IS NULL
|
|
10044
10658
|
`);
|
|
10045
|
-
await this.db.insert(
|
|
10659
|
+
await this.db.insert(this.passwordResetTokensTable).values({
|
|
10046
10660
|
userId,
|
|
10047
10661
|
tokenHash,
|
|
10048
10662
|
expiresAt
|
|
@@ -10053,13 +10667,14 @@ ${tableRelations.join(",\n")}
|
|
|
10053
10667
|
*/
|
|
10054
10668
|
async findValidByHash(tokenHash) {
|
|
10055
10669
|
const [token] = await this.db.select({
|
|
10056
|
-
userId:
|
|
10057
|
-
expiresAt:
|
|
10058
|
-
}).from(
|
|
10670
|
+
userId: this.passwordResetTokensTable.userId,
|
|
10671
|
+
expiresAt: this.passwordResetTokensTable.expiresAt
|
|
10672
|
+
}).from(this.passwordResetTokensTable).where(drizzleOrm.eq(this.passwordResetTokensTable.tokenHash, tokenHash));
|
|
10059
10673
|
if (!token) return null;
|
|
10674
|
+
const tableName = this.getQualifiedPasswordResetTokensTableName();
|
|
10060
10675
|
const result = await this.db.execute(drizzleOrm.sql`
|
|
10061
10676
|
SELECT user_id, expires_at
|
|
10062
|
-
FROM
|
|
10677
|
+
FROM ${drizzleOrm.sql.raw(tableName)}
|
|
10063
10678
|
WHERE token_hash = ${tokenHash}
|
|
10064
10679
|
AND used_at IS NULL
|
|
10065
10680
|
AND expires_at > NOW()
|
|
@@ -10075,31 +10690,32 @@ ${tableRelations.join(",\n")}
|
|
|
10075
10690
|
* Mark token as used
|
|
10076
10691
|
*/
|
|
10077
10692
|
async markAsUsed(tokenHash) {
|
|
10078
|
-
await this.db.update(
|
|
10693
|
+
await this.db.update(this.passwordResetTokensTable).set({
|
|
10079
10694
|
usedAt: /* @__PURE__ */ new Date()
|
|
10080
|
-
}).where(drizzleOrm.eq(
|
|
10695
|
+
}).where(drizzleOrm.eq(this.passwordResetTokensTable.tokenHash, tokenHash));
|
|
10081
10696
|
}
|
|
10082
10697
|
/**
|
|
10083
10698
|
* Delete all tokens for a user
|
|
10084
10699
|
*/
|
|
10085
10700
|
async deleteAllForUser(userId) {
|
|
10086
|
-
await this.db.delete(
|
|
10701
|
+
await this.db.delete(this.passwordResetTokensTable).where(drizzleOrm.eq(this.passwordResetTokensTable.userId, userId));
|
|
10087
10702
|
}
|
|
10088
10703
|
/**
|
|
10089
10704
|
* Clean up expired tokens
|
|
10090
10705
|
*/
|
|
10091
10706
|
async deleteExpired() {
|
|
10707
|
+
const tableName = this.getQualifiedPasswordResetTokensTableName();
|
|
10092
10708
|
await this.db.execute(drizzleOrm.sql`
|
|
10093
|
-
DELETE FROM
|
|
10709
|
+
DELETE FROM ${drizzleOrm.sql.raw(tableName)}
|
|
10094
10710
|
WHERE expires_at < NOW()
|
|
10095
10711
|
`);
|
|
10096
10712
|
}
|
|
10097
10713
|
}
|
|
10098
10714
|
class PostgresTokenRepository {
|
|
10099
|
-
constructor(db) {
|
|
10715
|
+
constructor(db, tableOrTables) {
|
|
10100
10716
|
this.db = db;
|
|
10101
|
-
this.refreshTokenService = new RefreshTokenService(db);
|
|
10102
|
-
this.passwordResetTokenService = new PasswordResetTokenService(db);
|
|
10717
|
+
this.refreshTokenService = new RefreshTokenService(db, tableOrTables);
|
|
10718
|
+
this.passwordResetTokenService = new PasswordResetTokenService(db, tableOrTables);
|
|
10103
10719
|
}
|
|
10104
10720
|
refreshTokenService;
|
|
10105
10721
|
passwordResetTokenService;
|
|
@@ -10140,11 +10756,11 @@ ${tableRelations.join(",\n")}
|
|
|
10140
10756
|
}
|
|
10141
10757
|
}
|
|
10142
10758
|
class PostgresAuthRepository {
|
|
10143
|
-
constructor(db) {
|
|
10759
|
+
constructor(db, tableOrTables) {
|
|
10144
10760
|
this.db = db;
|
|
10145
|
-
this.userService = new UserService(db);
|
|
10146
|
-
this.roleService = new RoleService(db);
|
|
10147
|
-
this.tokenRepository = new PostgresTokenRepository(db);
|
|
10761
|
+
this.userService = new UserService(db, tableOrTables);
|
|
10762
|
+
this.roleService = new RoleService(db, tableOrTables);
|
|
10763
|
+
this.tokenRepository = new PostgresTokenRepository(db, tableOrTables);
|
|
10148
10764
|
}
|
|
10149
10765
|
userService;
|
|
10150
10766
|
roleService;
|
|
@@ -10205,8 +10821,7 @@ ${tableRelations.join(",\n")}
|
|
|
10205
10821
|
await this.userService.assignDefaultRole(userId, roleId);
|
|
10206
10822
|
}
|
|
10207
10823
|
async getUserWithRoles(userId) {
|
|
10208
|
-
|
|
10209
|
-
return result;
|
|
10824
|
+
return this.userService.getUserWithRoles(userId);
|
|
10210
10825
|
}
|
|
10211
10826
|
// Role operations (delegate to RoleService)
|
|
10212
10827
|
async getRoleById(id) {
|
|
@@ -10394,6 +11009,24 @@ ${tableRelations.join(",\n")}
|
|
|
10394
11009
|
return result.rowCount ?? 0;
|
|
10395
11010
|
}
|
|
10396
11011
|
}
|
|
11012
|
+
function deepEqual(a, b) {
|
|
11013
|
+
if (a === b) return true;
|
|
11014
|
+
if (a == null || b == null) return false;
|
|
11015
|
+
if (a instanceof Date && b instanceof Date) return a.getTime() === b.getTime();
|
|
11016
|
+
if (Array.isArray(a) && Array.isArray(b)) {
|
|
11017
|
+
if (a.length !== b.length) return false;
|
|
11018
|
+
return a.every((v, i) => deepEqual(v, b[i]));
|
|
11019
|
+
}
|
|
11020
|
+
if (typeof a === "object" && typeof b === "object") {
|
|
11021
|
+
const aObj = a;
|
|
11022
|
+
const bObj = b;
|
|
11023
|
+
const aKeys = Object.keys(aObj);
|
|
11024
|
+
const bKeys = Object.keys(bObj);
|
|
11025
|
+
if (aKeys.length !== bKeys.length) return false;
|
|
11026
|
+
return aKeys.every((k) => deepEqual(aObj[k], bObj[k]));
|
|
11027
|
+
}
|
|
11028
|
+
return false;
|
|
11029
|
+
}
|
|
10397
11030
|
function findChangedFields(oldValues, newValues) {
|
|
10398
11031
|
const changed = [];
|
|
10399
11032
|
const allKeys = /* @__PURE__ */ new Set([...Object.keys(oldValues), ...Object.keys(newValues)]);
|
|
@@ -10403,7 +11036,7 @@ ${tableRelations.join(",\n")}
|
|
|
10403
11036
|
if (key.startsWith("__")) continue;
|
|
10404
11037
|
if (oldVal !== newVal) {
|
|
10405
11038
|
if (typeof oldVal === "object" && oldVal !== null && typeof newVal === "object" && newVal !== null) {
|
|
10406
|
-
if (
|
|
11039
|
+
if (!deepEqual(oldVal, newVal)) {
|
|
10407
11040
|
changed.push(key);
|
|
10408
11041
|
}
|
|
10409
11042
|
} else {
|
|
@@ -10521,14 +11154,32 @@ ${tableRelations.join(",\n")}
|
|
|
10521
11154
|
if (!authConfig) return void 0;
|
|
10522
11155
|
const internals = driverResult.internals;
|
|
10523
11156
|
const db = internals.db;
|
|
10524
|
-
|
|
11157
|
+
const registry = internals.registry;
|
|
11158
|
+
await ensureAuthTablesExist(db, registry);
|
|
10525
11159
|
let emailService;
|
|
10526
11160
|
if (authConfig.email) {
|
|
10527
11161
|
emailService = serverCore.createEmailService(authConfig.email);
|
|
10528
11162
|
}
|
|
10529
|
-
const
|
|
10530
|
-
const
|
|
10531
|
-
|
|
11163
|
+
const customUsersTable = registry?.getTable("users");
|
|
11164
|
+
const customRolesTable = registry?.getTable("roles");
|
|
11165
|
+
let usersSchemaName = "rebase";
|
|
11166
|
+
let rolesSchemaName = "rebase";
|
|
11167
|
+
if (customUsersTable) {
|
|
11168
|
+
usersSchemaName = pgCore.getTableConfig(customUsersTable).schema || "public";
|
|
11169
|
+
}
|
|
11170
|
+
if (customRolesTable) {
|
|
11171
|
+
rolesSchemaName = pgCore.getTableConfig(customRolesTable).schema || "public";
|
|
11172
|
+
}
|
|
11173
|
+
const authTables = createAuthSchema(rolesSchemaName, usersSchemaName);
|
|
11174
|
+
if (customUsersTable) {
|
|
11175
|
+
authTables.users = customUsersTable;
|
|
11176
|
+
}
|
|
11177
|
+
if (customRolesTable) {
|
|
11178
|
+
authTables.roles = customRolesTable;
|
|
11179
|
+
}
|
|
11180
|
+
const userService = new UserService(db, authTables);
|
|
11181
|
+
const roleService = new RoleService(db, authTables);
|
|
11182
|
+
const authRepository = new PostgresAuthRepository(db, authTables);
|
|
10532
11183
|
return {
|
|
10533
11184
|
userService,
|
|
10534
11185
|
roleService,
|
|
@@ -10560,11 +11211,54 @@ ${tableRelations.join(",\n")}
|
|
|
10560
11211
|
},
|
|
10561
11212
|
mountRoutes(app, basePath, driverResult) {
|
|
10562
11213
|
},
|
|
10563
|
-
async initializeWebsockets(server, realtimeService, driver, config) {
|
|
11214
|
+
async initializeWebsockets(server, realtimeService, driver, config, adapter) {
|
|
10564
11215
|
const {
|
|
10565
11216
|
createPostgresWebSocket: createPostgresWebSocket2
|
|
10566
11217
|
} = await Promise.resolve().then(() => websocket);
|
|
10567
|
-
createPostgresWebSocket2(server, realtimeService, driver, config);
|
|
11218
|
+
createPostgresWebSocket2(server, realtimeService, driver, config, adapter);
|
|
11219
|
+
}
|
|
11220
|
+
};
|
|
11221
|
+
}
|
|
11222
|
+
function createPostgresAdapter(pgConfig) {
|
|
11223
|
+
const bootstrapper = createPostgresBootstrapper(pgConfig);
|
|
11224
|
+
return {
|
|
11225
|
+
type: bootstrapper.type,
|
|
11226
|
+
async initializeDriver(config) {
|
|
11227
|
+
return bootstrapper.initializeDriver(config);
|
|
11228
|
+
},
|
|
11229
|
+
async initializeRealtime(driverResult) {
|
|
11230
|
+
if (bootstrapper.initializeRealtime) {
|
|
11231
|
+
return bootstrapper.initializeRealtime({}, driverResult);
|
|
11232
|
+
}
|
|
11233
|
+
return void 0;
|
|
11234
|
+
},
|
|
11235
|
+
async initializeAuth(config, driverResult) {
|
|
11236
|
+
if (bootstrapper.initializeAuth) {
|
|
11237
|
+
return bootstrapper.initializeAuth(config, driverResult);
|
|
11238
|
+
}
|
|
11239
|
+
return void 0;
|
|
11240
|
+
},
|
|
11241
|
+
async initializeHistory(config, driverResult) {
|
|
11242
|
+
if (bootstrapper.initializeHistory) {
|
|
11243
|
+
return bootstrapper.initializeHistory(config, driverResult);
|
|
11244
|
+
}
|
|
11245
|
+
return void 0;
|
|
11246
|
+
},
|
|
11247
|
+
initializeWebsockets(server, realtimeService, driver, config) {
|
|
11248
|
+
if (bootstrapper.initializeWebsockets) {
|
|
11249
|
+
return bootstrapper.initializeWebsockets(server, realtimeService, driver, config);
|
|
11250
|
+
}
|
|
11251
|
+
},
|
|
11252
|
+
getAdmin(driverResult) {
|
|
11253
|
+
if (bootstrapper.getAdmin) {
|
|
11254
|
+
return bootstrapper.getAdmin(driverResult);
|
|
11255
|
+
}
|
|
11256
|
+
return void 0;
|
|
11257
|
+
},
|
|
11258
|
+
mountRoutes(app, basePath, driverResult) {
|
|
11259
|
+
if (bootstrapper.mountRoutes) {
|
|
11260
|
+
bootstrapper.mountRoutes(app, basePath, driverResult);
|
|
11261
|
+
}
|
|
10568
11262
|
}
|
|
10569
11263
|
};
|
|
10570
11264
|
}
|
|
@@ -10578,6 +11272,8 @@ ${tableRelations.join(",\n")}
|
|
|
10578
11272
|
exports2.PostgresRealtimeProvider = PostgresRealtimeProvider;
|
|
10579
11273
|
exports2.RealtimeService = RealtimeService;
|
|
10580
11274
|
exports2.appConfig = appConfig;
|
|
11275
|
+
exports2.createAuthSchema = createAuthSchema;
|
|
11276
|
+
exports2.createPostgresAdapter = createPostgresAdapter;
|
|
10581
11277
|
exports2.createPostgresBootstrapper = createPostgresBootstrapper;
|
|
10582
11278
|
exports2.createPostgresDatabaseConnection = createPostgresDatabaseConnection;
|
|
10583
11279
|
exports2.createPostgresWebSocket = createPostgresWebSocket;
|
|
@@ -10595,6 +11291,7 @@ ${tableRelations.join(",\n")}
|
|
|
10595
11291
|
exports2.userRolesRelations = userRolesRelations;
|
|
10596
11292
|
exports2.users = users;
|
|
10597
11293
|
exports2.usersRelations = usersRelations;
|
|
11294
|
+
exports2.usersSchema = usersSchema;
|
|
10598
11295
|
Object.defineProperty(exports2, Symbol.toStringTag, { value: "Module" });
|
|
10599
11296
|
});
|
|
10600
11297
|
//# sourceMappingURL=index.umd.js.map
|