@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.
@@ -899,146 +899,302 @@ export class ProcedureService extends BaseService {
899
899
  lastDoc: any;
900
900
  }> {
901
901
  try {
902
- // Determine if we're doing a geo query or a regular query
903
- const isGeoQuery =
904
- filters.location && filters.radiusInKm && filters.radiusInKm > 0;
905
-
906
- // Initialize base constraints
907
- const constraints: QueryConstraint[] = [];
908
-
909
- // Add active status filter (default to active if not specified)
910
- if (filters.isActive !== undefined) {
911
- constraints.push(where("isActive", "==", filters.isActive));
912
- } else {
913
- constraints.push(where("isActive", "==", true));
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
- // Filter by procedure family if specified
917
- if (filters.procedureFamily) {
918
- constraints.push(where("family", "==", filters.procedureFamily));
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
- if (filters.lastDoc) {
958
- // Firestore pagination: support both QueryDocumentSnapshot and value arrays
959
- if (typeof filters.lastDoc.data === "function") {
960
- // QueryDocumentSnapshot
961
- constraints.push(startAfter(filters.lastDoc));
962
- } else if (Array.isArray(filters.lastDoc)) {
963
- // Array of values for multiple orderBy fields
964
- constraints.push(startAfter(...filters.lastDoc));
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
- // Single value
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
- // Geo-query: special handling
975
- if (isGeoQuery) {
976
- // For geo queries, use geohash bounds and add all other constraints
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
- // Sort by distance
1006
- allDocs.sort((a, b) => a.distance - b.distance);
1007
- // Paginate
1008
- let paginated = allDocs;
1009
- if (filters.pagination && filters.pagination > 0) {
1010
- let startIndex = 0;
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
- const lastDocIndex = allDocs.findIndex(p => p.id === filters.lastDoc.id);
1013
- if (lastDocIndex !== -1) startIndex = lastDocIndex + 1;
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
- paginated = allDocs.slice(startIndex, startIndex + filters.pagination);
1001
+ return { procedures, lastDoc };
1002
+ } catch (error) {
1003
+ console.log("[PROCEDURE_SERVICE] Strategy 1 failed:", error);
1016
1004
  }
1017
- const lastVisibleDoc = paginated.length > 0 ? paginated[paginated.length - 1] : null;
1018
- return { procedures: paginated, lastDoc: lastVisibleDoc };
1019
- } else {
1020
- // Regular query
1021
- let q = query(collection(this.db, PROCEDURES_COLLECTION), ...constraints);
1022
- let querySnapshot = await getDocs(q);
1023
- // Fallback na name ako nema rezultata i koristi se nameLower
1024
- if (useNameLower && querySnapshot.empty && searchTerm) {
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
- q = query(collection(this.db, PROCEDURES_COLLECTION), ...constraints);
1033
- querySnapshot = await getDocs(q);
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
- const procedures = querySnapshot.docs.map((doc) => ({ ...doc.data(), id: doc.id } as Procedure));
1036
- const lastVisibleDoc = querySnapshot.docs.length > 0 ? querySnapshot.docs[querySnapshot.docs.length - 1] : null;
1037
- return { procedures, lastDoc: lastVisibleDoc };
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
- throw error;
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
- }