@rebasepro/server-core 0.3.0 → 0.4.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/dist/common/src/collections/default-collections.d.ts +5 -8
- package/dist/common/src/data/query_builder.d.ts +6 -2
- package/dist/index.es.js +318 -282
- package/dist/index.es.js.map +1 -1
- package/dist/index.umd.js +318 -282
- package/dist/index.umd.js.map +1 -1
- 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/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 +2 -2
- package/dist/types/src/types/backend_hooks.d.ts +2 -17
- package/dist/types/src/types/collections.d.ts +9 -5
- package/dist/types/src/types/entity_views.d.ts +19 -28
- package/dist/types/src/types/properties.d.ts +9 -7
- 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/rest/api-generator.ts +11 -9
- package/src/api/rest/query-parser.ts +184 -63
- package/src/api/types.ts +2 -1
- package/src/auth/admin-routes.ts +2 -91
- package/src/auth/builtin-auth-adapter.ts +5 -55
- package/src/auth/custom-auth-adapter.ts +1 -1
- package/src/email/smtp-email-service.ts +31 -0
- package/src/email/types.ts +1 -0
- package/src/init.ts +136 -24
- 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/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
|
/**
|
|
@@ -2904,68 +2890,184 @@
|
|
|
2904
2890
|
return null;
|
|
2905
2891
|
}
|
|
2906
2892
|
}
|
|
2893
|
+
function getLastValue(val) {
|
|
2894
|
+
if (Array.isArray(val)) {
|
|
2895
|
+
return val[val.length - 1];
|
|
2896
|
+
}
|
|
2897
|
+
return val;
|
|
2898
|
+
}
|
|
2899
|
+
function parseLogicalString(str) {
|
|
2900
|
+
str = str.trim();
|
|
2901
|
+
if (str.startsWith("or(") && str.endsWith(")")) {
|
|
2902
|
+
const inner = str.slice(3, -1);
|
|
2903
|
+
return {
|
|
2904
|
+
type: "or",
|
|
2905
|
+
conditions: parseLogicalList(inner)
|
|
2906
|
+
};
|
|
2907
|
+
}
|
|
2908
|
+
if (str.startsWith("and(") && str.endsWith(")")) {
|
|
2909
|
+
const inner = str.slice(4, -1);
|
|
2910
|
+
return {
|
|
2911
|
+
type: "and",
|
|
2912
|
+
conditions: parseLogicalList(inner)
|
|
2913
|
+
};
|
|
2914
|
+
}
|
|
2915
|
+
const firstDot = str.indexOf(".");
|
|
2916
|
+
if (firstDot === -1) {
|
|
2917
|
+
return {
|
|
2918
|
+
column: str,
|
|
2919
|
+
operator: "==",
|
|
2920
|
+
value: true
|
|
2921
|
+
};
|
|
2922
|
+
}
|
|
2923
|
+
const field = str.substring(0, firstDot);
|
|
2924
|
+
const rest = str.substring(firstDot + 1);
|
|
2925
|
+
const secondDot = rest.indexOf(".");
|
|
2926
|
+
let op = "eq";
|
|
2927
|
+
let valStr = rest;
|
|
2928
|
+
if (secondDot !== -1) {
|
|
2929
|
+
op = rest.substring(0, secondDot);
|
|
2930
|
+
valStr = rest.substring(secondDot + 1);
|
|
2931
|
+
} else {
|
|
2932
|
+
op = "eq";
|
|
2933
|
+
valStr = rest;
|
|
2934
|
+
}
|
|
2935
|
+
const rebaseOp = mapOperator(op) || "==";
|
|
2936
|
+
let parsedVal = valStr;
|
|
2937
|
+
if (valStr === "true") parsedVal = true;
|
|
2938
|
+
else if (valStr === "false") parsedVal = false;
|
|
2939
|
+
else if (valStr === "null") parsedVal = null;
|
|
2940
|
+
else if (!isNaN(Number(valStr)) && valStr.trim() !== "") parsedVal = Number(valStr);
|
|
2941
|
+
else if (valStr.startsWith("(")) {
|
|
2942
|
+
const arrayContent = valStr.endsWith(")") ? valStr.slice(1, -1) : valStr.slice(1);
|
|
2943
|
+
parsedVal = arrayContent.split(",").map((v) => {
|
|
2944
|
+
const trimmed = v.trim();
|
|
2945
|
+
if (!isNaN(Number(trimmed)) && trimmed !== "") return Number(trimmed);
|
|
2946
|
+
if (trimmed === "true") return true;
|
|
2947
|
+
if (trimmed === "false") return false;
|
|
2948
|
+
if (trimmed === "null") return null;
|
|
2949
|
+
return trimmed;
|
|
2950
|
+
});
|
|
2951
|
+
}
|
|
2952
|
+
return {
|
|
2953
|
+
column: field,
|
|
2954
|
+
operator: rebaseOp,
|
|
2955
|
+
value: parsedVal
|
|
2956
|
+
};
|
|
2957
|
+
}
|
|
2958
|
+
function parseLogicalList(str) {
|
|
2959
|
+
const list = [];
|
|
2960
|
+
let depth = 0;
|
|
2961
|
+
let current = "";
|
|
2962
|
+
for (let i2 = 0; i2 < str.length; i2++) {
|
|
2963
|
+
const char = str[i2];
|
|
2964
|
+
if (char === "(") depth++;
|
|
2965
|
+
if (char === ")") depth--;
|
|
2966
|
+
if (char === "," && depth === 0) {
|
|
2967
|
+
list.push(parseLogicalString(current));
|
|
2968
|
+
current = "";
|
|
2969
|
+
} else {
|
|
2970
|
+
current += char;
|
|
2971
|
+
}
|
|
2972
|
+
}
|
|
2973
|
+
if (current) {
|
|
2974
|
+
list.push(parseLogicalString(current));
|
|
2975
|
+
}
|
|
2976
|
+
return list;
|
|
2977
|
+
}
|
|
2907
2978
|
function parseQueryOptions(query) {
|
|
2908
2979
|
const options2 = {};
|
|
2909
|
-
|
|
2910
|
-
if (
|
|
2911
|
-
|
|
2912
|
-
|
|
2980
|
+
const limitVal = getLastValue(query.limit);
|
|
2981
|
+
if (limitVal) options2.limit = parseInt(String(limitVal));
|
|
2982
|
+
const offsetVal = getLastValue(query.offset);
|
|
2983
|
+
if (offsetVal) options2.offset = parseInt(String(offsetVal));
|
|
2984
|
+
const pageVal = getLastValue(query.page);
|
|
2985
|
+
if (pageVal) {
|
|
2986
|
+
const page = parseInt(String(pageVal));
|
|
2913
2987
|
const limit = options2.limit || 20;
|
|
2914
2988
|
options2.offset = (page - 1) * limit;
|
|
2915
2989
|
}
|
|
2916
2990
|
options2.where = {};
|
|
2917
|
-
const
|
|
2991
|
+
const orVal = getLastValue(query.or);
|
|
2992
|
+
const andVal = getLastValue(query.and);
|
|
2993
|
+
if (orVal) {
|
|
2994
|
+
let orStr = String(orVal).trim();
|
|
2995
|
+
if (orStr.startsWith("(") && orStr.endsWith(")")) {
|
|
2996
|
+
orStr = orStr.slice(1, -1);
|
|
2997
|
+
}
|
|
2998
|
+
options2.logical = {
|
|
2999
|
+
type: "or",
|
|
3000
|
+
conditions: parseLogicalList(orStr)
|
|
3001
|
+
};
|
|
3002
|
+
} else if (andVal) {
|
|
3003
|
+
let andStr = String(andVal).trim();
|
|
3004
|
+
if (andStr.startsWith("(") && andStr.endsWith(")")) {
|
|
3005
|
+
andStr = andStr.slice(1, -1);
|
|
3006
|
+
}
|
|
3007
|
+
options2.logical = {
|
|
3008
|
+
type: "and",
|
|
3009
|
+
conditions: parseLogicalList(andStr)
|
|
3010
|
+
};
|
|
3011
|
+
}
|
|
3012
|
+
const reservedQueryKeys = ["limit", "offset", "page", "orderBy", "include", "fields", "searchString", "vector_search", "vector", "vector_distance", "vector_threshold", "or", "and"];
|
|
2918
3013
|
for (const [key, rawValue] of Object.entries(query)) {
|
|
2919
3014
|
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
|
-
|
|
3015
|
+
const rawValues = Array.isArray(rawValue) ? rawValue : [rawValue];
|
|
3016
|
+
const conditions = [];
|
|
3017
|
+
for (const value of rawValues) {
|
|
3018
|
+
if (typeof value === "string") {
|
|
3019
|
+
const parts = value.split(".");
|
|
3020
|
+
if (parts.length >= 2) {
|
|
3021
|
+
const op = parts[0];
|
|
3022
|
+
const val = parts.slice(1).join(".");
|
|
3023
|
+
const rebaseOp = mapOperator(op);
|
|
3024
|
+
if (rebaseOp) {
|
|
3025
|
+
let parsedVal = val;
|
|
3026
|
+
if (val === "true") parsedVal = true;
|
|
3027
|
+
else if (val === "false") parsedVal = false;
|
|
3028
|
+
else if (val === "null") parsedVal = null;
|
|
3029
|
+
else if (!isNaN(Number(val)) && val.trim() !== "") parsedVal = Number(val);
|
|
3030
|
+
else if (val.startsWith("(")) {
|
|
3031
|
+
const arrayContent = val.endsWith(")") ? val.slice(1, -1) : val.slice(1);
|
|
3032
|
+
parsedVal = arrayContent.split(",").map((v) => {
|
|
3033
|
+
const trimmed = v.trim();
|
|
3034
|
+
if (!isNaN(Number(trimmed)) && trimmed !== "") return Number(trimmed);
|
|
3035
|
+
if (trimmed === "true") return true;
|
|
3036
|
+
if (trimmed === "false") return false;
|
|
3037
|
+
if (trimmed === "null") return null;
|
|
3038
|
+
return trimmed;
|
|
3039
|
+
});
|
|
3040
|
+
}
|
|
3041
|
+
conditions.push([rebaseOp, parsedVal]);
|
|
3042
|
+
} else {
|
|
3043
|
+
let parsedVal = value;
|
|
3044
|
+
if (!isNaN(Number(value)) && value.trim() !== "") parsedVal = Number(value);
|
|
3045
|
+
conditions.push(["==", parsedVal]);
|
|
2943
3046
|
}
|
|
2944
|
-
options2.where[key] = [rebaseOp, parsedVal];
|
|
2945
3047
|
} else {
|
|
2946
3048
|
let parsedVal = value;
|
|
2947
|
-
if (
|
|
2948
|
-
|
|
3049
|
+
if (value === "true") parsedVal = true;
|
|
3050
|
+
else if (value === "false") parsedVal = false;
|
|
3051
|
+
else if (value === "null") parsedVal = null;
|
|
3052
|
+
else if (!isNaN(Number(value)) && value.trim() !== "") parsedVal = Number(value);
|
|
3053
|
+
conditions.push(["==", parsedVal]);
|
|
2949
3054
|
}
|
|
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
3055
|
}
|
|
2958
3056
|
}
|
|
3057
|
+
if (conditions.length > 0) {
|
|
3058
|
+
options2.where[key] = conditions.length === 1 ? conditions[0] : conditions;
|
|
3059
|
+
}
|
|
2959
3060
|
}
|
|
2960
3061
|
if (Object.keys(options2.where).length === 0) {
|
|
2961
3062
|
delete options2.where;
|
|
2962
3063
|
}
|
|
2963
|
-
|
|
3064
|
+
const orderByVal = getLastValue(query.orderBy);
|
|
3065
|
+
if (orderByVal) {
|
|
2964
3066
|
try {
|
|
2965
|
-
options2.orderBy = typeof
|
|
3067
|
+
options2.orderBy = typeof orderByVal === "string" ? JSON.parse(orderByVal) : orderByVal;
|
|
2966
3068
|
} catch {
|
|
2967
|
-
if (typeof
|
|
2968
|
-
const [field, direction] =
|
|
3069
|
+
if (typeof orderByVal === "string") {
|
|
3070
|
+
const [field, direction] = orderByVal.split(":");
|
|
2969
3071
|
const dir = direction === "desc" ? "desc" : "asc";
|
|
2970
3072
|
options2.orderBy = [{
|
|
2971
3073
|
field,
|
|
@@ -2974,20 +3076,24 @@
|
|
|
2974
3076
|
}
|
|
2975
3077
|
}
|
|
2976
3078
|
}
|
|
2977
|
-
|
|
2978
|
-
|
|
3079
|
+
const includeVal = getLastValue(query.include);
|
|
3080
|
+
if (includeVal) {
|
|
3081
|
+
const includeStr = String(includeVal).trim();
|
|
2979
3082
|
if (includeStr === "*") {
|
|
2980
3083
|
options2.include = ["*"];
|
|
2981
3084
|
} else {
|
|
2982
3085
|
options2.include = includeStr.split(",").map((s2) => s2.trim()).filter(Boolean);
|
|
2983
3086
|
}
|
|
2984
3087
|
}
|
|
2985
|
-
|
|
2986
|
-
|
|
3088
|
+
const fieldsVal = getLastValue(query.fields);
|
|
3089
|
+
if (fieldsVal) {
|
|
3090
|
+
const fieldsStr = String(fieldsVal).trim();
|
|
2987
3091
|
options2.fields = fieldsStr.split(",").map((s2) => s2.trim()).filter(Boolean);
|
|
2988
3092
|
}
|
|
2989
|
-
|
|
2990
|
-
|
|
3093
|
+
const vectorSearchVal = getLastValue(query.vector_search);
|
|
3094
|
+
const vectorVal = getLastValue(query.vector);
|
|
3095
|
+
if (vectorSearchVal && vectorVal) {
|
|
3096
|
+
const vectorStr = String(vectorVal);
|
|
2991
3097
|
let queryVector;
|
|
2992
3098
|
try {
|
|
2993
3099
|
queryVector = JSON.parse(vectorStr);
|
|
@@ -2997,17 +3103,19 @@
|
|
|
2997
3103
|
} catch {
|
|
2998
3104
|
throw new Error("Invalid vector format. Expected JSON array of numbers, e.g. [0.1,0.2,0.3]");
|
|
2999
3105
|
}
|
|
3000
|
-
const
|
|
3106
|
+
const distanceParamVal = getLastValue(query.vector_distance);
|
|
3107
|
+
const distanceParam = distanceParamVal ? String(distanceParamVal) : "cosine";
|
|
3001
3108
|
if (distanceParam !== "cosine" && distanceParam !== "l2" && distanceParam !== "inner_product") {
|
|
3002
3109
|
throw new Error(`Invalid vector_distance: ${distanceParam}. Expected: cosine, l2, or inner_product`);
|
|
3003
3110
|
}
|
|
3004
3111
|
const vectorSearch = {
|
|
3005
|
-
property: String(
|
|
3112
|
+
property: String(vectorSearchVal),
|
|
3006
3113
|
vector: queryVector,
|
|
3007
3114
|
distance: distanceParam
|
|
3008
3115
|
};
|
|
3009
|
-
|
|
3010
|
-
|
|
3116
|
+
const thresholdVal = getLastValue(query.vector_threshold);
|
|
3117
|
+
if (thresholdVal) {
|
|
3118
|
+
const threshold = parseFloat(String(thresholdVal));
|
|
3011
3119
|
if (isNaN(threshold)) {
|
|
3012
3120
|
throw new Error("Invalid vector_threshold. Expected a number.");
|
|
3013
3121
|
}
|
|
@@ -3102,9 +3210,9 @@
|
|
|
3102
3210
|
const resolvedCollection = collection;
|
|
3103
3211
|
this.router.get(`${basePath}/count`, async (c) => {
|
|
3104
3212
|
this.enforceApiKeyPermission(c, collection.slug);
|
|
3105
|
-
const queryDict = c.req.
|
|
3213
|
+
const queryDict = c.req.queries();
|
|
3106
3214
|
const queryOptions = parseQueryOptions(queryDict);
|
|
3107
|
-
const searchString = queryDict.searchString;
|
|
3215
|
+
const searchString = Array.isArray(queryDict.searchString) ? queryDict.searchString[queryDict.searchString.length - 1] : void 0;
|
|
3108
3216
|
const driver = c.get("driver") || this.driver;
|
|
3109
3217
|
const total = await this.countRawEntities(driver, resolvedCollection, queryOptions, searchString);
|
|
3110
3218
|
return c.json({
|
|
@@ -3113,9 +3221,9 @@
|
|
|
3113
3221
|
});
|
|
3114
3222
|
this.router.get(basePath, async (c) => {
|
|
3115
3223
|
this.enforceApiKeyPermission(c, collection.slug);
|
|
3116
|
-
const queryDict = c.req.
|
|
3224
|
+
const queryDict = c.req.queries();
|
|
3117
3225
|
const queryOptions = parseQueryOptions(queryDict);
|
|
3118
|
-
const searchString = queryDict.searchString;
|
|
3226
|
+
const searchString = Array.isArray(queryDict.searchString) ? queryDict.searchString[queryDict.searchString.length - 1] : void 0;
|
|
3119
3227
|
const driver = c.get("driver") || this.driver;
|
|
3120
3228
|
const fetchService = this.getFetchService(driver);
|
|
3121
3229
|
const hookCtx = this.buildHookContext(c, "GET");
|
|
@@ -3158,7 +3266,7 @@
|
|
|
3158
3266
|
this.router.get(`${basePath}/:id`, async (c) => {
|
|
3159
3267
|
this.enforceApiKeyPermission(c, collection.slug);
|
|
3160
3268
|
const id = c.req.param("id");
|
|
3161
|
-
const queryDict = c.req.
|
|
3269
|
+
const queryDict = c.req.queries();
|
|
3162
3270
|
const queryOptions = parseQueryOptions(queryDict);
|
|
3163
3271
|
const driver = c.get("driver") || this.driver;
|
|
3164
3272
|
const fetchService = this.getFetchService(driver);
|
|
@@ -3327,12 +3435,13 @@
|
|
|
3327
3435
|
const driver = c.get("driver") || this.driver;
|
|
3328
3436
|
this.enforceApiKeyPermission(c, c.req.param("parent"));
|
|
3329
3437
|
if (parsed.entityId === "count") {
|
|
3330
|
-
const queryDict = c.req.
|
|
3438
|
+
const queryDict = c.req.queries();
|
|
3331
3439
|
const queryOptions = parseQueryOptions(queryDict);
|
|
3440
|
+
const searchString = Array.isArray(queryDict.searchString) ? queryDict.searchString[queryDict.searchString.length - 1] : void 0;
|
|
3332
3441
|
const total = driver.countEntities ? await driver.countEntities({
|
|
3333
3442
|
path: parsed.collectionPath,
|
|
3334
3443
|
filter: queryOptions.where,
|
|
3335
|
-
searchString
|
|
3444
|
+
searchString
|
|
3336
3445
|
}) : 0;
|
|
3337
3446
|
return c.json({
|
|
3338
3447
|
count: total
|
|
@@ -3345,15 +3454,16 @@
|
|
|
3345
3454
|
if (!entity) throw ApiError.notFound("Entity not found");
|
|
3346
3455
|
return c.json(this.flattenEntity(entity));
|
|
3347
3456
|
} else {
|
|
3348
|
-
const queryDict = c.req.
|
|
3457
|
+
const queryDict = c.req.queries();
|
|
3349
3458
|
const queryOptions = parseQueryOptions(queryDict);
|
|
3459
|
+
const searchString = Array.isArray(queryDict.searchString) ? queryDict.searchString[queryDict.searchString.length - 1] : void 0;
|
|
3350
3460
|
const entities = await driver.fetchCollection({
|
|
3351
3461
|
path: parsed.collectionPath,
|
|
3352
3462
|
filter: queryOptions.where,
|
|
3353
3463
|
limit: queryOptions.limit,
|
|
3354
3464
|
orderBy: queryOptions.orderBy?.[0]?.field,
|
|
3355
3465
|
order: queryOptions.orderBy?.[0]?.direction === "desc" ? "desc" : "asc",
|
|
3356
|
-
searchString
|
|
3466
|
+
searchString
|
|
3357
3467
|
});
|
|
3358
3468
|
return c.json({
|
|
3359
3469
|
data: entities.map((e) => this.flattenEntity(e)),
|
|
@@ -13022,11 +13132,6 @@ Si tienes alguna pregunta, no dudes en contactarnos respondiendo a este correo.
|
|
|
13022
13132
|
const results = await Promise.all(users.map((u) => applyUserAfterRead(u, ctx)));
|
|
13023
13133
|
return results.filter((u) => u !== null);
|
|
13024
13134
|
}
|
|
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
13135
|
function toAdminUser(u, roles) {
|
|
13031
13136
|
return {
|
|
13032
13137
|
uid: u.id,
|
|
@@ -13385,90 +13490,6 @@ Si tienes alguna pregunta, no dudes en contactarnos respondiendo a este correo.
|
|
|
13385
13490
|
success: true
|
|
13386
13491
|
});
|
|
13387
13492
|
});
|
|
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
13493
|
return router;
|
|
13473
13494
|
}
|
|
13474
13495
|
function createBuiltinAuthAdapter(config) {
|
|
@@ -13512,8 +13533,7 @@ Si tienes alguna pregunta, no dudes en contactarnos respondiendo a este correo.
|
|
|
13512
13533
|
const extendedPayload = payload;
|
|
13513
13534
|
let roles = payload.roles || [];
|
|
13514
13535
|
try {
|
|
13515
|
-
|
|
13516
|
-
roles = userRoles.map((r) => r.id);
|
|
13536
|
+
roles = await authRepository.getUserRoleIds(payload.userId);
|
|
13517
13537
|
} catch {
|
|
13518
13538
|
}
|
|
13519
13539
|
const isAdmin = roles.some((r) => r === "admin" || r === "schema-admin");
|
|
@@ -13543,8 +13563,7 @@ Si tienes alguna pregunta, no dudes en contactarnos respondiendo a este correo.
|
|
|
13543
13563
|
const extendedPayload = payload;
|
|
13544
13564
|
let roles = payload.roles || [];
|
|
13545
13565
|
try {
|
|
13546
|
-
|
|
13547
|
-
roles = userRoles.map((r) => r.id);
|
|
13566
|
+
roles = await authRepository.getUserRoleIds(payload.userId);
|
|
13548
13567
|
} catch {
|
|
13549
13568
|
}
|
|
13550
13569
|
const isAdmin = roles.some((r) => r === "admin" || r === "schema-admin");
|
|
@@ -13558,7 +13577,6 @@ Si tienes alguna pregunta, no dudes en contactarnos respondiendo a este correo.
|
|
|
13558
13577
|
};
|
|
13559
13578
|
},
|
|
13560
13579
|
userManagement: createUserManagementFromRepo(authRepository, resolvedOps, authHooks),
|
|
13561
|
-
roleManagement: createRoleManagementFromRepo(authRepository),
|
|
13562
13580
|
createAuthRoutes() {
|
|
13563
13581
|
return createAuthRoutes({
|
|
13564
13582
|
authRepo: authRepository,
|
|
@@ -13674,43 +13692,13 @@ Si tienes alguna pregunta, no dudes en contactarnos respondiendo a este correo.
|
|
|
13674
13692
|
}
|
|
13675
13693
|
},
|
|
13676
13694
|
async getUserRoles(userId) {
|
|
13677
|
-
|
|
13678
|
-
return roles.map(toAuthRoleData);
|
|
13695
|
+
return repo.getUserRoleIds(userId);
|
|
13679
13696
|
},
|
|
13680
13697
|
async setUserRoles(userId, roleIds) {
|
|
13681
13698
|
await repo.setUserRoles(userId, roleIds);
|
|
13682
13699
|
}
|
|
13683
13700
|
};
|
|
13684
13701
|
}
|
|
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
13702
|
function toAuthUserData(user) {
|
|
13715
13703
|
return {
|
|
13716
13704
|
id: user.id,
|
|
@@ -13723,15 +13711,6 @@ Si tienes alguna pregunta, no dudes en contactarnos respondiendo a este correo.
|
|
|
13723
13711
|
updatedAt: user.updatedAt
|
|
13724
13712
|
};
|
|
13725
13713
|
}
|
|
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
13714
|
function configureLogLevel(logLevel) {
|
|
13736
13715
|
const LOG_LEVEL = logLevel || process.env.LOG_LEVEL || "info";
|
|
13737
13716
|
const logLevels = {
|
|
@@ -24914,7 +24893,6 @@ ${credentialScope}
|
|
|
24914
24893
|
verifyRequest: options2.verifyRequest,
|
|
24915
24894
|
verifyToken: resolvedVerifyToken,
|
|
24916
24895
|
userManagement: options2.userManagement,
|
|
24917
|
-
roleManagement: options2.roleManagement,
|
|
24918
24896
|
getCapabilities() {
|
|
24919
24897
|
return defaultCapabilities;
|
|
24920
24898
|
}
|
|
@@ -26259,15 +26237,33 @@ ${credentialScope}
|
|
|
26259
26237
|
if (value === null) return "eq.null";
|
|
26260
26238
|
if (typeof value === "boolean") return `eq.${value}`;
|
|
26261
26239
|
if (typeof value === "number") return String(value);
|
|
26262
|
-
if (Array.isArray(value)
|
|
26263
|
-
const [
|
|
26264
|
-
const
|
|
26265
|
-
if (
|
|
26266
|
-
|
|
26267
|
-
|
|
26240
|
+
if (Array.isArray(value)) {
|
|
26241
|
+
const conditions = Array.isArray(value[0]) ? value : [value];
|
|
26242
|
+
const [rawOp, val] = conditions[0] || [];
|
|
26243
|
+
if (rawOp) {
|
|
26244
|
+
const op = OP_MAP[rawOp] ?? rawOp;
|
|
26245
|
+
if (val === null) return `${op}.null`;
|
|
26246
|
+
if (Array.isArray(val)) return `${op}.(${val.join(",")})`;
|
|
26247
|
+
return `${op}.${val}`;
|
|
26248
|
+
}
|
|
26268
26249
|
}
|
|
26269
26250
|
return String(value);
|
|
26270
26251
|
}
|
|
26252
|
+
function serializeLogicalCondition(cond2) {
|
|
26253
|
+
if ("type" in cond2) {
|
|
26254
|
+
const sub = cond2.conditions.map(serializeLogicalCondition).join(",");
|
|
26255
|
+
return `${cond2.type}(${sub})`;
|
|
26256
|
+
} else {
|
|
26257
|
+
const op = OP_MAP[cond2.operator] ?? cond2.operator;
|
|
26258
|
+
let formattedValue = cond2.value;
|
|
26259
|
+
if (Array.isArray(cond2.value)) {
|
|
26260
|
+
formattedValue = `(${cond2.value.join(",")})`;
|
|
26261
|
+
} else if (cond2.value === null) {
|
|
26262
|
+
formattedValue = "null";
|
|
26263
|
+
}
|
|
26264
|
+
return `${cond2.column}.${op}.${formattedValue}`;
|
|
26265
|
+
}
|
|
26266
|
+
}
|
|
26271
26267
|
function buildQueryString(params) {
|
|
26272
26268
|
if (!params) return "";
|
|
26273
26269
|
const parts = [];
|
|
@@ -26283,10 +26279,22 @@ ${credentialScope}
|
|
|
26283
26279
|
if (params.include && params.include.length > 0) {
|
|
26284
26280
|
parts.push(`include=${encodeURIComponent(params.include.join(","))}`);
|
|
26285
26281
|
}
|
|
26282
|
+
if (params.logical) {
|
|
26283
|
+
const root = params.logical;
|
|
26284
|
+
const serialized = root.conditions.map(serializeLogicalCondition).join(",");
|
|
26285
|
+
parts.push(`${root.type}=${encodeURIComponent(`(${serialized})`)}`);
|
|
26286
|
+
}
|
|
26286
26287
|
if (params.where) {
|
|
26287
26288
|
for (const [field, value] of Object.entries(params.where)) {
|
|
26288
|
-
|
|
26289
|
-
|
|
26289
|
+
if (Array.isArray(value) && value.length > 0 && Array.isArray(value[0])) {
|
|
26290
|
+
for (const subVal of value) {
|
|
26291
|
+
const normalized2 = normalizeWhereValue(subVal);
|
|
26292
|
+
parts.push(`${encodeURIComponent(field)}=${encodeURIComponent(normalized2)}`);
|
|
26293
|
+
}
|
|
26294
|
+
} else {
|
|
26295
|
+
const normalized2 = normalizeWhereValue(value);
|
|
26296
|
+
parts.push(`${encodeURIComponent(field)}=${encodeURIComponent(normalized2)}`);
|
|
26297
|
+
}
|
|
26290
26298
|
}
|
|
26291
26299
|
}
|
|
26292
26300
|
return parts.length > 0 ? "?" + parts.join("&") : "";
|
|
@@ -26961,33 +26969,6 @@ ${credentialScope}
|
|
|
26961
26969
|
method: "DELETE"
|
|
26962
26970
|
});
|
|
26963
26971
|
}
|
|
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
26972
|
async function bootstrap() {
|
|
26992
26973
|
return transport.request(adminPath + "/bootstrap", {
|
|
26993
26974
|
method: "POST"
|
|
@@ -27000,11 +26981,6 @@ ${credentialScope}
|
|
|
27000
26981
|
createUser,
|
|
27001
26982
|
updateUser,
|
|
27002
26983
|
deleteUser,
|
|
27003
|
-
listRoles,
|
|
27004
|
-
getRole,
|
|
27005
|
-
createRole,
|
|
27006
|
-
updateRole,
|
|
27007
|
-
deleteRole,
|
|
27008
26984
|
bootstrap
|
|
27009
26985
|
};
|
|
27010
26986
|
}
|
|
@@ -27070,11 +27046,18 @@ ${credentialScope}
|
|
|
27070
27046
|
};
|
|
27071
27047
|
},
|
|
27072
27048
|
async findById(id) {
|
|
27073
|
-
|
|
27074
|
-
|
|
27075
|
-
|
|
27076
|
-
|
|
27077
|
-
|
|
27049
|
+
try {
|
|
27050
|
+
const raw = await transport.request(`${basePath}/${encodeURIComponent(String(id))}`, {
|
|
27051
|
+
method: "GET"
|
|
27052
|
+
});
|
|
27053
|
+
if (!raw) return void 0;
|
|
27054
|
+
return rowToEntity(raw, slug);
|
|
27055
|
+
} catch (err) {
|
|
27056
|
+
if (err instanceof RebaseApiError && err.status === 404) {
|
|
27057
|
+
return void 0;
|
|
27058
|
+
}
|
|
27059
|
+
throw err;
|
|
27060
|
+
}
|
|
27078
27061
|
},
|
|
27079
27062
|
async create(data, id) {
|
|
27080
27063
|
const body = {
|
|
@@ -27114,8 +27097,12 @@ ${credentialScope}
|
|
|
27114
27097
|
return raw.count ?? 0;
|
|
27115
27098
|
},
|
|
27116
27099
|
// Fluent builder instantiation
|
|
27117
|
-
where(
|
|
27118
|
-
|
|
27100
|
+
where(columnOrCondition, operator, value) {
|
|
27101
|
+
const builder = new QueryBuilder(client);
|
|
27102
|
+
if (typeof columnOrCondition === "object") {
|
|
27103
|
+
return builder.where(columnOrCondition);
|
|
27104
|
+
}
|
|
27105
|
+
return builder.where(columnOrCondition, operator, value);
|
|
27119
27106
|
},
|
|
27120
27107
|
orderBy(column, ascending) {
|
|
27121
27108
|
return new QueryBuilder(client).orderBy(column, ascending);
|
|
@@ -38393,13 +38380,35 @@ ${credentialScope}
|
|
|
38393
38380
|
mailer2 = new Mailer(transporter, options2, defaults);
|
|
38394
38381
|
return mailer2;
|
|
38395
38382
|
};
|
|
38383
|
+
function getHostname(urlStr) {
|
|
38384
|
+
try {
|
|
38385
|
+
const url = new URL(urlStr.includes("://") ? urlStr : `https://${urlStr}`);
|
|
38386
|
+
return url.hostname;
|
|
38387
|
+
} catch {
|
|
38388
|
+
return void 0;
|
|
38389
|
+
}
|
|
38390
|
+
}
|
|
38396
38391
|
class SMTPEmailService {
|
|
38397
38392
|
transporter = null;
|
|
38398
38393
|
config;
|
|
38399
38394
|
constructor(config) {
|
|
38400
38395
|
this.config = config;
|
|
38401
38396
|
if (config.smtp) {
|
|
38397
|
+
let smtpName = config.smtp.name;
|
|
38398
|
+
if (!smtpName) {
|
|
38399
|
+
const urlsToTry = [process.env.FRONTEND_URL, config.resetPasswordUrl, config.verifyEmailUrl];
|
|
38400
|
+
for (const urlStr of urlsToTry) {
|
|
38401
|
+
if (urlStr) {
|
|
38402
|
+
const hostname = getHostname(urlStr);
|
|
38403
|
+
if (hostname) {
|
|
38404
|
+
smtpName = hostname;
|
|
38405
|
+
break;
|
|
38406
|
+
}
|
|
38407
|
+
}
|
|
38408
|
+
}
|
|
38409
|
+
}
|
|
38402
38410
|
this.transporter = createTransport({
|
|
38411
|
+
name: smtpName,
|
|
38403
38412
|
host: config.smtp.host,
|
|
38404
38413
|
port: config.smtp.port,
|
|
38405
38414
|
secure: config.smtp.secure ?? config.smtp.port === 465,
|
|
@@ -38549,6 +38558,14 @@ ${credentialScope}
|
|
|
38549
38558
|
dir: config.collectionsDir
|
|
38550
38559
|
});
|
|
38551
38560
|
}
|
|
38561
|
+
if (config.defaultSecurityRules?.length) {
|
|
38562
|
+
for (const collection of activeCollections) {
|
|
38563
|
+
if (isPostgresCollection(collection) && (!collection.securityRules || collection.securityRules.length === 0)) {
|
|
38564
|
+
collection.securityRules = config.defaultSecurityRules;
|
|
38565
|
+
}
|
|
38566
|
+
}
|
|
38567
|
+
logger.info("Default security rules applied to collections without explicit rules");
|
|
38568
|
+
}
|
|
38552
38569
|
const realtimeServices = {};
|
|
38553
38570
|
const delegates = {};
|
|
38554
38571
|
let bootstrappers = config.bootstrappers || [];
|
|
@@ -38617,8 +38634,7 @@ ${credentialScope}
|
|
|
38617
38634
|
id: authAdapter.id
|
|
38618
38635
|
});
|
|
38619
38636
|
authConfigResult = {
|
|
38620
|
-
userService: authAdapter.userManagement ?? {}
|
|
38621
|
-
roleService: authAdapter.roleManagement ?? {}
|
|
38637
|
+
userService: authAdapter.userManagement ?? {}
|
|
38622
38638
|
};
|
|
38623
38639
|
} else {
|
|
38624
38640
|
const safeAuthConfig = config.auth;
|
|
@@ -38963,6 +38979,26 @@ ${credentialScope}
|
|
|
38963
38979
|
logger.info("Email service attached to singleton", {
|
|
38964
38980
|
configured: emailService.isConfigured()
|
|
38965
38981
|
});
|
|
38982
|
+
if (emailService.isConfigured() && typeof emailService.verifyConnection === "function") {
|
|
38983
|
+
emailService.verifyConnection().then((success) => {
|
|
38984
|
+
if (!success) {
|
|
38985
|
+
logger.warn("Warning: SMTP connection verification failed. Email delivery may fail.");
|
|
38986
|
+
} else {
|
|
38987
|
+
logger.info("SMTP connection verified successfully.");
|
|
38988
|
+
}
|
|
38989
|
+
}).catch((err) => {
|
|
38990
|
+
logger.warn("Warning: SMTP connection verification failed. Email delivery may fail.", {
|
|
38991
|
+
error: err
|
|
38992
|
+
});
|
|
38993
|
+
});
|
|
38994
|
+
}
|
|
38995
|
+
}
|
|
38996
|
+
const driverAdmin = defaultBootstrapper.getAdmin?.(defaultDriverResult);
|
|
38997
|
+
if (isSQLAdmin(driverAdmin)) {
|
|
38998
|
+
Object.assign(serverClient, {
|
|
38999
|
+
sql: (query, options2) => driverAdmin.executeSql(query, options2)
|
|
39000
|
+
});
|
|
39001
|
+
logger.info("SQL capability attached to singleton");
|
|
38966
39002
|
}
|
|
38967
39003
|
_initRebase(serverClient);
|
|
38968
39004
|
logger.info("Rebase singleton initialized");
|