@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.umd.js
CHANGED
|
@@ -133,6 +133,9 @@
|
|
|
133
133
|
this.value = value;
|
|
134
134
|
}
|
|
135
135
|
}
|
|
136
|
+
function isPostgresCollection(collection) {
|
|
137
|
+
return !collection.driver || collection.driver === "postgres";
|
|
138
|
+
}
|
|
136
139
|
function isSQLAdmin(admin) {
|
|
137
140
|
return !!admin && typeof admin.executeSql === "function";
|
|
138
141
|
}
|
|
@@ -2534,30 +2537,6 @@
|
|
|
2534
2537
|
};
|
|
2535
2538
|
}
|
|
2536
2539
|
}
|
|
2537
|
-
function mapOperator$1(op) {
|
|
2538
|
-
switch (op) {
|
|
2539
|
-
case "==":
|
|
2540
|
-
return "eq";
|
|
2541
|
-
case "!=":
|
|
2542
|
-
return "neq";
|
|
2543
|
-
case ">":
|
|
2544
|
-
return "gt";
|
|
2545
|
-
case ">=":
|
|
2546
|
-
return "gte";
|
|
2547
|
-
case "<":
|
|
2548
|
-
return "lt";
|
|
2549
|
-
case "<=":
|
|
2550
|
-
return "lte";
|
|
2551
|
-
case "array-contains":
|
|
2552
|
-
return "cs";
|
|
2553
|
-
case "array-contains-any":
|
|
2554
|
-
return "csa";
|
|
2555
|
-
case "not-in":
|
|
2556
|
-
return "nin";
|
|
2557
|
-
default:
|
|
2558
|
-
return op;
|
|
2559
|
-
}
|
|
2560
|
-
}
|
|
2561
2540
|
class QueryBuilder {
|
|
2562
2541
|
constructor(collection) {
|
|
2563
2542
|
this.collection = collection;
|
|
@@ -2565,23 +2544,30 @@
|
|
|
2565
2544
|
params = {
|
|
2566
2545
|
where: {}
|
|
2567
2546
|
};
|
|
2568
|
-
|
|
2569
|
-
|
|
2570
|
-
|
|
2571
|
-
|
|
2572
|
-
|
|
2573
|
-
where(column, operator, value) {
|
|
2547
|
+
where(columnOrCondition, operator, value) {
|
|
2548
|
+
if (typeof columnOrCondition === "object" && columnOrCondition !== null && "type" in columnOrCondition) {
|
|
2549
|
+
this.params.logical = columnOrCondition;
|
|
2550
|
+
return this;
|
|
2551
|
+
}
|
|
2574
2552
|
if (!this.params.where) {
|
|
2575
2553
|
this.params.where = {};
|
|
2576
2554
|
}
|
|
2577
|
-
const
|
|
2578
|
-
|
|
2579
|
-
|
|
2580
|
-
|
|
2581
|
-
|
|
2582
|
-
|
|
2555
|
+
const column = columnOrCondition;
|
|
2556
|
+
const condition = [operator, value];
|
|
2557
|
+
const existing = this.params.where[column];
|
|
2558
|
+
if (existing === void 0) {
|
|
2559
|
+
this.params.where[column] = condition;
|
|
2560
|
+
} else if (Array.isArray(existing) && existing.length > 0 && Array.isArray(existing[0])) {
|
|
2561
|
+
this.params.where[column].push(condition);
|
|
2562
|
+
} else {
|
|
2563
|
+
let firstCondition;
|
|
2564
|
+
if (Array.isArray(existing) && existing.length === 2 && typeof existing[0] === "string") {
|
|
2565
|
+
firstCondition = existing;
|
|
2566
|
+
} else {
|
|
2567
|
+
firstCondition = ["==", existing];
|
|
2568
|
+
}
|
|
2569
|
+
this.params.where[column] = [firstCondition, condition];
|
|
2583
2570
|
}
|
|
2584
|
-
this.params.where[column] = mappedOp === "eq" ? String(formattedValue) : `${mappedOp}.${formattedValue}`;
|
|
2585
2571
|
return this;
|
|
2586
2572
|
}
|
|
2587
2573
|
/**
|
|
@@ -2791,6 +2777,9 @@
|
|
|
2791
2777
|
return new ApiError(503, code2, message);
|
|
2792
2778
|
}
|
|
2793
2779
|
}
|
|
2780
|
+
function isRebaseApiError(error2) {
|
|
2781
|
+
return error2 instanceof Error;
|
|
2782
|
+
}
|
|
2794
2783
|
const errorHandler = (err, c) => {
|
|
2795
2784
|
const error2 = err;
|
|
2796
2785
|
if (error2 instanceof ApiError || error2.name === "ApiError") {
|
|
@@ -2904,68 +2893,184 @@
|
|
|
2904
2893
|
return null;
|
|
2905
2894
|
}
|
|
2906
2895
|
}
|
|
2896
|
+
function getLastValue(val) {
|
|
2897
|
+
if (Array.isArray(val)) {
|
|
2898
|
+
return val[val.length - 1];
|
|
2899
|
+
}
|
|
2900
|
+
return val;
|
|
2901
|
+
}
|
|
2902
|
+
function parseLogicalString(str) {
|
|
2903
|
+
str = str.trim();
|
|
2904
|
+
if (str.startsWith("or(") && str.endsWith(")")) {
|
|
2905
|
+
const inner = str.slice(3, -1);
|
|
2906
|
+
return {
|
|
2907
|
+
type: "or",
|
|
2908
|
+
conditions: parseLogicalList(inner)
|
|
2909
|
+
};
|
|
2910
|
+
}
|
|
2911
|
+
if (str.startsWith("and(") && str.endsWith(")")) {
|
|
2912
|
+
const inner = str.slice(4, -1);
|
|
2913
|
+
return {
|
|
2914
|
+
type: "and",
|
|
2915
|
+
conditions: parseLogicalList(inner)
|
|
2916
|
+
};
|
|
2917
|
+
}
|
|
2918
|
+
const firstDot = str.indexOf(".");
|
|
2919
|
+
if (firstDot === -1) {
|
|
2920
|
+
return {
|
|
2921
|
+
column: str,
|
|
2922
|
+
operator: "==",
|
|
2923
|
+
value: true
|
|
2924
|
+
};
|
|
2925
|
+
}
|
|
2926
|
+
const field = str.substring(0, firstDot);
|
|
2927
|
+
const rest = str.substring(firstDot + 1);
|
|
2928
|
+
const secondDot = rest.indexOf(".");
|
|
2929
|
+
let op = "eq";
|
|
2930
|
+
let valStr = rest;
|
|
2931
|
+
if (secondDot !== -1) {
|
|
2932
|
+
op = rest.substring(0, secondDot);
|
|
2933
|
+
valStr = rest.substring(secondDot + 1);
|
|
2934
|
+
} else {
|
|
2935
|
+
op = "eq";
|
|
2936
|
+
valStr = rest;
|
|
2937
|
+
}
|
|
2938
|
+
const rebaseOp = mapOperator(op) || "==";
|
|
2939
|
+
let parsedVal = valStr;
|
|
2940
|
+
if (valStr === "true") parsedVal = true;
|
|
2941
|
+
else if (valStr === "false") parsedVal = false;
|
|
2942
|
+
else if (valStr === "null") parsedVal = null;
|
|
2943
|
+
else if (!isNaN(Number(valStr)) && valStr.trim() !== "") parsedVal = Number(valStr);
|
|
2944
|
+
else if (valStr.startsWith("(")) {
|
|
2945
|
+
const arrayContent = valStr.endsWith(")") ? valStr.slice(1, -1) : valStr.slice(1);
|
|
2946
|
+
parsedVal = arrayContent.split(",").map((v) => {
|
|
2947
|
+
const trimmed = v.trim();
|
|
2948
|
+
if (!isNaN(Number(trimmed)) && trimmed !== "") return Number(trimmed);
|
|
2949
|
+
if (trimmed === "true") return true;
|
|
2950
|
+
if (trimmed === "false") return false;
|
|
2951
|
+
if (trimmed === "null") return null;
|
|
2952
|
+
return trimmed;
|
|
2953
|
+
});
|
|
2954
|
+
}
|
|
2955
|
+
return {
|
|
2956
|
+
column: field,
|
|
2957
|
+
operator: rebaseOp,
|
|
2958
|
+
value: parsedVal
|
|
2959
|
+
};
|
|
2960
|
+
}
|
|
2961
|
+
function parseLogicalList(str) {
|
|
2962
|
+
const list = [];
|
|
2963
|
+
let depth = 0;
|
|
2964
|
+
let current = "";
|
|
2965
|
+
for (let i2 = 0; i2 < str.length; i2++) {
|
|
2966
|
+
const char = str[i2];
|
|
2967
|
+
if (char === "(") depth++;
|
|
2968
|
+
if (char === ")") depth--;
|
|
2969
|
+
if (char === "," && depth === 0) {
|
|
2970
|
+
list.push(parseLogicalString(current));
|
|
2971
|
+
current = "";
|
|
2972
|
+
} else {
|
|
2973
|
+
current += char;
|
|
2974
|
+
}
|
|
2975
|
+
}
|
|
2976
|
+
if (current) {
|
|
2977
|
+
list.push(parseLogicalString(current));
|
|
2978
|
+
}
|
|
2979
|
+
return list;
|
|
2980
|
+
}
|
|
2907
2981
|
function parseQueryOptions(query) {
|
|
2908
2982
|
const options2 = {};
|
|
2909
|
-
|
|
2910
|
-
if (
|
|
2911
|
-
|
|
2912
|
-
|
|
2983
|
+
const limitVal = getLastValue(query.limit);
|
|
2984
|
+
if (limitVal) options2.limit = parseInt(String(limitVal));
|
|
2985
|
+
const offsetVal = getLastValue(query.offset);
|
|
2986
|
+
if (offsetVal) options2.offset = parseInt(String(offsetVal));
|
|
2987
|
+
const pageVal = getLastValue(query.page);
|
|
2988
|
+
if (pageVal) {
|
|
2989
|
+
const page = parseInt(String(pageVal));
|
|
2913
2990
|
const limit = options2.limit || 20;
|
|
2914
2991
|
options2.offset = (page - 1) * limit;
|
|
2915
2992
|
}
|
|
2916
2993
|
options2.where = {};
|
|
2917
|
-
const
|
|
2994
|
+
const orVal = getLastValue(query.or);
|
|
2995
|
+
const andVal = getLastValue(query.and);
|
|
2996
|
+
if (orVal) {
|
|
2997
|
+
let orStr = String(orVal).trim();
|
|
2998
|
+
if (orStr.startsWith("(") && orStr.endsWith(")")) {
|
|
2999
|
+
orStr = orStr.slice(1, -1);
|
|
3000
|
+
}
|
|
3001
|
+
options2.logical = {
|
|
3002
|
+
type: "or",
|
|
3003
|
+
conditions: parseLogicalList(orStr)
|
|
3004
|
+
};
|
|
3005
|
+
} else if (andVal) {
|
|
3006
|
+
let andStr = String(andVal).trim();
|
|
3007
|
+
if (andStr.startsWith("(") && andStr.endsWith(")")) {
|
|
3008
|
+
andStr = andStr.slice(1, -1);
|
|
3009
|
+
}
|
|
3010
|
+
options2.logical = {
|
|
3011
|
+
type: "and",
|
|
3012
|
+
conditions: parseLogicalList(andStr)
|
|
3013
|
+
};
|
|
3014
|
+
}
|
|
3015
|
+
const reservedQueryKeys = ["limit", "offset", "page", "orderBy", "include", "fields", "searchString", "vector_search", "vector", "vector_distance", "vector_threshold", "or", "and"];
|
|
2918
3016
|
for (const [key, rawValue] of Object.entries(query)) {
|
|
2919
3017
|
if (reservedQueryKeys.includes(key)) continue;
|
|
2920
|
-
const
|
|
2921
|
-
|
|
2922
|
-
|
|
2923
|
-
if (
|
|
2924
|
-
const
|
|
2925
|
-
|
|
2926
|
-
|
|
2927
|
-
|
|
2928
|
-
|
|
2929
|
-
if (
|
|
2930
|
-
|
|
2931
|
-
|
|
2932
|
-
|
|
2933
|
-
|
|
2934
|
-
|
|
2935
|
-
|
|
2936
|
-
const
|
|
2937
|
-
|
|
2938
|
-
|
|
2939
|
-
|
|
2940
|
-
|
|
2941
|
-
|
|
2942
|
-
|
|
3018
|
+
const rawValues = Array.isArray(rawValue) ? rawValue : [rawValue];
|
|
3019
|
+
const conditions = [];
|
|
3020
|
+
for (const value of rawValues) {
|
|
3021
|
+
if (typeof value === "string") {
|
|
3022
|
+
const parts = value.split(".");
|
|
3023
|
+
if (parts.length >= 2) {
|
|
3024
|
+
const op = parts[0];
|
|
3025
|
+
const val = parts.slice(1).join(".");
|
|
3026
|
+
const rebaseOp = mapOperator(op);
|
|
3027
|
+
if (rebaseOp) {
|
|
3028
|
+
let parsedVal = val;
|
|
3029
|
+
if (val === "true") parsedVal = true;
|
|
3030
|
+
else if (val === "false") parsedVal = false;
|
|
3031
|
+
else if (val === "null") parsedVal = null;
|
|
3032
|
+
else if (!isNaN(Number(val)) && val.trim() !== "") parsedVal = Number(val);
|
|
3033
|
+
else if (val.startsWith("(")) {
|
|
3034
|
+
const arrayContent = val.endsWith(")") ? val.slice(1, -1) : val.slice(1);
|
|
3035
|
+
parsedVal = arrayContent.split(",").map((v) => {
|
|
3036
|
+
const trimmed = v.trim();
|
|
3037
|
+
if (!isNaN(Number(trimmed)) && trimmed !== "") return Number(trimmed);
|
|
3038
|
+
if (trimmed === "true") return true;
|
|
3039
|
+
if (trimmed === "false") return false;
|
|
3040
|
+
if (trimmed === "null") return null;
|
|
3041
|
+
return trimmed;
|
|
3042
|
+
});
|
|
3043
|
+
}
|
|
3044
|
+
conditions.push([rebaseOp, parsedVal]);
|
|
3045
|
+
} else {
|
|
3046
|
+
let parsedVal = value;
|
|
3047
|
+
if (!isNaN(Number(value)) && value.trim() !== "") parsedVal = Number(value);
|
|
3048
|
+
conditions.push(["==", parsedVal]);
|
|
2943
3049
|
}
|
|
2944
|
-
options2.where[key] = [rebaseOp, parsedVal];
|
|
2945
3050
|
} else {
|
|
2946
3051
|
let parsedVal = value;
|
|
2947
|
-
if (
|
|
2948
|
-
|
|
3052
|
+
if (value === "true") parsedVal = true;
|
|
3053
|
+
else if (value === "false") parsedVal = false;
|
|
3054
|
+
else if (value === "null") parsedVal = null;
|
|
3055
|
+
else if (!isNaN(Number(value)) && value.trim() !== "") parsedVal = Number(value);
|
|
3056
|
+
conditions.push(["==", parsedVal]);
|
|
2949
3057
|
}
|
|
2950
|
-
} else {
|
|
2951
|
-
let parsedVal = value;
|
|
2952
|
-
if (value === "true") parsedVal = true;
|
|
2953
|
-
else if (value === "false") parsedVal = false;
|
|
2954
|
-
else if (value === "null") parsedVal = null;
|
|
2955
|
-
else if (!isNaN(Number(value)) && value.trim() !== "") parsedVal = Number(value);
|
|
2956
|
-
options2.where[key] = ["==", parsedVal];
|
|
2957
3058
|
}
|
|
2958
3059
|
}
|
|
3060
|
+
if (conditions.length > 0) {
|
|
3061
|
+
options2.where[key] = conditions.length === 1 ? conditions[0] : conditions;
|
|
3062
|
+
}
|
|
2959
3063
|
}
|
|
2960
3064
|
if (Object.keys(options2.where).length === 0) {
|
|
2961
3065
|
delete options2.where;
|
|
2962
3066
|
}
|
|
2963
|
-
|
|
3067
|
+
const orderByVal = getLastValue(query.orderBy);
|
|
3068
|
+
if (orderByVal) {
|
|
2964
3069
|
try {
|
|
2965
|
-
options2.orderBy = typeof
|
|
3070
|
+
options2.orderBy = typeof orderByVal === "string" ? JSON.parse(orderByVal) : orderByVal;
|
|
2966
3071
|
} catch {
|
|
2967
|
-
if (typeof
|
|
2968
|
-
const [field, direction] =
|
|
3072
|
+
if (typeof orderByVal === "string") {
|
|
3073
|
+
const [field, direction] = orderByVal.split(":");
|
|
2969
3074
|
const dir = direction === "desc" ? "desc" : "asc";
|
|
2970
3075
|
options2.orderBy = [{
|
|
2971
3076
|
field,
|
|
@@ -2974,20 +3079,24 @@
|
|
|
2974
3079
|
}
|
|
2975
3080
|
}
|
|
2976
3081
|
}
|
|
2977
|
-
|
|
2978
|
-
|
|
3082
|
+
const includeVal = getLastValue(query.include);
|
|
3083
|
+
if (includeVal) {
|
|
3084
|
+
const includeStr = String(includeVal).trim();
|
|
2979
3085
|
if (includeStr === "*") {
|
|
2980
3086
|
options2.include = ["*"];
|
|
2981
3087
|
} else {
|
|
2982
3088
|
options2.include = includeStr.split(",").map((s2) => s2.trim()).filter(Boolean);
|
|
2983
3089
|
}
|
|
2984
3090
|
}
|
|
2985
|
-
|
|
2986
|
-
|
|
3091
|
+
const fieldsVal = getLastValue(query.fields);
|
|
3092
|
+
if (fieldsVal) {
|
|
3093
|
+
const fieldsStr = String(fieldsVal).trim();
|
|
2987
3094
|
options2.fields = fieldsStr.split(",").map((s2) => s2.trim()).filter(Boolean);
|
|
2988
3095
|
}
|
|
2989
|
-
|
|
2990
|
-
|
|
3096
|
+
const vectorSearchVal = getLastValue(query.vector_search);
|
|
3097
|
+
const vectorVal = getLastValue(query.vector);
|
|
3098
|
+
if (vectorSearchVal && vectorVal) {
|
|
3099
|
+
const vectorStr = String(vectorVal);
|
|
2991
3100
|
let queryVector;
|
|
2992
3101
|
try {
|
|
2993
3102
|
queryVector = JSON.parse(vectorStr);
|
|
@@ -2997,17 +3106,19 @@
|
|
|
2997
3106
|
} catch {
|
|
2998
3107
|
throw new Error("Invalid vector format. Expected JSON array of numbers, e.g. [0.1,0.2,0.3]");
|
|
2999
3108
|
}
|
|
3000
|
-
const
|
|
3109
|
+
const distanceParamVal = getLastValue(query.vector_distance);
|
|
3110
|
+
const distanceParam = distanceParamVal ? String(distanceParamVal) : "cosine";
|
|
3001
3111
|
if (distanceParam !== "cosine" && distanceParam !== "l2" && distanceParam !== "inner_product") {
|
|
3002
3112
|
throw new Error(`Invalid vector_distance: ${distanceParam}. Expected: cosine, l2, or inner_product`);
|
|
3003
3113
|
}
|
|
3004
3114
|
const vectorSearch = {
|
|
3005
|
-
property: String(
|
|
3115
|
+
property: String(vectorSearchVal),
|
|
3006
3116
|
vector: queryVector,
|
|
3007
3117
|
distance: distanceParam
|
|
3008
3118
|
};
|
|
3009
|
-
|
|
3010
|
-
|
|
3119
|
+
const thresholdVal = getLastValue(query.vector_threshold);
|
|
3120
|
+
if (thresholdVal) {
|
|
3121
|
+
const threshold = parseFloat(String(thresholdVal));
|
|
3011
3122
|
if (isNaN(threshold)) {
|
|
3012
3123
|
throw new Error("Invalid vector_threshold. Expected a number.");
|
|
3013
3124
|
}
|
|
@@ -3102,9 +3213,9 @@
|
|
|
3102
3213
|
const resolvedCollection = collection;
|
|
3103
3214
|
this.router.get(`${basePath}/count`, async (c) => {
|
|
3104
3215
|
this.enforceApiKeyPermission(c, collection.slug);
|
|
3105
|
-
const queryDict = c.req.
|
|
3216
|
+
const queryDict = c.req.queries();
|
|
3106
3217
|
const queryOptions = parseQueryOptions(queryDict);
|
|
3107
|
-
const searchString = queryDict.searchString;
|
|
3218
|
+
const searchString = Array.isArray(queryDict.searchString) ? queryDict.searchString[queryDict.searchString.length - 1] : void 0;
|
|
3108
3219
|
const driver = c.get("driver") || this.driver;
|
|
3109
3220
|
const total = await this.countRawEntities(driver, resolvedCollection, queryOptions, searchString);
|
|
3110
3221
|
return c.json({
|
|
@@ -3113,9 +3224,9 @@
|
|
|
3113
3224
|
});
|
|
3114
3225
|
this.router.get(basePath, async (c) => {
|
|
3115
3226
|
this.enforceApiKeyPermission(c, collection.slug);
|
|
3116
|
-
const queryDict = c.req.
|
|
3227
|
+
const queryDict = c.req.queries();
|
|
3117
3228
|
const queryOptions = parseQueryOptions(queryDict);
|
|
3118
|
-
const searchString = queryDict.searchString;
|
|
3229
|
+
const searchString = Array.isArray(queryDict.searchString) ? queryDict.searchString[queryDict.searchString.length - 1] : void 0;
|
|
3119
3230
|
const driver = c.get("driver") || this.driver;
|
|
3120
3231
|
const fetchService = this.getFetchService(driver);
|
|
3121
3232
|
const hookCtx = this.buildHookContext(c, "GET");
|
|
@@ -3158,7 +3269,7 @@
|
|
|
3158
3269
|
this.router.get(`${basePath}/:id`, async (c) => {
|
|
3159
3270
|
this.enforceApiKeyPermission(c, collection.slug);
|
|
3160
3271
|
const id = c.req.param("id");
|
|
3161
|
-
const queryDict = c.req.
|
|
3272
|
+
const queryDict = c.req.queries();
|
|
3162
3273
|
const queryOptions = parseQueryOptions(queryDict);
|
|
3163
3274
|
const driver = c.get("driver") || this.driver;
|
|
3164
3275
|
const fetchService = this.getFetchService(driver);
|
|
@@ -3209,9 +3320,10 @@
|
|
|
3209
3320
|
}
|
|
3210
3321
|
return c.json(response, 201);
|
|
3211
3322
|
} catch (error2) {
|
|
3212
|
-
|
|
3213
|
-
|
|
3214
|
-
|
|
3323
|
+
if (isRebaseApiError(error2) && !error2.code) {
|
|
3324
|
+
error2.code = "BAD_REQUEST";
|
|
3325
|
+
}
|
|
3326
|
+
throw error2;
|
|
3215
3327
|
}
|
|
3216
3328
|
});
|
|
3217
3329
|
this.router.put(`${basePath}/:id`, async (c) => {
|
|
@@ -3247,9 +3359,10 @@
|
|
|
3247
3359
|
}
|
|
3248
3360
|
return c.json(response);
|
|
3249
3361
|
} catch (error2) {
|
|
3250
|
-
|
|
3251
|
-
|
|
3252
|
-
|
|
3362
|
+
if (isRebaseApiError(error2) && !error2.code) {
|
|
3363
|
+
error2.code = "BAD_REQUEST";
|
|
3364
|
+
}
|
|
3365
|
+
throw error2;
|
|
3253
3366
|
}
|
|
3254
3367
|
});
|
|
3255
3368
|
this.router.delete(`${basePath}/:id`, async (c) => {
|
|
@@ -3326,13 +3439,15 @@
|
|
|
3326
3439
|
if (!parsed) return next();
|
|
3327
3440
|
const driver = c.get("driver") || this.driver;
|
|
3328
3441
|
this.enforceApiKeyPermission(c, c.req.param("parent"));
|
|
3442
|
+
const hookCtx = this.buildHookContext(c, "GET");
|
|
3329
3443
|
if (parsed.entityId === "count") {
|
|
3330
|
-
const queryDict = c.req.
|
|
3444
|
+
const queryDict = c.req.queries();
|
|
3331
3445
|
const queryOptions = parseQueryOptions(queryDict);
|
|
3446
|
+
const searchString = Array.isArray(queryDict.searchString) ? queryDict.searchString[queryDict.searchString.length - 1] : void 0;
|
|
3332
3447
|
const total = driver.countEntities ? await driver.countEntities({
|
|
3333
3448
|
path: parsed.collectionPath,
|
|
3334
3449
|
filter: queryOptions.where,
|
|
3335
|
-
searchString
|
|
3450
|
+
searchString
|
|
3336
3451
|
}) : 0;
|
|
3337
3452
|
return c.json({
|
|
3338
3453
|
count: total
|
|
@@ -3343,25 +3458,36 @@
|
|
|
3343
3458
|
entityId: parsed.entityId
|
|
3344
3459
|
});
|
|
3345
3460
|
if (!entity) throw ApiError.notFound("Entity not found");
|
|
3346
|
-
|
|
3461
|
+
const flatResult = this.flattenEntity(entity);
|
|
3462
|
+
const hookResult = await this.applyAfterRead(c.req.param("parent"), flatResult, hookCtx);
|
|
3463
|
+
if (!hookResult) throw ApiError.notFound("Entity not found");
|
|
3464
|
+
return c.json(hookResult);
|
|
3347
3465
|
} else {
|
|
3348
|
-
const queryDict = c.req.
|
|
3466
|
+
const queryDict = c.req.queries();
|
|
3349
3467
|
const queryOptions = parseQueryOptions(queryDict);
|
|
3468
|
+
const searchString = Array.isArray(queryDict.searchString) ? queryDict.searchString[queryDict.searchString.length - 1] : void 0;
|
|
3350
3469
|
const entities = await driver.fetchCollection({
|
|
3351
3470
|
path: parsed.collectionPath,
|
|
3352
3471
|
filter: queryOptions.where,
|
|
3353
3472
|
limit: queryOptions.limit,
|
|
3354
3473
|
orderBy: queryOptions.orderBy?.[0]?.field,
|
|
3355
3474
|
order: queryOptions.orderBy?.[0]?.direction === "desc" ? "desc" : "asc",
|
|
3356
|
-
searchString
|
|
3475
|
+
searchString
|
|
3357
3476
|
});
|
|
3477
|
+
let flatEntities = entities.map((e) => this.flattenEntity(e));
|
|
3478
|
+
flatEntities = await this.applyAfterReadBatch(c.req.param("parent"), flatEntities, hookCtx);
|
|
3479
|
+
const total = driver.countEntities ? await driver.countEntities({
|
|
3480
|
+
path: parsed.collectionPath,
|
|
3481
|
+
filter: queryOptions.where,
|
|
3482
|
+
searchString
|
|
3483
|
+
}) : flatEntities.length;
|
|
3358
3484
|
return c.json({
|
|
3359
|
-
data:
|
|
3485
|
+
data: flatEntities,
|
|
3360
3486
|
meta: {
|
|
3361
|
-
total
|
|
3487
|
+
total,
|
|
3362
3488
|
limit: queryOptions.limit,
|
|
3363
3489
|
offset: queryOptions.offset,
|
|
3364
|
-
hasMore:
|
|
3490
|
+
hasMore: (queryOptions.offset || 0) + flatEntities.length < total
|
|
3365
3491
|
}
|
|
3366
3492
|
});
|
|
3367
3493
|
}
|
|
@@ -3373,14 +3499,24 @@
|
|
|
3373
3499
|
const parsed = parseSubPath(rawPath);
|
|
3374
3500
|
if (!parsed || parsed.entityId) return next();
|
|
3375
3501
|
const driver = c.get("driver") || this.driver;
|
|
3502
|
+
const hookCtx = this.buildHookContext(c, "POST");
|
|
3376
3503
|
this.enforceApiKeyPermission(c, c.req.param("parent"));
|
|
3377
|
-
|
|
3504
|
+
let body = await c.req.json().catch(() => ({}));
|
|
3505
|
+
if (this.dataHooks?.beforeSave) {
|
|
3506
|
+
body = await this.dataHooks.beforeSave(parsed.collectionPath, body, void 0, hookCtx);
|
|
3507
|
+
}
|
|
3378
3508
|
const entity = await driver.saveEntity({
|
|
3379
3509
|
path: parsed.collectionPath,
|
|
3380
3510
|
values: body,
|
|
3381
3511
|
status: "new"
|
|
3382
3512
|
});
|
|
3383
|
-
|
|
3513
|
+
const response = this.formatResponse(entity);
|
|
3514
|
+
if (this.dataHooks?.afterSave) {
|
|
3515
|
+
Promise.resolve(this.dataHooks.afterSave(parsed.collectionPath, response, hookCtx)).catch((err) => {
|
|
3516
|
+
console.error("[BackendHooks] data.afterSave error:", err instanceof Error ? err.message : err);
|
|
3517
|
+
});
|
|
3518
|
+
}
|
|
3519
|
+
return c.json(response, 201);
|
|
3384
3520
|
});
|
|
3385
3521
|
this.router.put("/:parent/:parentId/:rest{.+}", async (c, next) => {
|
|
3386
3522
|
const rest = c.req.param("rest");
|
|
@@ -3389,15 +3525,25 @@
|
|
|
3389
3525
|
const parsed = parseSubPath(rawPath);
|
|
3390
3526
|
if (!parsed || !parsed.entityId) return next();
|
|
3391
3527
|
const driver = c.get("driver") || this.driver;
|
|
3528
|
+
const hookCtx = this.buildHookContext(c, "PUT");
|
|
3392
3529
|
this.enforceApiKeyPermission(c, c.req.param("parent"));
|
|
3393
|
-
|
|
3530
|
+
let body = await c.req.json().catch(() => ({}));
|
|
3531
|
+
if (this.dataHooks?.beforeSave) {
|
|
3532
|
+
body = await this.dataHooks.beforeSave(parsed.collectionPath, body, parsed.entityId, hookCtx);
|
|
3533
|
+
}
|
|
3394
3534
|
const entity = await driver.saveEntity({
|
|
3395
3535
|
path: parsed.collectionPath,
|
|
3396
3536
|
entityId: parsed.entityId,
|
|
3397
3537
|
values: body,
|
|
3398
3538
|
status: "existing"
|
|
3399
3539
|
});
|
|
3400
|
-
|
|
3540
|
+
const response = this.formatResponse(entity);
|
|
3541
|
+
if (this.dataHooks?.afterSave) {
|
|
3542
|
+
Promise.resolve(this.dataHooks.afterSave(parsed.collectionPath, response, hookCtx)).catch((err) => {
|
|
3543
|
+
console.error("[BackendHooks] data.afterSave error:", err instanceof Error ? err.message : err);
|
|
3544
|
+
});
|
|
3545
|
+
}
|
|
3546
|
+
return c.json(response);
|
|
3401
3547
|
});
|
|
3402
3548
|
this.router.delete("/:parent/:parentId/:rest{.+}", async (c, next) => {
|
|
3403
3549
|
const rest = c.req.param("rest");
|
|
@@ -3406,15 +3552,24 @@
|
|
|
3406
3552
|
const parsed = parseSubPath(rawPath);
|
|
3407
3553
|
if (!parsed || !parsed.entityId) return next();
|
|
3408
3554
|
const driver = c.get("driver") || this.driver;
|
|
3555
|
+
const hookCtx = this.buildHookContext(c, "DELETE");
|
|
3409
3556
|
this.enforceApiKeyPermission(c, c.req.param("parent"));
|
|
3410
3557
|
const existingEntity = await driver.fetchEntity({
|
|
3411
3558
|
path: parsed.collectionPath,
|
|
3412
3559
|
entityId: parsed.entityId
|
|
3413
3560
|
});
|
|
3414
3561
|
if (!existingEntity) throw ApiError.notFound("Entity not found");
|
|
3562
|
+
if (this.dataHooks?.beforeDelete) {
|
|
3563
|
+
await this.dataHooks.beforeDelete(parsed.collectionPath, parsed.entityId, hookCtx);
|
|
3564
|
+
}
|
|
3415
3565
|
await driver.deleteEntity({
|
|
3416
3566
|
entity: existingEntity
|
|
3417
3567
|
});
|
|
3568
|
+
if (this.dataHooks?.afterDelete) {
|
|
3569
|
+
Promise.resolve(this.dataHooks.afterDelete(parsed.collectionPath, parsed.entityId, hookCtx)).catch((err) => {
|
|
3570
|
+
console.error("[BackendHooks] data.afterDelete error:", err instanceof Error ? err.message : err);
|
|
3571
|
+
});
|
|
3572
|
+
}
|
|
3418
3573
|
return new Response(null, {
|
|
3419
3574
|
status: 204
|
|
3420
3575
|
});
|
|
@@ -12897,14 +13052,11 @@ Si tienes alguna pregunta, no dudes en contactarnos respondiendo a este correo.
|
|
|
12897
13052
|
if (!factor || factor.userId !== userCtx.userId) {
|
|
12898
13053
|
throw ApiError.notFound("MFA factor not found");
|
|
12899
13054
|
}
|
|
12900
|
-
const
|
|
12901
|
-
let isValid2 =
|
|
12902
|
-
if (
|
|
13055
|
+
const secretBuffer = base32Decode(factor.secretEncrypted);
|
|
13056
|
+
let isValid2 = verifyTotp(secretBuffer, code2);
|
|
13057
|
+
if (!isValid2) {
|
|
12903
13058
|
const codeHash = hashRecoveryCode(code2);
|
|
12904
13059
|
isValid2 = await authRepo.useRecoveryCode(userCtx.userId, codeHash);
|
|
12905
|
-
} else {
|
|
12906
|
-
const secretBuffer = base32Decode(factor.secretEncrypted);
|
|
12907
|
-
isValid2 = verifyTotp(secretBuffer, code2);
|
|
12908
13060
|
}
|
|
12909
13061
|
if (!isValid2) {
|
|
12910
13062
|
throw ApiError.unauthorized("Invalid verification code", "INVALID_CODE");
|
|
@@ -13022,11 +13174,6 @@ Si tienes alguna pregunta, no dudes en contactarnos respondiendo a este correo.
|
|
|
13022
13174
|
const results = await Promise.all(users.map((u) => applyUserAfterRead(u, ctx)));
|
|
13023
13175
|
return results.filter((u) => u !== null);
|
|
13024
13176
|
}
|
|
13025
|
-
async function applyRoleAfterReadBatch(roles, ctx) {
|
|
13026
|
-
if (!hooks?.roles?.afterRead) return roles;
|
|
13027
|
-
const results = await Promise.all(roles.map((r) => hooks.roles.afterRead(r, ctx)));
|
|
13028
|
-
return results.filter((r) => r !== null);
|
|
13029
|
-
}
|
|
13030
13177
|
function toAdminUser(u, roles) {
|
|
13031
13178
|
return {
|
|
13032
13179
|
uid: u.id,
|
|
@@ -13385,90 +13532,6 @@ Si tienes alguna pregunta, no dudes en contactarnos respondiendo a este correo.
|
|
|
13385
13532
|
success: true
|
|
13386
13533
|
});
|
|
13387
13534
|
});
|
|
13388
|
-
router.get("/roles", requireAdmin, async (c) => {
|
|
13389
|
-
const roles = await authRepo.listRoles();
|
|
13390
|
-
const hookCtx = buildHookContext(c, "GET");
|
|
13391
|
-
let adminRoles = roles.map((r) => ({
|
|
13392
|
-
id: r.id,
|
|
13393
|
-
name: r.name,
|
|
13394
|
-
isAdmin: r.isAdmin,
|
|
13395
|
-
defaultPermissions: r.defaultPermissions
|
|
13396
|
-
}));
|
|
13397
|
-
adminRoles = await applyRoleAfterReadBatch(adminRoles, hookCtx);
|
|
13398
|
-
return c.json({
|
|
13399
|
-
roles: adminRoles
|
|
13400
|
-
});
|
|
13401
|
-
});
|
|
13402
|
-
router.get("/roles/:roleId", requireAdmin, async (c) => {
|
|
13403
|
-
const roleId = c.req.param("roleId");
|
|
13404
|
-
const role = await authRepo.getRoleById(roleId);
|
|
13405
|
-
if (!role) {
|
|
13406
|
-
throw ApiError.notFound("Role not found");
|
|
13407
|
-
}
|
|
13408
|
-
return c.json({
|
|
13409
|
-
role
|
|
13410
|
-
});
|
|
13411
|
-
});
|
|
13412
|
-
router.post("/roles", requireAdmin, async (c) => {
|
|
13413
|
-
const body = await c.req.json();
|
|
13414
|
-
const {
|
|
13415
|
-
id,
|
|
13416
|
-
name: name2,
|
|
13417
|
-
isAdmin,
|
|
13418
|
-
defaultPermissions
|
|
13419
|
-
} = body;
|
|
13420
|
-
if (!id || !name2) {
|
|
13421
|
-
throw ApiError.badRequest("Role ID and name are required", "INVALID_INPUT");
|
|
13422
|
-
}
|
|
13423
|
-
const existing = await authRepo.getRoleById(id);
|
|
13424
|
-
if (existing) {
|
|
13425
|
-
throw ApiError.conflict("Role already exists", "ROLE_EXISTS");
|
|
13426
|
-
}
|
|
13427
|
-
const role = await authRepo.createRole({
|
|
13428
|
-
id,
|
|
13429
|
-
name: name2,
|
|
13430
|
-
isAdmin: isAdmin ?? false,
|
|
13431
|
-
defaultPermissions: defaultPermissions ?? null
|
|
13432
|
-
});
|
|
13433
|
-
return c.json({
|
|
13434
|
-
role
|
|
13435
|
-
}, 201);
|
|
13436
|
-
});
|
|
13437
|
-
router.put("/roles/:roleId", requireAdmin, async (c) => {
|
|
13438
|
-
const roleId = c.req.param("roleId");
|
|
13439
|
-
const body = await c.req.json();
|
|
13440
|
-
const {
|
|
13441
|
-
name: name2,
|
|
13442
|
-
isAdmin,
|
|
13443
|
-
defaultPermissions
|
|
13444
|
-
} = body;
|
|
13445
|
-
const existing = await authRepo.getRoleById(roleId);
|
|
13446
|
-
if (!existing) {
|
|
13447
|
-
throw ApiError.notFound("Role not found");
|
|
13448
|
-
}
|
|
13449
|
-
const role = await authRepo.updateRole(roleId, {
|
|
13450
|
-
name: name2,
|
|
13451
|
-
isAdmin,
|
|
13452
|
-
defaultPermissions
|
|
13453
|
-
});
|
|
13454
|
-
return c.json({
|
|
13455
|
-
role
|
|
13456
|
-
});
|
|
13457
|
-
});
|
|
13458
|
-
router.delete("/roles/:roleId", requireAdmin, async (c) => {
|
|
13459
|
-
const roleId = c.req.param("roleId");
|
|
13460
|
-
if (["admin", "editor", "viewer"].includes(roleId)) {
|
|
13461
|
-
throw ApiError.badRequest("Cannot delete built-in roles", "BUILTIN_ROLE");
|
|
13462
|
-
}
|
|
13463
|
-
const existing = await authRepo.getRoleById(roleId);
|
|
13464
|
-
if (!existing) {
|
|
13465
|
-
throw ApiError.notFound("Role not found");
|
|
13466
|
-
}
|
|
13467
|
-
await authRepo.deleteRole(roleId);
|
|
13468
|
-
return c.json({
|
|
13469
|
-
success: true
|
|
13470
|
-
});
|
|
13471
|
-
});
|
|
13472
13535
|
return router;
|
|
13473
13536
|
}
|
|
13474
13537
|
function createBuiltinAuthAdapter(config) {
|
|
@@ -13509,18 +13572,16 @@ Si tienes alguna pregunta, no dudes en contactarnos respondiendo a este correo.
|
|
|
13509
13572
|
if (!payload) {
|
|
13510
13573
|
return null;
|
|
13511
13574
|
}
|
|
13512
|
-
const extendedPayload = payload;
|
|
13513
13575
|
let roles = payload.roles || [];
|
|
13514
13576
|
try {
|
|
13515
|
-
|
|
13516
|
-
roles = userRoles.map((r) => r.id);
|
|
13577
|
+
roles = await authRepository.getUserRoleIds(payload.userId);
|
|
13517
13578
|
} catch {
|
|
13518
13579
|
}
|
|
13519
13580
|
const isAdmin = roles.some((r) => r === "admin" || r === "schema-admin");
|
|
13520
13581
|
return {
|
|
13521
13582
|
uid: payload.userId,
|
|
13522
|
-
email:
|
|
13523
|
-
displayName:
|
|
13583
|
+
email: payload.email ?? "",
|
|
13584
|
+
displayName: payload.displayName ?? null,
|
|
13524
13585
|
roles,
|
|
13525
13586
|
isAdmin,
|
|
13526
13587
|
rawToken: token
|
|
@@ -13540,25 +13601,22 @@ Si tienes alguna pregunta, no dudes en contactarnos respondiendo a este correo.
|
|
|
13540
13601
|
if (!payload) {
|
|
13541
13602
|
return null;
|
|
13542
13603
|
}
|
|
13543
|
-
const extendedPayload = payload;
|
|
13544
13604
|
let roles = payload.roles || [];
|
|
13545
13605
|
try {
|
|
13546
|
-
|
|
13547
|
-
roles = userRoles.map((r) => r.id);
|
|
13606
|
+
roles = await authRepository.getUserRoleIds(payload.userId);
|
|
13548
13607
|
} catch {
|
|
13549
13608
|
}
|
|
13550
13609
|
const isAdmin = roles.some((r) => r === "admin" || r === "schema-admin");
|
|
13551
13610
|
return {
|
|
13552
13611
|
uid: payload.userId,
|
|
13553
|
-
email:
|
|
13554
|
-
displayName:
|
|
13612
|
+
email: payload.email ?? "",
|
|
13613
|
+
displayName: payload.displayName ?? null,
|
|
13555
13614
|
roles,
|
|
13556
13615
|
isAdmin,
|
|
13557
13616
|
rawToken: token
|
|
13558
13617
|
};
|
|
13559
13618
|
},
|
|
13560
13619
|
userManagement: createUserManagementFromRepo(authRepository, resolvedOps, authHooks),
|
|
13561
|
-
roleManagement: createRoleManagementFromRepo(authRepository),
|
|
13562
13620
|
createAuthRoutes() {
|
|
13563
13621
|
return createAuthRoutes({
|
|
13564
13622
|
authRepo: authRepository,
|
|
@@ -13674,43 +13732,13 @@ Si tienes alguna pregunta, no dudes en contactarnos respondiendo a este correo.
|
|
|
13674
13732
|
}
|
|
13675
13733
|
},
|
|
13676
13734
|
async getUserRoles(userId) {
|
|
13677
|
-
|
|
13678
|
-
return roles.map(toAuthRoleData);
|
|
13735
|
+
return repo.getUserRoleIds(userId);
|
|
13679
13736
|
},
|
|
13680
13737
|
async setUserRoles(userId, roleIds) {
|
|
13681
13738
|
await repo.setUserRoles(userId, roleIds);
|
|
13682
13739
|
}
|
|
13683
13740
|
};
|
|
13684
13741
|
}
|
|
13685
|
-
function createRoleManagementFromRepo(repo) {
|
|
13686
|
-
return {
|
|
13687
|
-
async listRoles() {
|
|
13688
|
-
const roles = await repo.listRoles();
|
|
13689
|
-
return roles.map(toAuthRoleData);
|
|
13690
|
-
},
|
|
13691
|
-
async getRoleById(id) {
|
|
13692
|
-
const role = await repo.getRoleById(id);
|
|
13693
|
-
return role ? toAuthRoleData(role) : null;
|
|
13694
|
-
},
|
|
13695
|
-
async createRole(data) {
|
|
13696
|
-
const role = await repo.createRole({
|
|
13697
|
-
id: data.id,
|
|
13698
|
-
name: data.name,
|
|
13699
|
-
isAdmin: data.isAdmin,
|
|
13700
|
-
defaultPermissions: data.defaultPermissions,
|
|
13701
|
-
collectionPermissions: data.collectionPermissions
|
|
13702
|
-
});
|
|
13703
|
-
return toAuthRoleData(role);
|
|
13704
|
-
},
|
|
13705
|
-
async updateRole(id, data) {
|
|
13706
|
-
const role = await repo.updateRole(id, data);
|
|
13707
|
-
return role ? toAuthRoleData(role) : null;
|
|
13708
|
-
},
|
|
13709
|
-
async deleteRole(id) {
|
|
13710
|
-
await repo.deleteRole(id);
|
|
13711
|
-
}
|
|
13712
|
-
};
|
|
13713
|
-
}
|
|
13714
13742
|
function toAuthUserData(user) {
|
|
13715
13743
|
return {
|
|
13716
13744
|
id: user.id,
|
|
@@ -13723,15 +13751,6 @@ Si tienes alguna pregunta, no dudes en contactarnos respondiendo a este correo.
|
|
|
13723
13751
|
updatedAt: user.updatedAt
|
|
13724
13752
|
};
|
|
13725
13753
|
}
|
|
13726
|
-
function toAuthRoleData(role) {
|
|
13727
|
-
return {
|
|
13728
|
-
id: role.id,
|
|
13729
|
-
name: role.name,
|
|
13730
|
-
isAdmin: role.isAdmin,
|
|
13731
|
-
defaultPermissions: role.defaultPermissions,
|
|
13732
|
-
collectionPermissions: role.collectionPermissions
|
|
13733
|
-
};
|
|
13734
|
-
}
|
|
13735
13754
|
function configureLogLevel(logLevel) {
|
|
13736
13755
|
const LOG_LEVEL = logLevel || process.env.LOG_LEVEL || "info";
|
|
13737
13756
|
const logLevels = {
|
|
@@ -24914,7 +24933,6 @@ ${credentialScope}
|
|
|
24914
24933
|
verifyRequest: options2.verifyRequest,
|
|
24915
24934
|
verifyToken: resolvedVerifyToken,
|
|
24916
24935
|
userManagement: options2.userManagement,
|
|
24917
|
-
roleManagement: options2.roleManagement,
|
|
24918
24936
|
getCapabilities() {
|
|
24919
24937
|
return defaultCapabilities;
|
|
24920
24938
|
}
|
|
@@ -26259,15 +26277,33 @@ ${credentialScope}
|
|
|
26259
26277
|
if (value === null) return "eq.null";
|
|
26260
26278
|
if (typeof value === "boolean") return `eq.${value}`;
|
|
26261
26279
|
if (typeof value === "number") return String(value);
|
|
26262
|
-
if (Array.isArray(value)
|
|
26263
|
-
const [
|
|
26264
|
-
const
|
|
26265
|
-
if (
|
|
26266
|
-
|
|
26267
|
-
|
|
26280
|
+
if (Array.isArray(value)) {
|
|
26281
|
+
const conditions = Array.isArray(value[0]) ? value : [value];
|
|
26282
|
+
const [rawOp, val] = conditions[0] || [];
|
|
26283
|
+
if (rawOp) {
|
|
26284
|
+
const op = OP_MAP[rawOp] ?? rawOp;
|
|
26285
|
+
if (val === null) return `${op}.null`;
|
|
26286
|
+
if (Array.isArray(val)) return `${op}.(${val.join(",")})`;
|
|
26287
|
+
return `${op}.${val}`;
|
|
26288
|
+
}
|
|
26268
26289
|
}
|
|
26269
26290
|
return String(value);
|
|
26270
26291
|
}
|
|
26292
|
+
function serializeLogicalCondition(cond2) {
|
|
26293
|
+
if ("type" in cond2) {
|
|
26294
|
+
const sub = cond2.conditions.map(serializeLogicalCondition).join(",");
|
|
26295
|
+
return `${cond2.type}(${sub})`;
|
|
26296
|
+
} else {
|
|
26297
|
+
const op = OP_MAP[cond2.operator] ?? cond2.operator;
|
|
26298
|
+
let formattedValue = cond2.value;
|
|
26299
|
+
if (Array.isArray(cond2.value)) {
|
|
26300
|
+
formattedValue = `(${cond2.value.join(",")})`;
|
|
26301
|
+
} else if (cond2.value === null) {
|
|
26302
|
+
formattedValue = "null";
|
|
26303
|
+
}
|
|
26304
|
+
return `${cond2.column}.${op}.${formattedValue}`;
|
|
26305
|
+
}
|
|
26306
|
+
}
|
|
26271
26307
|
function buildQueryString(params) {
|
|
26272
26308
|
if (!params) return "";
|
|
26273
26309
|
const parts = [];
|
|
@@ -26283,10 +26319,22 @@ ${credentialScope}
|
|
|
26283
26319
|
if (params.include && params.include.length > 0) {
|
|
26284
26320
|
parts.push(`include=${encodeURIComponent(params.include.join(","))}`);
|
|
26285
26321
|
}
|
|
26322
|
+
if (params.logical) {
|
|
26323
|
+
const root = params.logical;
|
|
26324
|
+
const serialized = root.conditions.map(serializeLogicalCondition).join(",");
|
|
26325
|
+
parts.push(`${root.type}=${encodeURIComponent(`(${serialized})`)}`);
|
|
26326
|
+
}
|
|
26286
26327
|
if (params.where) {
|
|
26287
26328
|
for (const [field, value] of Object.entries(params.where)) {
|
|
26288
|
-
|
|
26289
|
-
|
|
26329
|
+
if (Array.isArray(value) && value.length > 0 && Array.isArray(value[0])) {
|
|
26330
|
+
for (const subVal of value) {
|
|
26331
|
+
const normalized2 = normalizeWhereValue(subVal);
|
|
26332
|
+
parts.push(`${encodeURIComponent(field)}=${encodeURIComponent(normalized2)}`);
|
|
26333
|
+
}
|
|
26334
|
+
} else {
|
|
26335
|
+
const normalized2 = normalizeWhereValue(value);
|
|
26336
|
+
parts.push(`${encodeURIComponent(field)}=${encodeURIComponent(normalized2)}`);
|
|
26337
|
+
}
|
|
26290
26338
|
}
|
|
26291
26339
|
}
|
|
26292
26340
|
return parts.length > 0 ? "?" + parts.join("&") : "";
|
|
@@ -26961,33 +27009,6 @@ ${credentialScope}
|
|
|
26961
27009
|
method: "DELETE"
|
|
26962
27010
|
});
|
|
26963
27011
|
}
|
|
26964
|
-
async function listRoles() {
|
|
26965
|
-
return transport.request(adminPath + "/roles", {
|
|
26966
|
-
method: "GET"
|
|
26967
|
-
});
|
|
26968
|
-
}
|
|
26969
|
-
async function getRole(roleId) {
|
|
26970
|
-
return transport.request(adminPath + "/roles/" + encodeURIComponent(roleId), {
|
|
26971
|
-
method: "GET"
|
|
26972
|
-
});
|
|
26973
|
-
}
|
|
26974
|
-
async function createRole(data) {
|
|
26975
|
-
return transport.request(adminPath + "/roles", {
|
|
26976
|
-
method: "POST",
|
|
26977
|
-
body: JSON.stringify(data)
|
|
26978
|
-
});
|
|
26979
|
-
}
|
|
26980
|
-
async function updateRole(roleId, data) {
|
|
26981
|
-
return transport.request(adminPath + "/roles/" + encodeURIComponent(roleId), {
|
|
26982
|
-
method: "PUT",
|
|
26983
|
-
body: JSON.stringify(data)
|
|
26984
|
-
});
|
|
26985
|
-
}
|
|
26986
|
-
async function deleteRole(roleId) {
|
|
26987
|
-
return transport.request(adminPath + "/roles/" + encodeURIComponent(roleId), {
|
|
26988
|
-
method: "DELETE"
|
|
26989
|
-
});
|
|
26990
|
-
}
|
|
26991
27012
|
async function bootstrap() {
|
|
26992
27013
|
return transport.request(adminPath + "/bootstrap", {
|
|
26993
27014
|
method: "POST"
|
|
@@ -27000,11 +27021,6 @@ ${credentialScope}
|
|
|
27000
27021
|
createUser,
|
|
27001
27022
|
updateUser,
|
|
27002
27023
|
deleteUser,
|
|
27003
|
-
listRoles,
|
|
27004
|
-
getRole,
|
|
27005
|
-
createRole,
|
|
27006
|
-
updateRole,
|
|
27007
|
-
deleteRole,
|
|
27008
27024
|
bootstrap
|
|
27009
27025
|
};
|
|
27010
27026
|
}
|
|
@@ -27070,11 +27086,18 @@ ${credentialScope}
|
|
|
27070
27086
|
};
|
|
27071
27087
|
},
|
|
27072
27088
|
async findById(id) {
|
|
27073
|
-
|
|
27074
|
-
|
|
27075
|
-
|
|
27076
|
-
|
|
27077
|
-
|
|
27089
|
+
try {
|
|
27090
|
+
const raw = await transport.request(`${basePath}/${encodeURIComponent(String(id))}`, {
|
|
27091
|
+
method: "GET"
|
|
27092
|
+
});
|
|
27093
|
+
if (!raw) return void 0;
|
|
27094
|
+
return rowToEntity(raw, slug);
|
|
27095
|
+
} catch (err) {
|
|
27096
|
+
if (err instanceof RebaseApiError && err.status === 404) {
|
|
27097
|
+
return void 0;
|
|
27098
|
+
}
|
|
27099
|
+
throw err;
|
|
27100
|
+
}
|
|
27078
27101
|
},
|
|
27079
27102
|
async create(data, id) {
|
|
27080
27103
|
const body = {
|
|
@@ -27114,8 +27137,12 @@ ${credentialScope}
|
|
|
27114
27137
|
return raw.count ?? 0;
|
|
27115
27138
|
},
|
|
27116
27139
|
// Fluent builder instantiation
|
|
27117
|
-
where(
|
|
27118
|
-
|
|
27140
|
+
where(columnOrCondition, operator, value) {
|
|
27141
|
+
const builder = new QueryBuilder(client);
|
|
27142
|
+
if (typeof columnOrCondition === "object") {
|
|
27143
|
+
return builder.where(columnOrCondition);
|
|
27144
|
+
}
|
|
27145
|
+
return builder.where(columnOrCondition, operator, value);
|
|
27119
27146
|
},
|
|
27120
27147
|
orderBy(column, ascending) {
|
|
27121
27148
|
return new QueryBuilder(client).orderBy(column, ascending);
|
|
@@ -38393,13 +38420,35 @@ ${credentialScope}
|
|
|
38393
38420
|
mailer2 = new Mailer(transporter, options2, defaults);
|
|
38394
38421
|
return mailer2;
|
|
38395
38422
|
};
|
|
38423
|
+
function getHostname(urlStr) {
|
|
38424
|
+
try {
|
|
38425
|
+
const url = new URL(urlStr.includes("://") ? urlStr : `https://${urlStr}`);
|
|
38426
|
+
return url.hostname;
|
|
38427
|
+
} catch {
|
|
38428
|
+
return void 0;
|
|
38429
|
+
}
|
|
38430
|
+
}
|
|
38396
38431
|
class SMTPEmailService {
|
|
38397
38432
|
transporter = null;
|
|
38398
38433
|
config;
|
|
38399
38434
|
constructor(config) {
|
|
38400
38435
|
this.config = config;
|
|
38401
38436
|
if (config.smtp) {
|
|
38437
|
+
let smtpName = config.smtp.name;
|
|
38438
|
+
if (!smtpName) {
|
|
38439
|
+
const urlsToTry = [process.env.FRONTEND_URL, config.resetPasswordUrl, config.verifyEmailUrl];
|
|
38440
|
+
for (const urlStr of urlsToTry) {
|
|
38441
|
+
if (urlStr) {
|
|
38442
|
+
const hostname = getHostname(urlStr);
|
|
38443
|
+
if (hostname) {
|
|
38444
|
+
smtpName = hostname;
|
|
38445
|
+
break;
|
|
38446
|
+
}
|
|
38447
|
+
}
|
|
38448
|
+
}
|
|
38449
|
+
}
|
|
38402
38450
|
this.transporter = createTransport({
|
|
38451
|
+
name: smtpName,
|
|
38403
38452
|
host: config.smtp.host,
|
|
38404
38453
|
port: config.smtp.port,
|
|
38405
38454
|
secure: config.smtp.secure ?? config.smtp.port === 465,
|
|
@@ -38549,6 +38598,14 @@ ${credentialScope}
|
|
|
38549
38598
|
dir: config.collectionsDir
|
|
38550
38599
|
});
|
|
38551
38600
|
}
|
|
38601
|
+
if (config.defaultSecurityRules?.length) {
|
|
38602
|
+
for (const collection of activeCollections) {
|
|
38603
|
+
if (isPostgresCollection(collection) && (!collection.securityRules || collection.securityRules.length === 0)) {
|
|
38604
|
+
collection.securityRules = config.defaultSecurityRules;
|
|
38605
|
+
}
|
|
38606
|
+
}
|
|
38607
|
+
logger.info("Default security rules applied to collections without explicit rules");
|
|
38608
|
+
}
|
|
38552
38609
|
const realtimeServices = {};
|
|
38553
38610
|
const delegates = {};
|
|
38554
38611
|
let bootstrappers = config.bootstrappers || [];
|
|
@@ -38592,7 +38649,9 @@ ${credentialScope}
|
|
|
38592
38649
|
}
|
|
38593
38650
|
if (bootstrapper.initializeRealtime) {
|
|
38594
38651
|
const realtime = await bootstrapper.initializeRealtime({}, driverResult);
|
|
38595
|
-
|
|
38652
|
+
if (realtime) {
|
|
38653
|
+
realtimeServices[b.id || bootstrapper.type] = realtime;
|
|
38654
|
+
}
|
|
38596
38655
|
}
|
|
38597
38656
|
}
|
|
38598
38657
|
const driverRegistry = DefaultDriverRegistry.create(delegates);
|
|
@@ -38617,8 +38676,7 @@ ${credentialScope}
|
|
|
38617
38676
|
id: authAdapter.id
|
|
38618
38677
|
});
|
|
38619
38678
|
authConfigResult = {
|
|
38620
|
-
userService: authAdapter.userManagement ?? {}
|
|
38621
|
-
roleService: authAdapter.roleManagement ?? {}
|
|
38679
|
+
userService: authAdapter.userManagement ?? {}
|
|
38622
38680
|
};
|
|
38623
38681
|
} else {
|
|
38624
38682
|
const safeAuthConfig = config.auth;
|
|
@@ -38963,6 +39021,26 @@ ${credentialScope}
|
|
|
38963
39021
|
logger.info("Email service attached to singleton", {
|
|
38964
39022
|
configured: emailService.isConfigured()
|
|
38965
39023
|
});
|
|
39024
|
+
if (emailService.isConfigured() && typeof emailService.verifyConnection === "function") {
|
|
39025
|
+
emailService.verifyConnection().then((success) => {
|
|
39026
|
+
if (!success) {
|
|
39027
|
+
logger.warn("Warning: SMTP connection verification failed. Email delivery may fail.");
|
|
39028
|
+
} else {
|
|
39029
|
+
logger.info("SMTP connection verified successfully.");
|
|
39030
|
+
}
|
|
39031
|
+
}).catch((err) => {
|
|
39032
|
+
logger.warn("Warning: SMTP connection verification failed. Email delivery may fail.", {
|
|
39033
|
+
error: err
|
|
39034
|
+
});
|
|
39035
|
+
});
|
|
39036
|
+
}
|
|
39037
|
+
}
|
|
39038
|
+
const driverAdmin = defaultBootstrapper.getAdmin?.(defaultDriverResult);
|
|
39039
|
+
if (isSQLAdmin(driverAdmin)) {
|
|
39040
|
+
Object.assign(serverClient, {
|
|
39041
|
+
sql: (query, options2) => driverAdmin.executeSql(query, options2)
|
|
39042
|
+
});
|
|
39043
|
+
logger.info("SQL capability attached to singleton");
|
|
38966
39044
|
}
|
|
38967
39045
|
_initRebase(serverClient);
|
|
38968
39046
|
logger.info("Rebase singleton initialized");
|
|
@@ -39052,7 +39130,7 @@ ${credentialScope}
|
|
|
39052
39130
|
});
|
|
39053
39131
|
}
|
|
39054
39132
|
}
|
|
39055
|
-
if (defaultBootstrapper.initializeWebsockets) {
|
|
39133
|
+
if (defaultBootstrapper.initializeWebsockets && defaultRealtimeService) {
|
|
39056
39134
|
await defaultBootstrapper.initializeWebsockets(config.server, defaultRealtimeService, defaultDriver, config.auth, authAdapter);
|
|
39057
39135
|
}
|
|
39058
39136
|
logger.info("Rebase Backend Initialized");
|
|
@@ -39098,12 +39176,11 @@ ${credentialScope}
|
|
|
39098
39176
|
}
|
|
39099
39177
|
for (const [key, rt] of Object.entries(realtimeServices)) {
|
|
39100
39178
|
try {
|
|
39101
|
-
|
|
39102
|
-
|
|
39103
|
-
await rtWithLifecycle.destroy();
|
|
39179
|
+
if (typeof rt.destroy === "function") {
|
|
39180
|
+
await rt.destroy();
|
|
39104
39181
|
logger.info(`Realtime service "${key}" destroyed`);
|
|
39105
|
-
} else if (typeof
|
|
39106
|
-
await
|
|
39182
|
+
} else if (typeof rt.stopListening === "function") {
|
|
39183
|
+
await rt.stopListening();
|
|
39107
39184
|
logger.info(`Realtime service "${key}" LISTEN client stopped`);
|
|
39108
39185
|
}
|
|
39109
39186
|
} catch (err) {
|
|
@@ -51257,6 +51334,7 @@ export default ${safeId}Collection;
|
|
|
51257
51334
|
exports2.isAuthAdapter = isAuthAdapter;
|
|
51258
51335
|
exports2.isDatabaseAdapter = isDatabaseAdapter;
|
|
51259
51336
|
exports2.isOperationAllowed = isOperationAllowed;
|
|
51337
|
+
exports2.isRebaseApiError = isRebaseApiError;
|
|
51260
51338
|
exports2.isTransformableImage = isTransformableImage;
|
|
51261
51339
|
exports2.listenWithPortRetry = listenWithPortRetry;
|
|
51262
51340
|
exports2.loadCronJobsFromDirectory = loadCronJobsFromDirectory;
|