@rebasepro/server-postgresql 0.3.0 → 0.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +69 -89
- package/dist/common/src/collections/default-collections.d.ts +5 -8
- package/dist/common/src/data/query_builder.d.ts +6 -2
- package/dist/common/src/util/permissions.d.ts +14 -6
- package/dist/index.es.js +379 -611
- package/dist/index.es.js.map +1 -1
- package/dist/index.umd.js +375 -607
- package/dist/index.umd.js.map +1 -1
- package/dist/server-postgresql/src/PostgresBackendDriver.d.ts +2 -0
- package/dist/server-postgresql/src/auth/ensure-tables.d.ts +7 -4
- package/dist/server-postgresql/src/auth/services.d.ts +17 -42
- package/dist/server-postgresql/src/data-transformer.d.ts +0 -3
- package/dist/server-postgresql/src/databasePoolManager.d.ts +1 -1
- package/dist/server-postgresql/src/schema/auth-schema.d.ts +87 -340
- package/dist/server-postgresql/src/services/EntityFetchService.d.ts +2 -1
- package/dist/server-postgresql/src/services/EntityPersistService.d.ts +4 -0
- package/dist/server-postgresql/src/services/entityService.d.ts +4 -0
- package/dist/server-postgresql/src/types.d.ts +3 -0
- package/dist/server-postgresql/src/utils/drizzle-conditions.d.ts +5 -1
- package/dist/server-postgresql/src/websocket.d.ts +8 -3
- package/dist/types/src/controllers/auth.d.ts +2 -2
- package/dist/types/src/controllers/client.d.ts +25 -40
- package/dist/types/src/controllers/data.d.ts +21 -3
- package/dist/types/src/controllers/data_driver.d.ts +5 -0
- package/dist/types/src/controllers/email.d.ts +2 -0
- package/dist/types/src/types/auth_adapter.d.ts +3 -56
- package/dist/types/src/types/backend.d.ts +38 -3
- package/dist/types/src/types/backend_hooks.d.ts +2 -17
- package/dist/types/src/types/collections.d.ts +30 -6
- package/dist/types/src/types/entity_views.d.ts +19 -28
- package/dist/types/src/types/properties.d.ts +9 -15
- package/dist/types/src/types/user_management_delegate.d.ts +16 -53
- package/dist/types/src/users/index.d.ts +0 -1
- package/dist/types/src/users/user.d.ts +0 -1
- package/package.json +6 -6
- package/src/PostgresBackendDriver.ts +10 -0
- package/src/PostgresBootstrapper.ts +27 -22
- package/src/auth/ensure-tables.ts +82 -129
- package/src/auth/services.ts +99 -197
- package/src/cli.ts +50 -23
- package/src/data-transformer.ts +57 -95
- package/src/databasePoolManager.ts +2 -1
- package/src/schema/auth-schema.ts +13 -69
- package/src/schema/doctor.ts +44 -3
- package/src/schema/generate-drizzle-schema-logic.ts +33 -3
- package/src/schema/generate-drizzle-schema.ts +2 -6
- package/src/schema/introspect-db-logic.ts +7 -0
- package/src/services/EntityFetchService.ts +13 -1
- package/src/services/EntityPersistService.ts +38 -12
- package/src/services/entityService.ts +7 -0
- package/src/types.ts +4 -0
- package/src/utils/drizzle-conditions.ts +40 -5
- package/src/websocket.ts +38 -25
- package/test/auth-services.test.ts +7 -150
- package/test/doctor.test.ts +6 -2
- package/test/relation-pipeline-gaps.test.ts +315 -0
- package/dist/server-postgresql/src/schema/default-collections.d.ts +0 -2
- package/dist/types/src/users/roles.d.ts +0 -14
- package/drizzle.test.config.ts +0 -10
- package/src/schema/default-collections.ts +0 -69
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
|
-
import {
|
|
4
|
-
import { PgVarchar, PgText, PgChar, pgSchema, pgTable, timestamp, jsonb, boolean, varchar, uuid,
|
|
3
|
+
import { or, and, sql, inArray, eq, ilike, asc, desc, gt, lt, getTableName as getTableName$1, count, relations, isTable } from "drizzle-orm";
|
|
4
|
+
import { PgVarchar, PgText, PgChar, pgSchema, pgTable, timestamp, jsonb, text, boolean, varchar, uuid, 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";
|
|
@@ -2711,114 +2711,6 @@ class CollectionRegistry {
|
|
|
2711
2711
|
};
|
|
2712
2712
|
}
|
|
2713
2713
|
}
|
|
2714
|
-
const defaultUsersCollection = {
|
|
2715
|
-
name: "Users",
|
|
2716
|
-
singularName: "User",
|
|
2717
|
-
slug: "users",
|
|
2718
|
-
table: "users",
|
|
2719
|
-
schema: "rebase",
|
|
2720
|
-
icon: "Users",
|
|
2721
|
-
group: "Settings",
|
|
2722
|
-
properties: {
|
|
2723
|
-
id: {
|
|
2724
|
-
name: "ID",
|
|
2725
|
-
type: "string",
|
|
2726
|
-
isId: "uuid"
|
|
2727
|
-
},
|
|
2728
|
-
email: {
|
|
2729
|
-
name: "Email",
|
|
2730
|
-
type: "string",
|
|
2731
|
-
validation: {
|
|
2732
|
-
required: true,
|
|
2733
|
-
unique: true
|
|
2734
|
-
}
|
|
2735
|
-
},
|
|
2736
|
-
password_hash: {
|
|
2737
|
-
name: "Password Hash",
|
|
2738
|
-
type: "string",
|
|
2739
|
-
ui: {
|
|
2740
|
-
hideFromCollection: true
|
|
2741
|
-
}
|
|
2742
|
-
},
|
|
2743
|
-
display_name: {
|
|
2744
|
-
name: "Display Name",
|
|
2745
|
-
type: "string"
|
|
2746
|
-
},
|
|
2747
|
-
photo_url: {
|
|
2748
|
-
name: "Photo URL",
|
|
2749
|
-
type: "string"
|
|
2750
|
-
},
|
|
2751
|
-
email_verified: {
|
|
2752
|
-
name: "Email Verified",
|
|
2753
|
-
type: "boolean",
|
|
2754
|
-
defaultValue: false
|
|
2755
|
-
},
|
|
2756
|
-
email_verification_token: {
|
|
2757
|
-
name: "Email Verification Token",
|
|
2758
|
-
type: "string",
|
|
2759
|
-
ui: {
|
|
2760
|
-
hideFromCollection: true
|
|
2761
|
-
}
|
|
2762
|
-
},
|
|
2763
|
-
email_verification_sent_at: {
|
|
2764
|
-
name: "Email Verification Sent At",
|
|
2765
|
-
type: "date",
|
|
2766
|
-
ui: {
|
|
2767
|
-
hideFromCollection: true
|
|
2768
|
-
}
|
|
2769
|
-
},
|
|
2770
|
-
metadata: {
|
|
2771
|
-
name: "Metadata",
|
|
2772
|
-
type: "map",
|
|
2773
|
-
defaultValue: {},
|
|
2774
|
-
ui: {
|
|
2775
|
-
hideFromCollection: true
|
|
2776
|
-
}
|
|
2777
|
-
},
|
|
2778
|
-
created_at: {
|
|
2779
|
-
name: "Created At",
|
|
2780
|
-
type: "date",
|
|
2781
|
-
autoValue: "on_create",
|
|
2782
|
-
ui: {
|
|
2783
|
-
readOnly: true,
|
|
2784
|
-
hideFromCollection: true
|
|
2785
|
-
}
|
|
2786
|
-
},
|
|
2787
|
-
updated_at: {
|
|
2788
|
-
name: "Updated At",
|
|
2789
|
-
type: "date",
|
|
2790
|
-
autoValue: "on_update",
|
|
2791
|
-
ui: {
|
|
2792
|
-
readOnly: true,
|
|
2793
|
-
hideFromCollection: true
|
|
2794
|
-
}
|
|
2795
|
-
}
|
|
2796
|
-
}
|
|
2797
|
-
};
|
|
2798
|
-
function mapOperator(op) {
|
|
2799
|
-
switch (op) {
|
|
2800
|
-
case "==":
|
|
2801
|
-
return "eq";
|
|
2802
|
-
case "!=":
|
|
2803
|
-
return "neq";
|
|
2804
|
-
case ">":
|
|
2805
|
-
return "gt";
|
|
2806
|
-
case ">=":
|
|
2807
|
-
return "gte";
|
|
2808
|
-
case "<":
|
|
2809
|
-
return "lt";
|
|
2810
|
-
case "<=":
|
|
2811
|
-
return "lte";
|
|
2812
|
-
case "array-contains":
|
|
2813
|
-
return "cs";
|
|
2814
|
-
case "array-contains-any":
|
|
2815
|
-
return "csa";
|
|
2816
|
-
case "not-in":
|
|
2817
|
-
return "nin";
|
|
2818
|
-
default:
|
|
2819
|
-
return op;
|
|
2820
|
-
}
|
|
2821
|
-
}
|
|
2822
2714
|
class QueryBuilder {
|
|
2823
2715
|
constructor(collection) {
|
|
2824
2716
|
this.collection = collection;
|
|
@@ -2826,23 +2718,30 @@ class QueryBuilder {
|
|
|
2826
2718
|
params = {
|
|
2827
2719
|
where: {}
|
|
2828
2720
|
};
|
|
2829
|
-
|
|
2830
|
-
|
|
2831
|
-
|
|
2832
|
-
|
|
2833
|
-
|
|
2834
|
-
where(column, operator, value) {
|
|
2721
|
+
where(columnOrCondition, operator, value) {
|
|
2722
|
+
if (typeof columnOrCondition === "object" && columnOrCondition !== null && "type" in columnOrCondition) {
|
|
2723
|
+
this.params.logical = columnOrCondition;
|
|
2724
|
+
return this;
|
|
2725
|
+
}
|
|
2835
2726
|
if (!this.params.where) {
|
|
2836
2727
|
this.params.where = {};
|
|
2837
2728
|
}
|
|
2838
|
-
const
|
|
2839
|
-
|
|
2840
|
-
|
|
2841
|
-
|
|
2842
|
-
|
|
2843
|
-
|
|
2729
|
+
const column = columnOrCondition;
|
|
2730
|
+
const condition = [operator, value];
|
|
2731
|
+
const existing = this.params.where[column];
|
|
2732
|
+
if (existing === void 0) {
|
|
2733
|
+
this.params.where[column] = condition;
|
|
2734
|
+
} else if (Array.isArray(existing) && existing.length > 0 && Array.isArray(existing[0])) {
|
|
2735
|
+
this.params.where[column].push(condition);
|
|
2736
|
+
} else {
|
|
2737
|
+
let firstCondition;
|
|
2738
|
+
if (Array.isArray(existing) && existing.length === 2 && typeof existing[0] === "string") {
|
|
2739
|
+
firstCondition = existing;
|
|
2740
|
+
} else {
|
|
2741
|
+
firstCondition = ["==", existing];
|
|
2742
|
+
}
|
|
2743
|
+
this.params.where[column] = [firstCondition, condition];
|
|
2844
2744
|
}
|
|
2845
|
-
this.params.where[column] = mappedOp === "eq" ? String(formattedValue) : `${mappedOp}.${formattedValue}`;
|
|
2846
2745
|
return this;
|
|
2847
2746
|
}
|
|
2848
2747
|
/**
|
|
@@ -2944,10 +2843,13 @@ function convertWhereToFilter(where) {
|
|
|
2944
2843
|
filter[field] = ["==", rawValue];
|
|
2945
2844
|
continue;
|
|
2946
2845
|
}
|
|
2947
|
-
if (Array.isArray(rawValue)
|
|
2948
|
-
const [
|
|
2949
|
-
const
|
|
2950
|
-
|
|
2846
|
+
if (Array.isArray(rawValue)) {
|
|
2847
|
+
const conditions = Array.isArray(rawValue[0]) ? rawValue : [rawValue];
|
|
2848
|
+
const mappedConditions = conditions.map(([rawOp, val]) => {
|
|
2849
|
+
const mappedOp = operatorMap[rawOp] ?? "==";
|
|
2850
|
+
return [mappedOp, val];
|
|
2851
|
+
});
|
|
2852
|
+
filter[field] = Array.isArray(rawValue[0]) ? mappedConditions : mappedConditions[0];
|
|
2951
2853
|
continue;
|
|
2952
2854
|
}
|
|
2953
2855
|
if (typeof rawValue === "string") {
|
|
@@ -3041,6 +2943,9 @@ function createDriverAccessor(driver, slug) {
|
|
|
3041
2943
|
}
|
|
3042
2944
|
});
|
|
3043
2945
|
},
|
|
2946
|
+
deleteAll: driver.deleteAll ? async () => {
|
|
2947
|
+
return driver.deleteAll(slug);
|
|
2948
|
+
} : void 0,
|
|
3044
2949
|
count: driver.countEntities ? async (params) => {
|
|
3045
2950
|
return driver.countEntities({
|
|
3046
2951
|
path: slug,
|
|
@@ -3082,8 +2987,12 @@ function createDriverAccessor(driver, slug) {
|
|
|
3082
2987
|
});
|
|
3083
2988
|
} : void 0,
|
|
3084
2989
|
// Fluent Query Builder
|
|
3085
|
-
where(
|
|
3086
|
-
|
|
2990
|
+
where(columnOrCondition, operator, value) {
|
|
2991
|
+
const builder = new QueryBuilder(accessor);
|
|
2992
|
+
if (typeof columnOrCondition === "object") {
|
|
2993
|
+
return builder.where(columnOrCondition);
|
|
2994
|
+
}
|
|
2995
|
+
return builder.where(columnOrCondition, operator, value);
|
|
3087
2996
|
},
|
|
3088
2997
|
orderBy(column, ascending) {
|
|
3089
2998
|
return new QueryBuilder(accessor).orderBy(column, ascending);
|
|
@@ -3134,7 +3043,6 @@ class DrizzleConditionBuilder {
|
|
|
3134
3043
|
const conditions = [];
|
|
3135
3044
|
for (const [field, filterParam] of Object.entries(filter)) {
|
|
3136
3045
|
if (!filterParam) continue;
|
|
3137
|
-
const [op, value] = filterParam;
|
|
3138
3046
|
let fieldColumn = table[field];
|
|
3139
3047
|
if (!fieldColumn) {
|
|
3140
3048
|
const relationKey = `${field}_id`;
|
|
@@ -3146,13 +3054,39 @@ class DrizzleConditionBuilder {
|
|
|
3146
3054
|
console.warn(`Filtering by field '${field}', but it does not exist in table for collection '${collectionPath}'`);
|
|
3147
3055
|
continue;
|
|
3148
3056
|
}
|
|
3149
|
-
const
|
|
3150
|
-
|
|
3151
|
-
|
|
3057
|
+
const paramsList = Array.isArray(filterParam) && filterParam.length > 0 && Array.isArray(filterParam[0]) ? filterParam : [filterParam];
|
|
3058
|
+
for (const [op, value] of paramsList) {
|
|
3059
|
+
const condition = this.buildSingleFilterCondition(fieldColumn, op, value);
|
|
3060
|
+
if (condition) {
|
|
3061
|
+
conditions.push(condition);
|
|
3062
|
+
}
|
|
3152
3063
|
}
|
|
3153
3064
|
}
|
|
3154
3065
|
return conditions;
|
|
3155
3066
|
}
|
|
3067
|
+
/**
|
|
3068
|
+
* Build logical conditions recursively from LogicalCondition or FilterCondition
|
|
3069
|
+
*/
|
|
3070
|
+
static buildLogicalConditions(cond, table, collectionPath) {
|
|
3071
|
+
if ("type" in cond) {
|
|
3072
|
+
const subSQLs = cond.conditions.map((c) => this.buildLogicalConditions(c, table, collectionPath)).filter((sql2) => sql2 !== null);
|
|
3073
|
+
if (subSQLs.length === 0) return null;
|
|
3074
|
+
return (cond.type === "or" ? or(...subSQLs) : and(...subSQLs)) ?? null;
|
|
3075
|
+
} else {
|
|
3076
|
+
let fieldColumn = table[cond.column];
|
|
3077
|
+
if (!fieldColumn) {
|
|
3078
|
+
const relationKey = `${cond.column}_id`;
|
|
3079
|
+
if (relationKey in table) {
|
|
3080
|
+
fieldColumn = table[relationKey];
|
|
3081
|
+
}
|
|
3082
|
+
}
|
|
3083
|
+
if (!fieldColumn) {
|
|
3084
|
+
console.warn(`Filtering by field '${cond.column}', but it does not exist in table for collection '${collectionPath}'`);
|
|
3085
|
+
return null;
|
|
3086
|
+
}
|
|
3087
|
+
return this.buildSingleFilterCondition(fieldColumn, cond.operator, cond.value);
|
|
3088
|
+
}
|
|
3089
|
+
}
|
|
3156
3090
|
/**
|
|
3157
3091
|
* Build a single filter condition for a specific operator and value
|
|
3158
3092
|
*/
|
|
@@ -4030,39 +3964,18 @@ function serializePropertyToServer(value, property) {
|
|
|
4030
3964
|
}
|
|
4031
3965
|
return value;
|
|
4032
3966
|
}
|
|
4033
|
-
case "binary":
|
|
4034
|
-
|
|
4035
|
-
|
|
4036
|
-
|
|
4037
|
-
if (base64Data) {
|
|
4038
|
-
return Buffer.from(base64Data, "base64");
|
|
4039
|
-
}
|
|
4040
|
-
}
|
|
4041
|
-
}
|
|
4042
|
-
if (Buffer.isBuffer(value)) {
|
|
4043
|
-
return value;
|
|
4044
|
-
}
|
|
3967
|
+
case "binary": {
|
|
3968
|
+
const decoded = tryDecodeBase64DataUrl(value);
|
|
3969
|
+
if (decoded) return decoded;
|
|
3970
|
+
if (Buffer.isBuffer(value)) return value;
|
|
4045
3971
|
return value;
|
|
3972
|
+
}
|
|
4046
3973
|
case "string":
|
|
4047
|
-
|
|
4048
|
-
|
|
4049
|
-
|
|
4050
|
-
if (base64Data) {
|
|
4051
|
-
return Buffer.from(base64Data, "base64");
|
|
4052
|
-
}
|
|
4053
|
-
}
|
|
4054
|
-
}
|
|
4055
|
-
return value;
|
|
4056
|
-
default:
|
|
4057
|
-
if (typeof value === "string") {
|
|
4058
|
-
if (value.startsWith("data:application/octet-stream;base64,")) {
|
|
4059
|
-
const base64Data = value.split(",")[1];
|
|
4060
|
-
if (base64Data) {
|
|
4061
|
-
return Buffer.from(base64Data, "base64");
|
|
4062
|
-
}
|
|
4063
|
-
}
|
|
4064
|
-
}
|
|
3974
|
+
default: {
|
|
3975
|
+
const decoded = tryDecodeBase64DataUrl(value);
|
|
3976
|
+
if (decoded) return decoded;
|
|
4065
3977
|
return value;
|
|
3978
|
+
}
|
|
4066
3979
|
}
|
|
4067
3980
|
}
|
|
4068
3981
|
async function parseDataFromServer(data, collection, db, registry) {
|
|
@@ -4182,21 +4095,36 @@ async function parseDataFromServer(data, collection, db, registry) {
|
|
|
4182
4095
|
}
|
|
4183
4096
|
return result;
|
|
4184
4097
|
}
|
|
4185
|
-
function
|
|
4186
|
-
if (
|
|
4187
|
-
|
|
4098
|
+
function tryDecodeBase64DataUrl(value) {
|
|
4099
|
+
if (typeof value !== "string") return null;
|
|
4100
|
+
if (!value.startsWith("data:application/octet-stream;base64,")) return null;
|
|
4101
|
+
const base64Data = value.split(",")[1];
|
|
4102
|
+
return base64Data ? Buffer.from(base64Data, "base64") : null;
|
|
4103
|
+
}
|
|
4104
|
+
function tryResolveBuffer(value) {
|
|
4105
|
+
if (Buffer.isBuffer(value)) return value;
|
|
4106
|
+
if (typeof value === "object" && value !== null) {
|
|
4107
|
+
const rawVal = value;
|
|
4108
|
+
if (rawVal.type === "Buffer" && Array.isArray(rawVal.data)) {
|
|
4109
|
+
return Buffer.from(rawVal.data);
|
|
4110
|
+
}
|
|
4111
|
+
}
|
|
4112
|
+
return null;
|
|
4113
|
+
}
|
|
4114
|
+
function bufferToStringOrBase64(buf) {
|
|
4115
|
+
for (let i = 0; i < buf.length; i++) {
|
|
4116
|
+
const b = buf[i];
|
|
4117
|
+
if ((b < 32 || b > 126) && b !== 9 && b !== 10 && b !== 13) {
|
|
4118
|
+
return `data:application/octet-stream;base64,${buf.toString("base64")}`;
|
|
4119
|
+
}
|
|
4188
4120
|
}
|
|
4121
|
+
return buf.toString("utf8");
|
|
4122
|
+
}
|
|
4123
|
+
function parsePropertyFromServer(value, property, collection, propertyKey) {
|
|
4124
|
+
if (value === null || value === void 0) return value;
|
|
4189
4125
|
switch (property.type) {
|
|
4190
4126
|
case "binary": {
|
|
4191
|
-
|
|
4192
|
-
if (Buffer.isBuffer(value)) {
|
|
4193
|
-
buf = value;
|
|
4194
|
-
} else if (typeof value === "object" && value !== null) {
|
|
4195
|
-
const rawVal = value;
|
|
4196
|
-
if (rawVal.type === "Buffer" && Array.isArray(rawVal.data)) {
|
|
4197
|
-
buf = Buffer.from(rawVal.data);
|
|
4198
|
-
}
|
|
4199
|
-
}
|
|
4127
|
+
const buf = tryResolveBuffer(value);
|
|
4200
4128
|
if (buf) {
|
|
4201
4129
|
return `data:application/octet-stream;base64,${buf.toString("base64")}`;
|
|
4202
4130
|
}
|
|
@@ -4204,28 +4132,9 @@ function parsePropertyFromServer(value, property, collection, propertyKey) {
|
|
|
4204
4132
|
}
|
|
4205
4133
|
case "string": {
|
|
4206
4134
|
if (typeof value === "string") return value;
|
|
4207
|
-
|
|
4208
|
-
|
|
4209
|
-
|
|
4210
|
-
isBuffer = true;
|
|
4211
|
-
buf = value;
|
|
4212
|
-
} else if (typeof value === "object" && value !== null) {
|
|
4213
|
-
const rawVal = value;
|
|
4214
|
-
if (rawVal.type === "Buffer" && Array.isArray(rawVal.data)) {
|
|
4215
|
-
isBuffer = true;
|
|
4216
|
-
buf = Buffer.from(rawVal.data);
|
|
4217
|
-
}
|
|
4218
|
-
}
|
|
4219
|
-
if (isBuffer && buf) {
|
|
4220
|
-
let isPrintable = true;
|
|
4221
|
-
for (let i = 0; i < buf.length; i++) {
|
|
4222
|
-
const b = buf[i];
|
|
4223
|
-
if ((b < 32 || b > 126) && b !== 9 && b !== 10 && b !== 13) {
|
|
4224
|
-
isPrintable = false;
|
|
4225
|
-
break;
|
|
4226
|
-
}
|
|
4227
|
-
}
|
|
4228
|
-
return isPrintable ? buf.toString("utf8") : `data:application/octet-stream;base64,${buf.toString("base64")}`;
|
|
4135
|
+
const buf = tryResolveBuffer(value);
|
|
4136
|
+
if (buf) {
|
|
4137
|
+
return bufferToStringOrBase64(buf);
|
|
4229
4138
|
}
|
|
4230
4139
|
if (typeof value === "object" && value !== null) {
|
|
4231
4140
|
try {
|
|
@@ -4339,28 +4248,9 @@ function parsePropertyFromServer(value, property, collection, propertyKey) {
|
|
|
4339
4248
|
return null;
|
|
4340
4249
|
}
|
|
4341
4250
|
default: {
|
|
4342
|
-
|
|
4343
|
-
|
|
4344
|
-
|
|
4345
|
-
isBuffer = true;
|
|
4346
|
-
buf = value;
|
|
4347
|
-
} else if (typeof value === "object" && value !== null) {
|
|
4348
|
-
const rawVal = value;
|
|
4349
|
-
if (rawVal.type === "Buffer" && Array.isArray(rawVal.data)) {
|
|
4350
|
-
isBuffer = true;
|
|
4351
|
-
buf = Buffer.from(rawVal.data);
|
|
4352
|
-
}
|
|
4353
|
-
}
|
|
4354
|
-
if (isBuffer && buf) {
|
|
4355
|
-
let isPrintable = true;
|
|
4356
|
-
for (let i = 0; i < buf.length; i++) {
|
|
4357
|
-
const b = buf[i];
|
|
4358
|
-
if ((b < 32 || b > 126) && b !== 9 && b !== 10 && b !== 13) {
|
|
4359
|
-
isPrintable = false;
|
|
4360
|
-
break;
|
|
4361
|
-
}
|
|
4362
|
-
}
|
|
4363
|
-
return isPrintable ? buf.toString("utf8") : `data:application/octet-stream;base64,${buf.toString("base64")}`;
|
|
4251
|
+
const buf = tryResolveBuffer(value);
|
|
4252
|
+
if (buf) {
|
|
4253
|
+
return bufferToStringOrBase64(buf);
|
|
4364
4254
|
}
|
|
4365
4255
|
return value;
|
|
4366
4256
|
}
|
|
@@ -5549,6 +5439,10 @@ class EntityFetchService {
|
|
|
5549
5439
|
const filterConditions = this.buildFilterConditions(options.filter, table, collectionPath);
|
|
5550
5440
|
if (filterConditions.length > 0) allConditions.push(...filterConditions);
|
|
5551
5441
|
}
|
|
5442
|
+
if (options.logical) {
|
|
5443
|
+
const logicalCondition = DrizzleConditionBuilder.buildLogicalConditions(options.logical, table, collectionPath);
|
|
5444
|
+
if (logicalCondition) allConditions.push(logicalCondition);
|
|
5445
|
+
}
|
|
5552
5446
|
if (options.startAfter) {
|
|
5553
5447
|
const cursorConditions = this.buildCursorConditions(table, idField, idInfo, options, collectionPath);
|
|
5554
5448
|
if (cursorConditions.length > 0) allConditions.push(...cursorConditions);
|
|
@@ -5720,6 +5614,10 @@ class EntityFetchService {
|
|
|
5720
5614
|
const filterConditions = this.buildFilterConditions(options.filter, table, collectionPath);
|
|
5721
5615
|
if (filterConditions.length > 0) allConditions.push(...filterConditions);
|
|
5722
5616
|
}
|
|
5617
|
+
if (options.logical) {
|
|
5618
|
+
const logicalCondition = DrizzleConditionBuilder.buildLogicalConditions(options.logical, table, collectionPath);
|
|
5619
|
+
if (logicalCondition) allConditions.push(logicalCondition);
|
|
5620
|
+
}
|
|
5723
5621
|
if (vectorMeta?.filter) {
|
|
5724
5622
|
allConditions.push(vectorMeta.filter);
|
|
5725
5623
|
}
|
|
@@ -6305,6 +6203,14 @@ class EntityPersistService {
|
|
|
6305
6203
|
const parsedId = parsedIdObj[idInfo.fieldName];
|
|
6306
6204
|
await this.db.delete(table).where(eq(idField, parsedId));
|
|
6307
6205
|
}
|
|
6206
|
+
/**
|
|
6207
|
+
* Delete all entities from a collection
|
|
6208
|
+
*/
|
|
6209
|
+
async deleteAll(collectionPath, _databaseId) {
|
|
6210
|
+
const collection = getCollectionByPath(collectionPath, this.registry);
|
|
6211
|
+
const table = getTableForCollection(collection, this.registry);
|
|
6212
|
+
await this.db.delete(table);
|
|
6213
|
+
}
|
|
6308
6214
|
/**
|
|
6309
6215
|
* Save an entity (create or update)
|
|
6310
6216
|
*/
|
|
@@ -6537,12 +6443,12 @@ class EntityPersistService {
|
|
|
6537
6443
|
*/
|
|
6538
6444
|
extractCauseMessage(error) {
|
|
6539
6445
|
if (!error || typeof error !== "object") return null;
|
|
6540
|
-
|
|
6541
|
-
if (
|
|
6542
|
-
const deeper = this.extractCauseMessage(
|
|
6446
|
+
if (!(error instanceof Error)) return null;
|
|
6447
|
+
if (error.cause && typeof error.cause === "object") {
|
|
6448
|
+
const deeper = this.extractCauseMessage(error.cause);
|
|
6543
6449
|
if (deeper) return deeper;
|
|
6544
|
-
if (
|
|
6545
|
-
return
|
|
6450
|
+
if (error.cause instanceof Error && error.cause.message) {
|
|
6451
|
+
return error.cause.message;
|
|
6546
6452
|
}
|
|
6547
6453
|
}
|
|
6548
6454
|
return null;
|
|
@@ -6563,12 +6469,17 @@ class EntityPersistService {
|
|
|
6563
6469
|
*/
|
|
6564
6470
|
extractPgError(error) {
|
|
6565
6471
|
if (!error || typeof error !== "object") return null;
|
|
6566
|
-
|
|
6567
|
-
|
|
6568
|
-
|
|
6472
|
+
if (!(error instanceof Error)) {
|
|
6473
|
+
if ("cause" in error && error.cause && typeof error.cause === "object") {
|
|
6474
|
+
return this.extractPgError(error.cause);
|
|
6475
|
+
}
|
|
6476
|
+
return null;
|
|
6477
|
+
}
|
|
6478
|
+
if ("code" in error && typeof error.code === "string" && /^[0-9A-Z]{5}$/.test(error.code)) {
|
|
6479
|
+
return error;
|
|
6569
6480
|
}
|
|
6570
|
-
if (
|
|
6571
|
-
return this.extractPgError(
|
|
6481
|
+
if (error.cause && typeof error.cause === "object") {
|
|
6482
|
+
return this.extractPgError(error.cause);
|
|
6572
6483
|
}
|
|
6573
6484
|
return null;
|
|
6574
6485
|
}
|
|
@@ -6636,6 +6547,12 @@ class EntityService {
|
|
|
6636
6547
|
async deleteEntity(collectionPath, entityId, databaseId) {
|
|
6637
6548
|
return this.persistService.deleteEntity(collectionPath, entityId, databaseId);
|
|
6638
6549
|
}
|
|
6550
|
+
/**
|
|
6551
|
+
* Delete all entities from a collection
|
|
6552
|
+
*/
|
|
6553
|
+
async deleteAll(collectionPath, databaseId) {
|
|
6554
|
+
return this.persistService.deleteAll(collectionPath, databaseId);
|
|
6555
|
+
}
|
|
6639
6556
|
/**
|
|
6640
6557
|
* Execute raw SQL
|
|
6641
6558
|
*/
|
|
@@ -7336,6 +7253,10 @@ class PostgresBackendDriver {
|
|
|
7336
7253
|
await this.realtimeService.notifyEntityUpdate(entity.path, entity.id.toString(), null, entity.databaseId || resolvedCollection?.databaseId);
|
|
7337
7254
|
}
|
|
7338
7255
|
}
|
|
7256
|
+
async deleteAll(path2) {
|
|
7257
|
+
await this.entityService.deleteAll(path2);
|
|
7258
|
+
await this.realtimeService.notifyEntityUpdate(path2, "*", null);
|
|
7259
|
+
}
|
|
7339
7260
|
async checkUniqueField(path2, name, value, entityId, collection) {
|
|
7340
7261
|
return this.entityService.checkUniqueField(path2, name, value, entityId, collection?.databaseId);
|
|
7341
7262
|
}
|
|
@@ -7605,11 +7526,11 @@ class AuthenticatedPostgresBackendDriver {
|
|
|
7605
7526
|
console.warn("[DataDriver] User ID (uid) is missing for authenticated delegate. Using 'anonymous'. User object:", this.user);
|
|
7606
7527
|
userId = "anonymous";
|
|
7607
7528
|
}
|
|
7608
|
-
const
|
|
7529
|
+
const userRoles = this.user?.roles ?? [];
|
|
7609
7530
|
if (!this.user?.roles) {
|
|
7610
7531
|
console.warn("[DataDriver] User roles are missing for authenticated delegate. Using empty array. User object:", this.user);
|
|
7611
7532
|
}
|
|
7612
|
-
const normalizedRoles =
|
|
7533
|
+
const normalizedRoles = userRoles.map((r) => typeof r === "string" ? r : r?.id ?? String(r));
|
|
7613
7534
|
const rolesString = normalizedRoles.join(",");
|
|
7614
7535
|
await tx.execute(sql`
|
|
7615
7536
|
SELECT
|
|
@@ -7617,7 +7538,7 @@ class AuthenticatedPostgresBackendDriver {
|
|
|
7617
7538
|
set_config('app.user_roles', ${rolesString}, true),
|
|
7618
7539
|
set_config('app.jwt', ${JSON.stringify({
|
|
7619
7540
|
sub: userId,
|
|
7620
|
-
roles:
|
|
7541
|
+
roles: userRoles
|
|
7621
7542
|
})}, true)
|
|
7622
7543
|
`);
|
|
7623
7544
|
const txEntityService = new EntityService(tx, this.delegate.registry);
|
|
@@ -7672,6 +7593,9 @@ class AuthenticatedPostgresBackendDriver {
|
|
|
7672
7593
|
async deleteEntity(props) {
|
|
7673
7594
|
return this.withTransaction((delegate) => delegate.deleteEntity(props));
|
|
7674
7595
|
}
|
|
7596
|
+
async deleteAll(path2) {
|
|
7597
|
+
return this.withTransaction((delegate) => delegate.deleteAll(path2));
|
|
7598
|
+
}
|
|
7675
7599
|
async checkUniqueField(path2, name, value, entityId, collection) {
|
|
7676
7600
|
return this.withTransaction((delegate) => delegate.checkUniqueField(path2, name, value, entityId, collection));
|
|
7677
7601
|
}
|
|
@@ -7749,13 +7673,13 @@ class DatabasePoolManager {
|
|
|
7749
7673
|
}
|
|
7750
7674
|
await Promise.all(promises2);
|
|
7751
7675
|
this.pools.clear();
|
|
7676
|
+
this.drizzleInstances.clear();
|
|
7752
7677
|
}
|
|
7753
7678
|
}
|
|
7754
|
-
function createAuthSchema(
|
|
7755
|
-
const rolesSchema = rolesSchemaName === "public" ? null : pgSchema(rolesSchemaName);
|
|
7679
|
+
function createAuthSchema(usersSchemaName = "rebase") {
|
|
7756
7680
|
const usersSchema2 = usersSchemaName === "public" ? null : pgSchema(usersSchemaName);
|
|
7757
|
-
const
|
|
7758
|
-
const usersTableCreator =
|
|
7681
|
+
const tableCreator = usersSchema2 ? usersSchema2.table.bind(usersSchema2) : pgTable;
|
|
7682
|
+
const usersTableCreator = tableCreator;
|
|
7759
7683
|
const users2 = usersTableCreator("users", {
|
|
7760
7684
|
id: uuid("id").defaultRandom().primaryKey(),
|
|
7761
7685
|
email: varchar("email", {
|
|
@@ -7777,37 +7701,12 @@ function createAuthSchema(rolesSchemaName = "rebase", usersSchemaName = "rebase"
|
|
|
7777
7701
|
}),
|
|
7778
7702
|
emailVerificationSentAt: timestamp("email_verification_sent_at"),
|
|
7779
7703
|
isAnonymous: boolean("is_anonymous").default(false).notNull(),
|
|
7704
|
+
roles: text("roles").array().default([]).notNull(),
|
|
7780
7705
|
metadata: jsonb("metadata").$type().default({}).notNull(),
|
|
7781
7706
|
createdAt: timestamp("created_at").defaultNow().notNull(),
|
|
7782
7707
|
updatedAt: timestamp("updated_at").defaultNow().notNull()
|
|
7783
7708
|
});
|
|
7784
|
-
const
|
|
7785
|
-
id: varchar("id", {
|
|
7786
|
-
length: 50
|
|
7787
|
-
}).primaryKey(),
|
|
7788
|
-
// 'admin', 'editor', 'viewer'
|
|
7789
|
-
name: varchar("name", {
|
|
7790
|
-
length: 100
|
|
7791
|
-
}).notNull(),
|
|
7792
|
-
isAdmin: boolean("is_admin").default(false).notNull(),
|
|
7793
|
-
defaultPermissions: jsonb("default_permissions").$type(),
|
|
7794
|
-
collectionPermissions: jsonb("collection_permissions").$type()
|
|
7795
|
-
});
|
|
7796
|
-
const userRoles2 = rolesTableCreator("user_roles", {
|
|
7797
|
-
userId: uuid("user_id").notNull().references(() => users2.id, {
|
|
7798
|
-
onDelete: "cascade"
|
|
7799
|
-
}),
|
|
7800
|
-
roleId: varchar("role_id", {
|
|
7801
|
-
length: 50
|
|
7802
|
-
}).notNull().references(() => roles2.id, {
|
|
7803
|
-
onDelete: "cascade"
|
|
7804
|
-
})
|
|
7805
|
-
}, (table) => ({
|
|
7806
|
-
pk: primaryKey({
|
|
7807
|
-
columns: [table.userId, table.roleId]
|
|
7808
|
-
})
|
|
7809
|
-
}));
|
|
7810
|
-
const refreshTokens2 = rolesTableCreator("refresh_tokens", {
|
|
7709
|
+
const refreshTokens2 = tableCreator("refresh_tokens", {
|
|
7811
7710
|
id: uuid("id").defaultRandom().primaryKey(),
|
|
7812
7711
|
userId: uuid("user_id").notNull().references(() => users2.id, {
|
|
7813
7712
|
onDelete: "cascade"
|
|
@@ -7826,7 +7725,7 @@ function createAuthSchema(rolesSchemaName = "rebase", usersSchemaName = "rebase"
|
|
|
7826
7725
|
}, (table) => ({
|
|
7827
7726
|
uniqueDeviceSession: unique("unique_device_session").on(table.userId, table.userAgent, table.ipAddress)
|
|
7828
7727
|
}));
|
|
7829
|
-
const passwordResetTokens2 =
|
|
7728
|
+
const passwordResetTokens2 = tableCreator("password_reset_tokens", {
|
|
7830
7729
|
id: uuid("id").defaultRandom().primaryKey(),
|
|
7831
7730
|
userId: uuid("user_id").notNull().references(() => users2.id, {
|
|
7832
7731
|
onDelete: "cascade"
|
|
@@ -7838,14 +7737,14 @@ function createAuthSchema(rolesSchemaName = "rebase", usersSchemaName = "rebase"
|
|
|
7838
7737
|
usedAt: timestamp("used_at"),
|
|
7839
7738
|
createdAt: timestamp("created_at").defaultNow().notNull()
|
|
7840
7739
|
});
|
|
7841
|
-
const appConfig2 =
|
|
7740
|
+
const appConfig2 = tableCreator("app_config", {
|
|
7842
7741
|
key: varchar("key", {
|
|
7843
7742
|
length: 100
|
|
7844
7743
|
}).primaryKey(),
|
|
7845
7744
|
value: jsonb("value").notNull(),
|
|
7846
7745
|
updatedAt: timestamp("updated_at").defaultNow().notNull()
|
|
7847
7746
|
});
|
|
7848
|
-
const userIdentities2 =
|
|
7747
|
+
const userIdentities2 = tableCreator("user_identities", {
|
|
7849
7748
|
id: uuid("id").defaultRandom().primaryKey(),
|
|
7850
7749
|
userId: uuid("user_id").notNull().references(() => users2.id, {
|
|
7851
7750
|
onDelete: "cascade"
|
|
@@ -7863,7 +7762,7 @@ function createAuthSchema(rolesSchemaName = "rebase", usersSchemaName = "rebase"
|
|
|
7863
7762
|
}, (table) => ({
|
|
7864
7763
|
uniqueProviderId: unique("unique_provider_id").on(table.provider, table.providerId)
|
|
7865
7764
|
}));
|
|
7866
|
-
const mfaFactors2 =
|
|
7765
|
+
const mfaFactors2 = tableCreator("mfa_factors", {
|
|
7867
7766
|
id: uuid("id").defaultRandom().primaryKey(),
|
|
7868
7767
|
userId: uuid("user_id").notNull().references(() => users2.id, {
|
|
7869
7768
|
onDelete: "cascade"
|
|
@@ -7882,7 +7781,7 @@ function createAuthSchema(rolesSchemaName = "rebase", usersSchemaName = "rebase"
|
|
|
7882
7781
|
createdAt: timestamp("created_at").defaultNow().notNull(),
|
|
7883
7782
|
updatedAt: timestamp("updated_at").defaultNow().notNull()
|
|
7884
7783
|
});
|
|
7885
|
-
const mfaChallenges2 =
|
|
7784
|
+
const mfaChallenges2 = tableCreator("mfa_challenges", {
|
|
7886
7785
|
id: uuid("id").defaultRandom().primaryKey(),
|
|
7887
7786
|
factorId: uuid("factor_id").notNull().references(() => mfaFactors2.id, {
|
|
7888
7787
|
onDelete: "cascade"
|
|
@@ -7894,7 +7793,7 @@ function createAuthSchema(rolesSchemaName = "rebase", usersSchemaName = "rebase"
|
|
|
7894
7793
|
}),
|
|
7895
7794
|
expiresAt: timestamp("expires_at").notNull()
|
|
7896
7795
|
});
|
|
7897
|
-
const recoveryCodes2 =
|
|
7796
|
+
const recoveryCodes2 = tableCreator("recovery_codes", {
|
|
7898
7797
|
id: uuid("id").defaultRandom().primaryKey(),
|
|
7899
7798
|
userId: uuid("user_id").notNull().references(() => users2.id, {
|
|
7900
7799
|
onDelete: "cascade"
|
|
@@ -7906,11 +7805,8 @@ function createAuthSchema(rolesSchemaName = "rebase", usersSchemaName = "rebase"
|
|
|
7906
7805
|
createdAt: timestamp("created_at").defaultNow().notNull()
|
|
7907
7806
|
});
|
|
7908
7807
|
return {
|
|
7909
|
-
rolesSchema,
|
|
7910
7808
|
usersSchema: usersSchema2,
|
|
7911
7809
|
users: users2,
|
|
7912
|
-
roles: roles2,
|
|
7913
|
-
userRoles: userRoles2,
|
|
7914
7810
|
refreshTokens: refreshTokens2,
|
|
7915
7811
|
passwordResetTokens: passwordResetTokens2,
|
|
7916
7812
|
appConfig: appConfig2,
|
|
@@ -7920,12 +7816,9 @@ function createAuthSchema(rolesSchemaName = "rebase", usersSchemaName = "rebase"
|
|
|
7920
7816
|
recoveryCodes: recoveryCodes2
|
|
7921
7817
|
};
|
|
7922
7818
|
}
|
|
7923
|
-
const defaultAuthSchema = createAuthSchema("rebase"
|
|
7924
|
-
const rebaseSchema = defaultAuthSchema.rolesSchema;
|
|
7819
|
+
const defaultAuthSchema = createAuthSchema("rebase");
|
|
7925
7820
|
const usersSchema = defaultAuthSchema.usersSchema;
|
|
7926
7821
|
const users = defaultAuthSchema.users;
|
|
7927
|
-
const roles = defaultAuthSchema.roles;
|
|
7928
|
-
const userRoles = defaultAuthSchema.userRoles;
|
|
7929
7822
|
const refreshTokens = defaultAuthSchema.refreshTokens;
|
|
7930
7823
|
const passwordResetTokens = defaultAuthSchema.passwordResetTokens;
|
|
7931
7824
|
const appConfig = defaultAuthSchema.appConfig;
|
|
@@ -7936,30 +7829,12 @@ const recoveryCodes = defaultAuthSchema.recoveryCodes;
|
|
|
7936
7829
|
const usersRelations = relations(users, ({
|
|
7937
7830
|
many
|
|
7938
7831
|
}) => ({
|
|
7939
|
-
userRoles: many(userRoles),
|
|
7940
7832
|
refreshTokens: many(refreshTokens),
|
|
7941
7833
|
passwordResetTokens: many(passwordResetTokens),
|
|
7942
7834
|
userIdentities: many(userIdentities),
|
|
7943
7835
|
mfaFactors: many(mfaFactors),
|
|
7944
7836
|
recoveryCodes: many(recoveryCodes)
|
|
7945
7837
|
}));
|
|
7946
|
-
const rolesRelations = relations(roles, ({
|
|
7947
|
-
many
|
|
7948
|
-
}) => ({
|
|
7949
|
-
userRoles: many(userRoles)
|
|
7950
|
-
}));
|
|
7951
|
-
const userRolesRelations = relations(userRoles, ({
|
|
7952
|
-
one
|
|
7953
|
-
}) => ({
|
|
7954
|
-
user: one(users, {
|
|
7955
|
-
fields: [userRoles.userId],
|
|
7956
|
-
references: [users.id]
|
|
7957
|
-
}),
|
|
7958
|
-
role: one(roles, {
|
|
7959
|
-
fields: [userRoles.roleId],
|
|
7960
|
-
references: [roles.id]
|
|
7961
|
-
})
|
|
7962
|
-
}));
|
|
7963
7838
|
const refreshTokensRelations = relations(refreshTokens, ({
|
|
7964
7839
|
one
|
|
7965
7840
|
}) => ({
|
|
@@ -8066,6 +7941,8 @@ const getDrizzleColumn = (propName, prop, collection, collections) => {
|
|
|
8066
7941
|
columnDefinition = `${enumName}("${colName}")`;
|
|
8067
7942
|
} else if ("isId" in stringProp && stringProp.isId === "uuid") {
|
|
8068
7943
|
columnDefinition = `uuid("${colName}")`;
|
|
7944
|
+
} else if (stringProp.columnType === "uuid") {
|
|
7945
|
+
columnDefinition = `uuid("${colName}")`;
|
|
8069
7946
|
} else if (stringProp.columnType === "text") {
|
|
8070
7947
|
columnDefinition = `text("${colName}")`;
|
|
8071
7948
|
} else if (stringProp.columnType === "char") {
|
|
@@ -8133,11 +8010,38 @@ const getDrizzleColumn = (propName, prop, collection, collections) => {
|
|
|
8133
8010
|
}
|
|
8134
8011
|
break;
|
|
8135
8012
|
}
|
|
8136
|
-
case "map":
|
|
8013
|
+
case "map": {
|
|
8014
|
+
const mapProp = prop;
|
|
8015
|
+
if (mapProp.columnType === "json") {
|
|
8016
|
+
columnDefinition = `json("${colName}")`;
|
|
8017
|
+
} else {
|
|
8018
|
+
columnDefinition = `jsonb("${colName}")`;
|
|
8019
|
+
}
|
|
8020
|
+
break;
|
|
8021
|
+
}
|
|
8137
8022
|
case "array": {
|
|
8138
|
-
const
|
|
8139
|
-
|
|
8023
|
+
const arrayProp = prop;
|
|
8024
|
+
let colType = arrayProp.columnType;
|
|
8025
|
+
if (!colType && arrayProp.of && !Array.isArray(arrayProp.of)) {
|
|
8026
|
+
const ofProp = arrayProp.of;
|
|
8027
|
+
if (ofProp.type === "string") {
|
|
8028
|
+
colType = "text[]";
|
|
8029
|
+
} else if (ofProp.type === "number") {
|
|
8030
|
+
colType = ofProp.validation?.integer ? "integer[]" : "numeric[]";
|
|
8031
|
+
} else if (ofProp.type === "boolean") {
|
|
8032
|
+
colType = "boolean[]";
|
|
8033
|
+
}
|
|
8034
|
+
}
|
|
8035
|
+
if (colType === "json") {
|
|
8140
8036
|
columnDefinition = `json("${colName}")`;
|
|
8037
|
+
} else if (colType === "text[]") {
|
|
8038
|
+
columnDefinition = `text("${colName}").array()`;
|
|
8039
|
+
} else if (colType === "integer[]") {
|
|
8040
|
+
columnDefinition = `integer("${colName}").array()`;
|
|
8041
|
+
} else if (colType === "boolean[]") {
|
|
8042
|
+
columnDefinition = `boolean("${colName}").array()`;
|
|
8043
|
+
} else if (colType === "numeric[]") {
|
|
8044
|
+
columnDefinition = `numeric("${colName}").array()`;
|
|
8141
8045
|
} else {
|
|
8142
8046
|
columnDefinition = `jsonb("${colName}")`;
|
|
8143
8047
|
}
|
|
@@ -8221,8 +8125,8 @@ const resolveRawSql = (expression) => {
|
|
|
8221
8125
|
const resolved = expression.replace(/\{(\w+)\}/g, (_, col) => col);
|
|
8222
8126
|
return `sql\`${resolved}\``;
|
|
8223
8127
|
};
|
|
8224
|
-
const wrapWithRoleCheck = (clause,
|
|
8225
|
-
const rolesArrayString = `ARRAY[${
|
|
8128
|
+
const wrapWithRoleCheck = (clause, roles) => {
|
|
8129
|
+
const rolesArrayString = `ARRAY[${roles.map((r) => `'${r}'`).join(",")}]`;
|
|
8226
8130
|
const roleCondition = `string_to_array(auth.roles(), ',') @> ${rolesArrayString}`;
|
|
8227
8131
|
return `sql\`(${unwrapSql(clause)}) AND (${roleCondition})\``;
|
|
8228
8132
|
};
|
|
@@ -8275,22 +8179,22 @@ const generatePolicyCode = (collection, rule, index) => {
|
|
|
8275
8179
|
};
|
|
8276
8180
|
const generateSinglePolicyCode = (collection, rule, operation, policyName) => {
|
|
8277
8181
|
const mode = rule.mode ?? "permissive";
|
|
8278
|
-
const
|
|
8182
|
+
const roles = rule.roles ? [...rule.roles].sort() : void 0;
|
|
8279
8183
|
const needsUsing = operation !== "insert";
|
|
8280
8184
|
const needsWithCheck = operation !== "select" && operation !== "delete";
|
|
8281
8185
|
let usingClause = needsUsing ? buildUsingClause(rule, collection) : null;
|
|
8282
8186
|
let withCheckClause = needsWithCheck ? buildWithCheckClause(rule, collection) : null;
|
|
8283
|
-
if (
|
|
8187
|
+
if (roles && roles.length > 0) {
|
|
8284
8188
|
if (usingClause) {
|
|
8285
|
-
usingClause = wrapWithRoleCheck(usingClause,
|
|
8189
|
+
usingClause = wrapWithRoleCheck(usingClause, roles);
|
|
8286
8190
|
} else if (needsUsing) {
|
|
8287
|
-
const rolesArrayString = `ARRAY[${
|
|
8191
|
+
const rolesArrayString = `ARRAY[${roles.map((r) => `'${r}'`).join(",")}]`;
|
|
8288
8192
|
usingClause = `sql\`string_to_array(auth.roles(), ',') @> ${rolesArrayString}\``;
|
|
8289
8193
|
}
|
|
8290
8194
|
if (withCheckClause) {
|
|
8291
|
-
withCheckClause = wrapWithRoleCheck(withCheckClause,
|
|
8195
|
+
withCheckClause = wrapWithRoleCheck(withCheckClause, roles);
|
|
8292
8196
|
} else if (needsWithCheck) {
|
|
8293
|
-
const rolesArrayString = `ARRAY[${
|
|
8197
|
+
const rolesArrayString = `ARRAY[${roles.map((r) => `'${r}'`).join(",")}]`;
|
|
8294
8198
|
withCheckClause = `sql\`string_to_array(auth.roles(), ',') @> ${rolesArrayString}\``;
|
|
8295
8199
|
}
|
|
8296
8200
|
}
|
|
@@ -8613,7 +8517,7 @@ ${tableRelations.join(",\n")}
|
|
|
8613
8517
|
schemaContent += tablesExport + enumsExport + relationsExport;
|
|
8614
8518
|
return schemaContent;
|
|
8615
8519
|
};
|
|
8616
|
-
const formatTerminalText = (
|
|
8520
|
+
const formatTerminalText = (text2, options = {}) => {
|
|
8617
8521
|
let codes = "";
|
|
8618
8522
|
if (options.bold) codes += "\x1B[1m";
|
|
8619
8523
|
if (options.backgroundColor) {
|
|
@@ -8640,7 +8544,7 @@ const formatTerminalText = (text, options = {}) => {
|
|
|
8640
8544
|
};
|
|
8641
8545
|
codes += textColors[options.textColor];
|
|
8642
8546
|
}
|
|
8643
|
-
return `${codes}${
|
|
8547
|
+
return `${codes}${text2}\x1B[0m`;
|
|
8644
8548
|
};
|
|
8645
8549
|
const runGeneration = async (collectionsFilePath, outputPath) => {
|
|
8646
8550
|
try {
|
|
@@ -8678,7 +8582,6 @@ const runGeneration = async (collectionsFilePath, outputPath) => {
|
|
|
8678
8582
|
if (!collections || !Array.isArray(collections)) {
|
|
8679
8583
|
collections = [];
|
|
8680
8584
|
}
|
|
8681
|
-
collections = Array.from(new Map([defaultUsersCollection, ...collections].map((c) => [c.slug, c])).values());
|
|
8682
8585
|
collections.sort((a, b) => a.slug.localeCompare(b.slug));
|
|
8683
8586
|
const schemaContent = await generateSchema(collections);
|
|
8684
8587
|
if (outputPath) {
|
|
@@ -9664,20 +9567,19 @@ class RealtimeService extends EventEmitter {
|
|
|
9664
9567
|
}
|
|
9665
9568
|
}
|
|
9666
9569
|
const PostgresRealtimeProvider = RealtimeService;
|
|
9667
|
-
const clientSessions = /* @__PURE__ */ new Map();
|
|
9668
9570
|
const WS_RATE_LIMIT = 2e3;
|
|
9669
9571
|
const WS_RATE_WINDOW_MS = 6e4;
|
|
9670
9572
|
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"]);
|
|
9671
9573
|
function extractErrorMessage(error) {
|
|
9672
9574
|
if (!error) return "Unknown error";
|
|
9673
|
-
if (
|
|
9674
|
-
|
|
9675
|
-
|
|
9676
|
-
return extractErrorMessage(err.cause);
|
|
9677
|
-
}
|
|
9678
|
-
if (typeof err.message === "string") {
|
|
9679
|
-
return err.message;
|
|
9575
|
+
if (error instanceof Error) {
|
|
9576
|
+
if ("cause" in error && error.cause) {
|
|
9577
|
+
return extractErrorMessage(error.cause);
|
|
9680
9578
|
}
|
|
9579
|
+
return error.message;
|
|
9580
|
+
}
|
|
9581
|
+
if (typeof error === "object" && "message" in error && typeof error.message === "string") {
|
|
9582
|
+
return error.message;
|
|
9681
9583
|
}
|
|
9682
9584
|
return String(error);
|
|
9683
9585
|
}
|
|
@@ -9685,14 +9587,10 @@ function isAdminSession(session) {
|
|
|
9685
9587
|
if (!session?.user) return false;
|
|
9686
9588
|
if (session.user.isAdmin) return true;
|
|
9687
9589
|
if (!session.user.roles) return false;
|
|
9688
|
-
return session.user.roles.some((r) =>
|
|
9689
|
-
if (typeof r === "string") return r === "admin";
|
|
9690
|
-
if (r && typeof r === "object" && "isAdmin" in r) return r.isAdmin;
|
|
9691
|
-
if (r && typeof r === "object" && "id" in r) return r.id === "admin";
|
|
9692
|
-
return false;
|
|
9693
|
-
});
|
|
9590
|
+
return session.user.roles.some((r) => r === "admin");
|
|
9694
9591
|
}
|
|
9695
9592
|
function createPostgresWebSocket(server, realtimeService, driver, authConfig, authAdapter) {
|
|
9593
|
+
const clientSessions = /* @__PURE__ */ new Map();
|
|
9696
9594
|
const isProduction = process.env.NODE_ENV === "production";
|
|
9697
9595
|
const wsDebug = (...args) => {
|
|
9698
9596
|
if (!isProduction) console.debug(...args);
|
|
@@ -9831,13 +9729,23 @@ function createPostgresWebSocket(server, realtimeService, driver, authConfig, au
|
|
|
9831
9729
|
}
|
|
9832
9730
|
const getScopedDelegate = async () => {
|
|
9833
9731
|
const session = clientSessions.get(clientId);
|
|
9834
|
-
if (
|
|
9732
|
+
if (typeof driver.withAuth === "function") {
|
|
9835
9733
|
try {
|
|
9836
9734
|
const userForAuth = session?.user ? {
|
|
9837
9735
|
uid: session.user.userId,
|
|
9736
|
+
displayName: null,
|
|
9737
|
+
email: null,
|
|
9738
|
+
photoURL: null,
|
|
9739
|
+
providerId: "websocket",
|
|
9740
|
+
isAnonymous: false,
|
|
9838
9741
|
roles: session.user.roles ?? []
|
|
9839
9742
|
} : {
|
|
9840
9743
|
uid: "anon",
|
|
9744
|
+
displayName: null,
|
|
9745
|
+
email: null,
|
|
9746
|
+
photoURL: null,
|
|
9747
|
+
providerId: "websocket",
|
|
9748
|
+
isAnonymous: true,
|
|
9841
9749
|
roles: ["anon"]
|
|
9842
9750
|
};
|
|
9843
9751
|
return await driver.withAuth(userForAuth);
|
|
@@ -10024,15 +9932,15 @@ function createPostgresWebSocket(server, realtimeService, driver, authConfig, au
|
|
|
10024
9932
|
wsDebug("👤 [WebSocket Server] Processing FETCH_ROLES request");
|
|
10025
9933
|
const delegate = await getScopedDelegate();
|
|
10026
9934
|
const admin = delegate.admin;
|
|
10027
|
-
let
|
|
9935
|
+
let roles = [];
|
|
10028
9936
|
if (isSQLAdmin(admin) && admin.fetchAvailableRoles) {
|
|
10029
|
-
|
|
9937
|
+
roles = await admin.fetchAvailableRoles();
|
|
10030
9938
|
}
|
|
10031
|
-
wsDebug(`👤 [WebSocket Server] Fetched ${
|
|
9939
|
+
wsDebug(`👤 [WebSocket Server] Fetched ${roles.length} roles.`);
|
|
10032
9940
|
const response = {
|
|
10033
9941
|
type: "FETCH_ROLES_SUCCESS",
|
|
10034
9942
|
payload: {
|
|
10035
|
-
roles
|
|
9943
|
+
roles
|
|
10036
9944
|
},
|
|
10037
9945
|
requestId
|
|
10038
9946
|
};
|
|
@@ -10286,85 +10194,35 @@ class PostgresCollectionRegistry extends CollectionRegistry {
|
|
|
10286
10194
|
return collection.relations.map((r) => r.relationName || r.localKey || "").filter(Boolean);
|
|
10287
10195
|
}
|
|
10288
10196
|
}
|
|
10289
|
-
|
|
10290
|
-
id: "admin",
|
|
10291
|
-
name: "Admin",
|
|
10292
|
-
is_admin: true,
|
|
10293
|
-
default_permissions: {
|
|
10294
|
-
read: true,
|
|
10295
|
-
create: true,
|
|
10296
|
-
edit: true,
|
|
10297
|
-
delete: true
|
|
10298
|
-
}
|
|
10299
|
-
}, {
|
|
10300
|
-
id: "editor",
|
|
10301
|
-
name: "Editor",
|
|
10302
|
-
is_admin: false,
|
|
10303
|
-
default_permissions: {
|
|
10304
|
-
read: true,
|
|
10305
|
-
create: true,
|
|
10306
|
-
edit: true,
|
|
10307
|
-
delete: true
|
|
10308
|
-
}
|
|
10309
|
-
}, {
|
|
10310
|
-
id: "viewer",
|
|
10311
|
-
name: "Viewer",
|
|
10312
|
-
is_admin: false,
|
|
10313
|
-
default_permissions: {
|
|
10314
|
-
read: true,
|
|
10315
|
-
create: false,
|
|
10316
|
-
edit: false,
|
|
10317
|
-
delete: false
|
|
10318
|
-
}
|
|
10319
|
-
}];
|
|
10320
|
-
async function ensureAuthTablesExist(db, registry) {
|
|
10197
|
+
async function ensureAuthTablesExist(db, collection) {
|
|
10321
10198
|
logger.info("🔍 Checking auth tables...");
|
|
10322
10199
|
try {
|
|
10323
|
-
let usersTableName = '"users"';
|
|
10200
|
+
let usersTableName = '"rebase"."users"';
|
|
10324
10201
|
let userIdType = "TEXT";
|
|
10325
|
-
let usersSchema2 = "
|
|
10326
|
-
if (
|
|
10327
|
-
const
|
|
10328
|
-
|
|
10329
|
-
|
|
10330
|
-
|
|
10331
|
-
|
|
10332
|
-
|
|
10333
|
-
|
|
10334
|
-
|
|
10335
|
-
|
|
10336
|
-
|
|
10337
|
-
const columnType = meta.columnType;
|
|
10338
|
-
if (columnType === "PgUUID") {
|
|
10339
|
-
userIdType = "UUID";
|
|
10340
|
-
} else if (columnType === "PgSerial" || columnType === "PgInteger") {
|
|
10341
|
-
userIdType = "INTEGER";
|
|
10342
|
-
} else if (columnType === "PgBigInt" || columnType === "PgBigSerial") {
|
|
10343
|
-
userIdType = "BIGINT";
|
|
10344
|
-
}
|
|
10202
|
+
let usersSchema2 = "rebase";
|
|
10203
|
+
if (collection) {
|
|
10204
|
+
const rawTable = "table" in collection && typeof collection.table === "string" ? collection.table : collection.slug;
|
|
10205
|
+
usersSchema2 = "schema" in collection && typeof collection.schema === "string" ? collection.schema : "public";
|
|
10206
|
+
usersTableName = usersSchema2 === "public" ? `"${rawTable}"` : `"${usersSchema2}"."${rawTable}"`;
|
|
10207
|
+
const idProp = collection.properties?.id;
|
|
10208
|
+
if (idProp) {
|
|
10209
|
+
const isId = "isId" in idProp ? idProp.isId : void 0;
|
|
10210
|
+
if (isId === "uuid") {
|
|
10211
|
+
userIdType = "UUID";
|
|
10212
|
+
} else if (isId === "autoincrement") {
|
|
10213
|
+
userIdType = "INTEGER";
|
|
10345
10214
|
}
|
|
10346
10215
|
}
|
|
10347
10216
|
}
|
|
10348
|
-
let rolesSchema = "rebase";
|
|
10349
|
-
if (registry) {
|
|
10350
|
-
const rolesTable = registry.getTable("roles");
|
|
10351
|
-
if (rolesTable) {
|
|
10352
|
-
rolesSchema = getTableConfig(rolesTable).schema || "public";
|
|
10353
|
-
}
|
|
10354
|
-
}
|
|
10355
10217
|
if (usersSchema2 !== "public") {
|
|
10356
10218
|
await db.execute(sql`CREATE SCHEMA IF NOT EXISTS ${sql.raw(usersSchema2)}`);
|
|
10357
10219
|
}
|
|
10358
|
-
if (rolesSchema !== "public" && rolesSchema !== usersSchema2) {
|
|
10359
|
-
await db.execute(sql`CREATE SCHEMA IF NOT EXISTS ${sql.raw(rolesSchema)}`);
|
|
10360
|
-
}
|
|
10361
10220
|
await db.execute(sql`CREATE SCHEMA IF NOT EXISTS rebase`);
|
|
10362
|
-
const
|
|
10363
|
-
const
|
|
10364
|
-
const
|
|
10365
|
-
const
|
|
10366
|
-
const
|
|
10367
|
-
const appConfigTableName = `"${rolesSchema}"."app_config"`;
|
|
10221
|
+
const authSchema = usersSchema2 === "public" ? "rebase" : usersSchema2;
|
|
10222
|
+
const userIdentitiesTable = `"${authSchema}"."user_identities"`;
|
|
10223
|
+
const refreshTokensTableName = `"${authSchema}"."refresh_tokens"`;
|
|
10224
|
+
const passwordResetTokensTableName = `"${authSchema}"."password_reset_tokens"`;
|
|
10225
|
+
const appConfigTableName = `"${authSchema}"."app_config"`;
|
|
10368
10226
|
await db.execute(sql`
|
|
10369
10227
|
CREATE TABLE IF NOT EXISTS ${sql.raw(userIdentitiesTable)} (
|
|
10370
10228
|
id TEXT PRIMARY KEY DEFAULT gen_random_uuid()::text,
|
|
@@ -10381,27 +10239,6 @@ async function ensureAuthTablesExist(db, registry) {
|
|
|
10381
10239
|
CREATE INDEX IF NOT EXISTS idx_user_identities_user
|
|
10382
10240
|
ON ${sql.raw(userIdentitiesTable)}(user_id)
|
|
10383
10241
|
`);
|
|
10384
|
-
await db.execute(sql`
|
|
10385
|
-
CREATE TABLE IF NOT EXISTS ${sql.raw(rolesTableName)} (
|
|
10386
|
-
id TEXT PRIMARY KEY,
|
|
10387
|
-
name TEXT NOT NULL,
|
|
10388
|
-
is_admin BOOLEAN DEFAULT FALSE,
|
|
10389
|
-
default_permissions JSONB,
|
|
10390
|
-
collection_permissions JSONB,
|
|
10391
|
-
created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW()
|
|
10392
|
-
)
|
|
10393
|
-
`);
|
|
10394
|
-
await db.execute(sql`
|
|
10395
|
-
CREATE TABLE IF NOT EXISTS ${sql.raw(userRolesTableName)} (
|
|
10396
|
-
user_id ${sql.raw(userIdType)} NOT NULL REFERENCES ${sql.raw(usersTableName)}(id) ON DELETE CASCADE,
|
|
10397
|
-
role_id TEXT NOT NULL REFERENCES ${sql.raw(rolesTableName)}(id) ON DELETE CASCADE,
|
|
10398
|
-
PRIMARY KEY (user_id, role_id)
|
|
10399
|
-
)
|
|
10400
|
-
`);
|
|
10401
|
-
await db.execute(sql`
|
|
10402
|
-
CREATE INDEX IF NOT EXISTS idx_user_roles_user
|
|
10403
|
-
ON ${sql.raw(userRolesTableName)}(user_id)
|
|
10404
|
-
`);
|
|
10405
10242
|
await db.execute(sql`
|
|
10406
10243
|
CREATE TABLE IF NOT EXISTS ${sql.raw(refreshTokensTableName)} (
|
|
10407
10244
|
id TEXT PRIMARY KEY DEFAULT gen_random_uuid()::text,
|
|
@@ -10469,14 +10306,43 @@ async function ensureAuthTablesExist(db, registry) {
|
|
|
10469
10306
|
$$ LANGUAGE sql STABLE
|
|
10470
10307
|
`);
|
|
10471
10308
|
});
|
|
10472
|
-
await seedDefaultRoles(db, rolesTableName);
|
|
10473
10309
|
await db.execute(sql`
|
|
10474
10310
|
ALTER TABLE ${sql.raw(usersTableName)}
|
|
10475
10311
|
ADD COLUMN IF NOT EXISTS is_anonymous BOOLEAN DEFAULT FALSE
|
|
10476
10312
|
`);
|
|
10477
|
-
|
|
10478
|
-
|
|
10479
|
-
|
|
10313
|
+
await db.execute(sql`
|
|
10314
|
+
ALTER TABLE ${sql.raw(usersTableName)}
|
|
10315
|
+
ADD COLUMN IF NOT EXISTS roles TEXT[] DEFAULT '{}' NOT NULL
|
|
10316
|
+
`);
|
|
10317
|
+
try {
|
|
10318
|
+
const legacyCheck = await db.execute(sql`
|
|
10319
|
+
SELECT EXISTS (
|
|
10320
|
+
SELECT 1 FROM information_schema.tables
|
|
10321
|
+
WHERE table_schema = 'rebase' AND table_name = 'user_roles'
|
|
10322
|
+
) AS has_user_roles
|
|
10323
|
+
`);
|
|
10324
|
+
const hasLegacyTables = legacyCheck.rows[0].has_user_roles;
|
|
10325
|
+
if (hasLegacyTables) {
|
|
10326
|
+
logger.info("🔄 Migrating roles from legacy user_roles table...");
|
|
10327
|
+
await db.execute(sql`
|
|
10328
|
+
UPDATE ${sql.raw(usersTableName)} u
|
|
10329
|
+
SET roles = COALESCE((
|
|
10330
|
+
SELECT array_agg(ur.role_id)
|
|
10331
|
+
FROM "rebase"."user_roles" ur
|
|
10332
|
+
WHERE ur.user_id = u.id
|
|
10333
|
+
), '{}')
|
|
10334
|
+
WHERE u.roles = '{}' OR u.roles IS NULL
|
|
10335
|
+
`);
|
|
10336
|
+
await db.execute(sql`DROP TABLE IF EXISTS "rebase"."user_roles" CASCADE`);
|
|
10337
|
+
await db.execute(sql`DROP TABLE IF EXISTS "rebase"."roles" CASCADE`);
|
|
10338
|
+
logger.info("✅ Legacy roles tables migrated and dropped");
|
|
10339
|
+
}
|
|
10340
|
+
} catch (migrationError) {
|
|
10341
|
+
logger.warn(`⚠️ Legacy roles migration skipped: ${migrationError instanceof Error ? migrationError.message : String(migrationError)}`);
|
|
10342
|
+
}
|
|
10343
|
+
const mfaFactorsTableName = `"${authSchema}"."mfa_factors"`;
|
|
10344
|
+
const mfaChallengesTableName = `"${authSchema}"."mfa_challenges"`;
|
|
10345
|
+
const recoveryCodesTableName = `"${authSchema}"."recovery_codes"`;
|
|
10480
10346
|
await db.execute(sql`
|
|
10481
10347
|
CREATE TABLE IF NOT EXISTS ${sql.raw(mfaFactorsTableName)} (
|
|
10482
10348
|
id TEXT PRIMARY KEY DEFAULT gen_random_uuid()::text,
|
|
@@ -10528,28 +10394,6 @@ async function ensureAuthTablesExist(db, registry) {
|
|
|
10528
10394
|
logger.warn("⚠️ Continuing without creating auth tables.");
|
|
10529
10395
|
}
|
|
10530
10396
|
}
|
|
10531
|
-
async function seedDefaultRoles(db, rolesTableName) {
|
|
10532
|
-
const result = await db.execute(sql`SELECT COUNT(*) as count FROM ${sql.raw(rolesTableName)}`);
|
|
10533
|
-
const count2 = parseInt(result.rows[0]?.count || "0", 10);
|
|
10534
|
-
if (count2 > 0) {
|
|
10535
|
-
logger.info(`📋 Found ${count2} existing roles`);
|
|
10536
|
-
return;
|
|
10537
|
-
}
|
|
10538
|
-
logger.info("🌱 Seeding default roles...");
|
|
10539
|
-
for (const role of DEFAULT_ROLES) {
|
|
10540
|
-
await db.execute(sql`
|
|
10541
|
-
INSERT INTO ${sql.raw(rolesTableName)} (id, name, is_admin, default_permissions)
|
|
10542
|
-
VALUES (
|
|
10543
|
-
${role.id},
|
|
10544
|
-
${role.name},
|
|
10545
|
-
${role.is_admin},
|
|
10546
|
-
${JSON.stringify(role.default_permissions)}::jsonb
|
|
10547
|
-
)
|
|
10548
|
-
ON CONFLICT (id) DO NOTHING
|
|
10549
|
-
`);
|
|
10550
|
-
}
|
|
10551
|
-
logger.info("✅ Default roles created: admin, editor, viewer");
|
|
10552
|
-
}
|
|
10553
10397
|
function getColumnKey(table, ...keys2) {
|
|
10554
10398
|
if (!table) return void 0;
|
|
10555
10399
|
for (const key of keys2) {
|
|
@@ -10569,24 +10413,18 @@ function getColumn(table, ...keys2) {
|
|
|
10569
10413
|
class UserService {
|
|
10570
10414
|
constructor(db, tableOrTables) {
|
|
10571
10415
|
this.db = db;
|
|
10572
|
-
if (tableOrTables &&
|
|
10416
|
+
if (tableOrTables && tableOrTables.users) {
|
|
10573
10417
|
const tables = tableOrTables;
|
|
10574
10418
|
this.usersTable = tables.users || users;
|
|
10575
10419
|
this.userIdentitiesTable = tables.userIdentities || userIdentities;
|
|
10576
|
-
this.userRolesTable = tables.userRoles || userRoles;
|
|
10577
|
-
this.rolesTable = tables.roles || roles;
|
|
10578
10420
|
} else {
|
|
10579
10421
|
const table = tableOrTables;
|
|
10580
10422
|
this.usersTable = table || users;
|
|
10581
10423
|
this.userIdentitiesTable = userIdentities;
|
|
10582
|
-
this.userRolesTable = userRoles;
|
|
10583
|
-
this.rolesTable = roles;
|
|
10584
10424
|
}
|
|
10585
10425
|
}
|
|
10586
10426
|
usersTable;
|
|
10587
10427
|
userIdentitiesTable;
|
|
10588
|
-
userRolesTable;
|
|
10589
|
-
rolesTable;
|
|
10590
10428
|
getQualifiedUsersTableName() {
|
|
10591
10429
|
const name = getTableName$1(this.usersTable);
|
|
10592
10430
|
const schema = getTableConfig(this.usersTable).schema || "public";
|
|
@@ -10608,7 +10446,7 @@ class UserService {
|
|
|
10608
10446
|
const metadata = {
|
|
10609
10447
|
...row.metadata || {}
|
|
10610
10448
|
};
|
|
10611
|
-
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", "is_anonymous", "isAnonymous", "created_at", "createdAt", "updated_at", "updatedAt", "metadata"]);
|
|
10449
|
+
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", "is_anonymous", "isAnonymous", "roles", "created_at", "createdAt", "updated_at", "updatedAt", "metadata"]);
|
|
10612
10450
|
for (const [key, val] of Object.entries(row)) {
|
|
10613
10451
|
if (!knownKeys.has(key)) {
|
|
10614
10452
|
const camelKey = camelCase(key);
|
|
@@ -10759,19 +10597,18 @@ class UserService {
|
|
|
10759
10597
|
const displayNameCol = getColumn(this.usersTable, "displayName", "display_name");
|
|
10760
10598
|
const displayNameColumn = displayNameCol ? displayNameCol.name : "display_name";
|
|
10761
10599
|
const idCol = getColumn(this.usersTable, "id");
|
|
10762
|
-
|
|
10600
|
+
idCol ? idCol.name : "id";
|
|
10763
10601
|
const usersTableName = this.getQualifiedUsersTableName();
|
|
10764
|
-
const rolesSchema = getTableConfig(this.userRolesTable).schema || "public";
|
|
10765
10602
|
const conditions = [];
|
|
10766
10603
|
if (roleId) {
|
|
10767
|
-
conditions.push(sql
|
|
10604
|
+
conditions.push(sql`${roleId} = ANY(${sql.raw(usersTableName)}.roles)`);
|
|
10768
10605
|
}
|
|
10769
10606
|
if (search) {
|
|
10770
10607
|
const pattern = `%${search}%`;
|
|
10771
10608
|
conditions.push(sql`(${sql.raw(usersTableName)}.${sql.raw(emailColumn)} ILIKE ${pattern} OR ${sql.raw(usersTableName)}.${sql.raw(displayNameColumn)} ILIKE ${pattern})`);
|
|
10772
10609
|
}
|
|
10773
10610
|
const whereClause = conditions.length > 0 ? sql`WHERE ${sql.join(conditions, sql` AND `)}` : sql``;
|
|
10774
|
-
const orderByClause = roleId ? sql`ORDER BY ${sql.raw(usersTableName)}.${sql.raw(orderColumn)} ${direction}` : sql`ORDER BY (
|
|
10611
|
+
const orderByClause = roleId ? sql`ORDER BY ${sql.raw(usersTableName)}.${sql.raw(orderColumn)} ${direction}` : sql`ORDER BY array_length(${sql.raw(usersTableName)}.roles, 1) DESC NULLS LAST, ${sql.raw(usersTableName)}.${sql.raw(orderColumn)} ${direction}`;
|
|
10775
10612
|
const countResult = await this.db.execute(sql`
|
|
10776
10613
|
SELECT count(*)::int as total FROM ${sql.raw(usersTableName)}
|
|
10777
10614
|
${whereClause}
|
|
@@ -10845,54 +10682,57 @@ class UserService {
|
|
|
10845
10682
|
return row ? this.mapRowToUser(row) : null;
|
|
10846
10683
|
}
|
|
10847
10684
|
/**
|
|
10848
|
-
* Get roles for a user from database
|
|
10685
|
+
* Get roles for a user from database (inline TEXT[] column)
|
|
10849
10686
|
*/
|
|
10850
10687
|
async getUserRoles(userId) {
|
|
10851
|
-
const
|
|
10688
|
+
const usersTableName = this.getQualifiedUsersTableName();
|
|
10852
10689
|
const result = await this.db.execute(sql`
|
|
10853
|
-
SELECT
|
|
10854
|
-
FROM ${sql.raw(`"${rolesSchema}"."roles"`)} r
|
|
10855
|
-
INNER JOIN ${sql.raw(`"${rolesSchema}"."user_roles"`)} ur ON r.id = ur.role_id
|
|
10856
|
-
WHERE ur.user_id = ${userId}
|
|
10690
|
+
SELECT roles FROM ${sql.raw(usersTableName)} WHERE id = ${userId}
|
|
10857
10691
|
`);
|
|
10858
|
-
|
|
10859
|
-
|
|
10860
|
-
|
|
10861
|
-
|
|
10862
|
-
|
|
10863
|
-
|
|
10692
|
+
if (result.rows.length === 0) return [];
|
|
10693
|
+
const row = result.rows[0];
|
|
10694
|
+
const roleIds = row.roles ?? [];
|
|
10695
|
+
return roleIds.map((id) => ({
|
|
10696
|
+
id,
|
|
10697
|
+
name: id,
|
|
10698
|
+
isAdmin: id === "admin",
|
|
10699
|
+
defaultPermissions: null,
|
|
10700
|
+
collectionPermissions: null
|
|
10864
10701
|
}));
|
|
10865
10702
|
}
|
|
10866
10703
|
/**
|
|
10867
10704
|
* Get role IDs for a user
|
|
10868
10705
|
*/
|
|
10869
10706
|
async getUserRoleIds(userId) {
|
|
10870
|
-
const
|
|
10871
|
-
|
|
10707
|
+
const usersTableName = this.getQualifiedUsersTableName();
|
|
10708
|
+
const result = await this.db.execute(sql`
|
|
10709
|
+
SELECT roles FROM ${sql.raw(usersTableName)} WHERE id = ${userId}
|
|
10710
|
+
`);
|
|
10711
|
+
if (result.rows.length === 0) return [];
|
|
10712
|
+
const row = result.rows[0];
|
|
10713
|
+
return row.roles ?? [];
|
|
10872
10714
|
}
|
|
10873
10715
|
/**
|
|
10874
|
-
* Set roles for a user
|
|
10716
|
+
* Set roles for a user (replaces existing roles)
|
|
10875
10717
|
*/
|
|
10876
10718
|
async setUserRoles(userId, roleIds) {
|
|
10877
|
-
const
|
|
10878
|
-
|
|
10879
|
-
|
|
10880
|
-
|
|
10881
|
-
|
|
10882
|
-
|
|
10883
|
-
|
|
10884
|
-
`);
|
|
10885
|
-
}
|
|
10719
|
+
const usersTableName = this.getQualifiedUsersTableName();
|
|
10720
|
+
const rolesArray = `{${roleIds.join(",")}}`;
|
|
10721
|
+
await this.db.execute(sql`
|
|
10722
|
+
UPDATE ${sql.raw(usersTableName)}
|
|
10723
|
+
SET roles = ${rolesArray}::text[], updated_at = NOW()
|
|
10724
|
+
WHERE id = ${userId}
|
|
10725
|
+
`);
|
|
10886
10726
|
}
|
|
10887
10727
|
/**
|
|
10888
|
-
* Assign a specific role to new user
|
|
10728
|
+
* Assign a specific role to new user (appends if not present)
|
|
10889
10729
|
*/
|
|
10890
10730
|
async assignDefaultRole(userId, roleId) {
|
|
10891
|
-
const
|
|
10731
|
+
const usersTableName = this.getQualifiedUsersTableName();
|
|
10892
10732
|
await this.db.execute(sql`
|
|
10893
|
-
|
|
10894
|
-
|
|
10895
|
-
|
|
10733
|
+
UPDATE ${sql.raw(usersTableName)}
|
|
10734
|
+
SET roles = array_append(roles, ${roleId}), updated_at = NOW()
|
|
10735
|
+
WHERE id = ${userId} AND NOT (${roleId} = ANY(roles))
|
|
10896
10736
|
`);
|
|
10897
10737
|
}
|
|
10898
10738
|
/**
|
|
@@ -10901,101 +10741,12 @@ class UserService {
|
|
|
10901
10741
|
async getUserWithRoles(userId) {
|
|
10902
10742
|
const user = await this.getUserById(userId);
|
|
10903
10743
|
if (!user) return null;
|
|
10904
|
-
const
|
|
10744
|
+
const roles = await this.getUserRoles(userId);
|
|
10905
10745
|
return {
|
|
10906
10746
|
user,
|
|
10907
|
-
roles
|
|
10908
|
-
};
|
|
10909
|
-
}
|
|
10910
|
-
}
|
|
10911
|
-
class RoleService {
|
|
10912
|
-
constructor(db, tableOrTables) {
|
|
10913
|
-
this.db = db;
|
|
10914
|
-
if (tableOrTables && (tableOrTables.roles || tableOrTables.users)) {
|
|
10915
|
-
this.rolesTable = tableOrTables.roles || roles;
|
|
10916
|
-
} else {
|
|
10917
|
-
this.rolesTable = tableOrTables || roles;
|
|
10918
|
-
}
|
|
10919
|
-
}
|
|
10920
|
-
rolesTable;
|
|
10921
|
-
getQualifiedRolesTableName() {
|
|
10922
|
-
const name = getTableName$1(this.rolesTable);
|
|
10923
|
-
const schema = getTableConfig(this.rolesTable).schema || "public";
|
|
10924
|
-
return `"${schema}"."${name}"`;
|
|
10925
|
-
}
|
|
10926
|
-
async getRoleById(id) {
|
|
10927
|
-
const tableName = this.getQualifiedRolesTableName();
|
|
10928
|
-
const result = await this.db.execute(sql`
|
|
10929
|
-
SELECT id, name, is_admin, default_permissions, collection_permissions
|
|
10930
|
-
FROM ${sql.raw(tableName)}
|
|
10931
|
-
WHERE id = ${id}
|
|
10932
|
-
`);
|
|
10933
|
-
if (result.rows.length === 0) return null;
|
|
10934
|
-
const row = result.rows[0];
|
|
10935
|
-
return {
|
|
10936
|
-
id: row.id,
|
|
10937
|
-
name: row.name,
|
|
10938
|
-
isAdmin: row.is_admin,
|
|
10939
|
-
defaultPermissions: row.default_permissions,
|
|
10940
|
-
collectionPermissions: row.collection_permissions
|
|
10941
|
-
};
|
|
10942
|
-
}
|
|
10943
|
-
async listRoles() {
|
|
10944
|
-
const tableName = this.getQualifiedRolesTableName();
|
|
10945
|
-
const result = await this.db.execute(sql`
|
|
10946
|
-
SELECT id, name, is_admin, default_permissions, collection_permissions
|
|
10947
|
-
FROM ${sql.raw(tableName)}
|
|
10948
|
-
ORDER BY name
|
|
10949
|
-
`);
|
|
10950
|
-
return result.rows.map((row) => ({
|
|
10951
|
-
id: row.id,
|
|
10952
|
-
name: row.name,
|
|
10953
|
-
isAdmin: row.is_admin,
|
|
10954
|
-
defaultPermissions: row.default_permissions,
|
|
10955
|
-
collectionPermissions: row.collection_permissions
|
|
10956
|
-
}));
|
|
10957
|
-
}
|
|
10958
|
-
async createRole(data) {
|
|
10959
|
-
const tableName = this.getQualifiedRolesTableName();
|
|
10960
|
-
const result = await this.db.execute(sql`
|
|
10961
|
-
INSERT INTO ${sql.raw(tableName)} (id, name, is_admin, default_permissions, collection_permissions)
|
|
10962
|
-
VALUES (
|
|
10963
|
-
${data.id},
|
|
10964
|
-
${data.name},
|
|
10965
|
-
${data.isAdmin ?? false},
|
|
10966
|
-
${data.defaultPermissions ? JSON.stringify(data.defaultPermissions) : null}::jsonb,
|
|
10967
|
-
${data.collectionPermissions ? JSON.stringify(data.collectionPermissions) : null}::jsonb
|
|
10968
|
-
)
|
|
10969
|
-
RETURNING id, name, is_admin, default_permissions, collection_permissions
|
|
10970
|
-
`);
|
|
10971
|
-
const row = result.rows[0];
|
|
10972
|
-
return {
|
|
10973
|
-
id: row.id,
|
|
10974
|
-
name: row.name,
|
|
10975
|
-
isAdmin: row.is_admin,
|
|
10976
|
-
defaultPermissions: row.default_permissions,
|
|
10977
|
-
collectionPermissions: row.collection_permissions
|
|
10747
|
+
roles
|
|
10978
10748
|
};
|
|
10979
10749
|
}
|
|
10980
|
-
async updateRole(id, data) {
|
|
10981
|
-
const existing = await this.getRoleById(id);
|
|
10982
|
-
if (!existing) return null;
|
|
10983
|
-
const tableName = this.getQualifiedRolesTableName();
|
|
10984
|
-
await this.db.execute(sql`
|
|
10985
|
-
UPDATE ${sql.raw(tableName)}
|
|
10986
|
-
SET
|
|
10987
|
-
name = ${data.name ?? existing.name},
|
|
10988
|
-
is_admin = ${data.isAdmin ?? existing.isAdmin},
|
|
10989
|
-
default_permissions = ${data.defaultPermissions ? JSON.stringify(data.defaultPermissions) : JSON.stringify(existing.defaultPermissions)}::jsonb,
|
|
10990
|
-
collection_permissions = ${data.collectionPermissions !== void 0 ? data.collectionPermissions ? JSON.stringify(data.collectionPermissions) : null : existing.collectionPermissions ? JSON.stringify(existing.collectionPermissions) : null}::jsonb
|
|
10991
|
-
WHERE id = ${id}
|
|
10992
|
-
`);
|
|
10993
|
-
return this.getRoleById(id);
|
|
10994
|
-
}
|
|
10995
|
-
async deleteRole(id) {
|
|
10996
|
-
const tableName = this.getQualifiedRolesTableName();
|
|
10997
|
-
await this.db.execute(sql`DELETE FROM ${sql.raw(tableName)} WHERE id = ${id}`);
|
|
10998
|
-
}
|
|
10999
10750
|
}
|
|
11000
10751
|
class RefreshTokenService {
|
|
11001
10752
|
constructor(db, tableOrTables) {
|
|
@@ -11191,11 +10942,9 @@ class PostgresAuthRepository {
|
|
|
11191
10942
|
constructor(db, tableOrTables) {
|
|
11192
10943
|
this.db = db;
|
|
11193
10944
|
this.userService = new UserService(db, tableOrTables);
|
|
11194
|
-
this.roleService = new RoleService(db, tableOrTables);
|
|
11195
10945
|
this.tokenRepository = new PostgresTokenRepository(db, tableOrTables);
|
|
11196
10946
|
}
|
|
11197
10947
|
userService;
|
|
11198
|
-
roleService;
|
|
11199
10948
|
tokenRepository;
|
|
11200
10949
|
// User operations (delegate to UserService)
|
|
11201
10950
|
async createUser(data) {
|
|
@@ -11255,25 +11004,56 @@ class PostgresAuthRepository {
|
|
|
11255
11004
|
async getUserWithRoles(userId) {
|
|
11256
11005
|
return this.userService.getUserWithRoles(userId);
|
|
11257
11006
|
}
|
|
11258
|
-
// Role operations (
|
|
11007
|
+
// Role operations (roles are inline on users, synthesized from string IDs)
|
|
11259
11008
|
async getRoleById(id) {
|
|
11260
|
-
return
|
|
11009
|
+
return {
|
|
11010
|
+
id,
|
|
11011
|
+
name: id,
|
|
11012
|
+
isAdmin: id === "admin",
|
|
11013
|
+
defaultPermissions: null,
|
|
11014
|
+
collectionPermissions: null
|
|
11015
|
+
};
|
|
11261
11016
|
}
|
|
11262
11017
|
async listRoles() {
|
|
11263
|
-
return
|
|
11018
|
+
return [{
|
|
11019
|
+
id: "admin",
|
|
11020
|
+
name: "Admin",
|
|
11021
|
+
isAdmin: true,
|
|
11022
|
+
defaultPermissions: null,
|
|
11023
|
+
collectionPermissions: null
|
|
11024
|
+
}, {
|
|
11025
|
+
id: "editor",
|
|
11026
|
+
name: "Editor",
|
|
11027
|
+
isAdmin: false,
|
|
11028
|
+
defaultPermissions: null,
|
|
11029
|
+
collectionPermissions: null
|
|
11030
|
+
}, {
|
|
11031
|
+
id: "viewer",
|
|
11032
|
+
name: "Viewer",
|
|
11033
|
+
isAdmin: false,
|
|
11034
|
+
defaultPermissions: null,
|
|
11035
|
+
collectionPermissions: null
|
|
11036
|
+
}];
|
|
11037
|
+
}
|
|
11038
|
+
async createRole(_data) {
|
|
11039
|
+
return {
|
|
11040
|
+
id: _data.id,
|
|
11041
|
+
name: _data.name,
|
|
11042
|
+
isAdmin: _data.isAdmin ?? false,
|
|
11043
|
+
defaultPermissions: _data.defaultPermissions ?? null,
|
|
11044
|
+
collectionPermissions: _data.collectionPermissions ?? null
|
|
11045
|
+
};
|
|
11264
11046
|
}
|
|
11265
|
-
async
|
|
11266
|
-
return
|
|
11267
|
-
|
|
11047
|
+
async updateRole(id, data) {
|
|
11048
|
+
return {
|
|
11049
|
+
id,
|
|
11050
|
+
name: data.name ?? id,
|
|
11051
|
+
isAdmin: data.isAdmin ?? id === "admin",
|
|
11268
11052
|
defaultPermissions: data.defaultPermissions ?? null,
|
|
11269
11053
|
collectionPermissions: data.collectionPermissions ?? null
|
|
11270
|
-
}
|
|
11271
|
-
}
|
|
11272
|
-
async updateRole(id, data) {
|
|
11273
|
-
return this.roleService.updateRole(id, data);
|
|
11054
|
+
};
|
|
11274
11055
|
}
|
|
11275
|
-
async deleteRole(
|
|
11276
|
-
await this.roleService.deleteRole(id);
|
|
11056
|
+
async deleteRole(_id) {
|
|
11277
11057
|
}
|
|
11278
11058
|
// Token operations (delegate to PostgresTokenRepository)
|
|
11279
11059
|
async createRefreshToken(userId, tokenHash, expiresAt, userAgent, ipAddress) {
|
|
@@ -11823,34 +11603,27 @@ function createPostgresBootstrapper(pgConfig) {
|
|
|
11823
11603
|
const internals = driverResult.internals;
|
|
11824
11604
|
const db = internals.db;
|
|
11825
11605
|
const registry = internals.registry;
|
|
11826
|
-
|
|
11606
|
+
const authCollection = authConfig.collection;
|
|
11607
|
+
await ensureAuthTablesExist(db, authCollection);
|
|
11827
11608
|
let emailService;
|
|
11828
11609
|
if (authConfig.email) {
|
|
11829
11610
|
emailService = createEmailService(authConfig.email);
|
|
11830
11611
|
}
|
|
11831
|
-
const
|
|
11832
|
-
const
|
|
11612
|
+
const tableName = authCollection ? "table" in authCollection && typeof authCollection.table === "string" ? authCollection.table : authCollection.slug : void 0;
|
|
11613
|
+
const usersTable = tableName ? registry.getTable(tableName) : void 0;
|
|
11833
11614
|
let usersSchemaName = "rebase";
|
|
11834
|
-
|
|
11835
|
-
|
|
11836
|
-
usersSchemaName = getTableConfig(customUsersTable).schema || "public";
|
|
11615
|
+
if (authCollection && "schema" in authCollection && typeof authCollection.schema === "string") {
|
|
11616
|
+
usersSchemaName = authCollection.schema;
|
|
11837
11617
|
}
|
|
11838
|
-
|
|
11839
|
-
|
|
11840
|
-
|
|
11841
|
-
const authTables = createAuthSchema(rolesSchemaName, usersSchemaName);
|
|
11842
|
-
if (customUsersTable) {
|
|
11843
|
-
authTables.users = customUsersTable;
|
|
11844
|
-
}
|
|
11845
|
-
if (customRolesTable) {
|
|
11846
|
-
authTables.roles = customRolesTable;
|
|
11618
|
+
const authTables = createAuthSchema(usersSchemaName);
|
|
11619
|
+
if (usersTable) {
|
|
11620
|
+
authTables.users = usersTable;
|
|
11847
11621
|
}
|
|
11848
11622
|
const userService = new UserService(db, authTables);
|
|
11849
|
-
const roleService = new RoleService(db, authTables);
|
|
11850
11623
|
const authRepository = new PostgresAuthRepository(db, authTables);
|
|
11851
11624
|
return {
|
|
11852
11625
|
userService,
|
|
11853
|
-
roleService,
|
|
11626
|
+
roleService: userService,
|
|
11854
11627
|
emailService,
|
|
11855
11628
|
authRepository
|
|
11856
11629
|
};
|
|
@@ -11955,17 +11728,12 @@ export {
|
|
|
11955
11728
|
mfaFactorsRelations,
|
|
11956
11729
|
passwordResetTokens,
|
|
11957
11730
|
passwordResetTokensRelations,
|
|
11958
|
-
rebaseSchema,
|
|
11959
11731
|
recoveryCodes,
|
|
11960
11732
|
recoveryCodesRelations,
|
|
11961
11733
|
refreshTokens,
|
|
11962
11734
|
refreshTokensRelations,
|
|
11963
|
-
roles,
|
|
11964
|
-
rolesRelations,
|
|
11965
11735
|
userIdentities,
|
|
11966
11736
|
userIdentitiesRelations,
|
|
11967
|
-
userRoles,
|
|
11968
|
-
userRolesRelations,
|
|
11969
11737
|
users,
|
|
11970
11738
|
usersRelations,
|
|
11971
11739
|
usersSchema
|