@rebasepro/server-core 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 +62 -25
- 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 +393 -315
- package/dist/index.es.js.map +1 -1
- package/dist/index.umd.js +393 -315
- package/dist/index.umd.js.map +1 -1
- package/dist/server-core/src/api/errors.d.ts +15 -0
- package/dist/server-core/src/api/rest/query-parser.d.ts +2 -0
- package/dist/server-core/src/api/types.d.ts +2 -1
- package/dist/server-core/src/auth/jwt.d.ts +10 -0
- package/dist/server-core/src/email/types.d.ts +1 -0
- package/dist/server-core/src/init.d.ts +31 -1
- 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 +5 -5
- package/src/api/errors.ts +20 -1
- package/src/api/openapi-generator.ts +1 -1
- package/src/api/rest/api-generator.ts +82 -24
- package/src/api/rest/query-parser.ts +184 -63
- package/src/api/server.ts +1 -1
- package/src/api/types.ts +2 -1
- package/src/auth/admin-routes.ts +2 -91
- package/src/auth/builtin-auth-adapter.ts +9 -70
- package/src/auth/custom-auth-adapter.ts +1 -1
- package/src/auth/jwt.ts +10 -0
- package/src/auth/routes.ts +5 -9
- package/src/email/smtp-email-service.ts +31 -0
- package/src/email/types.ts +1 -0
- package/src/init.ts +135 -31
- package/src/storage/image-transform.ts +2 -1
- package/test/admin-routes.test.ts +0 -169
- package/test/backend-hooks-admin.test.ts +0 -25
- package/test/custom-auth-adapter.test.ts +2 -10
- package/test/smtp-email-service.test.ts +169 -0
- package/dist/types/src/users/roles.d.ts +0 -14
- package/test.ts +0 -6
package/dist/index.es.js
CHANGED
|
@@ -146,6 +146,9 @@ class Vector {
|
|
|
146
146
|
this.value = value;
|
|
147
147
|
}
|
|
148
148
|
}
|
|
149
|
+
function isPostgresCollection(collection) {
|
|
150
|
+
return !collection.driver || collection.driver === "postgres";
|
|
151
|
+
}
|
|
149
152
|
function isSQLAdmin(admin) {
|
|
150
153
|
return !!admin && typeof admin.executeSql === "function";
|
|
151
154
|
}
|
|
@@ -2547,30 +2550,6 @@ class CollectionRegistry {
|
|
|
2547
2550
|
};
|
|
2548
2551
|
}
|
|
2549
2552
|
}
|
|
2550
|
-
function mapOperator$1(op) {
|
|
2551
|
-
switch (op) {
|
|
2552
|
-
case "==":
|
|
2553
|
-
return "eq";
|
|
2554
|
-
case "!=":
|
|
2555
|
-
return "neq";
|
|
2556
|
-
case ">":
|
|
2557
|
-
return "gt";
|
|
2558
|
-
case ">=":
|
|
2559
|
-
return "gte";
|
|
2560
|
-
case "<":
|
|
2561
|
-
return "lt";
|
|
2562
|
-
case "<=":
|
|
2563
|
-
return "lte";
|
|
2564
|
-
case "array-contains":
|
|
2565
|
-
return "cs";
|
|
2566
|
-
case "array-contains-any":
|
|
2567
|
-
return "csa";
|
|
2568
|
-
case "not-in":
|
|
2569
|
-
return "nin";
|
|
2570
|
-
default:
|
|
2571
|
-
return op;
|
|
2572
|
-
}
|
|
2573
|
-
}
|
|
2574
2553
|
class QueryBuilder {
|
|
2575
2554
|
constructor(collection) {
|
|
2576
2555
|
this.collection = collection;
|
|
@@ -2578,23 +2557,30 @@ class QueryBuilder {
|
|
|
2578
2557
|
params = {
|
|
2579
2558
|
where: {}
|
|
2580
2559
|
};
|
|
2581
|
-
|
|
2582
|
-
|
|
2583
|
-
|
|
2584
|
-
|
|
2585
|
-
|
|
2586
|
-
where(column, operator, value) {
|
|
2560
|
+
where(columnOrCondition, operator, value) {
|
|
2561
|
+
if (typeof columnOrCondition === "object" && columnOrCondition !== null && "type" in columnOrCondition) {
|
|
2562
|
+
this.params.logical = columnOrCondition;
|
|
2563
|
+
return this;
|
|
2564
|
+
}
|
|
2587
2565
|
if (!this.params.where) {
|
|
2588
2566
|
this.params.where = {};
|
|
2589
2567
|
}
|
|
2590
|
-
const
|
|
2591
|
-
|
|
2592
|
-
|
|
2593
|
-
|
|
2594
|
-
|
|
2595
|
-
|
|
2568
|
+
const column = columnOrCondition;
|
|
2569
|
+
const condition = [operator, value];
|
|
2570
|
+
const existing = this.params.where[column];
|
|
2571
|
+
if (existing === void 0) {
|
|
2572
|
+
this.params.where[column] = condition;
|
|
2573
|
+
} else if (Array.isArray(existing) && existing.length > 0 && Array.isArray(existing[0])) {
|
|
2574
|
+
this.params.where[column].push(condition);
|
|
2575
|
+
} else {
|
|
2576
|
+
let firstCondition;
|
|
2577
|
+
if (Array.isArray(existing) && existing.length === 2 && typeof existing[0] === "string") {
|
|
2578
|
+
firstCondition = existing;
|
|
2579
|
+
} else {
|
|
2580
|
+
firstCondition = ["==", existing];
|
|
2581
|
+
}
|
|
2582
|
+
this.params.where[column] = [firstCondition, condition];
|
|
2596
2583
|
}
|
|
2597
|
-
this.params.where[column] = mappedOp === "eq" ? String(formattedValue) : `${mappedOp}.${formattedValue}`;
|
|
2598
2584
|
return this;
|
|
2599
2585
|
}
|
|
2600
2586
|
/**
|
|
@@ -2804,6 +2790,9 @@ class ApiError extends Error {
|
|
|
2804
2790
|
return new ApiError(503, code2, message);
|
|
2805
2791
|
}
|
|
2806
2792
|
}
|
|
2793
|
+
function isRebaseApiError(error2) {
|
|
2794
|
+
return error2 instanceof Error;
|
|
2795
|
+
}
|
|
2807
2796
|
const errorHandler = (err, c) => {
|
|
2808
2797
|
const error2 = err;
|
|
2809
2798
|
if (error2 instanceof ApiError || error2.name === "ApiError") {
|
|
@@ -2917,68 +2906,184 @@ function mapOperator(op) {
|
|
|
2917
2906
|
return null;
|
|
2918
2907
|
}
|
|
2919
2908
|
}
|
|
2909
|
+
function getLastValue(val) {
|
|
2910
|
+
if (Array.isArray(val)) {
|
|
2911
|
+
return val[val.length - 1];
|
|
2912
|
+
}
|
|
2913
|
+
return val;
|
|
2914
|
+
}
|
|
2915
|
+
function parseLogicalString(str) {
|
|
2916
|
+
str = str.trim();
|
|
2917
|
+
if (str.startsWith("or(") && str.endsWith(")")) {
|
|
2918
|
+
const inner = str.slice(3, -1);
|
|
2919
|
+
return {
|
|
2920
|
+
type: "or",
|
|
2921
|
+
conditions: parseLogicalList(inner)
|
|
2922
|
+
};
|
|
2923
|
+
}
|
|
2924
|
+
if (str.startsWith("and(") && str.endsWith(")")) {
|
|
2925
|
+
const inner = str.slice(4, -1);
|
|
2926
|
+
return {
|
|
2927
|
+
type: "and",
|
|
2928
|
+
conditions: parseLogicalList(inner)
|
|
2929
|
+
};
|
|
2930
|
+
}
|
|
2931
|
+
const firstDot = str.indexOf(".");
|
|
2932
|
+
if (firstDot === -1) {
|
|
2933
|
+
return {
|
|
2934
|
+
column: str,
|
|
2935
|
+
operator: "==",
|
|
2936
|
+
value: true
|
|
2937
|
+
};
|
|
2938
|
+
}
|
|
2939
|
+
const field = str.substring(0, firstDot);
|
|
2940
|
+
const rest = str.substring(firstDot + 1);
|
|
2941
|
+
const secondDot = rest.indexOf(".");
|
|
2942
|
+
let op = "eq";
|
|
2943
|
+
let valStr = rest;
|
|
2944
|
+
if (secondDot !== -1) {
|
|
2945
|
+
op = rest.substring(0, secondDot);
|
|
2946
|
+
valStr = rest.substring(secondDot + 1);
|
|
2947
|
+
} else {
|
|
2948
|
+
op = "eq";
|
|
2949
|
+
valStr = rest;
|
|
2950
|
+
}
|
|
2951
|
+
const rebaseOp = mapOperator(op) || "==";
|
|
2952
|
+
let parsedVal = valStr;
|
|
2953
|
+
if (valStr === "true") parsedVal = true;
|
|
2954
|
+
else if (valStr === "false") parsedVal = false;
|
|
2955
|
+
else if (valStr === "null") parsedVal = null;
|
|
2956
|
+
else if (!isNaN(Number(valStr)) && valStr.trim() !== "") parsedVal = Number(valStr);
|
|
2957
|
+
else if (valStr.startsWith("(")) {
|
|
2958
|
+
const arrayContent = valStr.endsWith(")") ? valStr.slice(1, -1) : valStr.slice(1);
|
|
2959
|
+
parsedVal = arrayContent.split(",").map((v) => {
|
|
2960
|
+
const trimmed = v.trim();
|
|
2961
|
+
if (!isNaN(Number(trimmed)) && trimmed !== "") return Number(trimmed);
|
|
2962
|
+
if (trimmed === "true") return true;
|
|
2963
|
+
if (trimmed === "false") return false;
|
|
2964
|
+
if (trimmed === "null") return null;
|
|
2965
|
+
return trimmed;
|
|
2966
|
+
});
|
|
2967
|
+
}
|
|
2968
|
+
return {
|
|
2969
|
+
column: field,
|
|
2970
|
+
operator: rebaseOp,
|
|
2971
|
+
value: parsedVal
|
|
2972
|
+
};
|
|
2973
|
+
}
|
|
2974
|
+
function parseLogicalList(str) {
|
|
2975
|
+
const list = [];
|
|
2976
|
+
let depth = 0;
|
|
2977
|
+
let current = "";
|
|
2978
|
+
for (let i = 0; i < str.length; i++) {
|
|
2979
|
+
const char = str[i];
|
|
2980
|
+
if (char === "(") depth++;
|
|
2981
|
+
if (char === ")") depth--;
|
|
2982
|
+
if (char === "," && depth === 0) {
|
|
2983
|
+
list.push(parseLogicalString(current));
|
|
2984
|
+
current = "";
|
|
2985
|
+
} else {
|
|
2986
|
+
current += char;
|
|
2987
|
+
}
|
|
2988
|
+
}
|
|
2989
|
+
if (current) {
|
|
2990
|
+
list.push(parseLogicalString(current));
|
|
2991
|
+
}
|
|
2992
|
+
return list;
|
|
2993
|
+
}
|
|
2920
2994
|
function parseQueryOptions(query) {
|
|
2921
2995
|
const options2 = {};
|
|
2922
|
-
|
|
2923
|
-
if (
|
|
2924
|
-
|
|
2925
|
-
|
|
2996
|
+
const limitVal = getLastValue(query.limit);
|
|
2997
|
+
if (limitVal) options2.limit = parseInt(String(limitVal));
|
|
2998
|
+
const offsetVal = getLastValue(query.offset);
|
|
2999
|
+
if (offsetVal) options2.offset = parseInt(String(offsetVal));
|
|
3000
|
+
const pageVal = getLastValue(query.page);
|
|
3001
|
+
if (pageVal) {
|
|
3002
|
+
const page = parseInt(String(pageVal));
|
|
2926
3003
|
const limit = options2.limit || 20;
|
|
2927
3004
|
options2.offset = (page - 1) * limit;
|
|
2928
3005
|
}
|
|
2929
3006
|
options2.where = {};
|
|
2930
|
-
const
|
|
3007
|
+
const orVal = getLastValue(query.or);
|
|
3008
|
+
const andVal = getLastValue(query.and);
|
|
3009
|
+
if (orVal) {
|
|
3010
|
+
let orStr = String(orVal).trim();
|
|
3011
|
+
if (orStr.startsWith("(") && orStr.endsWith(")")) {
|
|
3012
|
+
orStr = orStr.slice(1, -1);
|
|
3013
|
+
}
|
|
3014
|
+
options2.logical = {
|
|
3015
|
+
type: "or",
|
|
3016
|
+
conditions: parseLogicalList(orStr)
|
|
3017
|
+
};
|
|
3018
|
+
} else if (andVal) {
|
|
3019
|
+
let andStr = String(andVal).trim();
|
|
3020
|
+
if (andStr.startsWith("(") && andStr.endsWith(")")) {
|
|
3021
|
+
andStr = andStr.slice(1, -1);
|
|
3022
|
+
}
|
|
3023
|
+
options2.logical = {
|
|
3024
|
+
type: "and",
|
|
3025
|
+
conditions: parseLogicalList(andStr)
|
|
3026
|
+
};
|
|
3027
|
+
}
|
|
3028
|
+
const reservedQueryKeys = ["limit", "offset", "page", "orderBy", "include", "fields", "searchString", "vector_search", "vector", "vector_distance", "vector_threshold", "or", "and"];
|
|
2931
3029
|
for (const [key, rawValue] of Object.entries(query)) {
|
|
2932
3030
|
if (reservedQueryKeys.includes(key)) continue;
|
|
2933
|
-
const
|
|
2934
|
-
|
|
2935
|
-
|
|
2936
|
-
if (
|
|
2937
|
-
const
|
|
2938
|
-
|
|
2939
|
-
|
|
2940
|
-
|
|
2941
|
-
|
|
2942
|
-
if (
|
|
2943
|
-
|
|
2944
|
-
|
|
2945
|
-
|
|
2946
|
-
|
|
2947
|
-
|
|
2948
|
-
|
|
2949
|
-
const
|
|
2950
|
-
|
|
2951
|
-
|
|
2952
|
-
|
|
2953
|
-
|
|
2954
|
-
|
|
2955
|
-
|
|
3031
|
+
const rawValues = Array.isArray(rawValue) ? rawValue : [rawValue];
|
|
3032
|
+
const conditions = [];
|
|
3033
|
+
for (const value of rawValues) {
|
|
3034
|
+
if (typeof value === "string") {
|
|
3035
|
+
const parts = value.split(".");
|
|
3036
|
+
if (parts.length >= 2) {
|
|
3037
|
+
const op = parts[0];
|
|
3038
|
+
const val = parts.slice(1).join(".");
|
|
3039
|
+
const rebaseOp = mapOperator(op);
|
|
3040
|
+
if (rebaseOp) {
|
|
3041
|
+
let parsedVal = val;
|
|
3042
|
+
if (val === "true") parsedVal = true;
|
|
3043
|
+
else if (val === "false") parsedVal = false;
|
|
3044
|
+
else if (val === "null") parsedVal = null;
|
|
3045
|
+
else if (!isNaN(Number(val)) && val.trim() !== "") parsedVal = Number(val);
|
|
3046
|
+
else if (val.startsWith("(")) {
|
|
3047
|
+
const arrayContent = val.endsWith(")") ? val.slice(1, -1) : val.slice(1);
|
|
3048
|
+
parsedVal = arrayContent.split(",").map((v) => {
|
|
3049
|
+
const trimmed = v.trim();
|
|
3050
|
+
if (!isNaN(Number(trimmed)) && trimmed !== "") return Number(trimmed);
|
|
3051
|
+
if (trimmed === "true") return true;
|
|
3052
|
+
if (trimmed === "false") return false;
|
|
3053
|
+
if (trimmed === "null") return null;
|
|
3054
|
+
return trimmed;
|
|
3055
|
+
});
|
|
3056
|
+
}
|
|
3057
|
+
conditions.push([rebaseOp, parsedVal]);
|
|
3058
|
+
} else {
|
|
3059
|
+
let parsedVal = value;
|
|
3060
|
+
if (!isNaN(Number(value)) && value.trim() !== "") parsedVal = Number(value);
|
|
3061
|
+
conditions.push(["==", parsedVal]);
|
|
2956
3062
|
}
|
|
2957
|
-
options2.where[key] = [rebaseOp, parsedVal];
|
|
2958
3063
|
} else {
|
|
2959
3064
|
let parsedVal = value;
|
|
2960
|
-
if (
|
|
2961
|
-
|
|
3065
|
+
if (value === "true") parsedVal = true;
|
|
3066
|
+
else if (value === "false") parsedVal = false;
|
|
3067
|
+
else if (value === "null") parsedVal = null;
|
|
3068
|
+
else if (!isNaN(Number(value)) && value.trim() !== "") parsedVal = Number(value);
|
|
3069
|
+
conditions.push(["==", parsedVal]);
|
|
2962
3070
|
}
|
|
2963
|
-
} else {
|
|
2964
|
-
let parsedVal = value;
|
|
2965
|
-
if (value === "true") parsedVal = true;
|
|
2966
|
-
else if (value === "false") parsedVal = false;
|
|
2967
|
-
else if (value === "null") parsedVal = null;
|
|
2968
|
-
else if (!isNaN(Number(value)) && value.trim() !== "") parsedVal = Number(value);
|
|
2969
|
-
options2.where[key] = ["==", parsedVal];
|
|
2970
3071
|
}
|
|
2971
3072
|
}
|
|
3073
|
+
if (conditions.length > 0) {
|
|
3074
|
+
options2.where[key] = conditions.length === 1 ? conditions[0] : conditions;
|
|
3075
|
+
}
|
|
2972
3076
|
}
|
|
2973
3077
|
if (Object.keys(options2.where).length === 0) {
|
|
2974
3078
|
delete options2.where;
|
|
2975
3079
|
}
|
|
2976
|
-
|
|
3080
|
+
const orderByVal = getLastValue(query.orderBy);
|
|
3081
|
+
if (orderByVal) {
|
|
2977
3082
|
try {
|
|
2978
|
-
options2.orderBy = typeof
|
|
3083
|
+
options2.orderBy = typeof orderByVal === "string" ? JSON.parse(orderByVal) : orderByVal;
|
|
2979
3084
|
} catch {
|
|
2980
|
-
if (typeof
|
|
2981
|
-
const [field, direction] =
|
|
3085
|
+
if (typeof orderByVal === "string") {
|
|
3086
|
+
const [field, direction] = orderByVal.split(":");
|
|
2982
3087
|
const dir = direction === "desc" ? "desc" : "asc";
|
|
2983
3088
|
options2.orderBy = [{
|
|
2984
3089
|
field,
|
|
@@ -2987,20 +3092,24 @@ function parseQueryOptions(query) {
|
|
|
2987
3092
|
}
|
|
2988
3093
|
}
|
|
2989
3094
|
}
|
|
2990
|
-
|
|
2991
|
-
|
|
3095
|
+
const includeVal = getLastValue(query.include);
|
|
3096
|
+
if (includeVal) {
|
|
3097
|
+
const includeStr = String(includeVal).trim();
|
|
2992
3098
|
if (includeStr === "*") {
|
|
2993
3099
|
options2.include = ["*"];
|
|
2994
3100
|
} else {
|
|
2995
3101
|
options2.include = includeStr.split(",").map((s2) => s2.trim()).filter(Boolean);
|
|
2996
3102
|
}
|
|
2997
3103
|
}
|
|
2998
|
-
|
|
2999
|
-
|
|
3104
|
+
const fieldsVal = getLastValue(query.fields);
|
|
3105
|
+
if (fieldsVal) {
|
|
3106
|
+
const fieldsStr = String(fieldsVal).trim();
|
|
3000
3107
|
options2.fields = fieldsStr.split(",").map((s2) => s2.trim()).filter(Boolean);
|
|
3001
3108
|
}
|
|
3002
|
-
|
|
3003
|
-
|
|
3109
|
+
const vectorSearchVal = getLastValue(query.vector_search);
|
|
3110
|
+
const vectorVal = getLastValue(query.vector);
|
|
3111
|
+
if (vectorSearchVal && vectorVal) {
|
|
3112
|
+
const vectorStr = String(vectorVal);
|
|
3004
3113
|
let queryVector;
|
|
3005
3114
|
try {
|
|
3006
3115
|
queryVector = JSON.parse(vectorStr);
|
|
@@ -3010,17 +3119,19 @@ function parseQueryOptions(query) {
|
|
|
3010
3119
|
} catch {
|
|
3011
3120
|
throw new Error("Invalid vector format. Expected JSON array of numbers, e.g. [0.1,0.2,0.3]");
|
|
3012
3121
|
}
|
|
3013
|
-
const
|
|
3122
|
+
const distanceParamVal = getLastValue(query.vector_distance);
|
|
3123
|
+
const distanceParam = distanceParamVal ? String(distanceParamVal) : "cosine";
|
|
3014
3124
|
if (distanceParam !== "cosine" && distanceParam !== "l2" && distanceParam !== "inner_product") {
|
|
3015
3125
|
throw new Error(`Invalid vector_distance: ${distanceParam}. Expected: cosine, l2, or inner_product`);
|
|
3016
3126
|
}
|
|
3017
3127
|
const vectorSearch = {
|
|
3018
|
-
property: String(
|
|
3128
|
+
property: String(vectorSearchVal),
|
|
3019
3129
|
vector: queryVector,
|
|
3020
3130
|
distance: distanceParam
|
|
3021
3131
|
};
|
|
3022
|
-
|
|
3023
|
-
|
|
3132
|
+
const thresholdVal = getLastValue(query.vector_threshold);
|
|
3133
|
+
if (thresholdVal) {
|
|
3134
|
+
const threshold = parseFloat(String(thresholdVal));
|
|
3024
3135
|
if (isNaN(threshold)) {
|
|
3025
3136
|
throw new Error("Invalid vector_threshold. Expected a number.");
|
|
3026
3137
|
}
|
|
@@ -3115,9 +3226,9 @@ class RestApiGenerator {
|
|
|
3115
3226
|
const resolvedCollection = collection;
|
|
3116
3227
|
this.router.get(`${basePath}/count`, async (c) => {
|
|
3117
3228
|
this.enforceApiKeyPermission(c, collection.slug);
|
|
3118
|
-
const queryDict = c.req.
|
|
3229
|
+
const queryDict = c.req.queries();
|
|
3119
3230
|
const queryOptions = parseQueryOptions(queryDict);
|
|
3120
|
-
const searchString = queryDict.searchString;
|
|
3231
|
+
const searchString = Array.isArray(queryDict.searchString) ? queryDict.searchString[queryDict.searchString.length - 1] : void 0;
|
|
3121
3232
|
const driver = c.get("driver") || this.driver;
|
|
3122
3233
|
const total = await this.countRawEntities(driver, resolvedCollection, queryOptions, searchString);
|
|
3123
3234
|
return c.json({
|
|
@@ -3126,9 +3237,9 @@ class RestApiGenerator {
|
|
|
3126
3237
|
});
|
|
3127
3238
|
this.router.get(basePath, async (c) => {
|
|
3128
3239
|
this.enforceApiKeyPermission(c, collection.slug);
|
|
3129
|
-
const queryDict = c.req.
|
|
3240
|
+
const queryDict = c.req.queries();
|
|
3130
3241
|
const queryOptions = parseQueryOptions(queryDict);
|
|
3131
|
-
const searchString = queryDict.searchString;
|
|
3242
|
+
const searchString = Array.isArray(queryDict.searchString) ? queryDict.searchString[queryDict.searchString.length - 1] : void 0;
|
|
3132
3243
|
const driver = c.get("driver") || this.driver;
|
|
3133
3244
|
const fetchService = this.getFetchService(driver);
|
|
3134
3245
|
const hookCtx = this.buildHookContext(c, "GET");
|
|
@@ -3171,7 +3282,7 @@ class RestApiGenerator {
|
|
|
3171
3282
|
this.router.get(`${basePath}/:id`, async (c) => {
|
|
3172
3283
|
this.enforceApiKeyPermission(c, collection.slug);
|
|
3173
3284
|
const id = c.req.param("id");
|
|
3174
|
-
const queryDict = c.req.
|
|
3285
|
+
const queryDict = c.req.queries();
|
|
3175
3286
|
const queryOptions = parseQueryOptions(queryDict);
|
|
3176
3287
|
const driver = c.get("driver") || this.driver;
|
|
3177
3288
|
const fetchService = this.getFetchService(driver);
|
|
@@ -3222,9 +3333,10 @@ class RestApiGenerator {
|
|
|
3222
3333
|
}
|
|
3223
3334
|
return c.json(response, 201);
|
|
3224
3335
|
} catch (error2) {
|
|
3225
|
-
|
|
3226
|
-
|
|
3227
|
-
|
|
3336
|
+
if (isRebaseApiError(error2) && !error2.code) {
|
|
3337
|
+
error2.code = "BAD_REQUEST";
|
|
3338
|
+
}
|
|
3339
|
+
throw error2;
|
|
3228
3340
|
}
|
|
3229
3341
|
});
|
|
3230
3342
|
this.router.put(`${basePath}/:id`, async (c) => {
|
|
@@ -3260,9 +3372,10 @@ class RestApiGenerator {
|
|
|
3260
3372
|
}
|
|
3261
3373
|
return c.json(response);
|
|
3262
3374
|
} catch (error2) {
|
|
3263
|
-
|
|
3264
|
-
|
|
3265
|
-
|
|
3375
|
+
if (isRebaseApiError(error2) && !error2.code) {
|
|
3376
|
+
error2.code = "BAD_REQUEST";
|
|
3377
|
+
}
|
|
3378
|
+
throw error2;
|
|
3266
3379
|
}
|
|
3267
3380
|
});
|
|
3268
3381
|
this.router.delete(`${basePath}/:id`, async (c) => {
|
|
@@ -3339,13 +3452,15 @@ class RestApiGenerator {
|
|
|
3339
3452
|
if (!parsed) return next();
|
|
3340
3453
|
const driver = c.get("driver") || this.driver;
|
|
3341
3454
|
this.enforceApiKeyPermission(c, c.req.param("parent"));
|
|
3455
|
+
const hookCtx = this.buildHookContext(c, "GET");
|
|
3342
3456
|
if (parsed.entityId === "count") {
|
|
3343
|
-
const queryDict = c.req.
|
|
3457
|
+
const queryDict = c.req.queries();
|
|
3344
3458
|
const queryOptions = parseQueryOptions(queryDict);
|
|
3459
|
+
const searchString = Array.isArray(queryDict.searchString) ? queryDict.searchString[queryDict.searchString.length - 1] : void 0;
|
|
3345
3460
|
const total = driver.countEntities ? await driver.countEntities({
|
|
3346
3461
|
path: parsed.collectionPath,
|
|
3347
3462
|
filter: queryOptions.where,
|
|
3348
|
-
searchString
|
|
3463
|
+
searchString
|
|
3349
3464
|
}) : 0;
|
|
3350
3465
|
return c.json({
|
|
3351
3466
|
count: total
|
|
@@ -3356,25 +3471,36 @@ class RestApiGenerator {
|
|
|
3356
3471
|
entityId: parsed.entityId
|
|
3357
3472
|
});
|
|
3358
3473
|
if (!entity) throw ApiError.notFound("Entity not found");
|
|
3359
|
-
|
|
3474
|
+
const flatResult = this.flattenEntity(entity);
|
|
3475
|
+
const hookResult = await this.applyAfterRead(c.req.param("parent"), flatResult, hookCtx);
|
|
3476
|
+
if (!hookResult) throw ApiError.notFound("Entity not found");
|
|
3477
|
+
return c.json(hookResult);
|
|
3360
3478
|
} else {
|
|
3361
|
-
const queryDict = c.req.
|
|
3479
|
+
const queryDict = c.req.queries();
|
|
3362
3480
|
const queryOptions = parseQueryOptions(queryDict);
|
|
3481
|
+
const searchString = Array.isArray(queryDict.searchString) ? queryDict.searchString[queryDict.searchString.length - 1] : void 0;
|
|
3363
3482
|
const entities = await driver.fetchCollection({
|
|
3364
3483
|
path: parsed.collectionPath,
|
|
3365
3484
|
filter: queryOptions.where,
|
|
3366
3485
|
limit: queryOptions.limit,
|
|
3367
3486
|
orderBy: queryOptions.orderBy?.[0]?.field,
|
|
3368
3487
|
order: queryOptions.orderBy?.[0]?.direction === "desc" ? "desc" : "asc",
|
|
3369
|
-
searchString
|
|
3488
|
+
searchString
|
|
3370
3489
|
});
|
|
3490
|
+
let flatEntities = entities.map((e) => this.flattenEntity(e));
|
|
3491
|
+
flatEntities = await this.applyAfterReadBatch(c.req.param("parent"), flatEntities, hookCtx);
|
|
3492
|
+
const total = driver.countEntities ? await driver.countEntities({
|
|
3493
|
+
path: parsed.collectionPath,
|
|
3494
|
+
filter: queryOptions.where,
|
|
3495
|
+
searchString
|
|
3496
|
+
}) : flatEntities.length;
|
|
3371
3497
|
return c.json({
|
|
3372
|
-
data:
|
|
3498
|
+
data: flatEntities,
|
|
3373
3499
|
meta: {
|
|
3374
|
-
total
|
|
3500
|
+
total,
|
|
3375
3501
|
limit: queryOptions.limit,
|
|
3376
3502
|
offset: queryOptions.offset,
|
|
3377
|
-
hasMore:
|
|
3503
|
+
hasMore: (queryOptions.offset || 0) + flatEntities.length < total
|
|
3378
3504
|
}
|
|
3379
3505
|
});
|
|
3380
3506
|
}
|
|
@@ -3386,14 +3512,24 @@ class RestApiGenerator {
|
|
|
3386
3512
|
const parsed = parseSubPath(rawPath);
|
|
3387
3513
|
if (!parsed || parsed.entityId) return next();
|
|
3388
3514
|
const driver = c.get("driver") || this.driver;
|
|
3515
|
+
const hookCtx = this.buildHookContext(c, "POST");
|
|
3389
3516
|
this.enforceApiKeyPermission(c, c.req.param("parent"));
|
|
3390
|
-
|
|
3517
|
+
let body = await c.req.json().catch(() => ({}));
|
|
3518
|
+
if (this.dataHooks?.beforeSave) {
|
|
3519
|
+
body = await this.dataHooks.beforeSave(parsed.collectionPath, body, void 0, hookCtx);
|
|
3520
|
+
}
|
|
3391
3521
|
const entity = await driver.saveEntity({
|
|
3392
3522
|
path: parsed.collectionPath,
|
|
3393
3523
|
values: body,
|
|
3394
3524
|
status: "new"
|
|
3395
3525
|
});
|
|
3396
|
-
|
|
3526
|
+
const response = this.formatResponse(entity);
|
|
3527
|
+
if (this.dataHooks?.afterSave) {
|
|
3528
|
+
Promise.resolve(this.dataHooks.afterSave(parsed.collectionPath, response, hookCtx)).catch((err) => {
|
|
3529
|
+
console.error("[BackendHooks] data.afterSave error:", err instanceof Error ? err.message : err);
|
|
3530
|
+
});
|
|
3531
|
+
}
|
|
3532
|
+
return c.json(response, 201);
|
|
3397
3533
|
});
|
|
3398
3534
|
this.router.put("/:parent/:parentId/:rest{.+}", async (c, next) => {
|
|
3399
3535
|
const rest = c.req.param("rest");
|
|
@@ -3402,15 +3538,25 @@ class RestApiGenerator {
|
|
|
3402
3538
|
const parsed = parseSubPath(rawPath);
|
|
3403
3539
|
if (!parsed || !parsed.entityId) return next();
|
|
3404
3540
|
const driver = c.get("driver") || this.driver;
|
|
3541
|
+
const hookCtx = this.buildHookContext(c, "PUT");
|
|
3405
3542
|
this.enforceApiKeyPermission(c, c.req.param("parent"));
|
|
3406
|
-
|
|
3543
|
+
let body = await c.req.json().catch(() => ({}));
|
|
3544
|
+
if (this.dataHooks?.beforeSave) {
|
|
3545
|
+
body = await this.dataHooks.beforeSave(parsed.collectionPath, body, parsed.entityId, hookCtx);
|
|
3546
|
+
}
|
|
3407
3547
|
const entity = await driver.saveEntity({
|
|
3408
3548
|
path: parsed.collectionPath,
|
|
3409
3549
|
entityId: parsed.entityId,
|
|
3410
3550
|
values: body,
|
|
3411
3551
|
status: "existing"
|
|
3412
3552
|
});
|
|
3413
|
-
|
|
3553
|
+
const response = this.formatResponse(entity);
|
|
3554
|
+
if (this.dataHooks?.afterSave) {
|
|
3555
|
+
Promise.resolve(this.dataHooks.afterSave(parsed.collectionPath, response, hookCtx)).catch((err) => {
|
|
3556
|
+
console.error("[BackendHooks] data.afterSave error:", err instanceof Error ? err.message : err);
|
|
3557
|
+
});
|
|
3558
|
+
}
|
|
3559
|
+
return c.json(response);
|
|
3414
3560
|
});
|
|
3415
3561
|
this.router.delete("/:parent/:parentId/:rest{.+}", async (c, next) => {
|
|
3416
3562
|
const rest = c.req.param("rest");
|
|
@@ -3419,15 +3565,24 @@ class RestApiGenerator {
|
|
|
3419
3565
|
const parsed = parseSubPath(rawPath);
|
|
3420
3566
|
if (!parsed || !parsed.entityId) return next();
|
|
3421
3567
|
const driver = c.get("driver") || this.driver;
|
|
3568
|
+
const hookCtx = this.buildHookContext(c, "DELETE");
|
|
3422
3569
|
this.enforceApiKeyPermission(c, c.req.param("parent"));
|
|
3423
3570
|
const existingEntity = await driver.fetchEntity({
|
|
3424
3571
|
path: parsed.collectionPath,
|
|
3425
3572
|
entityId: parsed.entityId
|
|
3426
3573
|
});
|
|
3427
3574
|
if (!existingEntity) throw ApiError.notFound("Entity not found");
|
|
3575
|
+
if (this.dataHooks?.beforeDelete) {
|
|
3576
|
+
await this.dataHooks.beforeDelete(parsed.collectionPath, parsed.entityId, hookCtx);
|
|
3577
|
+
}
|
|
3428
3578
|
await driver.deleteEntity({
|
|
3429
3579
|
entity: existingEntity
|
|
3430
3580
|
});
|
|
3581
|
+
if (this.dataHooks?.afterDelete) {
|
|
3582
|
+
Promise.resolve(this.dataHooks.afterDelete(parsed.collectionPath, parsed.entityId, hookCtx)).catch((err) => {
|
|
3583
|
+
console.error("[BackendHooks] data.afterDelete error:", err instanceof Error ? err.message : err);
|
|
3584
|
+
});
|
|
3585
|
+
}
|
|
3431
3586
|
return new Response(null, {
|
|
3432
3587
|
status: 204
|
|
3433
3588
|
});
|
|
@@ -12910,14 +13065,11 @@ function createAuthRoutes(config) {
|
|
|
12910
13065
|
if (!factor || factor.userId !== userCtx.userId) {
|
|
12911
13066
|
throw ApiError.notFound("MFA factor not found");
|
|
12912
13067
|
}
|
|
12913
|
-
const
|
|
12914
|
-
let isValid2 =
|
|
12915
|
-
if (
|
|
13068
|
+
const secretBuffer = base32Decode(factor.secretEncrypted);
|
|
13069
|
+
let isValid2 = verifyTotp(secretBuffer, code2);
|
|
13070
|
+
if (!isValid2) {
|
|
12916
13071
|
const codeHash = hashRecoveryCode(code2);
|
|
12917
13072
|
isValid2 = await authRepo.useRecoveryCode(userCtx.userId, codeHash);
|
|
12918
|
-
} else {
|
|
12919
|
-
const secretBuffer = base32Decode(factor.secretEncrypted);
|
|
12920
|
-
isValid2 = verifyTotp(secretBuffer, code2);
|
|
12921
13073
|
}
|
|
12922
13074
|
if (!isValid2) {
|
|
12923
13075
|
throw ApiError.unauthorized("Invalid verification code", "INVALID_CODE");
|
|
@@ -13035,11 +13187,6 @@ function createAdminRoutes(config) {
|
|
|
13035
13187
|
const results = await Promise.all(users.map((u) => applyUserAfterRead(u, ctx)));
|
|
13036
13188
|
return results.filter((u) => u !== null);
|
|
13037
13189
|
}
|
|
13038
|
-
async function applyRoleAfterReadBatch(roles, ctx) {
|
|
13039
|
-
if (!hooks?.roles?.afterRead) return roles;
|
|
13040
|
-
const results = await Promise.all(roles.map((r) => hooks.roles.afterRead(r, ctx)));
|
|
13041
|
-
return results.filter((r) => r !== null);
|
|
13042
|
-
}
|
|
13043
13190
|
function toAdminUser(u, roles) {
|
|
13044
13191
|
return {
|
|
13045
13192
|
uid: u.id,
|
|
@@ -13398,90 +13545,6 @@ function createAdminRoutes(config) {
|
|
|
13398
13545
|
success: true
|
|
13399
13546
|
});
|
|
13400
13547
|
});
|
|
13401
|
-
router.get("/roles", requireAdmin, async (c) => {
|
|
13402
|
-
const roles = await authRepo.listRoles();
|
|
13403
|
-
const hookCtx = buildHookContext(c, "GET");
|
|
13404
|
-
let adminRoles = roles.map((r) => ({
|
|
13405
|
-
id: r.id,
|
|
13406
|
-
name: r.name,
|
|
13407
|
-
isAdmin: r.isAdmin,
|
|
13408
|
-
defaultPermissions: r.defaultPermissions
|
|
13409
|
-
}));
|
|
13410
|
-
adminRoles = await applyRoleAfterReadBatch(adminRoles, hookCtx);
|
|
13411
|
-
return c.json({
|
|
13412
|
-
roles: adminRoles
|
|
13413
|
-
});
|
|
13414
|
-
});
|
|
13415
|
-
router.get("/roles/:roleId", requireAdmin, async (c) => {
|
|
13416
|
-
const roleId = c.req.param("roleId");
|
|
13417
|
-
const role = await authRepo.getRoleById(roleId);
|
|
13418
|
-
if (!role) {
|
|
13419
|
-
throw ApiError.notFound("Role not found");
|
|
13420
|
-
}
|
|
13421
|
-
return c.json({
|
|
13422
|
-
role
|
|
13423
|
-
});
|
|
13424
|
-
});
|
|
13425
|
-
router.post("/roles", requireAdmin, async (c) => {
|
|
13426
|
-
const body = await c.req.json();
|
|
13427
|
-
const {
|
|
13428
|
-
id,
|
|
13429
|
-
name: name2,
|
|
13430
|
-
isAdmin,
|
|
13431
|
-
defaultPermissions
|
|
13432
|
-
} = body;
|
|
13433
|
-
if (!id || !name2) {
|
|
13434
|
-
throw ApiError.badRequest("Role ID and name are required", "INVALID_INPUT");
|
|
13435
|
-
}
|
|
13436
|
-
const existing = await authRepo.getRoleById(id);
|
|
13437
|
-
if (existing) {
|
|
13438
|
-
throw ApiError.conflict("Role already exists", "ROLE_EXISTS");
|
|
13439
|
-
}
|
|
13440
|
-
const role = await authRepo.createRole({
|
|
13441
|
-
id,
|
|
13442
|
-
name: name2,
|
|
13443
|
-
isAdmin: isAdmin ?? false,
|
|
13444
|
-
defaultPermissions: defaultPermissions ?? null
|
|
13445
|
-
});
|
|
13446
|
-
return c.json({
|
|
13447
|
-
role
|
|
13448
|
-
}, 201);
|
|
13449
|
-
});
|
|
13450
|
-
router.put("/roles/:roleId", requireAdmin, async (c) => {
|
|
13451
|
-
const roleId = c.req.param("roleId");
|
|
13452
|
-
const body = await c.req.json();
|
|
13453
|
-
const {
|
|
13454
|
-
name: name2,
|
|
13455
|
-
isAdmin,
|
|
13456
|
-
defaultPermissions
|
|
13457
|
-
} = body;
|
|
13458
|
-
const existing = await authRepo.getRoleById(roleId);
|
|
13459
|
-
if (!existing) {
|
|
13460
|
-
throw ApiError.notFound("Role not found");
|
|
13461
|
-
}
|
|
13462
|
-
const role = await authRepo.updateRole(roleId, {
|
|
13463
|
-
name: name2,
|
|
13464
|
-
isAdmin,
|
|
13465
|
-
defaultPermissions
|
|
13466
|
-
});
|
|
13467
|
-
return c.json({
|
|
13468
|
-
role
|
|
13469
|
-
});
|
|
13470
|
-
});
|
|
13471
|
-
router.delete("/roles/:roleId", requireAdmin, async (c) => {
|
|
13472
|
-
const roleId = c.req.param("roleId");
|
|
13473
|
-
if (["admin", "editor", "viewer"].includes(roleId)) {
|
|
13474
|
-
throw ApiError.badRequest("Cannot delete built-in roles", "BUILTIN_ROLE");
|
|
13475
|
-
}
|
|
13476
|
-
const existing = await authRepo.getRoleById(roleId);
|
|
13477
|
-
if (!existing) {
|
|
13478
|
-
throw ApiError.notFound("Role not found");
|
|
13479
|
-
}
|
|
13480
|
-
await authRepo.deleteRole(roleId);
|
|
13481
|
-
return c.json({
|
|
13482
|
-
success: true
|
|
13483
|
-
});
|
|
13484
|
-
});
|
|
13485
13548
|
return router;
|
|
13486
13549
|
}
|
|
13487
13550
|
function createBuiltinAuthAdapter(config) {
|
|
@@ -13522,18 +13585,16 @@ function createBuiltinAuthAdapter(config) {
|
|
|
13522
13585
|
if (!payload) {
|
|
13523
13586
|
return null;
|
|
13524
13587
|
}
|
|
13525
|
-
const extendedPayload = payload;
|
|
13526
13588
|
let roles = payload.roles || [];
|
|
13527
13589
|
try {
|
|
13528
|
-
|
|
13529
|
-
roles = userRoles.map((r) => r.id);
|
|
13590
|
+
roles = await authRepository.getUserRoleIds(payload.userId);
|
|
13530
13591
|
} catch {
|
|
13531
13592
|
}
|
|
13532
13593
|
const isAdmin = roles.some((r) => r === "admin" || r === "schema-admin");
|
|
13533
13594
|
return {
|
|
13534
13595
|
uid: payload.userId,
|
|
13535
|
-
email:
|
|
13536
|
-
displayName:
|
|
13596
|
+
email: payload.email ?? "",
|
|
13597
|
+
displayName: payload.displayName ?? null,
|
|
13537
13598
|
roles,
|
|
13538
13599
|
isAdmin,
|
|
13539
13600
|
rawToken: token
|
|
@@ -13553,25 +13614,22 @@ function createBuiltinAuthAdapter(config) {
|
|
|
13553
13614
|
if (!payload) {
|
|
13554
13615
|
return null;
|
|
13555
13616
|
}
|
|
13556
|
-
const extendedPayload = payload;
|
|
13557
13617
|
let roles = payload.roles || [];
|
|
13558
13618
|
try {
|
|
13559
|
-
|
|
13560
|
-
roles = userRoles.map((r) => r.id);
|
|
13619
|
+
roles = await authRepository.getUserRoleIds(payload.userId);
|
|
13561
13620
|
} catch {
|
|
13562
13621
|
}
|
|
13563
13622
|
const isAdmin = roles.some((r) => r === "admin" || r === "schema-admin");
|
|
13564
13623
|
return {
|
|
13565
13624
|
uid: payload.userId,
|
|
13566
|
-
email:
|
|
13567
|
-
displayName:
|
|
13625
|
+
email: payload.email ?? "",
|
|
13626
|
+
displayName: payload.displayName ?? null,
|
|
13568
13627
|
roles,
|
|
13569
13628
|
isAdmin,
|
|
13570
13629
|
rawToken: token
|
|
13571
13630
|
};
|
|
13572
13631
|
},
|
|
13573
13632
|
userManagement: createUserManagementFromRepo(authRepository, resolvedOps, authHooks),
|
|
13574
|
-
roleManagement: createRoleManagementFromRepo(authRepository),
|
|
13575
13633
|
createAuthRoutes() {
|
|
13576
13634
|
return createAuthRoutes({
|
|
13577
13635
|
authRepo: authRepository,
|
|
@@ -13687,43 +13745,13 @@ function createUserManagementFromRepo(repo, resolvedOps, authHooks) {
|
|
|
13687
13745
|
}
|
|
13688
13746
|
},
|
|
13689
13747
|
async getUserRoles(userId) {
|
|
13690
|
-
|
|
13691
|
-
return roles.map(toAuthRoleData);
|
|
13748
|
+
return repo.getUserRoleIds(userId);
|
|
13692
13749
|
},
|
|
13693
13750
|
async setUserRoles(userId, roleIds) {
|
|
13694
13751
|
await repo.setUserRoles(userId, roleIds);
|
|
13695
13752
|
}
|
|
13696
13753
|
};
|
|
13697
13754
|
}
|
|
13698
|
-
function createRoleManagementFromRepo(repo) {
|
|
13699
|
-
return {
|
|
13700
|
-
async listRoles() {
|
|
13701
|
-
const roles = await repo.listRoles();
|
|
13702
|
-
return roles.map(toAuthRoleData);
|
|
13703
|
-
},
|
|
13704
|
-
async getRoleById(id) {
|
|
13705
|
-
const role = await repo.getRoleById(id);
|
|
13706
|
-
return role ? toAuthRoleData(role) : null;
|
|
13707
|
-
},
|
|
13708
|
-
async createRole(data) {
|
|
13709
|
-
const role = await repo.createRole({
|
|
13710
|
-
id: data.id,
|
|
13711
|
-
name: data.name,
|
|
13712
|
-
isAdmin: data.isAdmin,
|
|
13713
|
-
defaultPermissions: data.defaultPermissions,
|
|
13714
|
-
collectionPermissions: data.collectionPermissions
|
|
13715
|
-
});
|
|
13716
|
-
return toAuthRoleData(role);
|
|
13717
|
-
},
|
|
13718
|
-
async updateRole(id, data) {
|
|
13719
|
-
const role = await repo.updateRole(id, data);
|
|
13720
|
-
return role ? toAuthRoleData(role) : null;
|
|
13721
|
-
},
|
|
13722
|
-
async deleteRole(id) {
|
|
13723
|
-
await repo.deleteRole(id);
|
|
13724
|
-
}
|
|
13725
|
-
};
|
|
13726
|
-
}
|
|
13727
13755
|
function toAuthUserData(user) {
|
|
13728
13756
|
return {
|
|
13729
13757
|
id: user.id,
|
|
@@ -13736,15 +13764,6 @@ function toAuthUserData(user) {
|
|
|
13736
13764
|
updatedAt: user.updatedAt
|
|
13737
13765
|
};
|
|
13738
13766
|
}
|
|
13739
|
-
function toAuthRoleData(role) {
|
|
13740
|
-
return {
|
|
13741
|
-
id: role.id,
|
|
13742
|
-
name: role.name,
|
|
13743
|
-
isAdmin: role.isAdmin,
|
|
13744
|
-
defaultPermissions: role.defaultPermissions,
|
|
13745
|
-
collectionPermissions: role.collectionPermissions
|
|
13746
|
-
};
|
|
13747
|
-
}
|
|
13748
13767
|
function configureLogLevel(logLevel) {
|
|
13749
13768
|
const LOG_LEVEL = logLevel || process.env.LOG_LEVEL || "info";
|
|
13750
13769
|
const logLevels = {
|
|
@@ -24927,7 +24946,6 @@ function createCustomAuthAdapter(options2) {
|
|
|
24927
24946
|
verifyRequest: options2.verifyRequest,
|
|
24928
24947
|
verifyToken: resolvedVerifyToken,
|
|
24929
24948
|
userManagement: options2.userManagement,
|
|
24930
|
-
roleManagement: options2.roleManagement,
|
|
24931
24949
|
getCapabilities() {
|
|
24932
24950
|
return defaultCapabilities;
|
|
24933
24951
|
}
|
|
@@ -26225,15 +26243,33 @@ function normalizeWhereValue(value) {
|
|
|
26225
26243
|
if (value === null) return "eq.null";
|
|
26226
26244
|
if (typeof value === "boolean") return `eq.${value}`;
|
|
26227
26245
|
if (typeof value === "number") return String(value);
|
|
26228
|
-
if (Array.isArray(value)
|
|
26229
|
-
const [
|
|
26230
|
-
const
|
|
26231
|
-
if (
|
|
26232
|
-
|
|
26233
|
-
|
|
26246
|
+
if (Array.isArray(value)) {
|
|
26247
|
+
const conditions = Array.isArray(value[0]) ? value : [value];
|
|
26248
|
+
const [rawOp, val] = conditions[0] || [];
|
|
26249
|
+
if (rawOp) {
|
|
26250
|
+
const op = OP_MAP[rawOp] ?? rawOp;
|
|
26251
|
+
if (val === null) return `${op}.null`;
|
|
26252
|
+
if (Array.isArray(val)) return `${op}.(${val.join(",")})`;
|
|
26253
|
+
return `${op}.${val}`;
|
|
26254
|
+
}
|
|
26234
26255
|
}
|
|
26235
26256
|
return String(value);
|
|
26236
26257
|
}
|
|
26258
|
+
function serializeLogicalCondition(cond2) {
|
|
26259
|
+
if ("type" in cond2) {
|
|
26260
|
+
const sub = cond2.conditions.map(serializeLogicalCondition).join(",");
|
|
26261
|
+
return `${cond2.type}(${sub})`;
|
|
26262
|
+
} else {
|
|
26263
|
+
const op = OP_MAP[cond2.operator] ?? cond2.operator;
|
|
26264
|
+
let formattedValue = cond2.value;
|
|
26265
|
+
if (Array.isArray(cond2.value)) {
|
|
26266
|
+
formattedValue = `(${cond2.value.join(",")})`;
|
|
26267
|
+
} else if (cond2.value === null) {
|
|
26268
|
+
formattedValue = "null";
|
|
26269
|
+
}
|
|
26270
|
+
return `${cond2.column}.${op}.${formattedValue}`;
|
|
26271
|
+
}
|
|
26272
|
+
}
|
|
26237
26273
|
function buildQueryString(params) {
|
|
26238
26274
|
if (!params) return "";
|
|
26239
26275
|
const parts = [];
|
|
@@ -26249,10 +26285,22 @@ function buildQueryString(params) {
|
|
|
26249
26285
|
if (params.include && params.include.length > 0) {
|
|
26250
26286
|
parts.push(`include=${encodeURIComponent(params.include.join(","))}`);
|
|
26251
26287
|
}
|
|
26288
|
+
if (params.logical) {
|
|
26289
|
+
const root = params.logical;
|
|
26290
|
+
const serialized = root.conditions.map(serializeLogicalCondition).join(",");
|
|
26291
|
+
parts.push(`${root.type}=${encodeURIComponent(`(${serialized})`)}`);
|
|
26292
|
+
}
|
|
26252
26293
|
if (params.where) {
|
|
26253
26294
|
for (const [field, value] of Object.entries(params.where)) {
|
|
26254
|
-
|
|
26255
|
-
|
|
26295
|
+
if (Array.isArray(value) && value.length > 0 && Array.isArray(value[0])) {
|
|
26296
|
+
for (const subVal of value) {
|
|
26297
|
+
const normalized2 = normalizeWhereValue(subVal);
|
|
26298
|
+
parts.push(`${encodeURIComponent(field)}=${encodeURIComponent(normalized2)}`);
|
|
26299
|
+
}
|
|
26300
|
+
} else {
|
|
26301
|
+
const normalized2 = normalizeWhereValue(value);
|
|
26302
|
+
parts.push(`${encodeURIComponent(field)}=${encodeURIComponent(normalized2)}`);
|
|
26303
|
+
}
|
|
26256
26304
|
}
|
|
26257
26305
|
}
|
|
26258
26306
|
return parts.length > 0 ? "?" + parts.join("&") : "";
|
|
@@ -26927,33 +26975,6 @@ function createAdmin(transport, options2) {
|
|
|
26927
26975
|
method: "DELETE"
|
|
26928
26976
|
});
|
|
26929
26977
|
}
|
|
26930
|
-
async function listRoles() {
|
|
26931
|
-
return transport.request(adminPath + "/roles", {
|
|
26932
|
-
method: "GET"
|
|
26933
|
-
});
|
|
26934
|
-
}
|
|
26935
|
-
async function getRole(roleId) {
|
|
26936
|
-
return transport.request(adminPath + "/roles/" + encodeURIComponent(roleId), {
|
|
26937
|
-
method: "GET"
|
|
26938
|
-
});
|
|
26939
|
-
}
|
|
26940
|
-
async function createRole(data) {
|
|
26941
|
-
return transport.request(adminPath + "/roles", {
|
|
26942
|
-
method: "POST",
|
|
26943
|
-
body: JSON.stringify(data)
|
|
26944
|
-
});
|
|
26945
|
-
}
|
|
26946
|
-
async function updateRole(roleId, data) {
|
|
26947
|
-
return transport.request(adminPath + "/roles/" + encodeURIComponent(roleId), {
|
|
26948
|
-
method: "PUT",
|
|
26949
|
-
body: JSON.stringify(data)
|
|
26950
|
-
});
|
|
26951
|
-
}
|
|
26952
|
-
async function deleteRole(roleId) {
|
|
26953
|
-
return transport.request(adminPath + "/roles/" + encodeURIComponent(roleId), {
|
|
26954
|
-
method: "DELETE"
|
|
26955
|
-
});
|
|
26956
|
-
}
|
|
26957
26978
|
async function bootstrap() {
|
|
26958
26979
|
return transport.request(adminPath + "/bootstrap", {
|
|
26959
26980
|
method: "POST"
|
|
@@ -26966,11 +26987,6 @@ function createAdmin(transport, options2) {
|
|
|
26966
26987
|
createUser,
|
|
26967
26988
|
updateUser,
|
|
26968
26989
|
deleteUser,
|
|
26969
|
-
listRoles,
|
|
26970
|
-
getRole,
|
|
26971
|
-
createRole,
|
|
26972
|
-
updateRole,
|
|
26973
|
-
deleteRole,
|
|
26974
26990
|
bootstrap
|
|
26975
26991
|
};
|
|
26976
26992
|
}
|
|
@@ -27036,11 +27052,18 @@ function createCollectionClient(transport, slug, ws) {
|
|
|
27036
27052
|
};
|
|
27037
27053
|
},
|
|
27038
27054
|
async findById(id) {
|
|
27039
|
-
|
|
27040
|
-
|
|
27041
|
-
|
|
27042
|
-
|
|
27043
|
-
|
|
27055
|
+
try {
|
|
27056
|
+
const raw = await transport.request(`${basePath}/${encodeURIComponent(String(id))}`, {
|
|
27057
|
+
method: "GET"
|
|
27058
|
+
});
|
|
27059
|
+
if (!raw) return void 0;
|
|
27060
|
+
return rowToEntity(raw, slug);
|
|
27061
|
+
} catch (err) {
|
|
27062
|
+
if (err instanceof RebaseApiError && err.status === 404) {
|
|
27063
|
+
return void 0;
|
|
27064
|
+
}
|
|
27065
|
+
throw err;
|
|
27066
|
+
}
|
|
27044
27067
|
},
|
|
27045
27068
|
async create(data, id) {
|
|
27046
27069
|
const body = {
|
|
@@ -27080,8 +27103,12 @@ function createCollectionClient(transport, slug, ws) {
|
|
|
27080
27103
|
return raw.count ?? 0;
|
|
27081
27104
|
},
|
|
27082
27105
|
// Fluent builder instantiation
|
|
27083
|
-
where(
|
|
27084
|
-
|
|
27106
|
+
where(columnOrCondition, operator, value) {
|
|
27107
|
+
const builder = new QueryBuilder(client);
|
|
27108
|
+
if (typeof columnOrCondition === "object") {
|
|
27109
|
+
return builder.where(columnOrCondition);
|
|
27110
|
+
}
|
|
27111
|
+
return builder.where(columnOrCondition, operator, value);
|
|
27085
27112
|
},
|
|
27086
27113
|
orderBy(column, ascending) {
|
|
27087
27114
|
return new QueryBuilder(client).orderBy(column, ascending);
|
|
@@ -38359,13 +38386,35 @@ var createTransport = function(transporter, defaults) {
|
|
|
38359
38386
|
mailer2 = new Mailer(transporter, options2, defaults);
|
|
38360
38387
|
return mailer2;
|
|
38361
38388
|
};
|
|
38389
|
+
function getHostname(urlStr) {
|
|
38390
|
+
try {
|
|
38391
|
+
const url = new URL(urlStr.includes("://") ? urlStr : `https://${urlStr}`);
|
|
38392
|
+
return url.hostname;
|
|
38393
|
+
} catch {
|
|
38394
|
+
return void 0;
|
|
38395
|
+
}
|
|
38396
|
+
}
|
|
38362
38397
|
class SMTPEmailService {
|
|
38363
38398
|
transporter = null;
|
|
38364
38399
|
config;
|
|
38365
38400
|
constructor(config) {
|
|
38366
38401
|
this.config = config;
|
|
38367
38402
|
if (config.smtp) {
|
|
38403
|
+
let smtpName = config.smtp.name;
|
|
38404
|
+
if (!smtpName) {
|
|
38405
|
+
const urlsToTry = [process.env.FRONTEND_URL, config.resetPasswordUrl, config.verifyEmailUrl];
|
|
38406
|
+
for (const urlStr of urlsToTry) {
|
|
38407
|
+
if (urlStr) {
|
|
38408
|
+
const hostname = getHostname(urlStr);
|
|
38409
|
+
if (hostname) {
|
|
38410
|
+
smtpName = hostname;
|
|
38411
|
+
break;
|
|
38412
|
+
}
|
|
38413
|
+
}
|
|
38414
|
+
}
|
|
38415
|
+
}
|
|
38368
38416
|
this.transporter = createTransport({
|
|
38417
|
+
name: smtpName,
|
|
38369
38418
|
host: config.smtp.host,
|
|
38370
38419
|
port: config.smtp.port,
|
|
38371
38420
|
secure: config.smtp.secure ?? config.smtp.port === 465,
|
|
@@ -38515,6 +38564,14 @@ async function _initializeRebaseBackend(config) {
|
|
|
38515
38564
|
dir: config.collectionsDir
|
|
38516
38565
|
});
|
|
38517
38566
|
}
|
|
38567
|
+
if (config.defaultSecurityRules?.length) {
|
|
38568
|
+
for (const collection of activeCollections) {
|
|
38569
|
+
if (isPostgresCollection(collection) && (!collection.securityRules || collection.securityRules.length === 0)) {
|
|
38570
|
+
collection.securityRules = config.defaultSecurityRules;
|
|
38571
|
+
}
|
|
38572
|
+
}
|
|
38573
|
+
logger.info("Default security rules applied to collections without explicit rules");
|
|
38574
|
+
}
|
|
38518
38575
|
const realtimeServices = {};
|
|
38519
38576
|
const delegates = {};
|
|
38520
38577
|
let bootstrappers = config.bootstrappers || [];
|
|
@@ -38558,7 +38615,9 @@ async function _initializeRebaseBackend(config) {
|
|
|
38558
38615
|
}
|
|
38559
38616
|
if (bootstrapper.initializeRealtime) {
|
|
38560
38617
|
const realtime = await bootstrapper.initializeRealtime({}, driverResult);
|
|
38561
|
-
|
|
38618
|
+
if (realtime) {
|
|
38619
|
+
realtimeServices[b.id || bootstrapper.type] = realtime;
|
|
38620
|
+
}
|
|
38562
38621
|
}
|
|
38563
38622
|
}
|
|
38564
38623
|
const driverRegistry = DefaultDriverRegistry.create(delegates);
|
|
@@ -38583,8 +38642,7 @@ async function _initializeRebaseBackend(config) {
|
|
|
38583
38642
|
id: authAdapter.id
|
|
38584
38643
|
});
|
|
38585
38644
|
authConfigResult = {
|
|
38586
|
-
userService: authAdapter.userManagement ?? {}
|
|
38587
|
-
roleService: authAdapter.roleManagement ?? {}
|
|
38645
|
+
userService: authAdapter.userManagement ?? {}
|
|
38588
38646
|
};
|
|
38589
38647
|
} else {
|
|
38590
38648
|
const safeAuthConfig = config.auth;
|
|
@@ -38929,6 +38987,26 @@ async function _initializeRebaseBackend(config) {
|
|
|
38929
38987
|
logger.info("Email service attached to singleton", {
|
|
38930
38988
|
configured: emailService.isConfigured()
|
|
38931
38989
|
});
|
|
38990
|
+
if (emailService.isConfigured() && typeof emailService.verifyConnection === "function") {
|
|
38991
|
+
emailService.verifyConnection().then((success) => {
|
|
38992
|
+
if (!success) {
|
|
38993
|
+
logger.warn("Warning: SMTP connection verification failed. Email delivery may fail.");
|
|
38994
|
+
} else {
|
|
38995
|
+
logger.info("SMTP connection verified successfully.");
|
|
38996
|
+
}
|
|
38997
|
+
}).catch((err) => {
|
|
38998
|
+
logger.warn("Warning: SMTP connection verification failed. Email delivery may fail.", {
|
|
38999
|
+
error: err
|
|
39000
|
+
});
|
|
39001
|
+
});
|
|
39002
|
+
}
|
|
39003
|
+
}
|
|
39004
|
+
const driverAdmin = defaultBootstrapper.getAdmin?.(defaultDriverResult);
|
|
39005
|
+
if (isSQLAdmin(driverAdmin)) {
|
|
39006
|
+
Object.assign(serverClient, {
|
|
39007
|
+
sql: (query, options2) => driverAdmin.executeSql(query, options2)
|
|
39008
|
+
});
|
|
39009
|
+
logger.info("SQL capability attached to singleton");
|
|
38932
39010
|
}
|
|
38933
39011
|
_initRebase(serverClient);
|
|
38934
39012
|
logger.info("Rebase singleton initialized");
|
|
@@ -39018,7 +39096,7 @@ async function _initializeRebaseBackend(config) {
|
|
|
39018
39096
|
});
|
|
39019
39097
|
}
|
|
39020
39098
|
}
|
|
39021
|
-
if (defaultBootstrapper.initializeWebsockets) {
|
|
39099
|
+
if (defaultBootstrapper.initializeWebsockets && defaultRealtimeService) {
|
|
39022
39100
|
await defaultBootstrapper.initializeWebsockets(config.server, defaultRealtimeService, defaultDriver, config.auth, authAdapter);
|
|
39023
39101
|
}
|
|
39024
39102
|
logger.info("Rebase Backend Initialized");
|
|
@@ -39064,12 +39142,11 @@ async function _initializeRebaseBackend(config) {
|
|
|
39064
39142
|
}
|
|
39065
39143
|
for (const [key, rt] of Object.entries(realtimeServices)) {
|
|
39066
39144
|
try {
|
|
39067
|
-
|
|
39068
|
-
|
|
39069
|
-
await rtWithLifecycle.destroy();
|
|
39145
|
+
if (typeof rt.destroy === "function") {
|
|
39146
|
+
await rt.destroy();
|
|
39070
39147
|
logger.info(`Realtime service "${key}" destroyed`);
|
|
39071
|
-
} else if (typeof
|
|
39072
|
-
await
|
|
39148
|
+
} else if (typeof rt.stopListening === "function") {
|
|
39149
|
+
await rt.stopListening();
|
|
39073
39150
|
logger.info(`Realtime service "${key}" LISTEN client stopped`);
|
|
39074
39151
|
}
|
|
39075
39152
|
} catch (err) {
|
|
@@ -51216,6 +51293,7 @@ export {
|
|
|
51216
51293
|
isAuthAdapter,
|
|
51217
51294
|
isDatabaseAdapter,
|
|
51218
51295
|
isOperationAllowed,
|
|
51296
|
+
isRebaseApiError,
|
|
51219
51297
|
isTransformableImage,
|
|
51220
51298
|
listenWithPortRetry,
|
|
51221
51299
|
loadCronJobsFromDirectory,
|