@blackcode_sa/metaestetics-api 1.8.13 → 1.8.15
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/index.d.mts +1 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +536 -213
- package/dist/index.mjs +537 -214
- package/package.json +1 -1
- package/src/services/clinic/utils/filter.utils.ts +242 -57
- package/src/services/practitioner/practitioner.service.ts +195 -82
- package/src/services/procedure/procedure.service.ts +279 -123
- package/src/admin/scripts/migrateProcedures.js +0 -23
- package/src/admin/scripts/serviceAccountKey.json +0 -13
|
@@ -899,146 +899,302 @@ export class ProcedureService extends BaseService {
|
|
|
899
899
|
lastDoc: any;
|
|
900
900
|
}> {
|
|
901
901
|
try {
|
|
902
|
-
|
|
903
|
-
|
|
904
|
-
|
|
905
|
-
|
|
906
|
-
|
|
907
|
-
|
|
908
|
-
|
|
909
|
-
|
|
910
|
-
|
|
911
|
-
|
|
912
|
-
|
|
913
|
-
|
|
902
|
+
console.log("[PROCEDURE_SERVICE] Starting procedure filtering with multiple strategies");
|
|
903
|
+
|
|
904
|
+
// Geo query debug i validacija
|
|
905
|
+
if (filters.location && filters.radiusInKm) {
|
|
906
|
+
console.log('[PROCEDURE_SERVICE] Executing geo query:', {
|
|
907
|
+
location: filters.location,
|
|
908
|
+
radius: filters.radiusInKm,
|
|
909
|
+
serviceName: 'ProcedureService'
|
|
910
|
+
});
|
|
911
|
+
|
|
912
|
+
// Validacija location podataka
|
|
913
|
+
if (!filters.location.latitude || !filters.location.longitude) {
|
|
914
|
+
console.warn('[PROCEDURE_SERVICE] Invalid location data:', filters.location);
|
|
915
|
+
filters.location = undefined;
|
|
916
|
+
filters.radiusInKm = undefined;
|
|
917
|
+
}
|
|
914
918
|
}
|
|
915
919
|
|
|
916
|
-
//
|
|
917
|
-
|
|
918
|
-
|
|
919
|
-
|
|
920
|
-
if (filters.procedureCategory) {
|
|
921
|
-
constraints.push(where("category.id", "==", filters.procedureCategory));
|
|
922
|
-
}
|
|
923
|
-
if (filters.procedureSubcategory) {
|
|
924
|
-
constraints.push(where("subcategory.id", "==", filters.procedureSubcategory));
|
|
925
|
-
}
|
|
926
|
-
if (filters.procedureTechnology) {
|
|
927
|
-
constraints.push(where("technology.id", "==", filters.procedureTechnology));
|
|
928
|
-
}
|
|
929
|
-
if (filters.minPrice !== undefined) {
|
|
930
|
-
constraints.push(where("price", ">=", filters.minPrice));
|
|
931
|
-
}
|
|
932
|
-
if (filters.maxPrice !== undefined) {
|
|
933
|
-
constraints.push(where("price", "<=", filters.maxPrice));
|
|
934
|
-
}
|
|
935
|
-
if (filters.minRating !== undefined) {
|
|
936
|
-
constraints.push(where("reviewInfo.averageRating", ">=", filters.minRating));
|
|
937
|
-
}
|
|
938
|
-
if (filters.maxRating !== undefined) {
|
|
939
|
-
constraints.push(where("reviewInfo.averageRating", "<=", filters.maxRating));
|
|
940
|
-
}
|
|
941
|
-
if (filters.treatmentBenefits && filters.treatmentBenefits.length > 0) {
|
|
942
|
-
// Firestore ne podržava array-contains-all, koristi array-contains-any
|
|
943
|
-
constraints.push(where("treatmentBenefits", "array-contains-any", filters.treatmentBenefits));
|
|
944
|
-
}
|
|
945
|
-
// Text search by name (case-sensitive, Firestore limitation)
|
|
946
|
-
let useNameLower = false;
|
|
947
|
-
let searchTerm = "";
|
|
948
|
-
if (filters.nameSearch && filters.nameSearch.trim() !== "") {
|
|
949
|
-
searchTerm = filters.nameSearch.trim().toLowerCase();
|
|
950
|
-
useNameLower = true;
|
|
951
|
-
constraints.push(where("nameLower", ">=", searchTerm));
|
|
952
|
-
constraints.push(where("nameLower", "<=", searchTerm + "\uf8ff"));
|
|
953
|
-
constraints.push(orderBy("nameLower"));
|
|
954
|
-
} else {
|
|
955
|
-
constraints.push(orderBy("nameLower"));
|
|
920
|
+
// Handle geo queries separately (they work differently)
|
|
921
|
+
const isGeoQuery = filters.location && filters.radiusInKm && filters.radiusInKm > 0;
|
|
922
|
+
if (isGeoQuery) {
|
|
923
|
+
return this.handleGeoQuery(filters);
|
|
956
924
|
}
|
|
957
|
-
|
|
958
|
-
|
|
959
|
-
|
|
960
|
-
|
|
961
|
-
|
|
962
|
-
|
|
963
|
-
|
|
964
|
-
constraints.push(
|
|
925
|
+
|
|
926
|
+
// Base constraints (used in all strategies)
|
|
927
|
+
const getBaseConstraints = () => {
|
|
928
|
+
const constraints: QueryConstraint[] = [];
|
|
929
|
+
|
|
930
|
+
// Active status filter
|
|
931
|
+
if (filters.isActive !== undefined) {
|
|
932
|
+
constraints.push(where("isActive", "==", filters.isActive));
|
|
965
933
|
} else {
|
|
966
|
-
|
|
967
|
-
constraints.push(startAfter(filters.lastDoc));
|
|
934
|
+
constraints.push(where("isActive", "==", true));
|
|
968
935
|
}
|
|
969
|
-
}
|
|
970
|
-
if (filters.pagination && filters.pagination > 0) {
|
|
971
|
-
constraints.push(limit(filters.pagination));
|
|
972
|
-
}
|
|
973
936
|
|
|
974
|
-
|
|
975
|
-
|
|
976
|
-
|
|
977
|
-
const center = filters.location!;
|
|
978
|
-
const radiusInKm = filters.radiusInKm!;
|
|
979
|
-
const bounds = geohashQueryBounds(
|
|
980
|
-
[center.latitude, center.longitude],
|
|
981
|
-
radiusInKm * 1000 // Convert to meters
|
|
982
|
-
);
|
|
983
|
-
let allDocs: (Procedure & { distance: number })[] = [];
|
|
984
|
-
for (const bound of bounds) {
|
|
985
|
-
const geoConstraints = [
|
|
986
|
-
...constraints.filter(c => !(c as any).fieldPath || (c as any).fieldPath !== "name"), // Remove name orderBy for geo
|
|
987
|
-
where("clinicInfo.location.geohash", ">=", bound[0]),
|
|
988
|
-
where("clinicInfo.location.geohash", "<=", bound[1]),
|
|
989
|
-
orderBy("clinicInfo.location.geohash"),
|
|
990
|
-
];
|
|
991
|
-
const q = query(collection(this.db, PROCEDURES_COLLECTION), ...geoConstraints);
|
|
992
|
-
const querySnapshot = await getDocs(q);
|
|
993
|
-
for (const doc of querySnapshot.docs) {
|
|
994
|
-
const procedure = { ...doc.data(), id: doc.id } as Procedure;
|
|
995
|
-
const distance = distanceBetween(
|
|
996
|
-
[center.latitude, center.longitude],
|
|
997
|
-
[procedure.clinicInfo.location.latitude, procedure.clinicInfo.location.longitude]
|
|
998
|
-
);
|
|
999
|
-
const distanceInKm = distance / 1000;
|
|
1000
|
-
if (distanceInKm <= radiusInKm) {
|
|
1001
|
-
allDocs.push({ ...procedure, distance: distanceInKm });
|
|
1002
|
-
}
|
|
1003
|
-
}
|
|
937
|
+
// Filter constraints
|
|
938
|
+
if (filters.procedureFamily) {
|
|
939
|
+
constraints.push(where("family", "==", filters.procedureFamily));
|
|
1004
940
|
}
|
|
1005
|
-
|
|
1006
|
-
|
|
1007
|
-
|
|
1008
|
-
|
|
1009
|
-
|
|
1010
|
-
|
|
941
|
+
if (filters.procedureCategory) {
|
|
942
|
+
constraints.push(where("category.id", "==", filters.procedureCategory));
|
|
943
|
+
}
|
|
944
|
+
if (filters.procedureSubcategory) {
|
|
945
|
+
constraints.push(where("subcategory.id", "==", filters.procedureSubcategory));
|
|
946
|
+
}
|
|
947
|
+
if (filters.procedureTechnology) {
|
|
948
|
+
constraints.push(where("technology.id", "==", filters.procedureTechnology));
|
|
949
|
+
}
|
|
950
|
+
if (filters.minPrice !== undefined) {
|
|
951
|
+
constraints.push(where("price", ">=", filters.minPrice));
|
|
952
|
+
}
|
|
953
|
+
if (filters.maxPrice !== undefined) {
|
|
954
|
+
constraints.push(where("price", "<=", filters.maxPrice));
|
|
955
|
+
}
|
|
956
|
+
if (filters.minRating !== undefined) {
|
|
957
|
+
constraints.push(where("reviewInfo.averageRating", ">=", filters.minRating));
|
|
958
|
+
}
|
|
959
|
+
if (filters.maxRating !== undefined) {
|
|
960
|
+
constraints.push(where("reviewInfo.averageRating", "<=", filters.maxRating));
|
|
961
|
+
}
|
|
962
|
+
if (filters.treatmentBenefits && filters.treatmentBenefits.length > 0) {
|
|
963
|
+
constraints.push(where("treatmentBenefits", "array-contains-any", filters.treatmentBenefits));
|
|
964
|
+
}
|
|
965
|
+
|
|
966
|
+
return constraints;
|
|
967
|
+
};
|
|
968
|
+
|
|
969
|
+
// Strategy 1: Try nameLower search if nameSearch exists
|
|
970
|
+
if (filters.nameSearch && filters.nameSearch.trim()) {
|
|
971
|
+
try {
|
|
972
|
+
console.log("[PROCEDURE_SERVICE] Strategy 1: Trying nameLower search");
|
|
973
|
+
const searchTerm = filters.nameSearch.trim().toLowerCase();
|
|
974
|
+
const constraints = getBaseConstraints();
|
|
975
|
+
constraints.push(where("nameLower", ">=", searchTerm));
|
|
976
|
+
constraints.push(where("nameLower", "<=", searchTerm + "\uf8ff"));
|
|
977
|
+
constraints.push(orderBy("nameLower"));
|
|
978
|
+
|
|
1011
979
|
if (filters.lastDoc) {
|
|
1012
|
-
|
|
1013
|
-
|
|
980
|
+
if (typeof filters.lastDoc.data === "function") {
|
|
981
|
+
constraints.push(startAfter(filters.lastDoc));
|
|
982
|
+
} else if (Array.isArray(filters.lastDoc)) {
|
|
983
|
+
constraints.push(startAfter(...filters.lastDoc));
|
|
984
|
+
} else {
|
|
985
|
+
constraints.push(startAfter(filters.lastDoc));
|
|
986
|
+
}
|
|
987
|
+
}
|
|
988
|
+
constraints.push(limit(filters.pagination || 10));
|
|
989
|
+
|
|
990
|
+
const q = query(collection(this.db, PROCEDURES_COLLECTION), ...constraints);
|
|
991
|
+
const querySnapshot = await getDocs(q);
|
|
992
|
+
const procedures = querySnapshot.docs.map(doc => ({ ...doc.data(), id: doc.id } as Procedure));
|
|
993
|
+
const lastDoc = querySnapshot.docs.length > 0 ? querySnapshot.docs[querySnapshot.docs.length - 1] : null;
|
|
994
|
+
|
|
995
|
+
console.log(`[PROCEDURE_SERVICE] Strategy 1 success: ${procedures.length} procedures`);
|
|
996
|
+
|
|
997
|
+
// Fix Load More - ako je broj rezultata manji od pagination, nema više
|
|
998
|
+
if (procedures.length < (filters.pagination || 10)) {
|
|
999
|
+
return { procedures, lastDoc: null };
|
|
1014
1000
|
}
|
|
1015
|
-
|
|
1001
|
+
return { procedures, lastDoc };
|
|
1002
|
+
} catch (error) {
|
|
1003
|
+
console.log("[PROCEDURE_SERVICE] Strategy 1 failed:", error);
|
|
1016
1004
|
}
|
|
1017
|
-
|
|
1018
|
-
|
|
1019
|
-
|
|
1020
|
-
|
|
1021
|
-
|
|
1022
|
-
|
|
1023
|
-
|
|
1024
|
-
|
|
1025
|
-
// Ukloni poslednja 3 constraints (nameLower >=, nameLower <=, orderBy nameLower)
|
|
1026
|
-
constraints.pop();
|
|
1027
|
-
constraints.pop();
|
|
1028
|
-
constraints.pop();
|
|
1005
|
+
}
|
|
1006
|
+
|
|
1007
|
+
// Strategy 2: Try name field search as fallback
|
|
1008
|
+
if (filters.nameSearch && filters.nameSearch.trim()) {
|
|
1009
|
+
try {
|
|
1010
|
+
console.log("[PROCEDURE_SERVICE] Strategy 2: Trying name field search");
|
|
1011
|
+
const searchTerm = filters.nameSearch.trim().toLowerCase();
|
|
1012
|
+
const constraints = getBaseConstraints();
|
|
1029
1013
|
constraints.push(where("name", ">=", searchTerm));
|
|
1030
1014
|
constraints.push(where("name", "<=", searchTerm + "\uf8ff"));
|
|
1031
1015
|
constraints.push(orderBy("name"));
|
|
1032
|
-
|
|
1033
|
-
|
|
1016
|
+
|
|
1017
|
+
if (filters.lastDoc) {
|
|
1018
|
+
if (typeof filters.lastDoc.data === "function") {
|
|
1019
|
+
constraints.push(startAfter(filters.lastDoc));
|
|
1020
|
+
} else if (Array.isArray(filters.lastDoc)) {
|
|
1021
|
+
constraints.push(startAfter(...filters.lastDoc));
|
|
1022
|
+
} else {
|
|
1023
|
+
constraints.push(startAfter(filters.lastDoc));
|
|
1024
|
+
}
|
|
1025
|
+
}
|
|
1026
|
+
constraints.push(limit(filters.pagination || 10));
|
|
1027
|
+
|
|
1028
|
+
const q = query(collection(this.db, PROCEDURES_COLLECTION), ...constraints);
|
|
1029
|
+
const querySnapshot = await getDocs(q);
|
|
1030
|
+
const procedures = querySnapshot.docs.map(doc => ({ ...doc.data(), id: doc.id } as Procedure));
|
|
1031
|
+
const lastDoc = querySnapshot.docs.length > 0 ? querySnapshot.docs[querySnapshot.docs.length - 1] : null;
|
|
1032
|
+
|
|
1033
|
+
console.log(`[PROCEDURE_SERVICE] Strategy 2 success: ${procedures.length} procedures`);
|
|
1034
|
+
|
|
1035
|
+
// Fix Load More - ako je broj rezultata manji od pagination, nema više
|
|
1036
|
+
if (procedures.length < (filters.pagination || 10)) {
|
|
1037
|
+
return { procedures, lastDoc: null };
|
|
1038
|
+
}
|
|
1039
|
+
return { procedures, lastDoc };
|
|
1040
|
+
} catch (error) {
|
|
1041
|
+
console.log("[PROCEDURE_SERVICE] Strategy 2 failed:", error);
|
|
1042
|
+
}
|
|
1043
|
+
}
|
|
1044
|
+
|
|
1045
|
+
// Strategy 3: orderBy createdAt with client-side filtering
|
|
1046
|
+
try {
|
|
1047
|
+
console.log("[PROCEDURE_SERVICE] Strategy 3: Using createdAt orderBy with client-side filtering");
|
|
1048
|
+
const constraints = getBaseConstraints();
|
|
1049
|
+
constraints.push(orderBy("createdAt", "desc"));
|
|
1050
|
+
|
|
1051
|
+
if (filters.lastDoc) {
|
|
1052
|
+
if (typeof filters.lastDoc.data === "function") {
|
|
1053
|
+
constraints.push(startAfter(filters.lastDoc));
|
|
1054
|
+
} else if (Array.isArray(filters.lastDoc)) {
|
|
1055
|
+
constraints.push(startAfter(...filters.lastDoc));
|
|
1056
|
+
} else {
|
|
1057
|
+
constraints.push(startAfter(filters.lastDoc));
|
|
1058
|
+
}
|
|
1059
|
+
}
|
|
1060
|
+
constraints.push(limit(filters.pagination || 10));
|
|
1061
|
+
|
|
1062
|
+
const q = query(collection(this.db, PROCEDURES_COLLECTION), ...constraints);
|
|
1063
|
+
const querySnapshot = await getDocs(q);
|
|
1064
|
+
let procedures = querySnapshot.docs.map(doc => ({ ...doc.data(), id: doc.id } as Procedure));
|
|
1065
|
+
|
|
1066
|
+
// Poboljšan client-side name filtering
|
|
1067
|
+
if (filters.nameSearch && filters.nameSearch.trim()) {
|
|
1068
|
+
const searchTerm = filters.nameSearch.trim().toLowerCase();
|
|
1069
|
+
procedures = procedures.filter(procedure => {
|
|
1070
|
+
const name = (procedure.name || '').toLowerCase();
|
|
1071
|
+
const nameLower = procedure.nameLower || '';
|
|
1072
|
+
return name.includes(searchTerm) || nameLower.includes(searchTerm);
|
|
1073
|
+
});
|
|
1074
|
+
console.log(`[PROCEDURE_SERVICE] Applied name filter, results: ${procedures.length}`);
|
|
1075
|
+
}
|
|
1076
|
+
|
|
1077
|
+
const lastDoc = querySnapshot.docs.length > 0 ? querySnapshot.docs[querySnapshot.docs.length - 1] : null;
|
|
1078
|
+
console.log(`[PROCEDURE_SERVICE] Strategy 3 success: ${procedures.length} procedures`);
|
|
1079
|
+
|
|
1080
|
+
// Fix Load More - ako je broj rezultata manji od pagination, nema više
|
|
1081
|
+
if (procedures.length < (filters.pagination || 10)) {
|
|
1082
|
+
return { procedures, lastDoc: null };
|
|
1034
1083
|
}
|
|
1035
|
-
|
|
1036
|
-
|
|
1037
|
-
|
|
1084
|
+
return { procedures, lastDoc };
|
|
1085
|
+
} catch (error) {
|
|
1086
|
+
console.log("[PROCEDURE_SERVICE] Strategy 3 failed:", error);
|
|
1038
1087
|
}
|
|
1088
|
+
|
|
1089
|
+
// Strategy 4: Minimal query fallback
|
|
1090
|
+
try {
|
|
1091
|
+
console.log("[PROCEDURE_SERVICE] Strategy 4: Minimal query fallback");
|
|
1092
|
+
const constraints: QueryConstraint[] = [
|
|
1093
|
+
where("isActive", "==", true),
|
|
1094
|
+
orderBy("createdAt", "desc"),
|
|
1095
|
+
limit(filters.pagination || 10)
|
|
1096
|
+
];
|
|
1097
|
+
|
|
1098
|
+
const q = query(collection(this.db, PROCEDURES_COLLECTION), ...constraints);
|
|
1099
|
+
const querySnapshot = await getDocs(q);
|
|
1100
|
+
let procedures = querySnapshot.docs.map(doc => ({ ...doc.data(), id: doc.id } as Procedure));
|
|
1101
|
+
|
|
1102
|
+
// Poboljšan client-side name filtering
|
|
1103
|
+
if (filters.nameSearch && filters.nameSearch.trim()) {
|
|
1104
|
+
const searchTerm = filters.nameSearch.trim().toLowerCase();
|
|
1105
|
+
procedures = procedures.filter(procedure => {
|
|
1106
|
+
const name = (procedure.name || '').toLowerCase();
|
|
1107
|
+
const nameLower = procedure.nameLower || '';
|
|
1108
|
+
return name.includes(searchTerm) || nameLower.includes(searchTerm);
|
|
1109
|
+
});
|
|
1110
|
+
console.log(`[PROCEDURE_SERVICE] Applied name filter, results: ${procedures.length}`);
|
|
1111
|
+
}
|
|
1112
|
+
|
|
1113
|
+
const lastDoc = querySnapshot.docs.length > 0 ? querySnapshot.docs[querySnapshot.docs.length - 1] : null;
|
|
1114
|
+
console.log(`[PROCEDURE_SERVICE] Strategy 4 success: ${procedures.length} procedures`);
|
|
1115
|
+
|
|
1116
|
+
// Fix Load More - ako je broj rezultata manji od pagination, nema više
|
|
1117
|
+
if (procedures.length < (filters.pagination || 10)) {
|
|
1118
|
+
return { procedures, lastDoc: null };
|
|
1119
|
+
}
|
|
1120
|
+
return { procedures, lastDoc };
|
|
1121
|
+
} catch (error) {
|
|
1122
|
+
console.log("[PROCEDURE_SERVICE] Strategy 4 failed:", error);
|
|
1123
|
+
}
|
|
1124
|
+
|
|
1125
|
+
// All strategies failed
|
|
1126
|
+
console.log("[PROCEDURE_SERVICE] All strategies failed, returning empty result");
|
|
1127
|
+
return { procedures: [], lastDoc: null };
|
|
1128
|
+
|
|
1039
1129
|
} catch (error) {
|
|
1040
1130
|
console.error("[PROCEDURE_SERVICE] Error filtering procedures:", error);
|
|
1041
|
-
|
|
1131
|
+
return { procedures: [], lastDoc: null };
|
|
1132
|
+
}
|
|
1133
|
+
}
|
|
1134
|
+
|
|
1135
|
+
private handleGeoQuery(filters: any): Promise<{ procedures: (Procedure & { distance?: number })[]; lastDoc: any }> {
|
|
1136
|
+
console.log('[PROCEDURE_SERVICE] Executing geo query with enhanced debugging');
|
|
1137
|
+
|
|
1138
|
+
try {
|
|
1139
|
+
// Enhanced geo query implementation with proper debugging
|
|
1140
|
+
const location = filters.location;
|
|
1141
|
+
const radiusInKm = filters.radiusInKm;
|
|
1142
|
+
|
|
1143
|
+
console.log('[PROCEDURE_SERVICE] Geo query parameters:', {
|
|
1144
|
+
latitude: location.latitude,
|
|
1145
|
+
longitude: location.longitude,
|
|
1146
|
+
radiusInKm: radiusInKm,
|
|
1147
|
+
pagination: filters.pagination || 10
|
|
1148
|
+
});
|
|
1149
|
+
|
|
1150
|
+
// For now, return a basic query and apply geo filtering
|
|
1151
|
+
// This can be enhanced with proper geohash bounds later
|
|
1152
|
+
const constraints: QueryConstraint[] = [
|
|
1153
|
+
where("isActive", "==", true),
|
|
1154
|
+
orderBy("createdAt", "desc"),
|
|
1155
|
+
limit((filters.pagination || 10) * 3) // Get more results for geo filtering
|
|
1156
|
+
];
|
|
1157
|
+
|
|
1158
|
+
const q = query(collection(this.db, PROCEDURES_COLLECTION), ...constraints);
|
|
1159
|
+
|
|
1160
|
+
return getDocs(q).then(querySnapshot => {
|
|
1161
|
+
let procedures = querySnapshot.docs.map(doc => ({ ...doc.data(), id: doc.id } as Procedure));
|
|
1162
|
+
|
|
1163
|
+
// Apply geo filtering
|
|
1164
|
+
procedures = procedures.filter(procedure => {
|
|
1165
|
+
const clinicLocation = procedure.clinicInfo?.location;
|
|
1166
|
+
if (!clinicLocation?.latitude || !clinicLocation?.longitude) {
|
|
1167
|
+
return false;
|
|
1168
|
+
}
|
|
1169
|
+
|
|
1170
|
+
const distance = distanceBetween(
|
|
1171
|
+
[location.latitude, location.longitude],
|
|
1172
|
+
[clinicLocation.latitude, clinicLocation.longitude]
|
|
1173
|
+
) / 1000; // Convert to km
|
|
1174
|
+
|
|
1175
|
+
// Add distance to procedure object
|
|
1176
|
+
(procedure as any).distance = distance;
|
|
1177
|
+
|
|
1178
|
+
return distance <= radiusInKm;
|
|
1179
|
+
});
|
|
1180
|
+
|
|
1181
|
+
// Sort by distance
|
|
1182
|
+
procedures.sort((a, b) => ((a as any).distance || 0) - ((b as any).distance || 0));
|
|
1183
|
+
|
|
1184
|
+
// Limit to pagination size
|
|
1185
|
+
procedures = procedures.slice(0, filters.pagination || 10);
|
|
1186
|
+
|
|
1187
|
+
console.log(`[PROCEDURE_SERVICE] Geo query success: ${procedures.length} procedures within ${radiusInKm}km`);
|
|
1188
|
+
|
|
1189
|
+
// Fix Load More for geo queries
|
|
1190
|
+
const lastDoc = procedures.length < (filters.pagination || 10) ? null : querySnapshot.docs[querySnapshot.docs.length - 1];
|
|
1191
|
+
|
|
1192
|
+
return { procedures, lastDoc };
|
|
1193
|
+
});
|
|
1194
|
+
|
|
1195
|
+
} catch (error) {
|
|
1196
|
+
console.error('[PROCEDURE_SERVICE] Geo query failed:', error);
|
|
1197
|
+
return Promise.resolve({ procedures: [], lastDoc: null });
|
|
1042
1198
|
}
|
|
1043
1199
|
}
|
|
1044
1200
|
|
|
@@ -1,23 +0,0 @@
|
|
|
1
|
-
const admin = require("firebase-admin");
|
|
2
|
-
const serviceAccount = require("./serviceAccountKey.json");
|
|
3
|
-
|
|
4
|
-
admin.initializeApp({
|
|
5
|
-
credential: admin.credential.cert(serviceAccount),
|
|
6
|
-
projectId: "metaestetics"
|
|
7
|
-
});
|
|
8
|
-
|
|
9
|
-
const db = admin.firestore();
|
|
10
|
-
|
|
11
|
-
async function migrateProcedures() {
|
|
12
|
-
const snapshot = await db.collection("procedures").get();
|
|
13
|
-
for (const doc of snapshot.docs) {
|
|
14
|
-
const data = doc.data();
|
|
15
|
-
if (!data.nameLower && data.name) {
|
|
16
|
-
await doc.ref.update({ nameLower: data.name.toLowerCase() });
|
|
17
|
-
console.log(`Updated ${doc.id}`);
|
|
18
|
-
}
|
|
19
|
-
}
|
|
20
|
-
console.log("Migration complete!");
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
migrateProcedures();
|
|
@@ -1,13 +0,0 @@
|
|
|
1
|
-
{
|
|
2
|
-
"type": "service_account",
|
|
3
|
-
"project_id": "metaestetics",
|
|
4
|
-
"private_key_id": "e2dccd35845667bdebcea99959ca40a52f54d27f",
|
|
5
|
-
"private_key": "-----BEGIN PRIVATE KEY-----\nMIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQC7yzLPjlr/6pSA\ns3Og/2kuwdAfoBr8+M2HoVlydy/WXm73u29i9ps+ilvUBB90PRkidBCaBmqOwjdV\n0MEgUWF+7Zi3PLbfuFGX929uzpAEWuNR3ZqiyrtqsnGycNw0tdccH/RM4pTgDrLP\nETyN6HlnKIcaPgpU/qKXasOYEv8UlMRuSAFG6ZjsMr15fHzECgHNUqDwHz4u1aN3\nNDwYAnucVIpCQqKjwp6nxtSO8WSLKP5P/E3J2Dob1EYEPf8UvhXwtWVXXI/rbIQe\nfnQopHSLesvw2poks9hCQXNsLNptCRG3hj1asNZ6HutMPWqQPAKss/FpkkF6/RNu\niOnWOzf/AgMBAAECggEAFnQpUx/WSZsmvmy2ep2PWgPaeq2ODIlDKeBk7YbKtXr9\nEanbm52Y2lV4vVTw3dkgVDpEceYqf39BVoVrUg3o9mA6Tk54Hy/OsbjoHfucxKiJ\nXZR9lNFgr1U+uvM7oSHM4pP/heHhoxie0Jti/iS5v1fdL4oTei4oCqq9UEWVMkSS\n52yoBH7oeH15WVp2B3KEVU7cAI5MeFXQxQ8idFfqqShhR5uQnwx8uR38b9vaq+Cy\nK5zV+dStsphVq3/gEsjmFkYe7BgpMQyHkw7kmNuISDYdy/W1G6t0kUNn5LzhJ88E\nICr1FttaNQDeBE4VgnC3Daj1hBOQyrbGszbgxmklgQKBgQDm1f3uyLwmVTsTF3sE\nsggTYsmz9AppI778IuWjhiegfmbWI5YsgGl9c9XOcBCZ+2vI7rIDeFneRxBX4TrT\nAPcT+z8AG5PiJuyOa7Nny+tNzplfPf/2X2MrQXaYvWihPWDZtXmqYY3p/Z8wkYhA\n1C/2vMLyKiP/Md7mtSBXmjgnrwKBgQDQRAQL69osb07mJDNoln+FWyQhJQoVQFSw\npPBkcic0d1gglW7ne2O43mOUG7t3EQBPgXg8xXMiv4dqz1PQpVJp4CewCklbAh/C\nfFmhWCUvgG21Sc3t0hpvsPLN3xeqVOUeB5FNhf5t+j4HqTGClD98Plks0iXVnfFO\nPmNYK764sQKBgD4W+UKtQ86bxlQQUMqmiH2OaOq6jcJSFyEC0fn2L9p/pXGcCNzX\nfYh9C9mHUy/X7NoTOlasnJ+pRcAdmREAhXUec4e340NFbQOx/IPC2fwHwkFYD+1Z\nIveTmC7lY6tbMx3cLmmh6+Ywjg0mWBv39x7LDzTMGPqfk3FC7vwhQ1GJAoGARpoY\nKRZuYsvlGl3BU75ZQpMQH3BYB7ZEP5HasKKGKeIfbQRbkXuh5cT2SvpPxeBsk4dX\nhHqHOotlU88vIbc5xgyoR6RlE8YXkC3pkKm6CW1nQ6LefbXRInYBCcuMUUDwXwq/\ntmErTIsdxikUUKkDEJJuVqRzEQS3DghWU0iZIjECgYEA5PMdyGMZJQVTzP/QjSjC\no5uEDGOAjg/wMGFfXRbKBcJZ1yoqUuSOKuuacyNborfTj3RTckl9uO/I68wnFk5p\nRNNndYQXWAmjSvPBKnRuE+eSE1eDPmpZPPBgJqwbSxUfWMprY7yEWpKF2oEbkzpD\nrUkzrniMVABYZnj8CoNV3DM=\n-----END PRIVATE KEY-----\n",
|
|
6
|
-
"client_email": "firebase-adminsdk-5nsyj@metaestetics.iam.gserviceaccount.com",
|
|
7
|
-
"client_id": "114278988963249785723",
|
|
8
|
-
"auth_uri": "https://accounts.google.com/o/oauth2/auth",
|
|
9
|
-
"token_uri": "https://oauth2.googleapis.com/token",
|
|
10
|
-
"auth_provider_x509_cert_url": "https://www.googleapis.com/oauth2/v1/certs",
|
|
11
|
-
"client_x509_cert_url": "https://www.googleapis.com/robot/v1/metadata/x509/firebase-adminsdk-5nsyj%40metaestetics.iam.gserviceaccount.com",
|
|
12
|
-
"universe_domain": "googleapis.com"
|
|
13
|
-
}
|