@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.es.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { Pool, Client } from "pg";
|
|
2
2
|
import { drizzle } from "drizzle-orm/node-postgres";
|
|
3
3
|
import { sql, inArray, eq, and, or, ilike, asc, desc, gt, lt, getTableName as getTableName$1, count, relations, isTable } from "drizzle-orm";
|
|
4
|
-
import { pgSchema, timestamp, varchar, boolean, uuid,
|
|
4
|
+
import { PgVarchar, PgText, PgChar, pgSchema, pgTable, timestamp, jsonb, varchar, boolean, uuid, primaryKey, unique, getTableConfig } from "drizzle-orm/pg-core";
|
|
5
5
|
import { createHash, randomUUID } from "crypto";
|
|
6
6
|
import * as fs from "fs";
|
|
7
7
|
import { promises } from "fs";
|
|
@@ -51,6 +51,12 @@ function createPostgresDatabaseConnection(connectionString, schema, poolConfig)
|
|
|
51
51
|
connectionString
|
|
52
52
|
};
|
|
53
53
|
}
|
|
54
|
+
class Vector {
|
|
55
|
+
value;
|
|
56
|
+
constructor(value) {
|
|
57
|
+
this.value = value;
|
|
58
|
+
}
|
|
59
|
+
}
|
|
54
60
|
function isPostgresCollection(collection) {
|
|
55
61
|
return !collection.driver || collection.driver === "postgres";
|
|
56
62
|
}
|
|
@@ -127,16 +133,25 @@ const DEFAULT_ONE_OF_VALUE = "value";
|
|
|
127
133
|
const tokenizeRegex = /[A-Z]{2,}(?=[A-Z][a-z]|\b)|[A-Z]?[a-z]+|[0-9]+(?:[a-z](?![a-z]))?|[A-Z]/g;
|
|
128
134
|
const snakeCaseRegex = tokenizeRegex;
|
|
129
135
|
const toSnakeCase = (str) => {
|
|
136
|
+
if (!str || typeof str !== "string") return "";
|
|
130
137
|
const regExpMatchArray = str.match(snakeCaseRegex);
|
|
131
138
|
if (!regExpMatchArray) return "";
|
|
132
139
|
return regExpMatchArray.map((x) => x.toLowerCase()).join("_");
|
|
133
140
|
};
|
|
141
|
+
function camelCase(str) {
|
|
142
|
+
if (!str) return "";
|
|
143
|
+
if (str.length === 1) return str.toLowerCase();
|
|
144
|
+
const parts = str.split(/[-_ ]+/).filter(Boolean);
|
|
145
|
+
if (parts.length === 0) return "";
|
|
146
|
+
return parts[0].toLowerCase() + // Transform remaining parts to have first letter uppercase
|
|
147
|
+
parts.slice(1).map((part) => part.charAt(0).toUpperCase() + part.substring(1).toLowerCase()).join("");
|
|
148
|
+
}
|
|
134
149
|
var commonjsGlobal = typeof globalThis !== "undefined" ? globalThis : typeof window !== "undefined" ? window : typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : {};
|
|
135
150
|
function commonjsRequire(path2) {
|
|
136
151
|
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.');
|
|
137
152
|
}
|
|
138
153
|
var object_hash = { exports: {} };
|
|
139
|
-
(function(module, exports
|
|
154
|
+
(function(module, exports) {
|
|
140
155
|
!function(e) {
|
|
141
156
|
module.exports = e();
|
|
142
157
|
}(function() {
|
|
@@ -1143,126 +1158,58 @@ function enumToObjectEntries(enumValues) {
|
|
|
1143
1158
|
});
|
|
1144
1159
|
}
|
|
1145
1160
|
}
|
|
1146
|
-
function
|
|
1147
|
-
if (
|
|
1148
|
-
|
|
1149
|
-
}
|
|
1150
|
-
if (getDataSourceCapabilities(collection.driver).supportsSubcollections && collection.subcollections) {
|
|
1151
|
-
return collection.subcollections() ?? [];
|
|
1161
|
+
function sanitizeRelation(relation, sourceCollection, resolveCollection) {
|
|
1162
|
+
if (!relation.target) {
|
|
1163
|
+
throw new Error("Relation is missing a `target` collection.");
|
|
1152
1164
|
}
|
|
1153
|
-
|
|
1154
|
-
|
|
1155
|
-
|
|
1156
|
-
|
|
1157
|
-
|
|
1158
|
-
|
|
1159
|
-
|
|
1160
|
-
|
|
1161
|
-
|
|
1162
|
-
|
|
1163
|
-
customName = prop[1].name;
|
|
1164
|
-
}
|
|
1165
|
-
}
|
|
1166
|
-
const baseOverrides = {
|
|
1167
|
-
slug: relationKey
|
|
1168
|
-
};
|
|
1169
|
-
if (customName) {
|
|
1170
|
-
baseOverrides.name = customName;
|
|
1171
|
-
baseOverrides.singularName = customName;
|
|
1172
|
-
}
|
|
1173
|
-
const targetWithOverrides = {
|
|
1174
|
-
...target,
|
|
1175
|
-
...baseOverrides
|
|
1165
|
+
const rawTarget = relation.target;
|
|
1166
|
+
let targetCollection;
|
|
1167
|
+
if (typeof rawTarget === "string") {
|
|
1168
|
+
if (resolveCollection) {
|
|
1169
|
+
targetCollection = resolveCollection(rawTarget);
|
|
1170
|
+
}
|
|
1171
|
+
if (!targetCollection) {
|
|
1172
|
+
targetCollection = {
|
|
1173
|
+
slug: rawTarget,
|
|
1174
|
+
name: rawTarget
|
|
1176
1175
|
};
|
|
1177
|
-
return r.overrides ? mergeDeep(targetWithOverrides, r.overrides) : targetWithOverrides;
|
|
1178
|
-
}).filter((c) => Boolean(c));
|
|
1179
|
-
}
|
|
1180
|
-
return [];
|
|
1181
|
-
}
|
|
1182
|
-
function hasPropertyCallbacks(properties, callbackName) {
|
|
1183
|
-
if (!properties) return false;
|
|
1184
|
-
for (const property of Object.values(properties)) {
|
|
1185
|
-
if (property.callbacks?.[callbackName]) return true;
|
|
1186
|
-
if (property.type === "map" && property.properties) {
|
|
1187
|
-
if (hasPropertyCallbacks(property.properties, callbackName)) return true;
|
|
1188
|
-
} else if (property.type === "array" && property.of) {
|
|
1189
|
-
const ofs = Array.isArray(property.of) ? property.of : [property.of];
|
|
1190
|
-
for (const of of ofs) {
|
|
1191
|
-
if (of.callbacks?.[callbackName]) return true;
|
|
1192
|
-
if (of.type === "map" && of.properties && hasPropertyCallbacks(of.properties, callbackName)) return true;
|
|
1193
|
-
}
|
|
1194
1176
|
}
|
|
1195
|
-
}
|
|
1196
|
-
|
|
1197
|
-
|
|
1198
|
-
|
|
1199
|
-
|
|
1200
|
-
const result = {
|
|
1201
|
-
...values
|
|
1202
|
-
};
|
|
1203
|
-
for (const [key, property] of Object.entries(properties)) {
|
|
1204
|
-
if (result[key] === void 0) continue;
|
|
1205
|
-
let currentValue = result[key];
|
|
1206
|
-
const previousValue = previousValues?.[key];
|
|
1207
|
-
if (property.type === "array" && Array.isArray(currentValue)) {
|
|
1208
|
-
if (property.of && !Array.isArray(property.of)) {
|
|
1209
|
-
currentValue = await Promise.all(currentValue.map(async (item, index) => {
|
|
1210
|
-
const prevItem = Array.isArray(previousValue) ? previousValue[index] : void 0;
|
|
1211
|
-
const singlePropData = {
|
|
1212
|
-
"_tmp": property.of
|
|
1213
|
-
};
|
|
1214
|
-
const res = await processProperties(singlePropData, {
|
|
1215
|
-
"_tmp": item
|
|
1216
|
-
}, {
|
|
1217
|
-
"_tmp": prevItem
|
|
1218
|
-
}, propsContext, callbackName);
|
|
1219
|
-
return res["_tmp"];
|
|
1220
|
-
}));
|
|
1177
|
+
} else if (typeof rawTarget === "function") {
|
|
1178
|
+
const evaluated = rawTarget();
|
|
1179
|
+
if (typeof evaluated === "string") {
|
|
1180
|
+
if (resolveCollection) {
|
|
1181
|
+
targetCollection = resolveCollection(evaluated);
|
|
1221
1182
|
}
|
|
1222
|
-
|
|
1223
|
-
|
|
1224
|
-
|
|
1225
|
-
|
|
1226
|
-
|
|
1227
|
-
...propsContext,
|
|
1228
|
-
value: currentValue,
|
|
1229
|
-
previousValue
|
|
1230
|
-
}));
|
|
1231
|
-
if (cbRes !== void 0) {
|
|
1232
|
-
currentValue = cbRes;
|
|
1183
|
+
if (!targetCollection) {
|
|
1184
|
+
targetCollection = {
|
|
1185
|
+
slug: evaluated,
|
|
1186
|
+
name: evaluated
|
|
1187
|
+
};
|
|
1233
1188
|
}
|
|
1189
|
+
} else {
|
|
1190
|
+
targetCollection = evaluated;
|
|
1234
1191
|
}
|
|
1235
|
-
|
|
1236
|
-
|
|
1237
|
-
return result;
|
|
1238
|
-
}
|
|
1239
|
-
const buildPropertyCallbacks = (properties) => {
|
|
1240
|
-
if (!properties) return void 0;
|
|
1241
|
-
const propertyCallbacks = {};
|
|
1242
|
-
if (hasPropertyCallbacks(properties, "afterRead")) {
|
|
1243
|
-
propertyCallbacks.afterRead = async (props) => {
|
|
1244
|
-
const processedValues = await processProperties(properties, props.entity.values, props.entity.values, props, "afterRead");
|
|
1245
|
-
return {
|
|
1246
|
-
...props.entity,
|
|
1247
|
-
values: processedValues
|
|
1248
|
-
};
|
|
1249
|
-
};
|
|
1192
|
+
} else if (rawTarget && typeof rawTarget === "object") {
|
|
1193
|
+
targetCollection = rawTarget;
|
|
1250
1194
|
}
|
|
1251
|
-
if (
|
|
1252
|
-
|
|
1253
|
-
return await processProperties(properties, props.values, props.previousValues ?? {}, props, "beforeSave");
|
|
1254
|
-
};
|
|
1255
|
-
}
|
|
1256
|
-
return Object.keys(propertyCallbacks).length > 0 ? propertyCallbacks : void 0;
|
|
1257
|
-
};
|
|
1258
|
-
function sanitizeRelation(relation, sourceCollection) {
|
|
1259
|
-
if (!relation.target) {
|
|
1260
|
-
throw new Error("Relation is missing a `target` collection.");
|
|
1195
|
+
if (!targetCollection) {
|
|
1196
|
+
throw new Error("Relation is missing a valid `target` collection.");
|
|
1261
1197
|
}
|
|
1262
|
-
const targetCollection = relation.target();
|
|
1263
1198
|
const newRelation = {
|
|
1264
1199
|
...relation
|
|
1265
1200
|
};
|
|
1201
|
+
newRelation.target = () => {
|
|
1202
|
+
if (typeof rawTarget === "string") {
|
|
1203
|
+
return resolveCollection && resolveCollection(rawTarget) || targetCollection;
|
|
1204
|
+
} else if (typeof rawTarget === "function") {
|
|
1205
|
+
const evaluated = rawTarget();
|
|
1206
|
+
if (typeof evaluated === "string") {
|
|
1207
|
+
return resolveCollection && resolveCollection(evaluated) || targetCollection;
|
|
1208
|
+
}
|
|
1209
|
+
return evaluated;
|
|
1210
|
+
}
|
|
1211
|
+
return targetCollection;
|
|
1212
|
+
};
|
|
1266
1213
|
if (!newRelation.relationName) {
|
|
1267
1214
|
newRelation.relationName = toSnakeCase(targetCollection.slug);
|
|
1268
1215
|
}
|
|
@@ -1315,6 +1262,17 @@ function sanitizeRelation(relation, sourceCollection) {
|
|
|
1315
1262
|
break;
|
|
1316
1263
|
}
|
|
1317
1264
|
}
|
|
1265
|
+
if (!isManyToManyInverse && targetCollection.properties) {
|
|
1266
|
+
for (const [propKey, prop] of Object.entries(targetCollection.properties)) {
|
|
1267
|
+
if (prop.type !== "relation") continue;
|
|
1268
|
+
const relProp = prop;
|
|
1269
|
+
const relName = relProp.relationName || propKey;
|
|
1270
|
+
if (relName === newRelation.inverseRelationName && relProp.cardinality === "many" && (relProp.direction === "owning" || !relProp.direction)) {
|
|
1271
|
+
isManyToManyInverse = true;
|
|
1272
|
+
break;
|
|
1273
|
+
}
|
|
1274
|
+
}
|
|
1275
|
+
}
|
|
1318
1276
|
} catch (e) {
|
|
1319
1277
|
}
|
|
1320
1278
|
}
|
|
@@ -1352,11 +1310,14 @@ function resolveCollectionRelations(collection) {
|
|
|
1352
1310
|
const registeredRelationNames = /* @__PURE__ */ new Set();
|
|
1353
1311
|
if (relCollection.relations) {
|
|
1354
1312
|
relCollection.relations.forEach((relation) => {
|
|
1355
|
-
|
|
1356
|
-
|
|
1357
|
-
|
|
1358
|
-
|
|
1359
|
-
|
|
1313
|
+
try {
|
|
1314
|
+
const normalizedRelation = sanitizeRelation(relation, collection);
|
|
1315
|
+
const relationKey = normalizedRelation.relationName;
|
|
1316
|
+
if (relationKey) {
|
|
1317
|
+
relations2[relationKey] = normalizedRelation;
|
|
1318
|
+
registeredRelationNames.add(relationKey);
|
|
1319
|
+
}
|
|
1320
|
+
} catch (e) {
|
|
1360
1321
|
}
|
|
1361
1322
|
});
|
|
1362
1323
|
}
|
|
@@ -1404,12 +1365,8 @@ function resolvePropertyRelation({
|
|
|
1404
1365
|
overrides: relProp.overrides
|
|
1405
1366
|
};
|
|
1406
1367
|
}
|
|
1407
|
-
|
|
1408
|
-
|
|
1409
|
-
console.warn(`Unrecognized relation format for property '${propertyKey}' in collection '${sourceCollection.slug}'`);
|
|
1410
|
-
return void 0;
|
|
1411
|
-
}
|
|
1412
|
-
return relation;
|
|
1368
|
+
console.warn(`Unrecognized or missing relation target for property '${propertyKey}' in collection '${sourceCollection.slug}'`);
|
|
1369
|
+
return void 0;
|
|
1413
1370
|
}
|
|
1414
1371
|
function getTableName(collection) {
|
|
1415
1372
|
if (getDataSourceCapabilities(collection.driver).supportsRelations) {
|
|
@@ -1436,8 +1393,121 @@ function findRelation(resolvedRelations, key) {
|
|
|
1436
1393
|
if (snakeKey !== key && resolvedRelations[snakeKey]) return resolvedRelations[snakeKey];
|
|
1437
1394
|
return void 0;
|
|
1438
1395
|
}
|
|
1396
|
+
function getSubcollections(collection) {
|
|
1397
|
+
if (collection.childCollections) {
|
|
1398
|
+
return collection.childCollections() ?? [];
|
|
1399
|
+
}
|
|
1400
|
+
if (getDataSourceCapabilities(collection.driver).supportsSubcollections && collection.subcollections) {
|
|
1401
|
+
return collection.subcollections() ?? [];
|
|
1402
|
+
}
|
|
1403
|
+
if (getDataSourceCapabilities(collection.driver).supportsRelations) {
|
|
1404
|
+
const resolvedRelations = resolveCollectionRelations(collection);
|
|
1405
|
+
const manyRelations = Object.values(resolvedRelations).filter((r) => r.cardinality === "many");
|
|
1406
|
+
return manyRelations.map((r) => {
|
|
1407
|
+
const target = r.target();
|
|
1408
|
+
if (!target) return void 0;
|
|
1409
|
+
const relationKey = r.relationName || target.slug;
|
|
1410
|
+
let customName;
|
|
1411
|
+
if (collection.properties) {
|
|
1412
|
+
const prop = Object.entries(collection.properties).find(([_, p]) => p.type === "relation" && p.relationName === relationKey);
|
|
1413
|
+
if (prop && prop[1].name) {
|
|
1414
|
+
customName = prop[1].name;
|
|
1415
|
+
}
|
|
1416
|
+
}
|
|
1417
|
+
const baseOverrides = {
|
|
1418
|
+
slug: relationKey
|
|
1419
|
+
};
|
|
1420
|
+
if (customName) {
|
|
1421
|
+
baseOverrides.name = customName;
|
|
1422
|
+
baseOverrides.singularName = customName;
|
|
1423
|
+
}
|
|
1424
|
+
const targetWithOverrides = {
|
|
1425
|
+
...target,
|
|
1426
|
+
...baseOverrides
|
|
1427
|
+
};
|
|
1428
|
+
return r.overrides ? mergeDeep(targetWithOverrides, r.overrides) : targetWithOverrides;
|
|
1429
|
+
}).filter((c) => Boolean(c));
|
|
1430
|
+
}
|
|
1431
|
+
return [];
|
|
1432
|
+
}
|
|
1433
|
+
function hasPropertyCallbacks(properties, callbackName) {
|
|
1434
|
+
if (!properties) return false;
|
|
1435
|
+
for (const property of Object.values(properties)) {
|
|
1436
|
+
if (property.callbacks?.[callbackName]) return true;
|
|
1437
|
+
if (property.type === "map" && property.properties) {
|
|
1438
|
+
if (hasPropertyCallbacks(property.properties, callbackName)) return true;
|
|
1439
|
+
} else if (property.type === "array" && property.of) {
|
|
1440
|
+
const ofs = Array.isArray(property.of) ? property.of : [property.of];
|
|
1441
|
+
for (const of of ofs) {
|
|
1442
|
+
if (of.callbacks?.[callbackName]) return true;
|
|
1443
|
+
if (of.type === "map" && of.properties && hasPropertyCallbacks(of.properties, callbackName)) return true;
|
|
1444
|
+
}
|
|
1445
|
+
}
|
|
1446
|
+
}
|
|
1447
|
+
return false;
|
|
1448
|
+
}
|
|
1449
|
+
async function processProperties(properties, values, previousValues, propsContext, callbackName) {
|
|
1450
|
+
if (!values || typeof values !== "object") return values;
|
|
1451
|
+
const result = {
|
|
1452
|
+
...values
|
|
1453
|
+
};
|
|
1454
|
+
for (const [key, property] of Object.entries(properties)) {
|
|
1455
|
+
if (result[key] === void 0) continue;
|
|
1456
|
+
let currentValue = result[key];
|
|
1457
|
+
const previousValue = previousValues?.[key];
|
|
1458
|
+
if (property.type === "array" && Array.isArray(currentValue)) {
|
|
1459
|
+
if (property.of && !Array.isArray(property.of)) {
|
|
1460
|
+
currentValue = await Promise.all(currentValue.map(async (item, index) => {
|
|
1461
|
+
const prevItem = Array.isArray(previousValue) ? previousValue[index] : void 0;
|
|
1462
|
+
const singlePropData = {
|
|
1463
|
+
"_tmp": property.of
|
|
1464
|
+
};
|
|
1465
|
+
const res = await processProperties(singlePropData, {
|
|
1466
|
+
"_tmp": item
|
|
1467
|
+
}, {
|
|
1468
|
+
"_tmp": prevItem
|
|
1469
|
+
}, propsContext, callbackName);
|
|
1470
|
+
return res["_tmp"];
|
|
1471
|
+
}));
|
|
1472
|
+
}
|
|
1473
|
+
} else if (property.type === "map" && property.properties && typeof currentValue === "object") {
|
|
1474
|
+
currentValue = await processProperties(property.properties, currentValue, previousValue ?? {}, propsContext, callbackName);
|
|
1475
|
+
}
|
|
1476
|
+
if (property.callbacks?.[callbackName]) {
|
|
1477
|
+
const cbRes = await Promise.resolve(property.callbacks[callbackName]({
|
|
1478
|
+
...propsContext,
|
|
1479
|
+
value: currentValue,
|
|
1480
|
+
previousValue
|
|
1481
|
+
}));
|
|
1482
|
+
if (cbRes !== void 0) {
|
|
1483
|
+
currentValue = cbRes;
|
|
1484
|
+
}
|
|
1485
|
+
}
|
|
1486
|
+
result[key] = currentValue;
|
|
1487
|
+
}
|
|
1488
|
+
return result;
|
|
1489
|
+
}
|
|
1490
|
+
const buildPropertyCallbacks = (properties) => {
|
|
1491
|
+
if (!properties) return void 0;
|
|
1492
|
+
const propertyCallbacks = {};
|
|
1493
|
+
if (hasPropertyCallbacks(properties, "afterRead")) {
|
|
1494
|
+
propertyCallbacks.afterRead = async (props) => {
|
|
1495
|
+
const processedValues = await processProperties(properties, props.entity.values, props.entity.values, props, "afterRead");
|
|
1496
|
+
return {
|
|
1497
|
+
...props.entity,
|
|
1498
|
+
values: processedValues
|
|
1499
|
+
};
|
|
1500
|
+
};
|
|
1501
|
+
}
|
|
1502
|
+
if (hasPropertyCallbacks(properties, "beforeSave")) {
|
|
1503
|
+
propertyCallbacks.beforeSave = async (props) => {
|
|
1504
|
+
return await processProperties(properties, props.values, props.previousValues ?? {}, props, "beforeSave");
|
|
1505
|
+
};
|
|
1506
|
+
}
|
|
1507
|
+
return Object.keys(propertyCallbacks).length > 0 ? propertyCallbacks : void 0;
|
|
1508
|
+
};
|
|
1439
1509
|
var logic = { exports: {} };
|
|
1440
|
-
(function(module, exports
|
|
1510
|
+
(function(module, exports) {
|
|
1441
1511
|
(function(root, factory) {
|
|
1442
1512
|
{
|
|
1443
1513
|
module.exports = factory();
|
|
@@ -2194,7 +2264,7 @@ function createSupportedComparatorMap({ areArrayBuffersEqual: areArrayBuffersEqu
|
|
|
2194
2264
|
"[object Uint32Array]": areTypedArraysEqual2
|
|
2195
2265
|
};
|
|
2196
2266
|
}
|
|
2197
|
-
const deepEqual = createCustomEqual();
|
|
2267
|
+
const deepEqual$1 = createCustomEqual();
|
|
2198
2268
|
createCustomEqual({ strict: true });
|
|
2199
2269
|
createCustomEqual({ circular: true });
|
|
2200
2270
|
createCustomEqual({
|
|
@@ -2263,10 +2333,16 @@ class CollectionRegistry {
|
|
|
2263
2333
|
*/
|
|
2264
2334
|
registerMultiple(collections) {
|
|
2265
2335
|
const rawSnapshot = collections.map((c) => removeFunctions(c));
|
|
2266
|
-
if (this.lastRawInputSnapshot && deepEqual(this.lastRawInputSnapshot, rawSnapshot)) {
|
|
2336
|
+
if (this.lastRawInputSnapshot && deepEqual$1(this.lastRawInputSnapshot, rawSnapshot)) {
|
|
2267
2337
|
return false;
|
|
2268
2338
|
}
|
|
2269
2339
|
this.reset();
|
|
2340
|
+
collections.forEach((c) => {
|
|
2341
|
+
if (c.slug) {
|
|
2342
|
+
this.collectionsBySlug.set(c.slug, c);
|
|
2343
|
+
}
|
|
2344
|
+
this.collectionsByTableName.set(getTableName(c), c);
|
|
2345
|
+
});
|
|
2270
2346
|
const normalizedCollections = collections.map((c) => this.normalizeCollection({
|
|
2271
2347
|
...c
|
|
2272
2348
|
}));
|
|
@@ -2337,15 +2413,25 @@ class CollectionRegistry {
|
|
|
2337
2413
|
const mergedRelationsRaw = [...extractedRelations];
|
|
2338
2414
|
for (const manual of manualRelations) {
|
|
2339
2415
|
const name = manual.relationName;
|
|
2340
|
-
if (!name
|
|
2416
|
+
if (!name) {
|
|
2341
2417
|
mergedRelationsRaw.push(manual);
|
|
2418
|
+
} else {
|
|
2419
|
+
const existingIndex = mergedRelationsRaw.findIndex((r) => r.relationName === name);
|
|
2420
|
+
if (existingIndex === -1) {
|
|
2421
|
+
mergedRelationsRaw.push(manual);
|
|
2422
|
+
} else {
|
|
2423
|
+
mergedRelationsRaw[existingIndex] = {
|
|
2424
|
+
...manual,
|
|
2425
|
+
...mergedRelationsRaw[existingIndex]
|
|
2426
|
+
};
|
|
2427
|
+
}
|
|
2342
2428
|
}
|
|
2343
2429
|
}
|
|
2344
2430
|
let mergedRelations = mergedRelationsRaw;
|
|
2345
2431
|
if (getDataSourceCapabilities(result.driver).supportsRelations) {
|
|
2346
2432
|
mergedRelations = mergedRelationsRaw.map((r) => {
|
|
2347
2433
|
try {
|
|
2348
|
-
return sanitizeRelation(r, result);
|
|
2434
|
+
return sanitizeRelation(r, result, (slug) => this.get(slug));
|
|
2349
2435
|
} catch {
|
|
2350
2436
|
return r;
|
|
2351
2437
|
}
|
|
@@ -2558,6 +2644,118 @@ class CollectionRegistry {
|
|
|
2558
2644
|
};
|
|
2559
2645
|
}
|
|
2560
2646
|
}
|
|
2647
|
+
function mapOperator(op) {
|
|
2648
|
+
switch (op) {
|
|
2649
|
+
case "==":
|
|
2650
|
+
return "eq";
|
|
2651
|
+
case "!=":
|
|
2652
|
+
return "neq";
|
|
2653
|
+
case ">":
|
|
2654
|
+
return "gt";
|
|
2655
|
+
case ">=":
|
|
2656
|
+
return "gte";
|
|
2657
|
+
case "<":
|
|
2658
|
+
return "lt";
|
|
2659
|
+
case "<=":
|
|
2660
|
+
return "lte";
|
|
2661
|
+
case "array-contains":
|
|
2662
|
+
return "cs";
|
|
2663
|
+
case "array-contains-any":
|
|
2664
|
+
return "csa";
|
|
2665
|
+
case "not-in":
|
|
2666
|
+
return "nin";
|
|
2667
|
+
default:
|
|
2668
|
+
return op;
|
|
2669
|
+
}
|
|
2670
|
+
}
|
|
2671
|
+
class QueryBuilder {
|
|
2672
|
+
constructor(collection) {
|
|
2673
|
+
this.collection = collection;
|
|
2674
|
+
}
|
|
2675
|
+
params = {
|
|
2676
|
+
where: {}
|
|
2677
|
+
};
|
|
2678
|
+
/**
|
|
2679
|
+
* Add a filter condition to your query.
|
|
2680
|
+
* @example
|
|
2681
|
+
* client.collection('users').where('age', '>=', 18).find()
|
|
2682
|
+
*/
|
|
2683
|
+
where(column, operator, value) {
|
|
2684
|
+
if (!this.params.where) {
|
|
2685
|
+
this.params.where = {};
|
|
2686
|
+
}
|
|
2687
|
+
const mappedOp = mapOperator(operator);
|
|
2688
|
+
let formattedValue = value;
|
|
2689
|
+
if (Array.isArray(value) && ["in", "nin", "cs", "csa"].includes(mappedOp)) {
|
|
2690
|
+
formattedValue = `(${value.join(",")})`;
|
|
2691
|
+
} else if (value === null) {
|
|
2692
|
+
formattedValue = "null";
|
|
2693
|
+
}
|
|
2694
|
+
this.params.where[column] = mappedOp === "eq" ? String(formattedValue) : `${mappedOp}.${formattedValue}`;
|
|
2695
|
+
return this;
|
|
2696
|
+
}
|
|
2697
|
+
/**
|
|
2698
|
+
* Order the results by a specific column.
|
|
2699
|
+
* @example
|
|
2700
|
+
* client.collection('users').orderBy('createdAt', 'desc').find()
|
|
2701
|
+
*/
|
|
2702
|
+
orderBy(column, ascending = "asc") {
|
|
2703
|
+
this.params.orderBy = `${column}:${ascending}`;
|
|
2704
|
+
return this;
|
|
2705
|
+
}
|
|
2706
|
+
/**
|
|
2707
|
+
* Limit the number of results returned.
|
|
2708
|
+
*/
|
|
2709
|
+
limit(count2) {
|
|
2710
|
+
this.params.limit = count2;
|
|
2711
|
+
return this;
|
|
2712
|
+
}
|
|
2713
|
+
/**
|
|
2714
|
+
* Skip the first N results.
|
|
2715
|
+
*/
|
|
2716
|
+
offset(count2) {
|
|
2717
|
+
this.params.offset = count2;
|
|
2718
|
+
return this;
|
|
2719
|
+
}
|
|
2720
|
+
/**
|
|
2721
|
+
* Set a free-text search string if supported by the backend.
|
|
2722
|
+
*/
|
|
2723
|
+
search(searchString) {
|
|
2724
|
+
this.params.searchString = searchString;
|
|
2725
|
+
return this;
|
|
2726
|
+
}
|
|
2727
|
+
/**
|
|
2728
|
+
* Include related entities in the response.
|
|
2729
|
+
* Relations will be populated with full entity data instead of just IDs.
|
|
2730
|
+
*
|
|
2731
|
+
* @param relations - Relation names to include, or "*" for all.
|
|
2732
|
+
* @example
|
|
2733
|
+
* // Include specific relations
|
|
2734
|
+
* client.data.posts.include("tags", "author").find()
|
|
2735
|
+
*
|
|
2736
|
+
* // Include all relations
|
|
2737
|
+
* client.data.posts.include("*").find()
|
|
2738
|
+
*/
|
|
2739
|
+
include(...relations2) {
|
|
2740
|
+
this.params.include = relations2;
|
|
2741
|
+
return this;
|
|
2742
|
+
}
|
|
2743
|
+
/**
|
|
2744
|
+
* Execute the find query and return the results.
|
|
2745
|
+
*/
|
|
2746
|
+
async find() {
|
|
2747
|
+
return this.collection.find(this.params);
|
|
2748
|
+
}
|
|
2749
|
+
/**
|
|
2750
|
+
* Listen to realtime updates matching this query.
|
|
2751
|
+
*/
|
|
2752
|
+
listen(onUpdate, onError) {
|
|
2753
|
+
if (!this.collection.listen) {
|
|
2754
|
+
throw new Error("Listen is only available when RebaseClient is configured with a websocketUrl.");
|
|
2755
|
+
}
|
|
2756
|
+
return this.collection.listen(this.params, onUpdate, onError);
|
|
2757
|
+
}
|
|
2758
|
+
}
|
|
2561
2759
|
function convertWhereToFilter(where) {
|
|
2562
2760
|
if (!where) return void 0;
|
|
2563
2761
|
const operatorMap = {
|
|
@@ -2637,7 +2835,7 @@ function parseOrderBy(orderBy) {
|
|
|
2637
2835
|
return [field, direction];
|
|
2638
2836
|
}
|
|
2639
2837
|
function createDriverAccessor(driver, slug) {
|
|
2640
|
-
|
|
2838
|
+
const accessor = {
|
|
2641
2839
|
async find(params) {
|
|
2642
2840
|
const orderParsed = parseOrderBy(params?.orderBy);
|
|
2643
2841
|
const entities = await driver.fetchCollection({
|
|
@@ -2731,8 +2929,28 @@ function createDriverAccessor(driver, slug) {
|
|
|
2731
2929
|
onUpdate: (entity) => onUpdate(entity ?? void 0),
|
|
2732
2930
|
onError
|
|
2733
2931
|
});
|
|
2734
|
-
} : void 0
|
|
2932
|
+
} : void 0,
|
|
2933
|
+
// Fluent Query Builder
|
|
2934
|
+
where(column, operator, value) {
|
|
2935
|
+
return new QueryBuilder(accessor).where(column, operator, value);
|
|
2936
|
+
},
|
|
2937
|
+
orderBy(column, ascending) {
|
|
2938
|
+
return new QueryBuilder(accessor).orderBy(column, ascending);
|
|
2939
|
+
},
|
|
2940
|
+
limit(count2) {
|
|
2941
|
+
return new QueryBuilder(accessor).limit(count2);
|
|
2942
|
+
},
|
|
2943
|
+
offset(count2) {
|
|
2944
|
+
return new QueryBuilder(accessor).offset(count2);
|
|
2945
|
+
},
|
|
2946
|
+
search(searchString) {
|
|
2947
|
+
return new QueryBuilder(accessor).search(searchString);
|
|
2948
|
+
},
|
|
2949
|
+
include(...relations2) {
|
|
2950
|
+
return new QueryBuilder(accessor).include(...relations2);
|
|
2951
|
+
}
|
|
2735
2952
|
};
|
|
2953
|
+
return accessor;
|
|
2736
2954
|
}
|
|
2737
2955
|
function buildRebaseData(driver) {
|
|
2738
2956
|
const cache = /* @__PURE__ */ new Map();
|
|
@@ -2784,8 +3002,14 @@ class DrizzleConditionBuilder {
|
|
|
2784
3002
|
static buildSingleFilterCondition(column, op, value) {
|
|
2785
3003
|
switch (op) {
|
|
2786
3004
|
case "==":
|
|
3005
|
+
if (value === null || value === void 0) {
|
|
3006
|
+
return sql`${column} IS NULL`;
|
|
3007
|
+
}
|
|
2787
3008
|
return eq(column, value);
|
|
2788
3009
|
case "!=":
|
|
3010
|
+
if (value === null || value === void 0) {
|
|
3011
|
+
return sql`${column} IS NOT NULL`;
|
|
3012
|
+
}
|
|
2789
3013
|
return sql`${column} != ${value}`;
|
|
2790
3014
|
case ">":
|
|
2791
3015
|
return sql`${column} > ${value}`;
|
|
@@ -3116,7 +3340,10 @@ class DrizzleConditionBuilder {
|
|
|
3116
3340
|
if (p.type === "string" && !p.enum && p.isId !== "uuid") {
|
|
3117
3341
|
const fieldColumn = table[key];
|
|
3118
3342
|
if (fieldColumn) {
|
|
3119
|
-
|
|
3343
|
+
const supportsILike = fieldColumn instanceof PgVarchar || fieldColumn instanceof PgText || fieldColumn instanceof PgChar || fieldColumn && typeof fieldColumn === "object" && !("columnType" in fieldColumn);
|
|
3344
|
+
if (supportsILike) {
|
|
3345
|
+
searchConditions.push(ilike(fieldColumn, `%${searchString}%`));
|
|
3346
|
+
}
|
|
3120
3347
|
}
|
|
3121
3348
|
}
|
|
3122
3349
|
}
|
|
@@ -3589,6 +3816,31 @@ function serializePropertyToServer(value, property) {
|
|
|
3589
3816
|
return result;
|
|
3590
3817
|
}
|
|
3591
3818
|
return value;
|
|
3819
|
+
case "vector": {
|
|
3820
|
+
if (value instanceof Vector) {
|
|
3821
|
+
return value.value;
|
|
3822
|
+
}
|
|
3823
|
+
if (value && typeof value === "object" && "value" in value && Array.isArray(value.value)) {
|
|
3824
|
+
return value.value.map(Number);
|
|
3825
|
+
}
|
|
3826
|
+
if (Array.isArray(value)) {
|
|
3827
|
+
return value.map(Number);
|
|
3828
|
+
}
|
|
3829
|
+
return value;
|
|
3830
|
+
}
|
|
3831
|
+
case "binary":
|
|
3832
|
+
if (typeof value === "string") {
|
|
3833
|
+
if (value.startsWith("data:application/octet-stream;base64,")) {
|
|
3834
|
+
const base64Data = value.split(",")[1];
|
|
3835
|
+
if (base64Data) {
|
|
3836
|
+
return Buffer.from(base64Data, "base64");
|
|
3837
|
+
}
|
|
3838
|
+
}
|
|
3839
|
+
}
|
|
3840
|
+
if (Buffer.isBuffer(value)) {
|
|
3841
|
+
return value;
|
|
3842
|
+
}
|
|
3843
|
+
return value;
|
|
3592
3844
|
case "string":
|
|
3593
3845
|
if (typeof value === "string") {
|
|
3594
3846
|
if (value.startsWith("data:application/octet-stream;base64,")) {
|
|
@@ -3733,6 +3985,21 @@ function parsePropertyFromServer(value, property, collection, propertyKey) {
|
|
|
3733
3985
|
return value;
|
|
3734
3986
|
}
|
|
3735
3987
|
switch (property.type) {
|
|
3988
|
+
case "binary": {
|
|
3989
|
+
let buf = null;
|
|
3990
|
+
if (Buffer.isBuffer(value)) {
|
|
3991
|
+
buf = value;
|
|
3992
|
+
} else if (typeof value === "object" && value !== null) {
|
|
3993
|
+
const rawVal = value;
|
|
3994
|
+
if (rawVal.type === "Buffer" && Array.isArray(rawVal.data)) {
|
|
3995
|
+
buf = Buffer.from(rawVal.data);
|
|
3996
|
+
}
|
|
3997
|
+
}
|
|
3998
|
+
if (buf) {
|
|
3999
|
+
return `data:application/octet-stream;base64,${buf.toString("base64")}`;
|
|
4000
|
+
}
|
|
4001
|
+
return value;
|
|
4002
|
+
}
|
|
3736
4003
|
case "string": {
|
|
3737
4004
|
if (typeof value === "string") return value;
|
|
3738
4005
|
let isBuffer = false;
|
|
@@ -3740,9 +4007,12 @@ function parsePropertyFromServer(value, property, collection, propertyKey) {
|
|
|
3740
4007
|
if (Buffer.isBuffer(value)) {
|
|
3741
4008
|
isBuffer = true;
|
|
3742
4009
|
buf = value;
|
|
3743
|
-
} else if (typeof value === "object" && value !== null
|
|
3744
|
-
|
|
3745
|
-
|
|
4010
|
+
} else if (typeof value === "object" && value !== null) {
|
|
4011
|
+
const rawVal = value;
|
|
4012
|
+
if (rawVal.type === "Buffer" && Array.isArray(rawVal.data)) {
|
|
4013
|
+
isBuffer = true;
|
|
4014
|
+
buf = Buffer.from(rawVal.data);
|
|
4015
|
+
}
|
|
3746
4016
|
}
|
|
3747
4017
|
if (isBuffer && buf) {
|
|
3748
4018
|
let isPrintable = true;
|
|
@@ -3829,6 +4099,25 @@ function parsePropertyFromServer(value, property, collection, propertyKey) {
|
|
|
3829
4099
|
return isNaN(parsed) ? null : parsed;
|
|
3830
4100
|
}
|
|
3831
4101
|
return value;
|
|
4102
|
+
case "vector": {
|
|
4103
|
+
let nums = [];
|
|
4104
|
+
if (typeof value === "string") {
|
|
4105
|
+
nums = value.slice(1, -1).split(",").map(Number);
|
|
4106
|
+
} else if (Array.isArray(value)) {
|
|
4107
|
+
nums = value.map(Number);
|
|
4108
|
+
} else if (value instanceof Vector) {
|
|
4109
|
+
nums = value.value;
|
|
4110
|
+
} else if (typeof value === "object" && value !== null && "value" in value) {
|
|
4111
|
+
const valObj = value;
|
|
4112
|
+
if (Array.isArray(valObj.value)) {
|
|
4113
|
+
nums = valObj.value.map(Number);
|
|
4114
|
+
}
|
|
4115
|
+
}
|
|
4116
|
+
return {
|
|
4117
|
+
__type: "Vector",
|
|
4118
|
+
value: nums
|
|
4119
|
+
};
|
|
4120
|
+
}
|
|
3832
4121
|
case "date": {
|
|
3833
4122
|
let date;
|
|
3834
4123
|
if (value instanceof Date) {
|
|
@@ -3853,9 +4142,12 @@ function parsePropertyFromServer(value, property, collection, propertyKey) {
|
|
|
3853
4142
|
if (Buffer.isBuffer(value)) {
|
|
3854
4143
|
isBuffer = true;
|
|
3855
4144
|
buf = value;
|
|
3856
|
-
} else if (typeof value === "object" && value !== null
|
|
3857
|
-
|
|
3858
|
-
|
|
4145
|
+
} else if (typeof value === "object" && value !== null) {
|
|
4146
|
+
const rawVal = value;
|
|
4147
|
+
if (rawVal.type === "Buffer" && Array.isArray(rawVal.data)) {
|
|
4148
|
+
isBuffer = true;
|
|
4149
|
+
buf = Buffer.from(rawVal.data);
|
|
4150
|
+
}
|
|
3859
4151
|
}
|
|
3860
4152
|
if (isBuffer && buf) {
|
|
3861
4153
|
let isPrintable = true;
|
|
@@ -4645,7 +4937,7 @@ class RelationService {
|
|
|
4645
4937
|
if (parentFKValue !== null && parentFKValue !== void 0) {
|
|
4646
4938
|
await tx.update(targetTable).set({
|
|
4647
4939
|
[targetFKColName]: null
|
|
4648
|
-
}).where(eq(targetFKCol, parentFKValue));
|
|
4940
|
+
}).where(eq(targetFKCol, String(parentFKValue)));
|
|
4649
4941
|
}
|
|
4650
4942
|
continue;
|
|
4651
4943
|
}
|
|
@@ -4654,7 +4946,7 @@ class RelationService {
|
|
|
4654
4946
|
if (parentFKValue !== null && parentFKValue !== void 0) {
|
|
4655
4947
|
await tx.update(targetTable).set({
|
|
4656
4948
|
[targetFKColName]: null
|
|
4657
|
-
}).where(eq(targetFKCol, parentFKValue));
|
|
4949
|
+
}).where(eq(targetFKCol, String(parentFKValue)));
|
|
4658
4950
|
} else {
|
|
4659
4951
|
console.warn(`Cannot set joinPath relation '${relation.relationName}' because parent FK value is null/undefined`);
|
|
4660
4952
|
continue;
|
|
@@ -6366,7 +6658,7 @@ class PostgresBackendDriver {
|
|
|
6366
6658
|
callbacks: void 0,
|
|
6367
6659
|
propertyCallbacks: void 0
|
|
6368
6660
|
};
|
|
6369
|
-
const registryCollection = this.registry
|
|
6661
|
+
const registryCollection = this.registry?.getCollectionByPath(path2);
|
|
6370
6662
|
const resolvedCollection = registryCollection ? {
|
|
6371
6663
|
...collection,
|
|
6372
6664
|
...registryCollection
|
|
@@ -6414,7 +6706,8 @@ class PostgresBackendDriver {
|
|
|
6414
6706
|
user: this.user,
|
|
6415
6707
|
driver: this,
|
|
6416
6708
|
data: this.data,
|
|
6417
|
-
client: this.client
|
|
6709
|
+
client: this.client,
|
|
6710
|
+
storageSource: this.client?.storage
|
|
6418
6711
|
};
|
|
6419
6712
|
return Promise.all(entities.map(async (entity) => {
|
|
6420
6713
|
let fetched = entity;
|
|
@@ -6509,7 +6802,8 @@ class PostgresBackendDriver {
|
|
|
6509
6802
|
user: this.user,
|
|
6510
6803
|
driver: this,
|
|
6511
6804
|
data: this.data,
|
|
6512
|
-
client: this.client
|
|
6805
|
+
client: this.client,
|
|
6806
|
+
storageSource: this.client?.storage
|
|
6513
6807
|
};
|
|
6514
6808
|
if (callbacks?.afterRead) {
|
|
6515
6809
|
entity = await callbacks.afterRead({
|
|
@@ -6579,7 +6873,8 @@ class PostgresBackendDriver {
|
|
|
6579
6873
|
user: this.user,
|
|
6580
6874
|
driver: this,
|
|
6581
6875
|
data: this.data,
|
|
6582
|
-
client: this.client
|
|
6876
|
+
client: this.client,
|
|
6877
|
+
storageSource: this.client?.storage
|
|
6583
6878
|
};
|
|
6584
6879
|
let previousValuesForHistory;
|
|
6585
6880
|
if (status === "existing" && entityId) {
|
|
@@ -6728,26 +7023,37 @@ class PostgresBackendDriver {
|
|
|
6728
7023
|
user: this.user,
|
|
6729
7024
|
driver: this,
|
|
6730
7025
|
data: this.data,
|
|
6731
|
-
client: this.client
|
|
7026
|
+
client: this.client,
|
|
7027
|
+
storageSource: this.client?.storage
|
|
6732
7028
|
};
|
|
6733
7029
|
if (callbacks?.beforeDelete || propertyCallbacks?.beforeDelete) {
|
|
7030
|
+
let preventDefault = false;
|
|
6734
7031
|
if (callbacks?.beforeDelete) {
|
|
6735
|
-
await callbacks.beforeDelete({
|
|
7032
|
+
const result = await callbacks.beforeDelete({
|
|
6736
7033
|
collection: resolvedCollection,
|
|
6737
7034
|
path: entity.path,
|
|
6738
7035
|
entityId: entity.id,
|
|
6739
7036
|
entity,
|
|
6740
7037
|
context: contextForCallback
|
|
6741
7038
|
});
|
|
7039
|
+
if (result === false) {
|
|
7040
|
+
preventDefault = true;
|
|
7041
|
+
}
|
|
6742
7042
|
}
|
|
6743
7043
|
if (propertyCallbacks?.beforeDelete) {
|
|
6744
|
-
await propertyCallbacks.beforeDelete({
|
|
7044
|
+
const result = await propertyCallbacks.beforeDelete({
|
|
6745
7045
|
collection: resolvedCollection,
|
|
6746
7046
|
path: entity.path,
|
|
6747
7047
|
entityId: entity.id,
|
|
6748
7048
|
entity,
|
|
6749
7049
|
context: contextForCallback
|
|
6750
7050
|
});
|
|
7051
|
+
if (result === false) {
|
|
7052
|
+
preventDefault = true;
|
|
7053
|
+
}
|
|
7054
|
+
}
|
|
7055
|
+
if (preventDefault) {
|
|
7056
|
+
return;
|
|
6751
7057
|
}
|
|
6752
7058
|
}
|
|
6753
7059
|
await this.entityService.deleteEntity(entity.path, entity.id, entity.databaseId || resolvedCollection?.databaseId);
|
|
@@ -6820,7 +7126,17 @@ class PostgresBackendDriver {
|
|
|
6820
7126
|
}
|
|
6821
7127
|
const targetDb = this.getTargetDb(options?.database);
|
|
6822
7128
|
try {
|
|
6823
|
-
|
|
7129
|
+
let needsRoleSwitch = false;
|
|
7130
|
+
if (options?.role && process.env.DISABLE_DB_ROLE_SWITCHING !== "true") {
|
|
7131
|
+
try {
|
|
7132
|
+
const currentRoleResult = await targetDb.execute(sql.raw("SELECT current_user AS role"));
|
|
7133
|
+
const currentRole = currentRoleResult.rows?.[0]?.role;
|
|
7134
|
+
needsRoleSwitch = !!currentRole && currentRole !== options.role;
|
|
7135
|
+
} catch {
|
|
7136
|
+
needsRoleSwitch = true;
|
|
7137
|
+
}
|
|
7138
|
+
}
|
|
7139
|
+
if (needsRoleSwitch && options?.role) {
|
|
6824
7140
|
const safeRole = options.role.replace(/"/g, '""');
|
|
6825
7141
|
return await targetDb.transaction(async (tx) => {
|
|
6826
7142
|
await tx.execute(sql.raw(`SET LOCAL ROLE "${safeRole}"`));
|
|
@@ -6858,7 +7174,7 @@ class PostgresBackendDriver {
|
|
|
6858
7174
|
return databases;
|
|
6859
7175
|
}
|
|
6860
7176
|
async fetchAvailableRoles() {
|
|
6861
|
-
const result = await this.executeSql("SELECT rolname FROM pg_roles;");
|
|
7177
|
+
const result = await this.executeSql("SELECT rolname FROM pg_roles WHERE pg_has_role(current_user, rolname, 'member') ORDER BY rolname;");
|
|
6862
7178
|
return result.map((r) => r.rolname);
|
|
6863
7179
|
}
|
|
6864
7180
|
async fetchCurrentDatabase() {
|
|
@@ -7027,6 +7343,21 @@ class AuthenticatedPostgresBackendDriver {
|
|
|
7027
7343
|
* Typed admin capabilities — delegates to the base driver.
|
|
7028
7344
|
*/
|
|
7029
7345
|
admin;
|
|
7346
|
+
get restFetchService() {
|
|
7347
|
+
if (!this.delegate.restFetchService) return void 0;
|
|
7348
|
+
return {
|
|
7349
|
+
fetchCollectionForRest: async (collectionPath, options, include) => {
|
|
7350
|
+
return this.withTransaction(async (delegate) => {
|
|
7351
|
+
return delegate.restFetchService.fetchCollectionForRest(collectionPath, options, include);
|
|
7352
|
+
});
|
|
7353
|
+
},
|
|
7354
|
+
fetchEntityForRest: async (collectionPath, entityId, include, databaseId) => {
|
|
7355
|
+
return this.withTransaction(async (delegate) => {
|
|
7356
|
+
return delegate.restFetchService.fetchEntityForRest(collectionPath, entityId, include, databaseId);
|
|
7357
|
+
});
|
|
7358
|
+
}
|
|
7359
|
+
};
|
|
7360
|
+
}
|
|
7030
7361
|
async withTransaction(operation) {
|
|
7031
7362
|
const pendingNotifications = [];
|
|
7032
7363
|
const result = await this.delegate.db.transaction(async (tx) => {
|
|
@@ -7181,113 +7512,140 @@ class DatabasePoolManager {
|
|
|
7181
7512
|
this.pools.clear();
|
|
7182
7513
|
}
|
|
7183
7514
|
}
|
|
7184
|
-
|
|
7185
|
-
const
|
|
7186
|
-
|
|
7187
|
-
|
|
7188
|
-
|
|
7189
|
-
|
|
7190
|
-
|
|
7191
|
-
|
|
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
|
-
|
|
7515
|
+
function createAuthSchema(rolesSchemaName = "rebase", usersSchemaName = "rebase") {
|
|
7516
|
+
const rolesSchema = rolesSchemaName === "public" ? null : pgSchema(rolesSchemaName);
|
|
7517
|
+
const usersSchema2 = usersSchemaName === "public" ? null : pgSchema(usersSchemaName);
|
|
7518
|
+
const rolesTableCreator = rolesSchema ? rolesSchema.table.bind(rolesSchema) : pgTable;
|
|
7519
|
+
const usersTableCreator = usersSchema2 ? usersSchema2.table.bind(usersSchema2) : pgTable;
|
|
7520
|
+
const users2 = usersTableCreator("users", {
|
|
7521
|
+
id: uuid("id").defaultRandom().primaryKey(),
|
|
7522
|
+
email: varchar("email", {
|
|
7523
|
+
length: 255
|
|
7524
|
+
}).notNull().unique(),
|
|
7525
|
+
passwordHash: varchar("password_hash", {
|
|
7526
|
+
length: 255
|
|
7527
|
+
}),
|
|
7528
|
+
// NULL for OAuth-only users
|
|
7529
|
+
displayName: varchar("display_name", {
|
|
7530
|
+
length: 255
|
|
7531
|
+
}),
|
|
7532
|
+
photoUrl: varchar("photo_url", {
|
|
7533
|
+
length: 500
|
|
7534
|
+
}),
|
|
7535
|
+
emailVerified: boolean("email_verified").default(false).notNull(),
|
|
7536
|
+
emailVerificationToken: varchar("email_verification_token", {
|
|
7537
|
+
length: 255
|
|
7538
|
+
}),
|
|
7539
|
+
emailVerificationSentAt: timestamp("email_verification_sent_at"),
|
|
7540
|
+
metadata: jsonb("metadata").$type().default({}).notNull(),
|
|
7541
|
+
createdAt: timestamp("created_at").defaultNow().notNull(),
|
|
7542
|
+
updatedAt: timestamp("updated_at").defaultNow().notNull()
|
|
7543
|
+
});
|
|
7544
|
+
const roles2 = rolesTableCreator("roles", {
|
|
7545
|
+
id: varchar("id", {
|
|
7546
|
+
length: 50
|
|
7547
|
+
}).primaryKey(),
|
|
7548
|
+
// 'admin', 'editor', 'viewer'
|
|
7549
|
+
name: varchar("name", {
|
|
7550
|
+
length: 100
|
|
7551
|
+
}).notNull(),
|
|
7552
|
+
isAdmin: boolean("is_admin").default(false).notNull(),
|
|
7553
|
+
defaultPermissions: jsonb("default_permissions").$type(),
|
|
7554
|
+
collectionPermissions: jsonb("collection_permissions").$type(),
|
|
7555
|
+
config: jsonb("config").$type()
|
|
7556
|
+
});
|
|
7557
|
+
const userRoles2 = rolesTableCreator("user_roles", {
|
|
7558
|
+
userId: uuid("user_id").notNull().references(() => users2.id, {
|
|
7559
|
+
onDelete: "cascade"
|
|
7560
|
+
}),
|
|
7561
|
+
roleId: varchar("role_id", {
|
|
7562
|
+
length: 50
|
|
7563
|
+
}).notNull().references(() => roles2.id, {
|
|
7564
|
+
onDelete: "cascade"
|
|
7565
|
+
})
|
|
7566
|
+
}, (table) => ({
|
|
7567
|
+
pk: primaryKey({
|
|
7568
|
+
columns: [table.userId, table.roleId]
|
|
7569
|
+
})
|
|
7570
|
+
}));
|
|
7571
|
+
const refreshTokens2 = rolesTableCreator("refresh_tokens", {
|
|
7572
|
+
id: uuid("id").defaultRandom().primaryKey(),
|
|
7573
|
+
userId: uuid("user_id").notNull().references(() => users2.id, {
|
|
7574
|
+
onDelete: "cascade"
|
|
7575
|
+
}),
|
|
7576
|
+
tokenHash: varchar("token_hash", {
|
|
7577
|
+
length: 255
|
|
7578
|
+
}).notNull().unique(),
|
|
7579
|
+
expiresAt: timestamp("expires_at").notNull(),
|
|
7580
|
+
userAgent: varchar("user_agent", {
|
|
7581
|
+
length: 500
|
|
7582
|
+
}),
|
|
7583
|
+
ipAddress: varchar("ip_address", {
|
|
7584
|
+
length: 45
|
|
7585
|
+
}),
|
|
7586
|
+
createdAt: timestamp("created_at").defaultNow().notNull()
|
|
7587
|
+
}, (table) => ({
|
|
7588
|
+
uniqueDeviceSession: unique("unique_device_session").on(table.userId, table.userAgent, table.ipAddress)
|
|
7589
|
+
}));
|
|
7590
|
+
const passwordResetTokens2 = rolesTableCreator("password_reset_tokens", {
|
|
7591
|
+
id: uuid("id").defaultRandom().primaryKey(),
|
|
7592
|
+
userId: uuid("user_id").notNull().references(() => users2.id, {
|
|
7593
|
+
onDelete: "cascade"
|
|
7594
|
+
}),
|
|
7595
|
+
tokenHash: varchar("token_hash", {
|
|
7596
|
+
length: 255
|
|
7597
|
+
}).notNull().unique(),
|
|
7598
|
+
expiresAt: timestamp("expires_at").notNull(),
|
|
7599
|
+
usedAt: timestamp("used_at"),
|
|
7600
|
+
createdAt: timestamp("created_at").defaultNow().notNull()
|
|
7601
|
+
});
|
|
7602
|
+
const appConfig2 = rolesTableCreator("app_config", {
|
|
7603
|
+
key: varchar("key", {
|
|
7604
|
+
length: 100
|
|
7605
|
+
}).primaryKey(),
|
|
7606
|
+
value: jsonb("value").notNull(),
|
|
7607
|
+
updatedAt: timestamp("updated_at").defaultNow().notNull()
|
|
7608
|
+
});
|
|
7609
|
+
const userIdentities2 = rolesTableCreator("user_identities", {
|
|
7610
|
+
id: uuid("id").defaultRandom().primaryKey(),
|
|
7611
|
+
userId: uuid("user_id").notNull().references(() => users2.id, {
|
|
7612
|
+
onDelete: "cascade"
|
|
7613
|
+
}),
|
|
7614
|
+
provider: varchar("provider", {
|
|
7615
|
+
length: 50
|
|
7616
|
+
}).notNull(),
|
|
7617
|
+
// e.g. 'google', 'linkedin'
|
|
7618
|
+
providerId: varchar("provider_id", {
|
|
7619
|
+
length: 255
|
|
7620
|
+
}).notNull(),
|
|
7621
|
+
profileData: jsonb("profile_data"),
|
|
7622
|
+
createdAt: timestamp("created_at").defaultNow().notNull(),
|
|
7623
|
+
updatedAt: timestamp("updated_at").defaultNow().notNull()
|
|
7624
|
+
}, (table) => ({
|
|
7625
|
+
uniqueProviderId: unique("unique_provider_id").on(table.provider, table.providerId)
|
|
7626
|
+
}));
|
|
7627
|
+
return {
|
|
7628
|
+
rolesSchema,
|
|
7629
|
+
usersSchema: usersSchema2,
|
|
7630
|
+
users: users2,
|
|
7631
|
+
roles: roles2,
|
|
7632
|
+
userRoles: userRoles2,
|
|
7633
|
+
refreshTokens: refreshTokens2,
|
|
7634
|
+
passwordResetTokens: passwordResetTokens2,
|
|
7635
|
+
appConfig: appConfig2,
|
|
7636
|
+
userIdentities: userIdentities2
|
|
7637
|
+
};
|
|
7638
|
+
}
|
|
7639
|
+
const defaultAuthSchema = createAuthSchema("rebase", "rebase");
|
|
7640
|
+
const rebaseSchema = defaultAuthSchema.rolesSchema;
|
|
7641
|
+
const usersSchema = defaultAuthSchema.usersSchema;
|
|
7642
|
+
const users = defaultAuthSchema.users;
|
|
7643
|
+
const roles = defaultAuthSchema.roles;
|
|
7644
|
+
const userRoles = defaultAuthSchema.userRoles;
|
|
7645
|
+
const refreshTokens = defaultAuthSchema.refreshTokens;
|
|
7646
|
+
const passwordResetTokens = defaultAuthSchema.passwordResetTokens;
|
|
7647
|
+
const appConfig = defaultAuthSchema.appConfig;
|
|
7648
|
+
const userIdentities = defaultAuthSchema.userIdentities;
|
|
7291
7649
|
const usersRelations = relations(users, ({
|
|
7292
7650
|
many
|
|
7293
7651
|
}) => ({
|
|
@@ -7470,6 +7828,15 @@ const getDrizzleColumn = (propName, prop, collection, collections) => {
|
|
|
7470
7828
|
}
|
|
7471
7829
|
break;
|
|
7472
7830
|
}
|
|
7831
|
+
case "vector": {
|
|
7832
|
+
const vp = prop;
|
|
7833
|
+
columnDefinition = `vector("${colName}", { dimensions: ${vp.dimensions} })`;
|
|
7834
|
+
break;
|
|
7835
|
+
}
|
|
7836
|
+
case "binary": {
|
|
7837
|
+
columnDefinition = `customType({ dataType() { return 'bytea'; } })("${colName}")`;
|
|
7838
|
+
break;
|
|
7839
|
+
}
|
|
7473
7840
|
case "relation": {
|
|
7474
7841
|
const refProp = prop;
|
|
7475
7842
|
const resolvedRelations = resolveCollectionRelations(collection);
|
|
@@ -7536,7 +7903,7 @@ const getDrizzleColumn = (propName, prop, collection, collections) => {
|
|
|
7536
7903
|
return ` ${propName}: ${columnDefinition}`;
|
|
7537
7904
|
};
|
|
7538
7905
|
const resolveRawSql = (expression) => {
|
|
7539
|
-
const resolved = expression.replace(/\{(\w+)\}/g, (_, col) =>
|
|
7906
|
+
const resolved = expression.replace(/\{(\w+)\}/g, (_, col) => col);
|
|
7540
7907
|
return `sql\`${resolved}\``;
|
|
7541
7908
|
};
|
|
7542
7909
|
const wrapWithRoleCheck = (clause, roles2) => {
|
|
@@ -7548,7 +7915,7 @@ const unwrapSql = (sqlExpr) => {
|
|
|
7548
7915
|
const match = sqlExpr.match(/^sql`(.*)`$/s);
|
|
7549
7916
|
return match ? match[1] : sqlExpr;
|
|
7550
7917
|
};
|
|
7551
|
-
const buildUsingClause = (rule) => {
|
|
7918
|
+
const buildUsingClause = (rule, collection) => {
|
|
7552
7919
|
if (rule.using) {
|
|
7553
7920
|
return resolveRawSql(rule.using);
|
|
7554
7921
|
}
|
|
@@ -7556,15 +7923,17 @@ const buildUsingClause = (rule) => {
|
|
|
7556
7923
|
return "sql`true`";
|
|
7557
7924
|
}
|
|
7558
7925
|
if (rule.ownerField) {
|
|
7559
|
-
|
|
7926
|
+
const prop = collection.properties?.[rule.ownerField];
|
|
7927
|
+
const colName = resolveColumnName(rule.ownerField, prop);
|
|
7928
|
+
return `sql\`${colName} = auth.uid()\``;
|
|
7560
7929
|
}
|
|
7561
7930
|
return null;
|
|
7562
7931
|
};
|
|
7563
|
-
const buildWithCheckClause = (rule) => {
|
|
7932
|
+
const buildWithCheckClause = (rule, collection) => {
|
|
7564
7933
|
if (rule.withCheck) {
|
|
7565
7934
|
return resolveRawSql(rule.withCheck);
|
|
7566
7935
|
}
|
|
7567
|
-
return buildUsingClause(rule);
|
|
7936
|
+
return buildUsingClause(rule, collection);
|
|
7568
7937
|
};
|
|
7569
7938
|
const getPolicyNameHash = (rule) => {
|
|
7570
7939
|
const data = JSON.stringify({
|
|
@@ -7580,21 +7949,22 @@ const getPolicyNameHash = (rule) => {
|
|
|
7580
7949
|
});
|
|
7581
7950
|
return createHash("sha1").update(data).digest("hex").substring(0, 7);
|
|
7582
7951
|
};
|
|
7583
|
-
const generatePolicyCode = (
|
|
7952
|
+
const generatePolicyCode = (collection, rule, index) => {
|
|
7953
|
+
const tableName = getTableName(collection);
|
|
7584
7954
|
const ops = rule.operations && rule.operations.length > 0 ? rule.operations : [rule.operation ?? "all"];
|
|
7585
7955
|
const ruleHash = getPolicyNameHash(rule);
|
|
7586
7956
|
return ops.map((op, opIdx) => {
|
|
7587
7957
|
const policyName = rule.name ? ops.length > 1 ? `${rule.name}_${op}` : rule.name : `${tableName}_${op}_${ruleHash}${ops.length > 1 ? `_${opIdx}` : ""}`;
|
|
7588
|
-
return generateSinglePolicyCode(
|
|
7958
|
+
return generateSinglePolicyCode(collection, rule, op, policyName);
|
|
7589
7959
|
}).join("");
|
|
7590
7960
|
};
|
|
7591
|
-
const generateSinglePolicyCode = (
|
|
7961
|
+
const generateSinglePolicyCode = (collection, rule, operation, policyName) => {
|
|
7592
7962
|
const mode = rule.mode ?? "permissive";
|
|
7593
7963
|
const roles2 = rule.roles ? [...rule.roles].sort() : void 0;
|
|
7594
7964
|
const needsUsing = operation !== "insert";
|
|
7595
7965
|
const needsWithCheck = operation !== "select" && operation !== "delete";
|
|
7596
|
-
let usingClause = needsUsing ? buildUsingClause(rule) : null;
|
|
7597
|
-
let withCheckClause = needsWithCheck ? buildWithCheckClause(rule) : null;
|
|
7966
|
+
let usingClause = needsUsing ? buildUsingClause(rule, collection) : null;
|
|
7967
|
+
let withCheckClause = needsWithCheck ? buildWithCheckClause(rule, collection) : null;
|
|
7598
7968
|
if (roles2 && roles2.length > 0) {
|
|
7599
7969
|
if (usingClause) {
|
|
7600
7970
|
usingClause = wrapWithRoleCheck(usingClause, roles2);
|
|
@@ -7663,12 +8033,26 @@ const computeSharedRelationName = (rel, sourceCollection, _collections) => {
|
|
|
7663
8033
|
const generateSchema = async (collections, stripPolicies = false) => {
|
|
7664
8034
|
let schemaContent = "// This file is auto-generated by the Rebase Drizzle generator. Do not edit manually.\n\n";
|
|
7665
8035
|
const hasUuid = collections.some((c) => c.properties && Object.values(c.properties).some((p) => p.type === "string" && (p.autoValue === "uuid" || p.isId === "uuid")));
|
|
7666
|
-
collections.some((c) => c.properties && Object.values(c.properties).some((p) =>
|
|
8036
|
+
const hasVector = collections.some((c) => c.properties && Object.values(c.properties).some((p) => p.type === "vector"));
|
|
8037
|
+
const hasBinary = collections.some((c) => c.properties && Object.values(c.properties).some((p) => p.type === "binary"));
|
|
7667
8038
|
const pgCoreImports = ["primaryKey", "pgTable", "integer", "varchar", "text", "char", "boolean", "timestamp", "date", "time", "jsonb", "json", "pgEnum", "numeric", "real", "doublePrecision", "bigint", "serial", "bigserial", "pgPolicy"];
|
|
7668
8039
|
if (hasUuid) pgCoreImports.push("uuid");
|
|
8040
|
+
if (hasVector) pgCoreImports.push("vector");
|
|
8041
|
+
if (hasBinary) pgCoreImports.push("customType");
|
|
8042
|
+
const uniqueSchemas = Array.from(new Set(collections.map((c) => isPostgresCollection(c) ? c.schema : void 0).filter(Boolean)));
|
|
8043
|
+
if (uniqueSchemas.length > 0) {
|
|
8044
|
+
pgCoreImports.push("pgSchema");
|
|
8045
|
+
}
|
|
7669
8046
|
schemaContent += `import { ${pgCoreImports.join(", ")} } from 'drizzle-orm/pg-core';
|
|
7670
8047
|
`;
|
|
7671
8048
|
schemaContent += "import { relations as drizzleRelations, sql } from 'drizzle-orm';\n\n";
|
|
8049
|
+
uniqueSchemas.forEach((schema) => {
|
|
8050
|
+
schemaContent += `export const ${schema}Schema = pgSchema("${schema}");
|
|
8051
|
+
`;
|
|
8052
|
+
});
|
|
8053
|
+
if (uniqueSchemas.length > 0) {
|
|
8054
|
+
schemaContent += "\n";
|
|
8055
|
+
}
|
|
7672
8056
|
const exportedTableVars = [];
|
|
7673
8057
|
const exportedEnumVars = [];
|
|
7674
8058
|
const exportedRelationVars = [];
|
|
@@ -7723,6 +8107,9 @@ const generateSchema = async (collections, stripPolicies = false) => {
|
|
|
7723
8107
|
const tableVarName = getTableVarName(tableName);
|
|
7724
8108
|
if (isJunction && relation && sourceCollection && relation.through) {
|
|
7725
8109
|
const targetCollection = relation.target();
|
|
8110
|
+
const schema = (isPostgresCollection(targetCollection) ? targetCollection.schema : void 0) || (isPostgresCollection(sourceCollection) ? sourceCollection.schema : void 0);
|
|
8111
|
+
const tableCreator = schema ? `${schema}Schema.table` : "pgTable";
|
|
8112
|
+
const baseTableName = tableName.includes(".") ? tableName.split(".").pop() : tableName;
|
|
7726
8113
|
const {
|
|
7727
8114
|
sourceColumn,
|
|
7728
8115
|
targetColumn
|
|
@@ -7733,7 +8120,7 @@ const generateSchema = async (collections, stripPolicies = false) => {
|
|
|
7733
8120
|
const targetColType = isNumericId(targetCollection) ? "integer" : getPrimaryKeyProp(targetCollection).isUuid ? "uuid" : "varchar";
|
|
7734
8121
|
const sourceId = getPrimaryKeyName(sourceCollection);
|
|
7735
8122
|
const targetId = getPrimaryKeyName(targetCollection);
|
|
7736
|
-
schemaContent += `export const ${tableVarName} =
|
|
8123
|
+
schemaContent += `export const ${tableVarName} = ${tableCreator}("${baseTableName}", {
|
|
7737
8124
|
`;
|
|
7738
8125
|
schemaContent += ` ${sourceColumn}: ${sourceColType}("${sourceColumn}").notNull().references(() => ${getTableVarName(getTableName(sourceCollection))}.${sourceId}, ${refOptions}),
|
|
7739
8126
|
`;
|
|
@@ -7744,7 +8131,10 @@ const generateSchema = async (collections, stripPolicies = false) => {
|
|
|
7744
8131
|
`;
|
|
7745
8132
|
schemaContent += "}));\n\n";
|
|
7746
8133
|
} else if (!isJunction) {
|
|
7747
|
-
|
|
8134
|
+
const schema = isPostgresCollection(collection) ? collection.schema : void 0;
|
|
8135
|
+
const tableCreator = schema ? `${schema}Schema.table` : "pgTable";
|
|
8136
|
+
const baseTableName = tableName.includes(".") ? tableName.split(".").pop() : tableName;
|
|
8137
|
+
schemaContent += `export const ${tableVarName} = ${tableCreator}("${baseTableName}", {
|
|
7748
8138
|
`;
|
|
7749
8139
|
const columns = /* @__PURE__ */ new Set();
|
|
7750
8140
|
Object.entries(collection.properties ?? {}).forEach(([propName, prop]) => {
|
|
@@ -7760,7 +8150,7 @@ const generateSchema = async (collections, stripPolicies = false) => {
|
|
|
7760
8150
|
if (!stripPolicies && securityRules && securityRules.length > 0) {
|
|
7761
8151
|
schemaContent += "\n}, (table) => ([\n";
|
|
7762
8152
|
securityRules.forEach((rule, idx) => {
|
|
7763
|
-
schemaContent += generatePolicyCode(
|
|
8153
|
+
schemaContent += generatePolicyCode(collection, rule);
|
|
7764
8154
|
});
|
|
7765
8155
|
schemaContent += "])).enableRLS();\n\n";
|
|
7766
8156
|
} else {
|
|
@@ -7805,11 +8195,11 @@ const generateSchema = async (collections, stripPolicies = false) => {
|
|
|
7805
8195
|
references: [${sourceTableVar}.${sourceId}],
|
|
7806
8196
|
relationName: "${owningRelationName}"
|
|
7807
8197
|
})`);
|
|
7808
|
-
const
|
|
8198
|
+
const targetRelationName = inverseRelationName ? inverseRelationName : `${tableName}_${relation.through.targetColumn}`;
|
|
7809
8199
|
tableRelations.push(` "${relation.through.targetColumn}": one(${targetTableVar}, {
|
|
7810
8200
|
fields: [${tableVarName}.${relation.through.targetColumn}],
|
|
7811
8201
|
references: [${targetTableVar}.${targetId}],
|
|
7812
|
-
relationName: "${
|
|
8202
|
+
relationName: "${targetRelationName}"
|
|
7813
8203
|
})`);
|
|
7814
8204
|
}
|
|
7815
8205
|
} else {
|
|
@@ -7908,6 +8298,90 @@ ${tableRelations.join(",\n")}
|
|
|
7908
8298
|
schemaContent += tablesExport + enumsExport + relationsExport;
|
|
7909
8299
|
return schemaContent;
|
|
7910
8300
|
};
|
|
8301
|
+
const defaultUsersCollection = {
|
|
8302
|
+
name: "Users",
|
|
8303
|
+
singularName: "User",
|
|
8304
|
+
slug: "users",
|
|
8305
|
+
table: "users",
|
|
8306
|
+
schema: "rebase",
|
|
8307
|
+
icon: "Users",
|
|
8308
|
+
group: "Settings",
|
|
8309
|
+
properties: {
|
|
8310
|
+
id: {
|
|
8311
|
+
name: "ID",
|
|
8312
|
+
type: "string",
|
|
8313
|
+
isId: "uuid"
|
|
8314
|
+
},
|
|
8315
|
+
email: {
|
|
8316
|
+
name: "Email",
|
|
8317
|
+
type: "string",
|
|
8318
|
+
validation: {
|
|
8319
|
+
required: true,
|
|
8320
|
+
unique: true
|
|
8321
|
+
}
|
|
8322
|
+
},
|
|
8323
|
+
password_hash: {
|
|
8324
|
+
name: "Password Hash",
|
|
8325
|
+
type: "string",
|
|
8326
|
+
ui: {
|
|
8327
|
+
hideFromCollection: true
|
|
8328
|
+
}
|
|
8329
|
+
},
|
|
8330
|
+
display_name: {
|
|
8331
|
+
name: "Display Name",
|
|
8332
|
+
type: "string"
|
|
8333
|
+
},
|
|
8334
|
+
photo_url: {
|
|
8335
|
+
name: "Photo URL",
|
|
8336
|
+
type: "string"
|
|
8337
|
+
},
|
|
8338
|
+
email_verified: {
|
|
8339
|
+
name: "Email Verified",
|
|
8340
|
+
type: "boolean",
|
|
8341
|
+
defaultValue: false
|
|
8342
|
+
},
|
|
8343
|
+
email_verification_token: {
|
|
8344
|
+
name: "Email Verification Token",
|
|
8345
|
+
type: "string",
|
|
8346
|
+
ui: {
|
|
8347
|
+
hideFromCollection: true
|
|
8348
|
+
}
|
|
8349
|
+
},
|
|
8350
|
+
email_verification_sent_at: {
|
|
8351
|
+
name: "Email Verification Sent At",
|
|
8352
|
+
type: "date",
|
|
8353
|
+
ui: {
|
|
8354
|
+
hideFromCollection: true
|
|
8355
|
+
}
|
|
8356
|
+
},
|
|
8357
|
+
metadata: {
|
|
8358
|
+
name: "Metadata",
|
|
8359
|
+
type: "map",
|
|
8360
|
+
defaultValue: {},
|
|
8361
|
+
ui: {
|
|
8362
|
+
hideFromCollection: true
|
|
8363
|
+
}
|
|
8364
|
+
},
|
|
8365
|
+
created_at: {
|
|
8366
|
+
name: "Created At",
|
|
8367
|
+
type: "date",
|
|
8368
|
+
autoValue: "on_create",
|
|
8369
|
+
ui: {
|
|
8370
|
+
readOnly: true,
|
|
8371
|
+
hideFromCollection: true
|
|
8372
|
+
}
|
|
8373
|
+
},
|
|
8374
|
+
updated_at: {
|
|
8375
|
+
name: "Updated At",
|
|
8376
|
+
type: "date",
|
|
8377
|
+
autoValue: "on_update",
|
|
8378
|
+
ui: {
|
|
8379
|
+
readOnly: true,
|
|
8380
|
+
hideFromCollection: true
|
|
8381
|
+
}
|
|
8382
|
+
}
|
|
8383
|
+
}
|
|
8384
|
+
};
|
|
7911
8385
|
const formatTerminalText = (text, options = {}) => {
|
|
7912
8386
|
let codes = "";
|
|
7913
8387
|
if (options.bold) codes += "\x1B[1m";
|
|
@@ -7970,10 +8444,14 @@ const runGeneration = async (collectionsFilePath, outputPath) => {
|
|
|
7970
8444
|
const imported = await dynamicImport(fileUrl);
|
|
7971
8445
|
collections = imported.backendCollections || imported.collections;
|
|
7972
8446
|
}
|
|
7973
|
-
if (!collections || !Array.isArray(collections)
|
|
7974
|
-
|
|
7975
|
-
return;
|
|
8447
|
+
if (!collections || !Array.isArray(collections)) {
|
|
8448
|
+
collections = [];
|
|
7976
8449
|
}
|
|
8450
|
+
const hasUsersCollection = collections.some((c) => c.slug === "users");
|
|
8451
|
+
if (!hasUsersCollection) {
|
|
8452
|
+
collections.push(defaultUsersCollection);
|
|
8453
|
+
}
|
|
8454
|
+
collections.sort((a, b) => a.slug.localeCompare(b.slug));
|
|
7977
8455
|
const schemaContent = await generateSchema(collections);
|
|
7978
8456
|
if (outputPath) {
|
|
7979
8457
|
const outputDir = path.dirname(outputPath);
|
|
@@ -8201,29 +8679,14 @@ class RealtimeService extends EventEmitter {
|
|
|
8201
8679
|
},
|
|
8202
8680
|
authContext
|
|
8203
8681
|
});
|
|
8204
|
-
|
|
8205
|
-
|
|
8206
|
-
|
|
8207
|
-
|
|
8208
|
-
|
|
8209
|
-
|
|
8210
|
-
|
|
8211
|
-
|
|
8212
|
-
limit: request.limit,
|
|
8213
|
-
startAfter: request.startAfter,
|
|
8214
|
-
searchString: request.searchString
|
|
8215
|
-
});
|
|
8216
|
-
} else {
|
|
8217
|
-
entities = await this.entityService.fetchCollection(request.path, {
|
|
8218
|
-
filter: request.filter,
|
|
8219
|
-
orderBy: request.orderBy,
|
|
8220
|
-
order: request.order,
|
|
8221
|
-
limit: request.limit,
|
|
8222
|
-
startAfter: request.startAfter,
|
|
8223
|
-
databaseId: request.collection?.databaseId,
|
|
8224
|
-
searchString: request.searchString
|
|
8225
|
-
});
|
|
8226
|
-
}
|
|
8682
|
+
const entities = await this.fetchCollectionWithAuth(request.path, {
|
|
8683
|
+
filter: request.filter,
|
|
8684
|
+
orderBy: request.orderBy,
|
|
8685
|
+
order: request.order,
|
|
8686
|
+
limit: request.limit,
|
|
8687
|
+
startAfter: request.startAfter,
|
|
8688
|
+
searchString: request.searchString
|
|
8689
|
+
}, authContext);
|
|
8227
8690
|
this.sendCollectionUpdate(clientId, subscriptionId, entities);
|
|
8228
8691
|
} catch (error) {
|
|
8229
8692
|
this.sendError(clientId, `Failed to subscribe to collection: ${error}`, subscriptionId);
|
|
@@ -8247,16 +8710,7 @@ class RealtimeService extends EventEmitter {
|
|
|
8247
8710
|
entityId: request.entityId,
|
|
8248
8711
|
authContext
|
|
8249
8712
|
});
|
|
8250
|
-
|
|
8251
|
-
if (this.driver) {
|
|
8252
|
-
entity = await this.driver.fetchEntity({
|
|
8253
|
-
path: request.path,
|
|
8254
|
-
entityId: request.entityId,
|
|
8255
|
-
collection
|
|
8256
|
-
});
|
|
8257
|
-
} else {
|
|
8258
|
-
entity = await this.entityService.fetchEntity(request.path, request.entityId, request.collection?.databaseId);
|
|
8259
|
-
}
|
|
8713
|
+
const entity = await this.fetchEntityWithAuth(request.path, String(request.entityId), authContext);
|
|
8260
8714
|
this.sendEntityUpdate(clientId, subscriptionId, entity || null);
|
|
8261
8715
|
} catch (error) {
|
|
8262
8716
|
this.sendError(clientId, `Failed to subscribe to entity: ${request.path} ${request.entityId} ${error}`, subscriptionId);
|
|
@@ -8401,87 +8855,77 @@ class RealtimeService extends EventEmitter {
|
|
|
8401
8855
|
async fetchCollectionWithAuth(notifyPath, collectionRequest, authContext) {
|
|
8402
8856
|
if (this.driver) {
|
|
8403
8857
|
const collection = this.registry.getCollectionByPath(notifyPath);
|
|
8404
|
-
const
|
|
8405
|
-
|
|
8406
|
-
|
|
8407
|
-
|
|
8408
|
-
|
|
8409
|
-
|
|
8410
|
-
|
|
8411
|
-
|
|
8412
|
-
|
|
8413
|
-
|
|
8858
|
+
const activeAuth = authContext || {
|
|
8859
|
+
userId: "anon",
|
|
8860
|
+
roles: ["anon"]
|
|
8861
|
+
};
|
|
8862
|
+
return await this.db.transaction(async (tx) => {
|
|
8863
|
+
await tx.execute(sql`SELECT set_config('app.user_id', ${activeAuth.userId}, true)`);
|
|
8864
|
+
await tx.execute(sql`SELECT set_config('app.user_roles', ${activeAuth.roles.join(",")}, true)`);
|
|
8865
|
+
await tx.execute(sql`SELECT set_config('app.jwt', ${JSON.stringify({
|
|
8866
|
+
sub: activeAuth.userId,
|
|
8867
|
+
roles: activeAuth.roles
|
|
8868
|
+
})}, true)`);
|
|
8869
|
+
const txEntityService = new EntityService(tx, this.registry);
|
|
8870
|
+
let fetchedEntities;
|
|
8871
|
+
if (collectionRequest.searchString) {
|
|
8872
|
+
fetchedEntities = await txEntityService.searchEntities(notifyPath, collectionRequest.searchString, {
|
|
8873
|
+
filter: collectionRequest.filter,
|
|
8874
|
+
orderBy: collectionRequest.orderBy,
|
|
8875
|
+
order: collectionRequest.order,
|
|
8876
|
+
limit: collectionRequest.limit,
|
|
8877
|
+
databaseId: collectionRequest.databaseId
|
|
8878
|
+
});
|
|
8879
|
+
} else {
|
|
8880
|
+
fetchedEntities = await txEntityService.fetchCollection(notifyPath, {
|
|
8881
|
+
filter: collectionRequest.filter,
|
|
8882
|
+
orderBy: collectionRequest.orderBy,
|
|
8883
|
+
order: collectionRequest.order,
|
|
8884
|
+
limit: collectionRequest.limit,
|
|
8885
|
+
offset: collectionRequest.offset,
|
|
8886
|
+
startAfter: collectionRequest.startAfter,
|
|
8887
|
+
databaseId: collectionRequest.databaseId
|
|
8888
|
+
});
|
|
8889
|
+
}
|
|
8890
|
+
const registryCollection = this.registry.getCollectionByPath(notifyPath);
|
|
8891
|
+
const resolvedCollection = collection ? {
|
|
8892
|
+
...collection,
|
|
8893
|
+
...registryCollection
|
|
8894
|
+
} : registryCollection;
|
|
8895
|
+
const callbacks = resolvedCollection?.callbacks;
|
|
8896
|
+
const propertyCallbacks = resolvedCollection?.properties ? buildPropertyCallbacks(resolvedCollection.properties) : void 0;
|
|
8897
|
+
if (callbacks?.afterRead || propertyCallbacks?.afterRead) {
|
|
8898
|
+
const contextForCallback = {
|
|
8899
|
+
user: {
|
|
8900
|
+
uid: activeAuth.userId,
|
|
8901
|
+
roles: activeAuth.roles
|
|
8902
|
+
},
|
|
8903
|
+
driver: this.driver,
|
|
8904
|
+
data: this.driver && "data" in this.driver ? this.driver.data : void 0
|
|
8905
|
+
};
|
|
8906
|
+
return await Promise.all(fetchedEntities.map(async (entity) => {
|
|
8907
|
+
let processedEntity = entity;
|
|
8908
|
+
if (callbacks?.afterRead) {
|
|
8909
|
+
processedEntity = await callbacks.afterRead({
|
|
8910
|
+
collection: resolvedCollection,
|
|
8911
|
+
path: notifyPath,
|
|
8912
|
+
entity: processedEntity,
|
|
8913
|
+
context: contextForCallback
|
|
8914
|
+
}) ?? processedEntity;
|
|
8915
|
+
}
|
|
8916
|
+
if (propertyCallbacks?.afterRead) {
|
|
8917
|
+
processedEntity = await propertyCallbacks.afterRead({
|
|
8918
|
+
collection: resolvedCollection,
|
|
8919
|
+
path: notifyPath,
|
|
8920
|
+
entity: processedEntity,
|
|
8921
|
+
context: contextForCallback
|
|
8922
|
+
}) ?? processedEntity;
|
|
8923
|
+
}
|
|
8924
|
+
return processedEntity;
|
|
8925
|
+
}));
|
|
8926
|
+
}
|
|
8927
|
+
return fetchedEntities;
|
|
8414
8928
|
});
|
|
8415
|
-
if (authContext) {
|
|
8416
|
-
return await this.db.transaction(async (tx) => {
|
|
8417
|
-
await tx.execute(sql`SELECT set_config('app.user_id', ${authContext.userId}, true)`);
|
|
8418
|
-
await tx.execute(sql`SELECT set_config('app.user_roles', ${authContext.roles.join(",")}, true)`);
|
|
8419
|
-
await tx.execute(sql`SELECT set_config('app.jwt', ${JSON.stringify({
|
|
8420
|
-
sub: authContext.userId,
|
|
8421
|
-
roles: authContext.roles
|
|
8422
|
-
})}, true)`);
|
|
8423
|
-
const txEntityService = new EntityService(tx, this.registry);
|
|
8424
|
-
let fetchedEntities;
|
|
8425
|
-
if (collectionRequest.searchString) {
|
|
8426
|
-
fetchedEntities = await txEntityService.searchEntities(notifyPath, collectionRequest.searchString, {
|
|
8427
|
-
filter: collectionRequest.filter,
|
|
8428
|
-
orderBy: collectionRequest.orderBy,
|
|
8429
|
-
order: collectionRequest.order,
|
|
8430
|
-
limit: collectionRequest.limit,
|
|
8431
|
-
databaseId: collectionRequest.databaseId
|
|
8432
|
-
});
|
|
8433
|
-
} else {
|
|
8434
|
-
fetchedEntities = await txEntityService.fetchCollection(notifyPath, {
|
|
8435
|
-
filter: collectionRequest.filter,
|
|
8436
|
-
orderBy: collectionRequest.orderBy,
|
|
8437
|
-
order: collectionRequest.order,
|
|
8438
|
-
limit: collectionRequest.limit,
|
|
8439
|
-
offset: collectionRequest.offset,
|
|
8440
|
-
startAfter: collectionRequest.startAfter,
|
|
8441
|
-
databaseId: collectionRequest.databaseId
|
|
8442
|
-
});
|
|
8443
|
-
}
|
|
8444
|
-
const registryCollection = this.registry.getCollectionByPath(notifyPath);
|
|
8445
|
-
const resolvedCollection = collection ? {
|
|
8446
|
-
...collection,
|
|
8447
|
-
...registryCollection
|
|
8448
|
-
} : registryCollection;
|
|
8449
|
-
const callbacks = resolvedCollection?.callbacks;
|
|
8450
|
-
const propertyCallbacks = resolvedCollection?.properties ? buildPropertyCallbacks(resolvedCollection.properties) : void 0;
|
|
8451
|
-
if (callbacks?.afterRead || propertyCallbacks?.afterRead) {
|
|
8452
|
-
const contextForCallback = {
|
|
8453
|
-
user: {
|
|
8454
|
-
uid: authContext.userId,
|
|
8455
|
-
roles: authContext.roles
|
|
8456
|
-
},
|
|
8457
|
-
driver: this.driver,
|
|
8458
|
-
data: this.driver ? this.driver.data : void 0
|
|
8459
|
-
};
|
|
8460
|
-
return await Promise.all(fetchedEntities.map(async (entity) => {
|
|
8461
|
-
let processedEntity = entity;
|
|
8462
|
-
if (callbacks?.afterRead) {
|
|
8463
|
-
processedEntity = await callbacks.afterRead({
|
|
8464
|
-
collection: resolvedCollection,
|
|
8465
|
-
path: notifyPath,
|
|
8466
|
-
entity: processedEntity,
|
|
8467
|
-
context: contextForCallback
|
|
8468
|
-
}) ?? processedEntity;
|
|
8469
|
-
}
|
|
8470
|
-
if (propertyCallbacks?.afterRead) {
|
|
8471
|
-
processedEntity = await propertyCallbacks.afterRead({
|
|
8472
|
-
collection: resolvedCollection,
|
|
8473
|
-
path: notifyPath,
|
|
8474
|
-
entity: processedEntity,
|
|
8475
|
-
context: contextForCallback
|
|
8476
|
-
}) ?? processedEntity;
|
|
8477
|
-
}
|
|
8478
|
-
return processedEntity;
|
|
8479
|
-
}));
|
|
8480
|
-
}
|
|
8481
|
-
return fetchedEntities;
|
|
8482
|
-
});
|
|
8483
|
-
}
|
|
8484
|
-
return fetchFn();
|
|
8485
8929
|
}
|
|
8486
8930
|
if (collectionRequest.searchString) {
|
|
8487
8931
|
return await this.entityService.searchEntities(notifyPath, collectionRequest.searchString, {
|
|
@@ -8545,60 +8989,56 @@ class RealtimeService extends EventEmitter {
|
|
|
8545
8989
|
async fetchEntityWithAuth(notifyPath, entityId, authContext) {
|
|
8546
8990
|
if (this.driver) {
|
|
8547
8991
|
const collection = this.registry.getCollectionByPath(notifyPath);
|
|
8548
|
-
const
|
|
8549
|
-
|
|
8550
|
-
|
|
8551
|
-
|
|
8552
|
-
|
|
8553
|
-
|
|
8554
|
-
|
|
8555
|
-
|
|
8556
|
-
|
|
8557
|
-
|
|
8558
|
-
|
|
8559
|
-
|
|
8560
|
-
|
|
8561
|
-
|
|
8562
|
-
|
|
8563
|
-
|
|
8564
|
-
|
|
8565
|
-
|
|
8566
|
-
|
|
8567
|
-
|
|
8568
|
-
|
|
8569
|
-
|
|
8570
|
-
const
|
|
8571
|
-
|
|
8572
|
-
|
|
8573
|
-
|
|
8574
|
-
|
|
8575
|
-
|
|
8576
|
-
|
|
8577
|
-
|
|
8578
|
-
|
|
8579
|
-
|
|
8580
|
-
|
|
8581
|
-
|
|
8582
|
-
|
|
8583
|
-
|
|
8584
|
-
|
|
8585
|
-
|
|
8586
|
-
|
|
8587
|
-
|
|
8588
|
-
|
|
8589
|
-
|
|
8590
|
-
|
|
8591
|
-
|
|
8592
|
-
|
|
8593
|
-
context: contextForCallback
|
|
8594
|
-
}) ?? processedEntity;
|
|
8595
|
-
}
|
|
8992
|
+
const activeAuth = authContext || {
|
|
8993
|
+
userId: "anon",
|
|
8994
|
+
roles: ["anon"]
|
|
8995
|
+
};
|
|
8996
|
+
return await this.db.transaction(async (tx) => {
|
|
8997
|
+
await tx.execute(sql`SELECT set_config('app.user_id', ${activeAuth.userId}, true)`);
|
|
8998
|
+
await tx.execute(sql`SELECT set_config('app.user_roles', ${activeAuth.roles.join(",")}, true)`);
|
|
8999
|
+
await tx.execute(sql`SELECT set_config('app.jwt', ${JSON.stringify({
|
|
9000
|
+
sub: activeAuth.userId,
|
|
9001
|
+
roles: activeAuth.roles
|
|
9002
|
+
})}, true)`);
|
|
9003
|
+
const txEntityService = new EntityService(tx, this.registry);
|
|
9004
|
+
let processedEntity = await txEntityService.fetchEntity(notifyPath, entityId, collection?.databaseId);
|
|
9005
|
+
if (processedEntity) {
|
|
9006
|
+
const registryCollection = this.registry.getCollectionByPath(notifyPath);
|
|
9007
|
+
const resolvedCollection = collection ? {
|
|
9008
|
+
...collection,
|
|
9009
|
+
...registryCollection
|
|
9010
|
+
} : registryCollection;
|
|
9011
|
+
const callbacks = resolvedCollection?.callbacks;
|
|
9012
|
+
const propertyCallbacks = resolvedCollection?.properties ? buildPropertyCallbacks(resolvedCollection.properties) : void 0;
|
|
9013
|
+
if (callbacks?.afterRead || propertyCallbacks?.afterRead) {
|
|
9014
|
+
const contextForCallback = {
|
|
9015
|
+
user: {
|
|
9016
|
+
uid: activeAuth.userId,
|
|
9017
|
+
roles: activeAuth.roles
|
|
9018
|
+
},
|
|
9019
|
+
driver: this.driver,
|
|
9020
|
+
data: this.driver && "data" in this.driver ? this.driver.data : void 0
|
|
9021
|
+
};
|
|
9022
|
+
if (callbacks?.afterRead) {
|
|
9023
|
+
processedEntity = await callbacks.afterRead({
|
|
9024
|
+
collection: resolvedCollection,
|
|
9025
|
+
path: notifyPath,
|
|
9026
|
+
entity: processedEntity,
|
|
9027
|
+
context: contextForCallback
|
|
9028
|
+
}) ?? processedEntity;
|
|
9029
|
+
}
|
|
9030
|
+
if (propertyCallbacks?.afterRead) {
|
|
9031
|
+
processedEntity = await propertyCallbacks.afterRead({
|
|
9032
|
+
collection: resolvedCollection,
|
|
9033
|
+
path: notifyPath,
|
|
9034
|
+
entity: processedEntity,
|
|
9035
|
+
context: contextForCallback
|
|
9036
|
+
}) ?? processedEntity;
|
|
8596
9037
|
}
|
|
8597
9038
|
}
|
|
8598
|
-
|
|
8599
|
-
|
|
8600
|
-
}
|
|
8601
|
-
return fetchFn();
|
|
9039
|
+
}
|
|
9040
|
+
return processedEntity;
|
|
9041
|
+
});
|
|
8602
9042
|
}
|
|
8603
9043
|
return await this.entityService.fetchEntity(notifyPath, entityId);
|
|
8604
9044
|
}
|
|
@@ -8666,6 +9106,31 @@ class RealtimeService extends EventEmitter {
|
|
|
8666
9106
|
return parentPaths;
|
|
8667
9107
|
}
|
|
8668
9108
|
// =============================================================================
|
|
9109
|
+
// Lifecycle / Cleanup
|
|
9110
|
+
// =============================================================================
|
|
9111
|
+
/**
|
|
9112
|
+
* Gracefully tear down all realtime resources.
|
|
9113
|
+
*
|
|
9114
|
+
* This MUST be called during process shutdown, **before** `pool.end()`.
|
|
9115
|
+
* It ensures:
|
|
9116
|
+
* 1. All debounced refetch timers are cancelled (prevents queries after pool closes).
|
|
9117
|
+
* 2. All subscription state and callbacks are cleared.
|
|
9118
|
+
* 3. The dedicated LISTEN client (outside the pool) is disconnected.
|
|
9119
|
+
* 4. All WebSocket clients are removed (but not forcefully closed — the
|
|
9120
|
+
* HTTP server close will handle that).
|
|
9121
|
+
*/
|
|
9122
|
+
async destroy() {
|
|
9123
|
+
for (const [key, timer] of this.refetchTimers) {
|
|
9124
|
+
clearTimeout(timer);
|
|
9125
|
+
this.refetchTimers.delete(key);
|
|
9126
|
+
}
|
|
9127
|
+
this._subscriptions.clear();
|
|
9128
|
+
this.subscriptionCallbacks.clear();
|
|
9129
|
+
await this.stopListening();
|
|
9130
|
+
this.clients.clear();
|
|
9131
|
+
this.debugLog("🧹 [RealtimeService] destroy() complete — all resources released.");
|
|
9132
|
+
}
|
|
9133
|
+
// =============================================================================
|
|
8669
9134
|
// Cross-Instance LISTEN/NOTIFY
|
|
8670
9135
|
// =============================================================================
|
|
8671
9136
|
/**
|
|
@@ -8806,8 +9271,23 @@ const clientSessions = /* @__PURE__ */ new Map();
|
|
|
8806
9271
|
const WS_RATE_LIMIT = 2e3;
|
|
8807
9272
|
const WS_RATE_WINDOW_MS = 6e4;
|
|
8808
9273
|
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"]);
|
|
9274
|
+
function extractErrorMessage(error) {
|
|
9275
|
+
if (!error) return "Unknown error";
|
|
9276
|
+
if (typeof error === "object") {
|
|
9277
|
+
const err = error;
|
|
9278
|
+
if (err.cause) {
|
|
9279
|
+
return extractErrorMessage(err.cause);
|
|
9280
|
+
}
|
|
9281
|
+
if (typeof err.message === "string") {
|
|
9282
|
+
return err.message;
|
|
9283
|
+
}
|
|
9284
|
+
}
|
|
9285
|
+
return String(error);
|
|
9286
|
+
}
|
|
8809
9287
|
function isAdminSession(session) {
|
|
8810
|
-
if (!session?.user
|
|
9288
|
+
if (!session?.user) return false;
|
|
9289
|
+
if (session.user.isAdmin) return true;
|
|
9290
|
+
if (!session.user.roles) return false;
|
|
8811
9291
|
return session.user.roles.some((r) => {
|
|
8812
9292
|
if (typeof r === "string") return r === "admin";
|
|
8813
9293
|
if (r && typeof r === "object" && "isAdmin" in r) return r.isAdmin;
|
|
@@ -8815,7 +9295,7 @@ function isAdminSession(session) {
|
|
|
8815
9295
|
return false;
|
|
8816
9296
|
});
|
|
8817
9297
|
}
|
|
8818
|
-
function createPostgresWebSocket(server, realtimeService, driver, authConfig) {
|
|
9298
|
+
function createPostgresWebSocket(server, realtimeService, driver, authConfig, authAdapter) {
|
|
8819
9299
|
const isProduction = process.env.NODE_ENV === "production";
|
|
8820
9300
|
const wsDebug = (...args) => {
|
|
8821
9301
|
if (!isProduction) console.debug(...args);
|
|
@@ -8829,7 +9309,7 @@ function createPostgresWebSocket(server, realtimeService, driver, authConfig) {
|
|
|
8829
9309
|
}
|
|
8830
9310
|
console.error("❌ [WebSocket Server] Error:", err);
|
|
8831
9311
|
});
|
|
8832
|
-
const requireAuth = authConfig?.requireAuth !== false && authConfig?.jwtSecret;
|
|
9312
|
+
const requireAuth = authAdapter ? true : authConfig?.requireAuth !== false && !!authConfig?.jwtSecret;
|
|
8833
9313
|
wss.on("connection", (ws) => {
|
|
8834
9314
|
const clientId = `client_${Date.now()}_${Math.random().toString(36).substring(2, 9)}`;
|
|
8835
9315
|
wsDebug(`WebSocket client connected: ${clientId}`);
|
|
@@ -8874,11 +9354,37 @@ function createPostgresWebSocket(server, realtimeService, driver, authConfig) {
|
|
|
8874
9354
|
sendError("AUTH_ERROR", "INVALID_INPUT", "Token is required");
|
|
8875
9355
|
return;
|
|
8876
9356
|
}
|
|
8877
|
-
|
|
8878
|
-
if (
|
|
9357
|
+
let verifiedUser = null;
|
|
9358
|
+
if (authAdapter) {
|
|
9359
|
+
try {
|
|
9360
|
+
const adapterUser = authAdapter.verifyToken ? await authAdapter.verifyToken(token) : await authAdapter.verifyRequest(new Request("http://localhost/_ws_auth", {
|
|
9361
|
+
headers: {
|
|
9362
|
+
Authorization: `Bearer ${token}`
|
|
9363
|
+
}
|
|
9364
|
+
}));
|
|
9365
|
+
if (adapterUser) {
|
|
9366
|
+
verifiedUser = {
|
|
9367
|
+
userId: adapterUser.uid,
|
|
9368
|
+
roles: adapterUser.roles,
|
|
9369
|
+
isAdmin: adapterUser.isAdmin
|
|
9370
|
+
};
|
|
9371
|
+
}
|
|
9372
|
+
} catch {
|
|
9373
|
+
}
|
|
9374
|
+
} else {
|
|
9375
|
+
const jwtPayload = extractUserFromToken(token);
|
|
9376
|
+
if (jwtPayload) {
|
|
9377
|
+
verifiedUser = {
|
|
9378
|
+
userId: jwtPayload.userId,
|
|
9379
|
+
roles: jwtPayload.roles ?? [],
|
|
9380
|
+
isAdmin: (jwtPayload.roles ?? []).some((r) => r === "admin")
|
|
9381
|
+
};
|
|
9382
|
+
}
|
|
9383
|
+
}
|
|
9384
|
+
if (verifiedUser) {
|
|
8879
9385
|
const session = clientSessions.get(clientId);
|
|
8880
9386
|
if (session) {
|
|
8881
|
-
session.user =
|
|
9387
|
+
session.user = verifiedUser;
|
|
8882
9388
|
session.authenticated = true;
|
|
8883
9389
|
}
|
|
8884
9390
|
wsDebug(`[WS] replying AUTH_SUCCESS for requestId ${requestId}`);
|
|
@@ -8886,11 +9392,11 @@ function createPostgresWebSocket(server, realtimeService, driver, authConfig) {
|
|
|
8886
9392
|
type: "AUTH_SUCCESS",
|
|
8887
9393
|
requestId,
|
|
8888
9394
|
payload: {
|
|
8889
|
-
userId:
|
|
8890
|
-
roles:
|
|
9395
|
+
userId: verifiedUser.userId,
|
|
9396
|
+
roles: verifiedUser.roles
|
|
8891
9397
|
}
|
|
8892
9398
|
}));
|
|
8893
|
-
wsDebug(`🔐 [WebSocket Server] Client ${clientId} authenticated as ${
|
|
9399
|
+
wsDebug(`🔐 [WebSocket Server] Client ${clientId} authenticated as ${verifiedUser.userId}`);
|
|
8894
9400
|
} else {
|
|
8895
9401
|
wsDebug(`[WS] replying AUTH_ERROR for requestId ${requestId} (invalid token)`);
|
|
8896
9402
|
sendError("AUTH_ERROR", "INVALID_TOKEN", "Invalid or expired token");
|
|
@@ -8928,16 +9434,19 @@ function createPostgresWebSocket(server, realtimeService, driver, authConfig) {
|
|
|
8928
9434
|
}
|
|
8929
9435
|
const getScopedDelegate = async () => {
|
|
8930
9436
|
const session = clientSessions.get(clientId);
|
|
8931
|
-
if (
|
|
9437
|
+
if ("withAuth" in driver && typeof driver.withAuth === "function") {
|
|
8932
9438
|
try {
|
|
8933
|
-
const userForAuth = {
|
|
9439
|
+
const userForAuth = session?.user ? {
|
|
8934
9440
|
uid: session.user.userId,
|
|
8935
9441
|
roles: session.user.roles ?? []
|
|
9442
|
+
} : {
|
|
9443
|
+
uid: "anon",
|
|
9444
|
+
roles: ["anon"]
|
|
8936
9445
|
};
|
|
8937
9446
|
return await driver.withAuth(userForAuth);
|
|
8938
9447
|
} catch (e) {
|
|
8939
|
-
console.error("Failed to create
|
|
8940
|
-
|
|
9448
|
+
console.error("Failed to create RLS scoped delegate for WS request", e);
|
|
9449
|
+
throw new Error("Internal authentication error");
|
|
8941
9450
|
}
|
|
8942
9451
|
}
|
|
8943
9452
|
return driver;
|
|
@@ -9068,24 +9577,29 @@ function createPostgresWebSocket(server, realtimeService, driver, authConfig) {
|
|
|
9068
9577
|
sql: sql2,
|
|
9069
9578
|
options
|
|
9070
9579
|
} = payload;
|
|
9071
|
-
|
|
9072
|
-
|
|
9073
|
-
|
|
9074
|
-
|
|
9075
|
-
|
|
9076
|
-
|
|
9077
|
-
|
|
9078
|
-
|
|
9079
|
-
|
|
9580
|
+
try {
|
|
9581
|
+
const delegate = await getScopedDelegate();
|
|
9582
|
+
const admin = delegate.admin;
|
|
9583
|
+
if (!isSQLAdmin(admin)) {
|
|
9584
|
+
sendError("ERROR", "NOT_SUPPORTED", "SQL execution is not available for this driver.");
|
|
9585
|
+
break;
|
|
9586
|
+
}
|
|
9587
|
+
const result = await admin.executeSql(sql2, options);
|
|
9588
|
+
if (process.env.NODE_ENV !== "production") {
|
|
9589
|
+
wsDebug(`⚡ [WebSocket Server] SQL executed. Returned ${Array.isArray(result) ? result.length : "non-array"} rows.`);
|
|
9590
|
+
}
|
|
9591
|
+
const response = {
|
|
9592
|
+
type: "EXECUTE_SQL_SUCCESS",
|
|
9593
|
+
payload: {
|
|
9594
|
+
result
|
|
9595
|
+
},
|
|
9596
|
+
requestId
|
|
9597
|
+
};
|
|
9598
|
+
ws.send(JSON.stringify(response));
|
|
9599
|
+
} catch (sqlError) {
|
|
9600
|
+
const errMsg = extractErrorMessage(sqlError);
|
|
9601
|
+
sendError("ERROR", "SQL_ERROR", errMsg);
|
|
9080
9602
|
}
|
|
9081
|
-
const response = {
|
|
9082
|
-
type: "EXECUTE_SQL_SUCCESS",
|
|
9083
|
-
payload: {
|
|
9084
|
-
result
|
|
9085
|
-
},
|
|
9086
|
-
requestId
|
|
9087
|
-
};
|
|
9088
|
-
ws.send(JSON.stringify(response));
|
|
9089
9603
|
}
|
|
9090
9604
|
break;
|
|
9091
9605
|
case "FETCH_DATABASES":
|
|
@@ -9264,7 +9778,10 @@ function createPostgresWebSocket(server, realtimeService, driver, authConfig) {
|
|
|
9264
9778
|
const authContext = session?.user ? {
|
|
9265
9779
|
userId: session.user.userId,
|
|
9266
9780
|
roles: session.user.roles ?? []
|
|
9267
|
-
} :
|
|
9781
|
+
} : {
|
|
9782
|
+
userId: "anon",
|
|
9783
|
+
roles: ["anon"]
|
|
9784
|
+
};
|
|
9268
9785
|
await realtimeService.handleClientMessage(clientId, {
|
|
9269
9786
|
type,
|
|
9270
9787
|
payload,
|
|
@@ -9408,29 +9925,58 @@ const DEFAULT_ROLES = [{
|
|
|
9408
9925
|
},
|
|
9409
9926
|
config: null
|
|
9410
9927
|
}];
|
|
9411
|
-
async function ensureAuthTablesExist(db) {
|
|
9928
|
+
async function ensureAuthTablesExist(db, registry) {
|
|
9412
9929
|
console.log("🔍 Checking auth tables...");
|
|
9413
9930
|
try {
|
|
9931
|
+
let usersTableName = '"users"';
|
|
9932
|
+
let userIdType = "TEXT";
|
|
9933
|
+
let usersSchema2 = "public";
|
|
9934
|
+
if (registry) {
|
|
9935
|
+
const usersTable = registry.getTable("users");
|
|
9936
|
+
if (usersTable) {
|
|
9937
|
+
const {
|
|
9938
|
+
getTableName: getTableName2
|
|
9939
|
+
} = await import("drizzle-orm");
|
|
9940
|
+
usersSchema2 = getTableConfig(usersTable).schema || "public";
|
|
9941
|
+
usersTableName = usersSchema2 === "public" ? `"${getTableName2(usersTable)}"` : `"${usersSchema2}"."${getTableName2(usersTable)}"`;
|
|
9942
|
+
if (usersTable.id) {
|
|
9943
|
+
const col = usersTable.id;
|
|
9944
|
+
const meta = getColumnMeta(col);
|
|
9945
|
+
const columnType = meta.columnType;
|
|
9946
|
+
if (columnType === "PgUUID") {
|
|
9947
|
+
userIdType = "UUID";
|
|
9948
|
+
} else if (columnType === "PgSerial" || columnType === "PgInteger") {
|
|
9949
|
+
userIdType = "INTEGER";
|
|
9950
|
+
} else if (columnType === "PgBigInt" || columnType === "PgBigSerial") {
|
|
9951
|
+
userIdType = "BIGINT";
|
|
9952
|
+
}
|
|
9953
|
+
}
|
|
9954
|
+
}
|
|
9955
|
+
}
|
|
9956
|
+
let rolesSchema = "rebase";
|
|
9957
|
+
if (registry) {
|
|
9958
|
+
const rolesTable = registry.getTable("roles");
|
|
9959
|
+
if (rolesTable) {
|
|
9960
|
+
rolesSchema = getTableConfig(rolesTable).schema || "public";
|
|
9961
|
+
}
|
|
9962
|
+
}
|
|
9963
|
+
if (usersSchema2 !== "public") {
|
|
9964
|
+
await db.execute(sql`CREATE SCHEMA IF NOT EXISTS ${sql.raw(usersSchema2)}`);
|
|
9965
|
+
}
|
|
9966
|
+
if (rolesSchema !== "public" && rolesSchema !== usersSchema2) {
|
|
9967
|
+
await db.execute(sql`CREATE SCHEMA IF NOT EXISTS ${sql.raw(rolesSchema)}`);
|
|
9968
|
+
}
|
|
9414
9969
|
await db.execute(sql`CREATE SCHEMA IF NOT EXISTS rebase`);
|
|
9970
|
+
const userIdentitiesTable = `"${rolesSchema}"."user_identities"`;
|
|
9971
|
+
const rolesTableName = `"${rolesSchema}"."roles"`;
|
|
9972
|
+
const userRolesTableName = `"${rolesSchema}"."user_roles"`;
|
|
9973
|
+
const refreshTokensTableName = `"${rolesSchema}"."refresh_tokens"`;
|
|
9974
|
+
const passwordResetTokensTableName = `"${rolesSchema}"."password_reset_tokens"`;
|
|
9975
|
+
const appConfigTableName = `"${rolesSchema}"."app_config"`;
|
|
9415
9976
|
await db.execute(sql`
|
|
9416
|
-
CREATE TABLE IF NOT EXISTS
|
|
9417
|
-
id TEXT PRIMARY KEY DEFAULT gen_random_uuid()::text,
|
|
9418
|
-
email TEXT NOT NULL UNIQUE,
|
|
9419
|
-
password_hash TEXT,
|
|
9420
|
-
display_name TEXT,
|
|
9421
|
-
photo_url TEXT,
|
|
9422
|
-
created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
|
|
9423
|
-
updated_at TIMESTAMP WITH TIME ZONE DEFAULT NOW()
|
|
9424
|
-
)
|
|
9425
|
-
`);
|
|
9426
|
-
await db.execute(sql`
|
|
9427
|
-
CREATE INDEX IF NOT EXISTS idx_users_email
|
|
9428
|
-
ON rebase.users(email)
|
|
9429
|
-
`);
|
|
9430
|
-
await db.execute(sql`
|
|
9431
|
-
CREATE TABLE IF NOT EXISTS rebase.user_identities (
|
|
9977
|
+
CREATE TABLE IF NOT EXISTS ${sql.raw(userIdentitiesTable)} (
|
|
9432
9978
|
id TEXT PRIMARY KEY DEFAULT gen_random_uuid()::text,
|
|
9433
|
-
user_id
|
|
9979
|
+
user_id ${sql.raw(userIdType)} NOT NULL REFERENCES ${sql.raw(usersTableName)}(id) ON DELETE CASCADE,
|
|
9434
9980
|
provider TEXT NOT NULL,
|
|
9435
9981
|
provider_id TEXT NOT NULL,
|
|
9436
9982
|
profile_data JSONB,
|
|
@@ -9441,10 +9987,10 @@ async function ensureAuthTablesExist(db) {
|
|
|
9441
9987
|
`);
|
|
9442
9988
|
await db.execute(sql`
|
|
9443
9989
|
CREATE INDEX IF NOT EXISTS idx_user_identities_user
|
|
9444
|
-
ON
|
|
9990
|
+
ON ${sql.raw(userIdentitiesTable)}(user_id)
|
|
9445
9991
|
`);
|
|
9446
9992
|
await db.execute(sql`
|
|
9447
|
-
CREATE TABLE IF NOT EXISTS
|
|
9993
|
+
CREATE TABLE IF NOT EXISTS ${sql.raw(rolesTableName)} (
|
|
9448
9994
|
id TEXT PRIMARY KEY,
|
|
9449
9995
|
name TEXT NOT NULL,
|
|
9450
9996
|
is_admin BOOLEAN DEFAULT FALSE,
|
|
@@ -9455,20 +10001,20 @@ async function ensureAuthTablesExist(db) {
|
|
|
9455
10001
|
)
|
|
9456
10002
|
`);
|
|
9457
10003
|
await db.execute(sql`
|
|
9458
|
-
CREATE TABLE IF NOT EXISTS
|
|
9459
|
-
user_id
|
|
9460
|
-
role_id TEXT NOT NULL REFERENCES
|
|
10004
|
+
CREATE TABLE IF NOT EXISTS ${sql.raw(userRolesTableName)} (
|
|
10005
|
+
user_id ${sql.raw(userIdType)} NOT NULL REFERENCES ${sql.raw(usersTableName)}(id) ON DELETE CASCADE,
|
|
10006
|
+
role_id TEXT NOT NULL REFERENCES ${sql.raw(rolesTableName)}(id) ON DELETE CASCADE,
|
|
9461
10007
|
PRIMARY KEY (user_id, role_id)
|
|
9462
10008
|
)
|
|
9463
10009
|
`);
|
|
9464
10010
|
await db.execute(sql`
|
|
9465
10011
|
CREATE INDEX IF NOT EXISTS idx_user_roles_user
|
|
9466
|
-
ON
|
|
10012
|
+
ON ${sql.raw(userRolesTableName)}(user_id)
|
|
9467
10013
|
`);
|
|
9468
10014
|
await db.execute(sql`
|
|
9469
|
-
CREATE TABLE IF NOT EXISTS
|
|
10015
|
+
CREATE TABLE IF NOT EXISTS ${sql.raw(refreshTokensTableName)} (
|
|
9470
10016
|
id TEXT PRIMARY KEY DEFAULT gen_random_uuid()::text,
|
|
9471
|
-
user_id
|
|
10017
|
+
user_id ${sql.raw(userIdType)} NOT NULL REFERENCES ${sql.raw(usersTableName)}(id) ON DELETE CASCADE,
|
|
9472
10018
|
token_hash TEXT NOT NULL UNIQUE,
|
|
9473
10019
|
expires_at TIMESTAMP WITH TIME ZONE NOT NULL,
|
|
9474
10020
|
user_agent TEXT,
|
|
@@ -9479,16 +10025,16 @@ async function ensureAuthTablesExist(db) {
|
|
|
9479
10025
|
`);
|
|
9480
10026
|
await db.execute(sql`
|
|
9481
10027
|
CREATE INDEX IF NOT EXISTS idx_refresh_tokens_hash
|
|
9482
|
-
ON
|
|
10028
|
+
ON ${sql.raw(refreshTokensTableName)}(token_hash)
|
|
9483
10029
|
`);
|
|
9484
10030
|
await db.execute(sql`
|
|
9485
10031
|
CREATE INDEX IF NOT EXISTS idx_refresh_tokens_user
|
|
9486
|
-
ON
|
|
10032
|
+
ON ${sql.raw(refreshTokensTableName)}(user_id)
|
|
9487
10033
|
`);
|
|
9488
10034
|
await db.execute(sql`
|
|
9489
|
-
CREATE TABLE IF NOT EXISTS
|
|
10035
|
+
CREATE TABLE IF NOT EXISTS ${sql.raw(passwordResetTokensTableName)} (
|
|
9490
10036
|
id TEXT PRIMARY KEY DEFAULT gen_random_uuid()::text,
|
|
9491
|
-
user_id
|
|
10037
|
+
user_id ${sql.raw(userIdType)} NOT NULL REFERENCES ${sql.raw(usersTableName)}(id) ON DELETE CASCADE,
|
|
9492
10038
|
token_hash TEXT NOT NULL UNIQUE,
|
|
9493
10039
|
expires_at TIMESTAMP WITH TIME ZONE NOT NULL,
|
|
9494
10040
|
used_at TIMESTAMP WITH TIME ZONE,
|
|
@@ -9497,20 +10043,19 @@ async function ensureAuthTablesExist(db) {
|
|
|
9497
10043
|
`);
|
|
9498
10044
|
await db.execute(sql`
|
|
9499
10045
|
CREATE INDEX IF NOT EXISTS idx_password_reset_tokens_hash
|
|
9500
|
-
ON
|
|
10046
|
+
ON ${sql.raw(passwordResetTokensTableName)}(token_hash)
|
|
9501
10047
|
`);
|
|
9502
10048
|
await db.execute(sql`
|
|
9503
10049
|
CREATE INDEX IF NOT EXISTS idx_password_reset_tokens_user
|
|
9504
|
-
ON
|
|
10050
|
+
ON ${sql.raw(passwordResetTokensTableName)}(user_id)
|
|
9505
10051
|
`);
|
|
9506
10052
|
await db.execute(sql`
|
|
9507
|
-
CREATE TABLE IF NOT EXISTS
|
|
10053
|
+
CREATE TABLE IF NOT EXISTS ${sql.raw(appConfigTableName)} (
|
|
9508
10054
|
key TEXT PRIMARY KEY,
|
|
9509
10055
|
value JSONB NOT NULL,
|
|
9510
10056
|
updated_at TIMESTAMP WITH TIME ZONE DEFAULT NOW()
|
|
9511
10057
|
)
|
|
9512
10058
|
`);
|
|
9513
|
-
await applyInternalMigrations(db);
|
|
9514
10059
|
await db.execute(sql`CREATE SCHEMA IF NOT EXISTS auth`);
|
|
9515
10060
|
await db.transaction(async (tx) => {
|
|
9516
10061
|
await tx.execute(sql`SELECT pg_advisory_xact_lock(hashtext('rebase_auth_functions_init'))`);
|
|
@@ -9533,15 +10078,15 @@ async function ensureAuthTablesExist(db) {
|
|
|
9533
10078
|
$$ LANGUAGE sql STABLE
|
|
9534
10079
|
`);
|
|
9535
10080
|
});
|
|
9536
|
-
await seedDefaultRoles(db);
|
|
10081
|
+
await seedDefaultRoles(db, rolesTableName);
|
|
9537
10082
|
console.log("✅ Auth tables ready");
|
|
9538
10083
|
} catch (error) {
|
|
9539
10084
|
console.error("❌ Failed to create auth tables:", error);
|
|
9540
10085
|
console.warn("⚠️ Continuing without creating auth tables.");
|
|
9541
10086
|
}
|
|
9542
10087
|
}
|
|
9543
|
-
async function seedDefaultRoles(db) {
|
|
9544
|
-
const result = await db.execute(sql`SELECT COUNT(*) as count FROM
|
|
10088
|
+
async function seedDefaultRoles(db, rolesTableName) {
|
|
10089
|
+
const result = await db.execute(sql`SELECT COUNT(*) as count FROM ${sql.raw(rolesTableName)}`);
|
|
9545
10090
|
const count2 = parseInt(result.rows[0]?.count || "0", 10);
|
|
9546
10091
|
if (count2 > 0) {
|
|
9547
10092
|
console.log(`📋 Found ${count2} existing roles`);
|
|
@@ -9550,7 +10095,7 @@ async function seedDefaultRoles(db) {
|
|
|
9550
10095
|
console.log("🌱 Seeding default roles...");
|
|
9551
10096
|
for (const role of DEFAULT_ROLES) {
|
|
9552
10097
|
await db.execute(sql`
|
|
9553
|
-
INSERT INTO
|
|
10098
|
+
INSERT INTO ${sql.raw(rolesTableName)} (id, name, is_admin, default_permissions, config)
|
|
9554
10099
|
VALUES (
|
|
9555
10100
|
${role.id},
|
|
9556
10101
|
${role.name},
|
|
@@ -9563,142 +10108,156 @@ async function seedDefaultRoles(db) {
|
|
|
9563
10108
|
}
|
|
9564
10109
|
console.log("✅ Default roles created: admin, editor, viewer");
|
|
9565
10110
|
}
|
|
9566
|
-
|
|
9567
|
-
|
|
9568
|
-
|
|
9569
|
-
|
|
9570
|
-
|
|
9571
|
-
|
|
9572
|
-
|
|
9573
|
-
|
|
9574
|
-
const columnsCheck = await db.execute(sql`
|
|
9575
|
-
SELECT column_name
|
|
9576
|
-
FROM information_schema.columns
|
|
9577
|
-
WHERE table_schema='rebase' AND table_name='users' AND column_name IN ('google_id', 'linkedin_id', 'provider')
|
|
9578
|
-
`);
|
|
9579
|
-
const existingColumns = columnsCheck.rows.map((r) => r.column_name);
|
|
9580
|
-
if (existingColumns.includes("google_id")) {
|
|
9581
|
-
await db.execute(sql`
|
|
9582
|
-
INSERT INTO rebase.user_identities (user_id, provider, provider_id)
|
|
9583
|
-
SELECT id, 'google', google_id
|
|
9584
|
-
FROM rebase.users
|
|
9585
|
-
WHERE google_id IS NOT NULL
|
|
9586
|
-
ON CONFLICT (provider, provider_id) DO NOTHING
|
|
9587
|
-
`);
|
|
9588
|
-
}
|
|
9589
|
-
if (existingColumns.includes("linkedin_id")) {
|
|
9590
|
-
await db.execute(sql`
|
|
9591
|
-
INSERT INTO rebase.user_identities (user_id, provider, provider_id)
|
|
9592
|
-
SELECT id, 'linkedin', linkedin_id
|
|
9593
|
-
FROM rebase.users
|
|
9594
|
-
WHERE linkedin_id IS NOT NULL
|
|
9595
|
-
ON CONFLICT (provider, provider_id) DO NOTHING
|
|
9596
|
-
`);
|
|
9597
|
-
}
|
|
9598
|
-
if (existingColumns.length > 0) {
|
|
9599
|
-
await db.execute(sql`
|
|
9600
|
-
ALTER TABLE rebase.users
|
|
9601
|
-
DROP COLUMN IF EXISTS provider,
|
|
9602
|
-
DROP COLUMN IF EXISTS google_id,
|
|
9603
|
-
DROP COLUMN IF EXISTS linkedin_id
|
|
9604
|
-
`);
|
|
9605
|
-
await db.execute(sql`DROP INDEX IF EXISTS rebase.idx_users_google_id`);
|
|
9606
|
-
await db.execute(sql`DROP INDEX IF EXISTS rebase.idx_users_linkedin_id`);
|
|
9607
|
-
console.log("✅ Migrated to user_identities and dropped legacy columns.");
|
|
9608
|
-
}
|
|
9609
|
-
await db.execute(sql`
|
|
9610
|
-
ALTER TABLE rebase.roles
|
|
9611
|
-
ADD COLUMN IF NOT EXISTS collection_permissions JSONB
|
|
9612
|
-
`);
|
|
9613
|
-
await db.execute(sql`
|
|
9614
|
-
ALTER TABLE rebase.refresh_tokens
|
|
9615
|
-
ADD COLUMN IF NOT EXISTS user_agent TEXT,
|
|
9616
|
-
ADD COLUMN IF NOT EXISTS ip_address TEXT
|
|
9617
|
-
`);
|
|
9618
|
-
const constraintCheck = await db.execute(sql`
|
|
9619
|
-
SELECT 1 FROM information_schema.table_constraints
|
|
9620
|
-
WHERE constraint_name = 'unique_device_session'
|
|
9621
|
-
AND table_schema = 'rebase'
|
|
9622
|
-
AND table_name = 'refresh_tokens'
|
|
9623
|
-
`);
|
|
9624
|
-
if (constraintCheck.rows.length === 0) {
|
|
9625
|
-
try {
|
|
9626
|
-
await db.execute(sql`
|
|
9627
|
-
ALTER TABLE rebase.refresh_tokens
|
|
9628
|
-
ADD CONSTRAINT unique_device_session UNIQUE (user_id, user_agent, ip_address)
|
|
9629
|
-
`);
|
|
9630
|
-
console.log("✅ Added unique_device_session constraint");
|
|
9631
|
-
} catch (e) {
|
|
9632
|
-
const errorMessage = e instanceof Error ? e.message : String(e);
|
|
9633
|
-
if (errorMessage.includes("could not create unique index")) {
|
|
9634
|
-
console.warn("⚠️ Duplicate sessions found, cleaning up before adding constraint...");
|
|
9635
|
-
await db.execute(sql`
|
|
9636
|
-
DELETE FROM rebase.refresh_tokens a
|
|
9637
|
-
USING rebase.refresh_tokens b
|
|
9638
|
-
WHERE a.user_id = b.user_id
|
|
9639
|
-
AND COALESCE(a.user_agent, '') = COALESCE(b.user_agent, '')
|
|
9640
|
-
AND COALESCE(a.ip_address, '') = COALESCE(b.ip_address, '')
|
|
9641
|
-
AND a.created_at < b.created_at
|
|
9642
|
-
`);
|
|
9643
|
-
await db.execute(sql`
|
|
9644
|
-
ALTER TABLE rebase.refresh_tokens
|
|
9645
|
-
ADD CONSTRAINT unique_device_session UNIQUE (user_id, user_agent, ip_address)
|
|
9646
|
-
`).catch((retryErr) => {
|
|
9647
|
-
const retryMessage = retryErr instanceof Error ? retryErr.message : String(retryErr);
|
|
9648
|
-
console.error("Failed to add unique_device_session constraint after cleanup:", retryMessage);
|
|
9649
|
-
});
|
|
9650
|
-
} else {
|
|
9651
|
-
console.error("Constraint migration issue:", errorMessage);
|
|
9652
|
-
}
|
|
9653
|
-
}
|
|
9654
|
-
}
|
|
9655
|
-
} catch (error) {
|
|
9656
|
-
console.error("❌ Failed to run internal migrations:", error);
|
|
10111
|
+
function getColumnKey(table, ...keys2) {
|
|
10112
|
+
if (!table) return void 0;
|
|
10113
|
+
for (const key of keys2) {
|
|
10114
|
+
if (key in table) return key;
|
|
10115
|
+
const snake = toSnakeCase(key);
|
|
10116
|
+
if (snake in table) return snake;
|
|
10117
|
+
const camel = camelCase(key);
|
|
10118
|
+
if (camel in table) return camel;
|
|
9657
10119
|
}
|
|
10120
|
+
return void 0;
|
|
10121
|
+
}
|
|
10122
|
+
function getColumn(table, ...keys2) {
|
|
10123
|
+
if (!table) return void 0;
|
|
10124
|
+
const key = getColumnKey(table, ...keys2);
|
|
10125
|
+
return key ? table[key] : void 0;
|
|
9658
10126
|
}
|
|
9659
10127
|
class UserService {
|
|
9660
|
-
constructor(db) {
|
|
10128
|
+
constructor(db, tableOrTables) {
|
|
9661
10129
|
this.db = db;
|
|
10130
|
+
if (tableOrTables && (tableOrTables.users || tableOrTables.roles)) {
|
|
10131
|
+
const tables = tableOrTables;
|
|
10132
|
+
this.usersTable = tables.users || users;
|
|
10133
|
+
this.userIdentitiesTable = tables.userIdentities || userIdentities;
|
|
10134
|
+
this.userRolesTable = tables.userRoles || userRoles;
|
|
10135
|
+
this.rolesTable = tables.roles || roles;
|
|
10136
|
+
} else {
|
|
10137
|
+
const table = tableOrTables;
|
|
10138
|
+
this.usersTable = table || users;
|
|
10139
|
+
this.userIdentitiesTable = userIdentities;
|
|
10140
|
+
this.userRolesTable = userRoles;
|
|
10141
|
+
this.rolesTable = roles;
|
|
10142
|
+
}
|
|
10143
|
+
}
|
|
10144
|
+
usersTable;
|
|
10145
|
+
userIdentitiesTable;
|
|
10146
|
+
userRolesTable;
|
|
10147
|
+
rolesTable;
|
|
10148
|
+
getQualifiedUsersTableName() {
|
|
10149
|
+
const name = getTableName$1(this.usersTable);
|
|
10150
|
+
const schema = getTableConfig(this.usersTable).schema || "public";
|
|
10151
|
+
return `"${schema}"."${name}"`;
|
|
10152
|
+
}
|
|
10153
|
+
mapRowToUser(row) {
|
|
10154
|
+
if (!row) return row;
|
|
10155
|
+
const id = row.id ?? row.uid;
|
|
10156
|
+
const email = row.email;
|
|
10157
|
+
const passwordHash = row.password_hash ?? row.passwordHash ?? null;
|
|
10158
|
+
const displayName = row.display_name ?? row.displayName ?? null;
|
|
10159
|
+
const photoUrl = row.photo_url ?? row.photoUrl ?? row.photoURL ?? null;
|
|
10160
|
+
const emailVerified = row.email_verified ?? row.emailVerified ?? false;
|
|
10161
|
+
const emailVerificationToken = row.email_verification_token ?? row.emailVerificationToken ?? null;
|
|
10162
|
+
const emailVerificationSentAt = row.email_verification_sent_at ?? row.emailVerificationSentAt ?? null;
|
|
10163
|
+
const createdAt = row.created_at ?? row.createdAt;
|
|
10164
|
+
const updatedAt = row.updated_at ?? row.updatedAt;
|
|
10165
|
+
const metadata = {
|
|
10166
|
+
...row.metadata || {}
|
|
10167
|
+
};
|
|
10168
|
+
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"]);
|
|
10169
|
+
for (const [key, val] of Object.entries(row)) {
|
|
10170
|
+
if (!knownKeys.has(key)) {
|
|
10171
|
+
const camelKey = camelCase(key);
|
|
10172
|
+
metadata[camelKey] = val;
|
|
10173
|
+
}
|
|
10174
|
+
}
|
|
10175
|
+
return {
|
|
10176
|
+
id,
|
|
10177
|
+
email,
|
|
10178
|
+
passwordHash,
|
|
10179
|
+
displayName,
|
|
10180
|
+
photoUrl,
|
|
10181
|
+
emailVerified,
|
|
10182
|
+
emailVerificationToken,
|
|
10183
|
+
emailVerificationSentAt: emailVerificationSentAt ? new Date(emailVerificationSentAt) : null,
|
|
10184
|
+
createdAt: createdAt ? new Date(createdAt) : /* @__PURE__ */ new Date(),
|
|
10185
|
+
updatedAt: updatedAt ? new Date(updatedAt) : /* @__PURE__ */ new Date(),
|
|
10186
|
+
metadata
|
|
10187
|
+
};
|
|
10188
|
+
}
|
|
10189
|
+
mapPayload(data) {
|
|
10190
|
+
if (!data) return {};
|
|
10191
|
+
const payload = {};
|
|
10192
|
+
const idKey = getColumnKey(this.usersTable, "id") || "id";
|
|
10193
|
+
const emailKey = getColumnKey(this.usersTable, "email") || "email";
|
|
10194
|
+
const passwordHashKey = getColumnKey(this.usersTable, "passwordHash", "password_hash") || "passwordHash";
|
|
10195
|
+
const displayNameKey = getColumnKey(this.usersTable, "displayName", "display_name") || "displayName";
|
|
10196
|
+
const photoUrlKey = getColumnKey(this.usersTable, "photoUrl", "photo_url") || "photoUrl";
|
|
10197
|
+
const emailVerifiedKey = getColumnKey(this.usersTable, "emailVerified", "email_verified") || "emailVerified";
|
|
10198
|
+
const emailVerificationTokenKey = getColumnKey(this.usersTable, "emailVerificationToken", "email_verification_token") || "emailVerificationToken";
|
|
10199
|
+
const emailVerificationSentAtKey = getColumnKey(this.usersTable, "emailVerificationSentAt", "email_verification_sent_at") || "emailVerificationSentAt";
|
|
10200
|
+
const createdAtKey = getColumnKey(this.usersTable, "createdAt", "created_at") || "createdAt";
|
|
10201
|
+
const updatedAtKey = getColumnKey(this.usersTable, "updatedAt", "updated_at") || "updatedAt";
|
|
10202
|
+
const metadataKey = getColumnKey(this.usersTable, "metadata") || "metadata";
|
|
10203
|
+
if ("id" in data) payload[idKey] = data.id;
|
|
10204
|
+
if ("email" in data) payload[emailKey] = data.email;
|
|
10205
|
+
if ("passwordHash" in data) payload[passwordHashKey] = data.passwordHash;
|
|
10206
|
+
if ("displayName" in data) payload[displayNameKey] = data.displayName;
|
|
10207
|
+
if ("photoUrl" in data) payload[photoUrlKey] = data.photoUrl;
|
|
10208
|
+
if ("emailVerified" in data) payload[emailVerifiedKey] = data.emailVerified;
|
|
10209
|
+
if ("emailVerificationToken" in data) payload[emailVerificationTokenKey] = data.emailVerificationToken;
|
|
10210
|
+
if ("emailVerificationSentAt" in data) payload[emailVerificationSentAtKey] = data.emailVerificationSentAt;
|
|
10211
|
+
if ("createdAt" in data) payload[createdAtKey] = data.createdAt;
|
|
10212
|
+
if ("updatedAt" in data) payload[updatedAtKey] = data.updatedAt;
|
|
10213
|
+
const metadata = {
|
|
10214
|
+
...data.metadata || {}
|
|
10215
|
+
};
|
|
10216
|
+
const remainingMetadata = {};
|
|
10217
|
+
for (const [key, val] of Object.entries(metadata)) {
|
|
10218
|
+
const tableColKey = getColumnKey(this.usersTable, key);
|
|
10219
|
+
if (tableColKey && tableColKey !== idKey && tableColKey !== emailKey && tableColKey !== passwordHashKey && tableColKey !== displayNameKey && tableColKey !== photoUrlKey && tableColKey !== emailVerifiedKey && tableColKey !== emailVerificationTokenKey && tableColKey !== emailVerificationSentAtKey && tableColKey !== createdAtKey && tableColKey !== updatedAtKey && tableColKey !== metadataKey) {
|
|
10220
|
+
payload[tableColKey] = val;
|
|
10221
|
+
} else {
|
|
10222
|
+
remainingMetadata[key] = val;
|
|
10223
|
+
}
|
|
10224
|
+
}
|
|
10225
|
+
if (metadataKey in this.usersTable) {
|
|
10226
|
+
payload[metadataKey] = remainingMetadata;
|
|
10227
|
+
}
|
|
10228
|
+
return payload;
|
|
9662
10229
|
}
|
|
9663
10230
|
async createUser(data) {
|
|
9664
|
-
const
|
|
9665
|
-
|
|
10231
|
+
const payload = this.mapPayload(data);
|
|
10232
|
+
const [row] = await this.db.insert(this.usersTable).values(payload).returning();
|
|
10233
|
+
return this.mapRowToUser(row);
|
|
9666
10234
|
}
|
|
9667
10235
|
async getUserById(id) {
|
|
9668
|
-
const
|
|
9669
|
-
|
|
10236
|
+
const idCol = getColumn(this.usersTable, "id");
|
|
10237
|
+
if (!idCol) return null;
|
|
10238
|
+
const [row] = await this.db.select().from(this.usersTable).where(eq(idCol, id));
|
|
10239
|
+
return row ? this.mapRowToUser(row) : null;
|
|
9670
10240
|
}
|
|
9671
10241
|
async getUserByEmail(email) {
|
|
9672
|
-
const
|
|
9673
|
-
|
|
10242
|
+
const emailCol = getColumn(this.usersTable, "email");
|
|
10243
|
+
if (!emailCol) return null;
|
|
10244
|
+
const [row] = await this.db.select().from(this.usersTable).where(eq(emailCol, email.toLowerCase()));
|
|
10245
|
+
return row ? this.mapRowToUser(row) : null;
|
|
9674
10246
|
}
|
|
9675
10247
|
async getUserByIdentity(provider, providerId) {
|
|
9676
|
-
const
|
|
9677
|
-
|
|
9678
|
-
|
|
9679
|
-
|
|
9680
|
-
|
|
9681
|
-
|
|
9682
|
-
|
|
9683
|
-
if (result.rows.length === 0) return null;
|
|
9684
|
-
const row = result.rows[0];
|
|
9685
|
-
return {
|
|
9686
|
-
id: row.id,
|
|
9687
|
-
email: row.email,
|
|
9688
|
-
passwordHash: row.password_hash ?? null,
|
|
9689
|
-
displayName: row.display_name ?? null,
|
|
9690
|
-
photoUrl: row.photo_url ?? null,
|
|
9691
|
-
emailVerified: row.email_verified ?? false,
|
|
9692
|
-
emailVerificationToken: row.email_verification_token ?? null,
|
|
9693
|
-
emailVerificationSentAt: row.email_verification_sent_at ?? null,
|
|
9694
|
-
createdAt: row.created_at,
|
|
9695
|
-
updatedAt: row.updated_at
|
|
9696
|
-
};
|
|
10248
|
+
const userIdCol = getColumn(this.usersTable, "id");
|
|
10249
|
+
if (!userIdCol) return null;
|
|
10250
|
+
const result = await this.db.select({
|
|
10251
|
+
user: this.usersTable
|
|
10252
|
+
}).from(this.usersTable).innerJoin(this.userIdentitiesTable, eq(userIdCol, this.userIdentitiesTable.userId)).where(sql`${this.userIdentitiesTable.provider} = ${provider} AND ${this.userIdentitiesTable.providerId} = ${providerId}`).limit(1);
|
|
10253
|
+
if (result.length === 0) return null;
|
|
10254
|
+
return this.mapRowToUser(result[0].user);
|
|
9697
10255
|
}
|
|
9698
10256
|
async getUserIdentities(userId) {
|
|
10257
|
+
const schema = getTableConfig(this.userIdentitiesTable).schema || "public";
|
|
9699
10258
|
const result = await this.db.execute(sql`
|
|
9700
10259
|
SELECT id, user_id, provider, provider_id, profile_data, created_at, updated_at
|
|
9701
|
-
FROM
|
|
10260
|
+
FROM ${sql.raw(`"${schema}"."user_identities"`)}
|
|
9702
10261
|
WHERE user_id = ${userId}
|
|
9703
10262
|
`);
|
|
9704
10263
|
return result.rows.map((row) => ({
|
|
@@ -9712,27 +10271,32 @@ class UserService {
|
|
|
9712
10271
|
}));
|
|
9713
10272
|
}
|
|
9714
10273
|
async linkUserIdentity(userId, provider, providerId, profileData) {
|
|
9715
|
-
await this.db.insert(
|
|
10274
|
+
await this.db.insert(this.userIdentitiesTable).values({
|
|
9716
10275
|
userId,
|
|
9717
10276
|
provider,
|
|
9718
10277
|
providerId,
|
|
9719
10278
|
profileData: profileData || null
|
|
9720
10279
|
}).onConflictDoNothing({
|
|
9721
|
-
target: [
|
|
10280
|
+
target: [this.userIdentitiesTable.provider, this.userIdentitiesTable.providerId]
|
|
9722
10281
|
});
|
|
9723
10282
|
}
|
|
9724
10283
|
async updateUser(id, data) {
|
|
9725
|
-
const
|
|
9726
|
-
|
|
9727
|
-
|
|
9728
|
-
|
|
9729
|
-
|
|
10284
|
+
const idCol = getColumn(this.usersTable, "id");
|
|
10285
|
+
if (!idCol) return null;
|
|
10286
|
+
const payload = this.mapPayload(data);
|
|
10287
|
+
const updatedAtKey = getColumnKey(this.usersTable, "updatedAt", "updated_at") || "updatedAt";
|
|
10288
|
+
payload[updatedAtKey] = /* @__PURE__ */ new Date();
|
|
10289
|
+
const [row] = await this.db.update(this.usersTable).set(payload).where(eq(idCol, id)).returning();
|
|
10290
|
+
return row ? this.mapRowToUser(row) : null;
|
|
9730
10291
|
}
|
|
9731
10292
|
async deleteUser(id) {
|
|
9732
|
-
|
|
10293
|
+
const idCol = getColumn(this.usersTable, "id");
|
|
10294
|
+
if (!idCol) return;
|
|
10295
|
+
await this.db.delete(this.usersTable).where(eq(idCol, id));
|
|
9733
10296
|
}
|
|
9734
10297
|
async listUsers() {
|
|
9735
|
-
|
|
10298
|
+
const rows = await this.db.select().from(this.usersTable);
|
|
10299
|
+
return rows.map((row) => this.mapRowToUser(row));
|
|
9736
10300
|
}
|
|
9737
10301
|
async listUsersPaginated(options) {
|
|
9738
10302
|
const limit = options?.limit ?? 25;
|
|
@@ -9741,49 +10305,40 @@ class UserService {
|
|
|
9741
10305
|
const orderBy = options?.orderBy || "createdAt";
|
|
9742
10306
|
const orderDir = options?.orderDir || "desc";
|
|
9743
10307
|
const roleId = options?.roleId;
|
|
9744
|
-
const
|
|
9745
|
-
|
|
9746
|
-
displayName: "display_name",
|
|
9747
|
-
createdAt: "created_at",
|
|
9748
|
-
updatedAt: "updated_at",
|
|
9749
|
-
provider: "provider"
|
|
9750
|
-
};
|
|
9751
|
-
const orderColumn = columnMap[orderBy] || "created_at";
|
|
10308
|
+
const orderCol = getColumn(this.usersTable, orderBy);
|
|
10309
|
+
const orderColumn = orderCol ? orderCol.name : "created_at";
|
|
9752
10310
|
const direction = orderDir === "asc" ? sql`ASC` : sql`DESC`;
|
|
10311
|
+
const emailCol = getColumn(this.usersTable, "email");
|
|
10312
|
+
const emailColumn = emailCol ? emailCol.name : "email";
|
|
10313
|
+
const displayNameCol = getColumn(this.usersTable, "displayName", "display_name");
|
|
10314
|
+
const displayNameColumn = displayNameCol ? displayNameCol.name : "display_name";
|
|
10315
|
+
const idCol = getColumn(this.usersTable, "id");
|
|
10316
|
+
const idColumn = idCol ? idCol.name : "id";
|
|
10317
|
+
const usersTableName = this.getQualifiedUsersTableName();
|
|
10318
|
+
const rolesSchema = getTableConfig(this.userRolesTable).schema || "public";
|
|
9753
10319
|
const conditions = [];
|
|
9754
10320
|
if (roleId) {
|
|
9755
|
-
conditions.push(sql`EXISTS (SELECT 1 FROM
|
|
10321
|
+
conditions.push(sql`EXISTS (SELECT 1 FROM ${sql.raw(`"${rolesSchema}"."user_roles"`)} ur WHERE ur.user_id = ${sql.raw(usersTableName)}.${sql.raw(idColumn)} AND ur.role_id = ${roleId})`);
|
|
9756
10322
|
}
|
|
9757
10323
|
if (search) {
|
|
9758
10324
|
const pattern = `%${search}%`;
|
|
9759
|
-
conditions.push(sql`(
|
|
10325
|
+
conditions.push(sql`(${sql.raw(usersTableName)}.${sql.raw(emailColumn)} ILIKE ${pattern} OR ${sql.raw(usersTableName)}.${sql.raw(displayNameColumn)} ILIKE ${pattern})`);
|
|
9760
10326
|
}
|
|
9761
10327
|
const whereClause = conditions.length > 0 ? sql`WHERE ${sql.join(conditions, sql` AND `)}` : sql``;
|
|
9762
|
-
const orderByClause = roleId ? sql`ORDER BY ${sql.raw(orderColumn)} ${direction}` : sql`ORDER BY (SELECT count(*) FROM
|
|
10328
|
+
const orderByClause = roleId ? sql`ORDER BY ${sql.raw(usersTableName)}.${sql.raw(orderColumn)} ${direction}` : sql`ORDER BY (SELECT count(*) FROM ${sql.raw(`"${rolesSchema}"."user_roles"`)} ur WHERE ur.user_id = ${sql.raw(usersTableName)}.${sql.raw(idColumn)}) DESC, ${sql.raw(usersTableName)}.${sql.raw(orderColumn)} ${direction}`;
|
|
9763
10329
|
const countResult = await this.db.execute(sql`
|
|
9764
|
-
SELECT count(*)::int as total FROM
|
|
10330
|
+
SELECT count(*)::int as total FROM ${sql.raw(usersTableName)}
|
|
9765
10331
|
${whereClause}
|
|
9766
10332
|
`);
|
|
9767
10333
|
const total = countResult.rows[0].total;
|
|
9768
10334
|
const dataResult = await this.db.execute(sql`
|
|
9769
|
-
SELECT * FROM
|
|
10335
|
+
SELECT * FROM ${sql.raw(usersTableName)}
|
|
9770
10336
|
${whereClause}
|
|
9771
10337
|
${orderByClause}
|
|
9772
10338
|
LIMIT ${limit} OFFSET ${offset}
|
|
9773
10339
|
`);
|
|
9774
10340
|
const rows = dataResult.rows;
|
|
9775
|
-
const mappedUsers = rows.map((row) => (
|
|
9776
|
-
id: row.id,
|
|
9777
|
-
email: row.email,
|
|
9778
|
-
passwordHash: row.password_hash ?? row.passwordHash ?? null,
|
|
9779
|
-
displayName: row.display_name ?? row.displayName ?? null,
|
|
9780
|
-
photoUrl: row.photo_url ?? row.photoUrl ?? null,
|
|
9781
|
-
emailVerified: row.email_verified ?? row.emailVerified ?? false,
|
|
9782
|
-
emailVerificationToken: row.email_verification_token ?? row.emailVerificationToken ?? null,
|
|
9783
|
-
emailVerificationSentAt: row.email_verification_sent_at ?? row.emailVerificationSentAt ?? null,
|
|
9784
|
-
createdAt: row.created_at ?? row.createdAt,
|
|
9785
|
-
updatedAt: row.updated_at ?? row.updatedAt
|
|
9786
|
-
}));
|
|
10341
|
+
const mappedUsers = rows.map((row) => this.mapRowToUser(row));
|
|
9787
10342
|
return {
|
|
9788
10343
|
users: mappedUsers,
|
|
9789
10344
|
total,
|
|
@@ -9795,46 +10350,63 @@ class UserService {
|
|
|
9795
10350
|
* Update user's password hash
|
|
9796
10351
|
*/
|
|
9797
10352
|
async updatePassword(id, passwordHash) {
|
|
9798
|
-
|
|
9799
|
-
|
|
9800
|
-
|
|
9801
|
-
|
|
10353
|
+
const idCol = getColumn(this.usersTable, "id");
|
|
10354
|
+
if (!idCol) return;
|
|
10355
|
+
const passwordHashColKey = getColumnKey(this.usersTable, "passwordHash", "password_hash") || "passwordHash";
|
|
10356
|
+
const updatedAtColKey = getColumnKey(this.usersTable, "updatedAt", "updated_at") || "updatedAt";
|
|
10357
|
+
await this.db.update(this.usersTable).set({
|
|
10358
|
+
[passwordHashColKey]: passwordHash,
|
|
10359
|
+
[updatedAtColKey]: /* @__PURE__ */ new Date()
|
|
10360
|
+
}).where(eq(idCol, id));
|
|
9802
10361
|
}
|
|
9803
10362
|
/**
|
|
9804
10363
|
* Set email verification status
|
|
9805
10364
|
*/
|
|
9806
10365
|
async setEmailVerified(id, verified) {
|
|
9807
|
-
|
|
9808
|
-
|
|
9809
|
-
|
|
9810
|
-
|
|
9811
|
-
|
|
10366
|
+
const idCol = getColumn(this.usersTable, "id");
|
|
10367
|
+
if (!idCol) return;
|
|
10368
|
+
const emailVerifiedColKey = getColumnKey(this.usersTable, "emailVerified", "email_verified") || "emailVerified";
|
|
10369
|
+
const emailVerificationTokenColKey = getColumnKey(this.usersTable, "emailVerificationToken", "email_verification_token") || "emailVerificationToken";
|
|
10370
|
+
const updatedAtColKey = getColumnKey(this.usersTable, "updatedAt", "updated_at") || "updatedAt";
|
|
10371
|
+
await this.db.update(this.usersTable).set({
|
|
10372
|
+
[emailVerifiedColKey]: verified,
|
|
10373
|
+
[emailVerificationTokenColKey]: null,
|
|
10374
|
+
[updatedAtColKey]: /* @__PURE__ */ new Date()
|
|
10375
|
+
}).where(eq(idCol, id));
|
|
9812
10376
|
}
|
|
9813
10377
|
/**
|
|
9814
10378
|
* Set email verification token
|
|
9815
10379
|
*/
|
|
9816
10380
|
async setVerificationToken(id, token) {
|
|
9817
|
-
|
|
9818
|
-
|
|
9819
|
-
|
|
9820
|
-
|
|
9821
|
-
|
|
10381
|
+
const idCol = getColumn(this.usersTable, "id");
|
|
10382
|
+
if (!idCol) return;
|
|
10383
|
+
const emailVerificationTokenColKey = getColumnKey(this.usersTable, "emailVerificationToken", "email_verification_token") || "emailVerificationToken";
|
|
10384
|
+
const emailVerificationSentAtColKey = getColumnKey(this.usersTable, "emailVerificationSentAt", "email_verification_sent_at") || "emailVerificationSentAt";
|
|
10385
|
+
const updatedAtColKey = getColumnKey(this.usersTable, "updatedAt", "updated_at") || "updatedAt";
|
|
10386
|
+
await this.db.update(this.usersTable).set({
|
|
10387
|
+
[emailVerificationTokenColKey]: token,
|
|
10388
|
+
[emailVerificationSentAtColKey]: token ? /* @__PURE__ */ new Date() : null,
|
|
10389
|
+
[updatedAtColKey]: /* @__PURE__ */ new Date()
|
|
10390
|
+
}).where(eq(idCol, id));
|
|
9822
10391
|
}
|
|
9823
10392
|
/**
|
|
9824
10393
|
* Find user by email verification token
|
|
9825
10394
|
*/
|
|
9826
10395
|
async getUserByVerificationToken(token) {
|
|
9827
|
-
const
|
|
9828
|
-
|
|
10396
|
+
const tokenCol = getColumn(this.usersTable, "emailVerificationToken", "email_verification_token");
|
|
10397
|
+
if (!tokenCol) return null;
|
|
10398
|
+
const [row] = await this.db.select().from(this.usersTable).where(eq(tokenCol, token));
|
|
10399
|
+
return row ? this.mapRowToUser(row) : null;
|
|
9829
10400
|
}
|
|
9830
10401
|
/**
|
|
9831
10402
|
* Get roles for a user from database
|
|
9832
10403
|
*/
|
|
9833
10404
|
async getUserRoles(userId) {
|
|
10405
|
+
const rolesSchema = getTableConfig(this.rolesTable).schema || "public";
|
|
9834
10406
|
const result = await this.db.execute(sql`
|
|
9835
10407
|
SELECT r.id, r.name, r.is_admin, r.default_permissions, r.collection_permissions, r.config
|
|
9836
|
-
FROM
|
|
9837
|
-
INNER JOIN
|
|
10408
|
+
FROM ${sql.raw(`"${rolesSchema}"."roles"`)} r
|
|
10409
|
+
INNER JOIN ${sql.raw(`"${rolesSchema}"."user_roles"`)} ur ON r.id = ur.role_id
|
|
9838
10410
|
WHERE ur.user_id = ${userId}
|
|
9839
10411
|
`);
|
|
9840
10412
|
return result.rows.map((row) => ({
|
|
@@ -9857,10 +10429,11 @@ class UserService {
|
|
|
9857
10429
|
* Set roles for a user
|
|
9858
10430
|
*/
|
|
9859
10431
|
async setUserRoles(userId, roleIds) {
|
|
9860
|
-
|
|
10432
|
+
const rolesSchema = getTableConfig(this.userRolesTable).schema || "public";
|
|
10433
|
+
await this.db.execute(sql`DELETE FROM ${sql.raw(`"${rolesSchema}"."user_roles"`)} WHERE user_id = ${userId}`);
|
|
9861
10434
|
for (const roleId of roleIds) {
|
|
9862
10435
|
await this.db.execute(sql`
|
|
9863
|
-
INSERT INTO
|
|
10436
|
+
INSERT INTO ${sql.raw(`"${rolesSchema}"."user_roles"`)} (user_id, role_id)
|
|
9864
10437
|
VALUES (${userId}, ${roleId})
|
|
9865
10438
|
ON CONFLICT DO NOTHING
|
|
9866
10439
|
`);
|
|
@@ -9870,8 +10443,9 @@ class UserService {
|
|
|
9870
10443
|
* Assign a specific role to new user
|
|
9871
10444
|
*/
|
|
9872
10445
|
async assignDefaultRole(userId, roleId) {
|
|
10446
|
+
const rolesSchema = getTableConfig(this.userRolesTable).schema || "public";
|
|
9873
10447
|
await this.db.execute(sql`
|
|
9874
|
-
INSERT INTO
|
|
10448
|
+
INSERT INTO ${sql.raw(`"${rolesSchema}"."user_roles"`)} (user_id, role_id)
|
|
9875
10449
|
VALUES (${userId}, ${roleId})
|
|
9876
10450
|
ON CONFLICT DO NOTHING
|
|
9877
10451
|
`);
|
|
@@ -9890,13 +10464,25 @@ class UserService {
|
|
|
9890
10464
|
}
|
|
9891
10465
|
}
|
|
9892
10466
|
class RoleService {
|
|
9893
|
-
constructor(db) {
|
|
10467
|
+
constructor(db, tableOrTables) {
|
|
9894
10468
|
this.db = db;
|
|
10469
|
+
if (tableOrTables && (tableOrTables.roles || tableOrTables.users)) {
|
|
10470
|
+
this.rolesTable = tableOrTables.roles || roles;
|
|
10471
|
+
} else {
|
|
10472
|
+
this.rolesTable = tableOrTables || roles;
|
|
10473
|
+
}
|
|
10474
|
+
}
|
|
10475
|
+
rolesTable;
|
|
10476
|
+
getQualifiedRolesTableName() {
|
|
10477
|
+
const name = getTableName$1(this.rolesTable);
|
|
10478
|
+
const schema = getTableConfig(this.rolesTable).schema || "public";
|
|
10479
|
+
return `"${schema}"."${name}"`;
|
|
9895
10480
|
}
|
|
9896
10481
|
async getRoleById(id) {
|
|
10482
|
+
const tableName = this.getQualifiedRolesTableName();
|
|
9897
10483
|
const result = await this.db.execute(sql`
|
|
9898
10484
|
SELECT id, name, is_admin, default_permissions, collection_permissions, config
|
|
9899
|
-
FROM
|
|
10485
|
+
FROM ${sql.raw(tableName)}
|
|
9900
10486
|
WHERE id = ${id}
|
|
9901
10487
|
`);
|
|
9902
10488
|
if (result.rows.length === 0) return null;
|
|
@@ -9911,9 +10497,10 @@ class RoleService {
|
|
|
9911
10497
|
};
|
|
9912
10498
|
}
|
|
9913
10499
|
async listRoles() {
|
|
10500
|
+
const tableName = this.getQualifiedRolesTableName();
|
|
9914
10501
|
const result = await this.db.execute(sql`
|
|
9915
10502
|
SELECT id, name, is_admin, default_permissions, collection_permissions, config
|
|
9916
|
-
FROM
|
|
10503
|
+
FROM ${sql.raw(tableName)}
|
|
9917
10504
|
ORDER BY name
|
|
9918
10505
|
`);
|
|
9919
10506
|
return result.rows.map((row) => ({
|
|
@@ -9926,8 +10513,9 @@ class RoleService {
|
|
|
9926
10513
|
}));
|
|
9927
10514
|
}
|
|
9928
10515
|
async createRole(data) {
|
|
10516
|
+
const tableName = this.getQualifiedRolesTableName();
|
|
9929
10517
|
const result = await this.db.execute(sql`
|
|
9930
|
-
INSERT INTO
|
|
10518
|
+
INSERT INTO ${sql.raw(tableName)} (id, name, is_admin, default_permissions, collection_permissions, config)
|
|
9931
10519
|
VALUES (
|
|
9932
10520
|
${data.id},
|
|
9933
10521
|
${data.name},
|
|
@@ -9951,8 +10539,9 @@ class RoleService {
|
|
|
9951
10539
|
async updateRole(id, data) {
|
|
9952
10540
|
const existing = await this.getRoleById(id);
|
|
9953
10541
|
if (!existing) return null;
|
|
10542
|
+
const tableName = this.getQualifiedRolesTableName();
|
|
9954
10543
|
await this.db.execute(sql`
|
|
9955
|
-
UPDATE
|
|
10544
|
+
UPDATE ${sql.raw(tableName)}
|
|
9956
10545
|
SET
|
|
9957
10546
|
name = ${data.name ?? existing.name},
|
|
9958
10547
|
is_admin = ${data.isAdmin ?? existing.isAdmin},
|
|
@@ -9964,23 +10553,36 @@ class RoleService {
|
|
|
9964
10553
|
return this.getRoleById(id);
|
|
9965
10554
|
}
|
|
9966
10555
|
async deleteRole(id) {
|
|
9967
|
-
|
|
10556
|
+
const tableName = this.getQualifiedRolesTableName();
|
|
10557
|
+
await this.db.execute(sql`DELETE FROM ${sql.raw(tableName)} WHERE id = ${id}`);
|
|
9968
10558
|
}
|
|
9969
10559
|
}
|
|
9970
10560
|
class RefreshTokenService {
|
|
9971
|
-
constructor(db) {
|
|
10561
|
+
constructor(db, tableOrTables) {
|
|
9972
10562
|
this.db = db;
|
|
10563
|
+
if (tableOrTables && (tableOrTables.refreshTokens || tableOrTables.users)) {
|
|
10564
|
+
this.refreshTokensTable = tableOrTables.refreshTokens || refreshTokens;
|
|
10565
|
+
} else {
|
|
10566
|
+
this.refreshTokensTable = tableOrTables || refreshTokens;
|
|
10567
|
+
}
|
|
10568
|
+
}
|
|
10569
|
+
refreshTokensTable;
|
|
10570
|
+
getQualifiedRefreshTokensTableName() {
|
|
10571
|
+
const name = getTableName$1(this.refreshTokensTable);
|
|
10572
|
+
const schema = getTableConfig(this.refreshTokensTable).schema || "public";
|
|
10573
|
+
return `"${schema}"."${name}"`;
|
|
9973
10574
|
}
|
|
9974
10575
|
async createToken(userId, tokenHash, expiresAt, userAgent, ipAddress) {
|
|
9975
10576
|
const safeUserAgent = userAgent || "";
|
|
9976
10577
|
const safeIpAddress = ipAddress || "";
|
|
10578
|
+
const tableName = this.getQualifiedRefreshTokensTableName();
|
|
9977
10579
|
await this.db.execute(sql`
|
|
9978
|
-
DELETE FROM
|
|
10580
|
+
DELETE FROM ${sql.raw(tableName)}
|
|
9979
10581
|
WHERE user_id = ${userId}
|
|
9980
10582
|
AND user_agent = ${safeUserAgent}
|
|
9981
10583
|
AND ip_address = ${safeIpAddress}
|
|
9982
10584
|
`);
|
|
9983
|
-
await this.db.insert(
|
|
10585
|
+
await this.db.insert(this.refreshTokensTable).values({
|
|
9984
10586
|
userId,
|
|
9985
10587
|
tokenHash,
|
|
9986
10588
|
expiresAt,
|
|
@@ -9990,51 +10592,63 @@ class RefreshTokenService {
|
|
|
9990
10592
|
}
|
|
9991
10593
|
async findByHash(tokenHash) {
|
|
9992
10594
|
const [token] = await this.db.select({
|
|
9993
|
-
id:
|
|
9994
|
-
userId:
|
|
9995
|
-
tokenHash:
|
|
9996
|
-
expiresAt:
|
|
9997
|
-
createdAt:
|
|
9998
|
-
userAgent:
|
|
9999
|
-
ipAddress:
|
|
10000
|
-
}).from(
|
|
10595
|
+
id: this.refreshTokensTable.id,
|
|
10596
|
+
userId: this.refreshTokensTable.userId,
|
|
10597
|
+
tokenHash: this.refreshTokensTable.tokenHash,
|
|
10598
|
+
expiresAt: this.refreshTokensTable.expiresAt,
|
|
10599
|
+
createdAt: this.refreshTokensTable.createdAt,
|
|
10600
|
+
userAgent: this.refreshTokensTable.userAgent,
|
|
10601
|
+
ipAddress: this.refreshTokensTable.ipAddress
|
|
10602
|
+
}).from(this.refreshTokensTable).where(eq(this.refreshTokensTable.tokenHash, tokenHash));
|
|
10001
10603
|
return token || null;
|
|
10002
10604
|
}
|
|
10003
10605
|
async deleteByHash(tokenHash) {
|
|
10004
|
-
await this.db.delete(
|
|
10606
|
+
await this.db.delete(this.refreshTokensTable).where(eq(this.refreshTokensTable.tokenHash, tokenHash));
|
|
10005
10607
|
}
|
|
10006
10608
|
async deleteAllForUser(userId) {
|
|
10007
|
-
await this.db.delete(
|
|
10609
|
+
await this.db.delete(this.refreshTokensTable).where(eq(this.refreshTokensTable.userId, userId));
|
|
10008
10610
|
}
|
|
10009
10611
|
async listForUser(userId) {
|
|
10010
10612
|
const tokens = await this.db.select({
|
|
10011
|
-
id:
|
|
10012
|
-
userId:
|
|
10013
|
-
tokenHash:
|
|
10014
|
-
expiresAt:
|
|
10015
|
-
createdAt:
|
|
10016
|
-
userAgent:
|
|
10017
|
-
ipAddress:
|
|
10018
|
-
}).from(
|
|
10613
|
+
id: this.refreshTokensTable.id,
|
|
10614
|
+
userId: this.refreshTokensTable.userId,
|
|
10615
|
+
tokenHash: this.refreshTokensTable.tokenHash,
|
|
10616
|
+
expiresAt: this.refreshTokensTable.expiresAt,
|
|
10617
|
+
createdAt: this.refreshTokensTable.createdAt,
|
|
10618
|
+
userAgent: this.refreshTokensTable.userAgent,
|
|
10619
|
+
ipAddress: this.refreshTokensTable.ipAddress
|
|
10620
|
+
}).from(this.refreshTokensTable).where(eq(this.refreshTokensTable.userId, userId)).orderBy(this.refreshTokensTable.createdAt);
|
|
10019
10621
|
return tokens;
|
|
10020
10622
|
}
|
|
10021
10623
|
async deleteById(id, userId) {
|
|
10022
|
-
await this.db.delete(
|
|
10624
|
+
await this.db.delete(this.refreshTokensTable).where(sql`${this.refreshTokensTable.id} = ${id} AND ${this.refreshTokensTable.userId} = ${userId}`);
|
|
10023
10625
|
}
|
|
10024
10626
|
}
|
|
10025
10627
|
class PasswordResetTokenService {
|
|
10026
|
-
constructor(db) {
|
|
10628
|
+
constructor(db, tableOrTables) {
|
|
10027
10629
|
this.db = db;
|
|
10630
|
+
if (tableOrTables && (tableOrTables.passwordResetTokens || tableOrTables.users)) {
|
|
10631
|
+
this.passwordResetTokensTable = tableOrTables.passwordResetTokens || passwordResetTokens;
|
|
10632
|
+
} else {
|
|
10633
|
+
this.passwordResetTokensTable = tableOrTables || passwordResetTokens;
|
|
10634
|
+
}
|
|
10635
|
+
}
|
|
10636
|
+
passwordResetTokensTable;
|
|
10637
|
+
getQualifiedPasswordResetTokensTableName() {
|
|
10638
|
+
const name = getTableName$1(this.passwordResetTokensTable);
|
|
10639
|
+
const schema = getTableConfig(this.passwordResetTokensTable).schema || "public";
|
|
10640
|
+
return `"${schema}"."${name}"`;
|
|
10028
10641
|
}
|
|
10029
10642
|
/**
|
|
10030
10643
|
* Create a password reset token
|
|
10031
10644
|
*/
|
|
10032
10645
|
async createToken(userId, tokenHash, expiresAt) {
|
|
10646
|
+
const tableName = this.getQualifiedPasswordResetTokensTableName();
|
|
10033
10647
|
await this.db.execute(sql`
|
|
10034
|
-
DELETE FROM
|
|
10648
|
+
DELETE FROM ${sql.raw(tableName)}
|
|
10035
10649
|
WHERE user_id = ${userId} AND used_at IS NULL
|
|
10036
10650
|
`);
|
|
10037
|
-
await this.db.insert(
|
|
10651
|
+
await this.db.insert(this.passwordResetTokensTable).values({
|
|
10038
10652
|
userId,
|
|
10039
10653
|
tokenHash,
|
|
10040
10654
|
expiresAt
|
|
@@ -10045,13 +10659,14 @@ class PasswordResetTokenService {
|
|
|
10045
10659
|
*/
|
|
10046
10660
|
async findValidByHash(tokenHash) {
|
|
10047
10661
|
const [token] = await this.db.select({
|
|
10048
|
-
userId:
|
|
10049
|
-
expiresAt:
|
|
10050
|
-
}).from(
|
|
10662
|
+
userId: this.passwordResetTokensTable.userId,
|
|
10663
|
+
expiresAt: this.passwordResetTokensTable.expiresAt
|
|
10664
|
+
}).from(this.passwordResetTokensTable).where(eq(this.passwordResetTokensTable.tokenHash, tokenHash));
|
|
10051
10665
|
if (!token) return null;
|
|
10666
|
+
const tableName = this.getQualifiedPasswordResetTokensTableName();
|
|
10052
10667
|
const result = await this.db.execute(sql`
|
|
10053
10668
|
SELECT user_id, expires_at
|
|
10054
|
-
FROM
|
|
10669
|
+
FROM ${sql.raw(tableName)}
|
|
10055
10670
|
WHERE token_hash = ${tokenHash}
|
|
10056
10671
|
AND used_at IS NULL
|
|
10057
10672
|
AND expires_at > NOW()
|
|
@@ -10067,31 +10682,32 @@ class PasswordResetTokenService {
|
|
|
10067
10682
|
* Mark token as used
|
|
10068
10683
|
*/
|
|
10069
10684
|
async markAsUsed(tokenHash) {
|
|
10070
|
-
await this.db.update(
|
|
10685
|
+
await this.db.update(this.passwordResetTokensTable).set({
|
|
10071
10686
|
usedAt: /* @__PURE__ */ new Date()
|
|
10072
|
-
}).where(eq(
|
|
10687
|
+
}).where(eq(this.passwordResetTokensTable.tokenHash, tokenHash));
|
|
10073
10688
|
}
|
|
10074
10689
|
/**
|
|
10075
10690
|
* Delete all tokens for a user
|
|
10076
10691
|
*/
|
|
10077
10692
|
async deleteAllForUser(userId) {
|
|
10078
|
-
await this.db.delete(
|
|
10693
|
+
await this.db.delete(this.passwordResetTokensTable).where(eq(this.passwordResetTokensTable.userId, userId));
|
|
10079
10694
|
}
|
|
10080
10695
|
/**
|
|
10081
10696
|
* Clean up expired tokens
|
|
10082
10697
|
*/
|
|
10083
10698
|
async deleteExpired() {
|
|
10699
|
+
const tableName = this.getQualifiedPasswordResetTokensTableName();
|
|
10084
10700
|
await this.db.execute(sql`
|
|
10085
|
-
DELETE FROM
|
|
10701
|
+
DELETE FROM ${sql.raw(tableName)}
|
|
10086
10702
|
WHERE expires_at < NOW()
|
|
10087
10703
|
`);
|
|
10088
10704
|
}
|
|
10089
10705
|
}
|
|
10090
10706
|
class PostgresTokenRepository {
|
|
10091
|
-
constructor(db) {
|
|
10707
|
+
constructor(db, tableOrTables) {
|
|
10092
10708
|
this.db = db;
|
|
10093
|
-
this.refreshTokenService = new RefreshTokenService(db);
|
|
10094
|
-
this.passwordResetTokenService = new PasswordResetTokenService(db);
|
|
10709
|
+
this.refreshTokenService = new RefreshTokenService(db, tableOrTables);
|
|
10710
|
+
this.passwordResetTokenService = new PasswordResetTokenService(db, tableOrTables);
|
|
10095
10711
|
}
|
|
10096
10712
|
refreshTokenService;
|
|
10097
10713
|
passwordResetTokenService;
|
|
@@ -10132,11 +10748,11 @@ class PostgresTokenRepository {
|
|
|
10132
10748
|
}
|
|
10133
10749
|
}
|
|
10134
10750
|
class PostgresAuthRepository {
|
|
10135
|
-
constructor(db) {
|
|
10751
|
+
constructor(db, tableOrTables) {
|
|
10136
10752
|
this.db = db;
|
|
10137
|
-
this.userService = new UserService(db);
|
|
10138
|
-
this.roleService = new RoleService(db);
|
|
10139
|
-
this.tokenRepository = new PostgresTokenRepository(db);
|
|
10753
|
+
this.userService = new UserService(db, tableOrTables);
|
|
10754
|
+
this.roleService = new RoleService(db, tableOrTables);
|
|
10755
|
+
this.tokenRepository = new PostgresTokenRepository(db, tableOrTables);
|
|
10140
10756
|
}
|
|
10141
10757
|
userService;
|
|
10142
10758
|
roleService;
|
|
@@ -10197,8 +10813,7 @@ class PostgresAuthRepository {
|
|
|
10197
10813
|
await this.userService.assignDefaultRole(userId, roleId);
|
|
10198
10814
|
}
|
|
10199
10815
|
async getUserWithRoles(userId) {
|
|
10200
|
-
|
|
10201
|
-
return result;
|
|
10816
|
+
return this.userService.getUserWithRoles(userId);
|
|
10202
10817
|
}
|
|
10203
10818
|
// Role operations (delegate to RoleService)
|
|
10204
10819
|
async getRoleById(id) {
|
|
@@ -10386,6 +11001,24 @@ class HistoryService {
|
|
|
10386
11001
|
return result.rowCount ?? 0;
|
|
10387
11002
|
}
|
|
10388
11003
|
}
|
|
11004
|
+
function deepEqual(a, b) {
|
|
11005
|
+
if (a === b) return true;
|
|
11006
|
+
if (a == null || b == null) return false;
|
|
11007
|
+
if (a instanceof Date && b instanceof Date) return a.getTime() === b.getTime();
|
|
11008
|
+
if (Array.isArray(a) && Array.isArray(b)) {
|
|
11009
|
+
if (a.length !== b.length) return false;
|
|
11010
|
+
return a.every((v, i) => deepEqual(v, b[i]));
|
|
11011
|
+
}
|
|
11012
|
+
if (typeof a === "object" && typeof b === "object") {
|
|
11013
|
+
const aObj = a;
|
|
11014
|
+
const bObj = b;
|
|
11015
|
+
const aKeys = Object.keys(aObj);
|
|
11016
|
+
const bKeys = Object.keys(bObj);
|
|
11017
|
+
if (aKeys.length !== bKeys.length) return false;
|
|
11018
|
+
return aKeys.every((k) => deepEqual(aObj[k], bObj[k]));
|
|
11019
|
+
}
|
|
11020
|
+
return false;
|
|
11021
|
+
}
|
|
10389
11022
|
function findChangedFields(oldValues, newValues) {
|
|
10390
11023
|
const changed = [];
|
|
10391
11024
|
const allKeys = /* @__PURE__ */ new Set([...Object.keys(oldValues), ...Object.keys(newValues)]);
|
|
@@ -10395,7 +11028,7 @@ function findChangedFields(oldValues, newValues) {
|
|
|
10395
11028
|
if (key.startsWith("__")) continue;
|
|
10396
11029
|
if (oldVal !== newVal) {
|
|
10397
11030
|
if (typeof oldVal === "object" && oldVal !== null && typeof newVal === "object" && newVal !== null) {
|
|
10398
|
-
if (
|
|
11031
|
+
if (!deepEqual(oldVal, newVal)) {
|
|
10399
11032
|
changed.push(key);
|
|
10400
11033
|
}
|
|
10401
11034
|
} else {
|
|
@@ -10513,14 +11146,32 @@ function createPostgresBootstrapper(pgConfig) {
|
|
|
10513
11146
|
if (!authConfig) return void 0;
|
|
10514
11147
|
const internals = driverResult.internals;
|
|
10515
11148
|
const db = internals.db;
|
|
10516
|
-
|
|
11149
|
+
const registry = internals.registry;
|
|
11150
|
+
await ensureAuthTablesExist(db, registry);
|
|
10517
11151
|
let emailService;
|
|
10518
11152
|
if (authConfig.email) {
|
|
10519
11153
|
emailService = createEmailService(authConfig.email);
|
|
10520
11154
|
}
|
|
10521
|
-
const
|
|
10522
|
-
const
|
|
10523
|
-
|
|
11155
|
+
const customUsersTable = registry?.getTable("users");
|
|
11156
|
+
const customRolesTable = registry?.getTable("roles");
|
|
11157
|
+
let usersSchemaName = "rebase";
|
|
11158
|
+
let rolesSchemaName = "rebase";
|
|
11159
|
+
if (customUsersTable) {
|
|
11160
|
+
usersSchemaName = getTableConfig(customUsersTable).schema || "public";
|
|
11161
|
+
}
|
|
11162
|
+
if (customRolesTable) {
|
|
11163
|
+
rolesSchemaName = getTableConfig(customRolesTable).schema || "public";
|
|
11164
|
+
}
|
|
11165
|
+
const authTables = createAuthSchema(rolesSchemaName, usersSchemaName);
|
|
11166
|
+
if (customUsersTable) {
|
|
11167
|
+
authTables.users = customUsersTable;
|
|
11168
|
+
}
|
|
11169
|
+
if (customRolesTable) {
|
|
11170
|
+
authTables.roles = customRolesTable;
|
|
11171
|
+
}
|
|
11172
|
+
const userService = new UserService(db, authTables);
|
|
11173
|
+
const roleService = new RoleService(db, authTables);
|
|
11174
|
+
const authRepository = new PostgresAuthRepository(db, authTables);
|
|
10524
11175
|
return {
|
|
10525
11176
|
userService,
|
|
10526
11177
|
roleService,
|
|
@@ -10552,11 +11203,54 @@ function createPostgresBootstrapper(pgConfig) {
|
|
|
10552
11203
|
},
|
|
10553
11204
|
mountRoutes(app, basePath, driverResult) {
|
|
10554
11205
|
},
|
|
10555
|
-
async initializeWebsockets(server, realtimeService, driver, config) {
|
|
11206
|
+
async initializeWebsockets(server, realtimeService, driver, config, adapter) {
|
|
10556
11207
|
const {
|
|
10557
11208
|
createPostgresWebSocket: createPostgresWebSocket2
|
|
10558
11209
|
} = await Promise.resolve().then(() => websocket);
|
|
10559
|
-
createPostgresWebSocket2(server, realtimeService, driver, config);
|
|
11210
|
+
createPostgresWebSocket2(server, realtimeService, driver, config, adapter);
|
|
11211
|
+
}
|
|
11212
|
+
};
|
|
11213
|
+
}
|
|
11214
|
+
function createPostgresAdapter(pgConfig) {
|
|
11215
|
+
const bootstrapper = createPostgresBootstrapper(pgConfig);
|
|
11216
|
+
return {
|
|
11217
|
+
type: bootstrapper.type,
|
|
11218
|
+
async initializeDriver(config) {
|
|
11219
|
+
return bootstrapper.initializeDriver(config);
|
|
11220
|
+
},
|
|
11221
|
+
async initializeRealtime(driverResult) {
|
|
11222
|
+
if (bootstrapper.initializeRealtime) {
|
|
11223
|
+
return bootstrapper.initializeRealtime({}, driverResult);
|
|
11224
|
+
}
|
|
11225
|
+
return void 0;
|
|
11226
|
+
},
|
|
11227
|
+
async initializeAuth(config, driverResult) {
|
|
11228
|
+
if (bootstrapper.initializeAuth) {
|
|
11229
|
+
return bootstrapper.initializeAuth(config, driverResult);
|
|
11230
|
+
}
|
|
11231
|
+
return void 0;
|
|
11232
|
+
},
|
|
11233
|
+
async initializeHistory(config, driverResult) {
|
|
11234
|
+
if (bootstrapper.initializeHistory) {
|
|
11235
|
+
return bootstrapper.initializeHistory(config, driverResult);
|
|
11236
|
+
}
|
|
11237
|
+
return void 0;
|
|
11238
|
+
},
|
|
11239
|
+
initializeWebsockets(server, realtimeService, driver, config) {
|
|
11240
|
+
if (bootstrapper.initializeWebsockets) {
|
|
11241
|
+
return bootstrapper.initializeWebsockets(server, realtimeService, driver, config);
|
|
11242
|
+
}
|
|
11243
|
+
},
|
|
11244
|
+
getAdmin(driverResult) {
|
|
11245
|
+
if (bootstrapper.getAdmin) {
|
|
11246
|
+
return bootstrapper.getAdmin(driverResult);
|
|
11247
|
+
}
|
|
11248
|
+
return void 0;
|
|
11249
|
+
},
|
|
11250
|
+
mountRoutes(app, basePath, driverResult) {
|
|
11251
|
+
if (bootstrapper.mountRoutes) {
|
|
11252
|
+
bootstrapper.mountRoutes(app, basePath, driverResult);
|
|
11253
|
+
}
|
|
10560
11254
|
}
|
|
10561
11255
|
};
|
|
10562
11256
|
}
|
|
@@ -10571,6 +11265,8 @@ export {
|
|
|
10571
11265
|
PostgresRealtimeProvider,
|
|
10572
11266
|
RealtimeService,
|
|
10573
11267
|
appConfig,
|
|
11268
|
+
createAuthSchema,
|
|
11269
|
+
createPostgresAdapter,
|
|
10574
11270
|
createPostgresBootstrapper,
|
|
10575
11271
|
createPostgresDatabaseConnection,
|
|
10576
11272
|
createPostgresWebSocket,
|
|
@@ -10587,6 +11283,7 @@ export {
|
|
|
10587
11283
|
userRoles,
|
|
10588
11284
|
userRolesRelations,
|
|
10589
11285
|
users,
|
|
10590
|
-
usersRelations
|
|
11286
|
+
usersRelations,
|
|
11287
|
+
usersSchema
|
|
10591
11288
|
};
|
|
10592
11289
|
//# sourceMappingURL=index.es.js.map
|