Complix Integration added
This commit is contained in:
5
pom.xml
5
pom.xml
@@ -63,6 +63,11 @@
|
|||||||
<artifactId>spring-boot-starter-webmvc-test</artifactId>
|
<artifactId>spring-boot-starter-webmvc-test</artifactId>
|
||||||
<scope>test</scope>
|
<scope>test</scope>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>com.google.firebase</groupId>
|
||||||
|
<artifactId>firebase-admin</artifactId>
|
||||||
|
<version>9.3.0</version>
|
||||||
|
</dependency>
|
||||||
</dependencies>
|
</dependencies>
|
||||||
|
|
||||||
<build>
|
<build>
|
||||||
|
|||||||
68
src/main/java/com/rit/whatsapp/config/FirebaseConfig.java
Normal file
68
src/main/java/com/rit/whatsapp/config/FirebaseConfig.java
Normal file
@@ -0,0 +1,68 @@
|
|||||||
|
package com.rit.whatsapp.config;
|
||||||
|
|
||||||
|
import com.google.auth.oauth2.GoogleCredentials;
|
||||||
|
import com.google.cloud.firestore.Firestore;
|
||||||
|
import com.google.firebase.FirebaseApp;
|
||||||
|
import com.google.firebase.FirebaseOptions;
|
||||||
|
import com.google.firebase.cloud.FirestoreClient;
|
||||||
|
import com.google.firebase.cloud.StorageClient;
|
||||||
|
import com.google.cloud.storage.Bucket;
|
||||||
|
import org.springframework.beans.factory.annotation.Value;
|
||||||
|
import org.springframework.context.annotation.Bean;
|
||||||
|
import org.springframework.context.annotation.Configuration;
|
||||||
|
import org.springframework.core.io.Resource;
|
||||||
|
import org.springframework.core.io.ResourceLoader;
|
||||||
|
|
||||||
|
import java.io.InputStream;
|
||||||
|
|
||||||
|
@Configuration
|
||||||
|
public class FirebaseConfig {
|
||||||
|
|
||||||
|
@Value("${firebase.credentials.path:classpath:firebase-service-account.json}")
|
||||||
|
private String credentialsPath;
|
||||||
|
|
||||||
|
@Value("${firebase.storage.bucket:}")
|
||||||
|
private String storageBucket;
|
||||||
|
|
||||||
|
private final ResourceLoader resourceLoader;
|
||||||
|
|
||||||
|
public FirebaseConfig(ResourceLoader resourceLoader) {
|
||||||
|
this.resourceLoader = resourceLoader;
|
||||||
|
}
|
||||||
|
|
||||||
|
private synchronized void initFirebase() {
|
||||||
|
if (FirebaseApp.getApps().isEmpty()) {
|
||||||
|
try {
|
||||||
|
Resource resource = resourceLoader.getResource(credentialsPath);
|
||||||
|
try (InputStream serviceAccount = resource.getInputStream()) {
|
||||||
|
FirebaseOptions.Builder optionsBuilder = FirebaseOptions.builder()
|
||||||
|
.setCredentials(GoogleCredentials.fromStream(serviceAccount));
|
||||||
|
|
||||||
|
if (storageBucket != null && !storageBucket.isEmpty()) {
|
||||||
|
optionsBuilder.setStorageBucket(storageBucket);
|
||||||
|
}
|
||||||
|
|
||||||
|
FirebaseApp.initializeApp(optionsBuilder.build());
|
||||||
|
System.out.println("Firebase initialized successfully.");
|
||||||
|
}
|
||||||
|
} catch (Exception e) {
|
||||||
|
throw new RuntimeException("Failed to initialize Firebase", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Bean
|
||||||
|
public Firestore firestore() {
|
||||||
|
initFirebase();
|
||||||
|
return FirestoreClient.getFirestore();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Bean
|
||||||
|
public Bucket storageBucket() {
|
||||||
|
initFirebase();
|
||||||
|
if (storageBucket != null && !storageBucket.isEmpty()) {
|
||||||
|
return StorageClient.getInstance().bucket();
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -101,7 +101,6 @@ public class WebhookController {
|
|||||||
String body = messageNode.get("text").get("body").asText();
|
String body = messageNode.get("text").get("body").asText();
|
||||||
handleUnauthenticatedUser(fromPhoneNumber, body, currentState);
|
handleUnauthenticatedUser(fromPhoneNumber, body, currentState);
|
||||||
} else {
|
} else {
|
||||||
// If they send a sticker/image/etc before logging in, force login
|
|
||||||
handleUnauthenticatedUser(fromPhoneNumber, "", currentState);
|
handleUnauthenticatedUser(fromPhoneNumber, "", currentState);
|
||||||
}
|
}
|
||||||
return ResponseEntity.ok("EVENT_RECEIVED");
|
return ResponseEntity.ok("EVENT_RECEIVED");
|
||||||
@@ -111,6 +110,15 @@ public class WebhookController {
|
|||||||
if ("text".equals(messageType)) {
|
if ("text".equals(messageType)) {
|
||||||
String messageBody = messageNode.get("text").get("body").asText();
|
String messageBody = messageNode.get("text").get("body").asText();
|
||||||
handleAuthenticatedTextMessage(fromPhoneNumber, messageBody, currentState);
|
handleAuthenticatedTextMessage(fromPhoneNumber, messageBody, currentState);
|
||||||
|
} else if ("image".equals(messageType)) {
|
||||||
|
if (currentState == UserState.AWAITING_GRIEVANCE_IMAGE) {
|
||||||
|
JsonNode imageNode = messageNode.get("image");
|
||||||
|
String mediaId = imageNode.get("id").asText();
|
||||||
|
String mimeType = imageNode.has("mime_type") ? imageNode.get("mime_type").asText() : "image/jpeg";
|
||||||
|
grievanceHandler.handleImageInput(fromPhoneNumber, mediaId, mimeType);
|
||||||
|
} else {
|
||||||
|
studentMenuHandler.sendMainMenu(fromPhoneNumber);
|
||||||
|
}
|
||||||
} else if ("interactive".equals(messageType)) {
|
} else if ("interactive".equals(messageType)) {
|
||||||
JsonNode interactiveNode = messageNode.get("interactive");
|
JsonNode interactiveNode = messageNode.get("interactive");
|
||||||
String interactiveType = interactiveNode.get("type").asText();
|
String interactiveType = interactiveNode.get("type").asText();
|
||||||
@@ -142,7 +150,6 @@ public class WebhookController {
|
|||||||
loginHandler.handlePasscodeInput(phoneNumber, messageBody);
|
loginHandler.handlePasscodeInput(phoneNumber, messageBody);
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
// Trigger the start of the login flow
|
|
||||||
loginHandler.startLoginFlow(phoneNumber);
|
loginHandler.startLoginFlow(phoneNumber);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@@ -150,26 +157,40 @@ public class WebhookController {
|
|||||||
|
|
||||||
private void handleAuthenticatedTextMessage(String phoneNumber, String messageBody, UserState currentState) {
|
private void handleAuthenticatedTextMessage(String phoneNumber, String messageBody, UserState currentState) {
|
||||||
switch (currentState) {
|
switch (currentState) {
|
||||||
|
case AWAITING_GRIEVANCE_TITLE:
|
||||||
|
grievanceHandler.handleTitleInput(phoneNumber, messageBody);
|
||||||
|
break;
|
||||||
case AWAITING_GRIEVANCE_DESC:
|
case AWAITING_GRIEVANCE_DESC:
|
||||||
grievanceHandler.handleGrievanceDescription(phoneNumber, messageBody);
|
grievanceHandler.handleDescriptionInput(phoneNumber, messageBody);
|
||||||
|
break;
|
||||||
|
case AWAITING_GRIEVANCE_IMAGE:
|
||||||
|
if (messageBody.trim().equalsIgnoreCase("skip")) {
|
||||||
|
grievanceHandler.handleSkipImage(phoneNumber);
|
||||||
|
} else {
|
||||||
|
messageService.sendMessage(phoneNumber, "Please send a photo or type 'skip' / click 'Skip Image' button.");
|
||||||
|
}
|
||||||
break;
|
break;
|
||||||
case MAIN_MENU:
|
case MAIN_MENU:
|
||||||
default:
|
default:
|
||||||
// Fallback to main menu
|
|
||||||
studentMenuHandler.sendMainMenu(phoneNumber);
|
studentMenuHandler.sendMainMenu(phoneNumber);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void handleInteractiveListSelection(String phoneNumber, String selectedListId, UserState currentState) {
|
private void handleInteractiveListSelection(String phoneNumber, String selectedListId, UserState currentState) {
|
||||||
if (selectedListId.startsWith("student_")) {
|
if (selectedListId.startsWith("grv_action_")) {
|
||||||
// They clicked an option from the main menu
|
grievanceHandler.handleActionSelection(phoneNumber, selectedListId);
|
||||||
|
} else if (selectedListId.startsWith("grv_dept_")) {
|
||||||
|
grievanceHandler.handleDepartmentSelection(phoneNumber, selectedListId);
|
||||||
|
} else if (selectedListId.startsWith("grv_status_")) {
|
||||||
|
grievanceHandler.handleStatusSelection(phoneNumber, selectedListId);
|
||||||
|
} else if (selectedListId.startsWith("grv_delete_")) {
|
||||||
|
grievanceHandler.handleDeleteSelection(phoneNumber, selectedListId);
|
||||||
|
} else if (selectedListId.startsWith("student_")) {
|
||||||
studentMenuHandler.handleMenuSelection(phoneNumber, selectedListId);
|
studentMenuHandler.handleMenuSelection(phoneNumber, selectedListId);
|
||||||
} else if (selectedListId.startsWith("sem_")) {
|
} else if (selectedListId.startsWith("sem_")) {
|
||||||
// They clicked an option from the semester selection list
|
|
||||||
studentMenuHandler.handleSemesterSelection(phoneNumber, selectedListId);
|
studentMenuHandler.handleSemesterSelection(phoneNumber, selectedListId);
|
||||||
} else if (selectedListId.startsWith("receipt_") || selectedListId.startsWith("exam_") || selectedListId.startsWith("acad_") || selectedListId.startsWith("cons_")) {
|
} else if (selectedListId.startsWith("receipt_") || selectedListId.startsWith("exam_") || selectedListId.startsWith("acad_") || selectedListId.startsWith("cons_")) {
|
||||||
// They clicked a receipt download option
|
|
||||||
studentMenuHandler.handleReceiptSelection(phoneNumber, selectedListId);
|
studentMenuHandler.handleReceiptSelection(phoneNumber, selectedListId);
|
||||||
} else {
|
} else {
|
||||||
studentMenuHandler.sendMainMenu(phoneNumber);
|
studentMenuHandler.sendMainMenu(phoneNumber);
|
||||||
@@ -223,8 +244,8 @@ public class WebhookController {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (currentState == UserState.AWAITING_GRIEVANCE_CATEGORY) {
|
if ("grv_skip_image".equals(selectedButtonId)) {
|
||||||
grievanceHandler.handleCategorySelection(phoneNumber, selectedButtonId);
|
grievanceHandler.handleSkipImage(phoneNumber);
|
||||||
} else {
|
} else {
|
||||||
studentMenuHandler.sendMainMenu(phoneNumber);
|
studentMenuHandler.sendMainMenu(phoneNumber);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,19 +1,16 @@
|
|||||||
package com.rit.whatsapp.handler;
|
package com.rit.whatsapp.handler;
|
||||||
|
|
||||||
|
import com.rit.whatsapp.model.GrievanceDraft;
|
||||||
import com.rit.whatsapp.model.UserState;
|
import com.rit.whatsapp.model.UserState;
|
||||||
|
import com.rit.whatsapp.service.FirebaseService;
|
||||||
import com.rit.whatsapp.service.SessionService;
|
import com.rit.whatsapp.service.SessionService;
|
||||||
import com.rit.whatsapp.service.WhatsAppMessageBuilder;
|
import com.rit.whatsapp.service.WhatsAppMessageBuilder;
|
||||||
import com.rit.whatsapp.service.WhatsAppMessageService;
|
import com.rit.whatsapp.service.WhatsAppMessageService;
|
||||||
import org.springframework.beans.factory.annotation.Autowired;
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
|
import org.springframework.context.annotation.Lazy;
|
||||||
import org.springframework.stereotype.Component;
|
import org.springframework.stereotype.Component;
|
||||||
|
|
||||||
import org.springframework.context.annotation.Lazy;
|
import java.util.*;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.HashMap;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.Map;
|
|
||||||
import java.util.UUID;
|
|
||||||
|
|
||||||
@Component
|
@Component
|
||||||
public class GrievanceHandler {
|
public class GrievanceHandler {
|
||||||
@@ -21,54 +18,270 @@ public class GrievanceHandler {
|
|||||||
private final WhatsAppMessageService messageService;
|
private final WhatsAppMessageService messageService;
|
||||||
private final SessionService sessionService;
|
private final SessionService sessionService;
|
||||||
private final StudentMenuHandler studentMenuHandler;
|
private final StudentMenuHandler studentMenuHandler;
|
||||||
|
private final FirebaseService firebaseService;
|
||||||
|
|
||||||
@Autowired
|
@Autowired
|
||||||
public GrievanceHandler(WhatsAppMessageService messageService, SessionService sessionService, @Lazy StudentMenuHandler studentMenuHandler) {
|
public GrievanceHandler(WhatsAppMessageService messageService,
|
||||||
|
SessionService sessionService,
|
||||||
|
@Lazy StudentMenuHandler studentMenuHandler,
|
||||||
|
FirebaseService firebaseService) {
|
||||||
this.messageService = messageService;
|
this.messageService = messageService;
|
||||||
this.sessionService = sessionService;
|
this.sessionService = sessionService;
|
||||||
this.studentMenuHandler = studentMenuHandler;
|
this.studentMenuHandler = studentMenuHandler;
|
||||||
|
this.firebaseService = firebaseService;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void startGrievanceFlow(String phoneNumber) {
|
public void startGrievanceFlow(String phoneNumber) {
|
||||||
sessionService.resetState(phoneNumber);
|
sessionService.setUserState(phoneNumber, UserState.AWAITING_GRIEVANCE_ACTION);
|
||||||
|
|
||||||
|
List<Map<String, String>> rows = new ArrayList<>();
|
||||||
|
rows.add(createRow("grv_action_new", "Raise New Grievance", "Submit a new issue"));
|
||||||
|
rows.add(createRow("grv_action_status", "Check Status", "View status of existing grievances"));
|
||||||
|
rows.add(createRow("grv_action_delete", "Delete Grievance", "Cancel and remove an existing grievance"));
|
||||||
|
|
||||||
|
Map<String, Object> listMsg = WhatsAppMessageBuilder.buildListMessage(
|
||||||
|
"Grievance Portal",
|
||||||
|
"Please select an option:",
|
||||||
|
"Select Action",
|
||||||
|
rows
|
||||||
|
);
|
||||||
|
messageService.sendInteractiveMessage(phoneNumber, listMsg);
|
||||||
|
}
|
||||||
|
|
||||||
String message = "📢 *RIT Grievance Portal Notice*\n\n" +
|
public void handleActionSelection(String phoneNumber, String actionId) {
|
||||||
"Please note that grievances and complaints must be officially registered and tracked using our dedicated mobile application, *Complix-R*.\n\n" +
|
if ("grv_action_new".equals(actionId)) {
|
||||||
"Kindly download the app from the Google Play Store to submit your ticket:\n" +
|
sessionService.setUserState(phoneNumber, UserState.AWAITING_GRIEVANCE_TITLE);
|
||||||
"🔗 https://play.google.com/store/apps/details?id=com.ritchennai.complixr&pcampaignid=web_share";
|
GrievanceDraft draft = sessionService.getOrCreateGrievanceDraft(phoneNumber);
|
||||||
|
|
||||||
|
String token = sessionService.getImsToken(phoneNumber);
|
||||||
|
if (token != null) {
|
||||||
|
draft.setStudentName(sessionService.getStudentName(phoneNumber, token));
|
||||||
|
draft.setImsId(sessionService.getImsId(phoneNumber));
|
||||||
|
}
|
||||||
|
|
||||||
messageService.sendMessage(phoneNumber, message);
|
messageService.sendMessage(phoneNumber, "📝 Please type a short summary/title of the issue you are facing:");
|
||||||
|
} else if ("grv_action_status".equals(actionId)) {
|
||||||
|
String imsId = sessionService.getImsId(phoneNumber);
|
||||||
|
List<Map<String, Object>> grievances = firebaseService.getUserGrievances(imsId);
|
||||||
|
|
||||||
|
if (grievances.isEmpty()) {
|
||||||
|
messageService.sendMessage(phoneNumber, "You have no existing grievances.");
|
||||||
|
studentMenuHandler.sendStudentMenuDirectly(phoneNumber);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
sessionService.setUserState(phoneNumber, UserState.AWAITING_GRIEVANCE_STATUS_SELECTION);
|
||||||
|
|
||||||
|
Collections.sort(grievances, (a, b) -> {
|
||||||
|
com.google.cloud.Timestamp t1 = (com.google.cloud.Timestamp) a.get("createdAt");
|
||||||
|
com.google.cloud.Timestamp t2 = (com.google.cloud.Timestamp) b.get("createdAt");
|
||||||
|
if (t1 == null || t2 == null) return 0;
|
||||||
|
return t2.compareTo(t1);
|
||||||
|
});
|
||||||
|
|
||||||
|
List<Map<String, String>> rows = new ArrayList<>();
|
||||||
|
int count = 0;
|
||||||
|
for (Map<String, Object> g : grievances) {
|
||||||
|
if (count >= 10) break;
|
||||||
|
String id = (String) g.get("id");
|
||||||
|
String title = (String) g.get("title");
|
||||||
|
String status = (String) g.get("status");
|
||||||
|
String displayTitle = title != null && title.length() > 24 ? title.substring(0, 21) + "..." : title;
|
||||||
|
rows.add(createRow("grv_status_" + id, displayTitle, "Status: " + status));
|
||||||
|
count++;
|
||||||
|
}
|
||||||
|
|
||||||
|
Map<String, Object> listMsg = WhatsAppMessageBuilder.buildListMessage(
|
||||||
|
"Your Grievances",
|
||||||
|
"Select a grievance to see details:",
|
||||||
|
"Select Grievance",
|
||||||
|
rows
|
||||||
|
);
|
||||||
|
messageService.sendInteractiveMessage(phoneNumber, listMsg);
|
||||||
|
} else if ("grv_action_delete".equals(actionId)) {
|
||||||
|
String imsId = sessionService.getImsId(phoneNumber);
|
||||||
|
List<Map<String, Object>> grievances = firebaseService.getUserGrievances(imsId);
|
||||||
|
|
||||||
|
if (grievances.isEmpty()) {
|
||||||
|
messageService.sendMessage(phoneNumber, "You have no grievances to delete.");
|
||||||
|
studentMenuHandler.sendStudentMenuDirectly(phoneNumber);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
sessionService.setUserState(phoneNumber, UserState.AWAITING_GRIEVANCE_DELETE_SELECTION);
|
||||||
|
|
||||||
|
Collections.sort(grievances, (a, b) -> {
|
||||||
|
com.google.cloud.Timestamp t1 = (com.google.cloud.Timestamp) a.get("createdAt");
|
||||||
|
com.google.cloud.Timestamp t2 = (com.google.cloud.Timestamp) b.get("createdAt");
|
||||||
|
if (t1 == null || t2 == null) return 0;
|
||||||
|
return t2.compareTo(t1);
|
||||||
|
});
|
||||||
|
|
||||||
|
List<Map<String, String>> rows = new ArrayList<>();
|
||||||
|
int count = 0;
|
||||||
|
for (Map<String, Object> g : grievances) {
|
||||||
|
if (count >= 10) break;
|
||||||
|
String id = (String) g.get("id");
|
||||||
|
String title = (String) g.get("title");
|
||||||
|
String displayTitle = title != null && title.length() > 24 ? title.substring(0, 21) + "..." : title;
|
||||||
|
rows.add(createRow("grv_delete_" + id, displayTitle, "Select to delete"));
|
||||||
|
count++;
|
||||||
|
}
|
||||||
|
|
||||||
|
Map<String, Object> listMsg = WhatsAppMessageBuilder.buildListMessage(
|
||||||
|
"Delete Grievance",
|
||||||
|
"Select the grievance you want to delete:",
|
||||||
|
"Select Grievance",
|
||||||
|
rows
|
||||||
|
);
|
||||||
|
messageService.sendInteractiveMessage(phoneNumber, listMsg);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void handleStatusSelection(String phoneNumber, String selectionId) {
|
||||||
|
String id = selectionId.replace("grv_status_", "");
|
||||||
|
String imsId = sessionService.getImsId(phoneNumber);
|
||||||
|
List<Map<String, Object>> grievances = firebaseService.getUserGrievances(imsId);
|
||||||
|
|
||||||
|
Map<String, Object> selected = null;
|
||||||
|
for (Map<String, Object> g : grievances) {
|
||||||
|
if (id.equals(g.get("id"))) {
|
||||||
|
selected = g;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (selected != null) {
|
||||||
|
String msg = "🎫 *Ticket Details*\n\n" +
|
||||||
|
"📁 *Title:* " + selected.get("title") + "\n" +
|
||||||
|
"🏢 *Department:* " + selected.get("department") + "\n" +
|
||||||
|
"📝 *Description:* " + selected.get("description") + "\n" +
|
||||||
|
"📊 *Status:* " + selected.get("status");
|
||||||
|
messageService.sendMessage(phoneNumber, msg);
|
||||||
|
} else {
|
||||||
|
messageService.sendMessage(phoneNumber, "Grievance not found.");
|
||||||
|
}
|
||||||
|
|
||||||
studentMenuHandler.sendStudentMenuDirectly(phoneNumber);
|
studentMenuHandler.sendStudentMenuDirectly(phoneNumber);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void handleCategorySelection(String phoneNumber, String selectedCategory) {
|
public void handleDeleteSelection(String phoneNumber, String selectionId) {
|
||||||
|
String id = selectionId.replace("grv_delete_", "");
|
||||||
|
firebaseService.deleteGrievance(id);
|
||||||
|
messageService.sendMessage(phoneNumber, "✅ Grievance ticket *" + id + "* has been successfully deleted along with any uploaded attachments.");
|
||||||
|
studentMenuHandler.sendStudentMenuDirectly(phoneNumber);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void handleTitleInput(String phoneNumber, String title) {
|
||||||
|
GrievanceDraft draft = sessionService.getOrCreateGrievanceDraft(phoneNumber);
|
||||||
|
draft.setTitle(title);
|
||||||
|
|
||||||
|
sessionService.setUserState(phoneNumber, UserState.AWAITING_GRIEVANCE_DEPT);
|
||||||
|
|
||||||
|
List<String> deps = firebaseService.getDepartments("RIT");
|
||||||
|
List<Map<String, String>> deptRows = new ArrayList<>();
|
||||||
|
|
||||||
|
if (deps.isEmpty()) {
|
||||||
|
deptRows.add(createRow("grv_dept_CSE", "CSE", "Computer Science"));
|
||||||
|
deptRows.add(createRow("grv_dept_Admin", "Admin", "Admin Office"));
|
||||||
|
} else {
|
||||||
|
for (String dep : deps) {
|
||||||
|
if (deptRows.size() >= 10) break;
|
||||||
|
String displayTitle = dep != null && dep.length() > 24 ? dep.substring(0, 21) + "..." : dep;
|
||||||
|
deptRows.add(createRow("grv_dept_" + dep, displayTitle, "Department"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Map<String, Object> listMsg = WhatsAppMessageBuilder.buildListMessage(
|
||||||
|
"Department Selection",
|
||||||
|
"Issue: *" + title + "*\n\nPlease select the concerned department:",
|
||||||
|
"Select Dept",
|
||||||
|
deptRows
|
||||||
|
);
|
||||||
|
messageService.sendInteractiveMessage(phoneNumber, listMsg);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void handleDepartmentSelection(String phoneNumber, String deptId) {
|
||||||
|
String deptName = deptId.replace("grv_dept_", "");
|
||||||
|
GrievanceDraft draft = sessionService.getOrCreateGrievanceDraft(phoneNumber);
|
||||||
|
draft.setDepartment(deptName);
|
||||||
|
|
||||||
sessionService.setUserState(phoneNumber, UserState.AWAITING_GRIEVANCE_DESC);
|
sessionService.setUserState(phoneNumber, UserState.AWAITING_GRIEVANCE_DESC);
|
||||||
messageService.sendMessage(phoneNumber, "You selected " + selectedCategory + ". Please type a brief description of your issue:");
|
messageService.sendMessage(phoneNumber,
|
||||||
|
"Issue: *" + draft.getTitle() + "*\n" +
|
||||||
|
"Department: *" + deptName + "*\n\n" +
|
||||||
|
"📝 Please type a detailed description of your grievance:");
|
||||||
}
|
}
|
||||||
|
|
||||||
public void handleGrievanceDescription(String phoneNumber, String description) {
|
public void handleDescriptionInput(String phoneNumber, String description) {
|
||||||
// Generate a mock ticket ID
|
GrievanceDraft draft = sessionService.getOrCreateGrievanceDraft(phoneNumber);
|
||||||
String ticketId = "TKT-" + UUID.randomUUID().toString().substring(0, 6).toUpperCase();
|
draft.setDescription(description);
|
||||||
|
|
||||||
String confirmationMessage = "Thank you. Your grievance has been recorded.\n\n" +
|
|
||||||
"Ticket ID: *" + ticketId + "*\n" +
|
|
||||||
"Department Notified: Yes\n" +
|
|
||||||
"Status: Open\n\n" +
|
|
||||||
"You can check the status of this ticket anytime using the main menu.";
|
|
||||||
|
|
||||||
messageService.sendMessage(phoneNumber, confirmationMessage);
|
sessionService.setUserState(phoneNumber, UserState.AWAITING_GRIEVANCE_IMAGE);
|
||||||
|
|
||||||
// Reset state back to main menu
|
List<Map<String, String>> buttons = new ArrayList<>();
|
||||||
sessionService.resetState(phoneNumber);
|
Map<String, String> skipBtn = new HashMap<>();
|
||||||
|
skipBtn.put("id", "grv_skip_image");
|
||||||
// Display student menu
|
skipBtn.put("title", "Skip Image");
|
||||||
|
buttons.add(skipBtn);
|
||||||
|
|
||||||
|
Map<String, Object> buttonMsg = WhatsAppMessageBuilder.buildButtonMessage(
|
||||||
|
"📸 (Optional) Please send a photo attachment as supporting evidence for your grievance.\n\n" +
|
||||||
|
"Or click the button below to submit without an image.",
|
||||||
|
buttons
|
||||||
|
);
|
||||||
|
messageService.sendInteractiveMessage(phoneNumber, buttonMsg);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void handleImageInput(String phoneNumber, String mediaId, String mimeType) {
|
||||||
|
GrievanceDraft draft = sessionService.getOrCreateGrievanceDraft(phoneNumber);
|
||||||
|
String ticketId = generateTicketId();
|
||||||
|
draft.setTicketId(ticketId);
|
||||||
|
|
||||||
|
messageService.sendMessage(phoneNumber, "Uploading image attachment... ⏳");
|
||||||
|
|
||||||
|
byte[] mediaBytes = messageService.downloadMedia(mediaId);
|
||||||
|
if (mediaBytes != null) {
|
||||||
|
String imageUrl = firebaseService.uploadImage(mediaBytes, mimeType, ticketId);
|
||||||
|
draft.setImageUrl(imageUrl);
|
||||||
|
}
|
||||||
|
|
||||||
|
finalizeAndSaveGrievance(phoneNumber, draft);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void handleSkipImage(String phoneNumber) {
|
||||||
|
GrievanceDraft draft = sessionService.getOrCreateGrievanceDraft(phoneNumber);
|
||||||
|
draft.setTicketId(generateTicketId());
|
||||||
|
finalizeAndSaveGrievance(phoneNumber, draft);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void finalizeAndSaveGrievance(String phoneNumber, GrievanceDraft draft) {
|
||||||
|
firebaseService.saveGrievance(draft);
|
||||||
|
|
||||||
|
String confirmationMsg = "✅ *Grievance Submitted Successfully!*\n\n" +
|
||||||
|
"🎫 *Ticket ID:* `" + draft.getTicketId() + "`\n" +
|
||||||
|
"📁 *Title:* " + draft.getTitle() + "\n" +
|
||||||
|
"🏢 *Department:* " + draft.getDepartment() + "\n" +
|
||||||
|
"📝 *Description:* " + draft.getDescription() + "\n" +
|
||||||
|
(draft.getImageUrl() != null ? "🖼️ *Attachment:* Uploaded\n" : "") +
|
||||||
|
"📊 *Status:* pending\n\n" +
|
||||||
|
"Our administration team will review your ticket shortly.";
|
||||||
|
|
||||||
|
messageService.sendMessage(phoneNumber, confirmationMsg);
|
||||||
|
|
||||||
|
sessionService.clearGrievanceDraft(phoneNumber);
|
||||||
studentMenuHandler.sendStudentMenuDirectly(phoneNumber);
|
studentMenuHandler.sendStudentMenuDirectly(phoneNumber);
|
||||||
}
|
}
|
||||||
|
|
||||||
private Map<String, String> createButton(String id, String title) {
|
private String generateTicketId() {
|
||||||
Map<String, String> btn = new HashMap<>();
|
return "TKT-" + UUID.randomUUID().toString().substring(0, 8).toUpperCase();
|
||||||
btn.put("id", id);
|
}
|
||||||
btn.put("title", title);
|
|
||||||
return btn;
|
private Map<String, String> createRow(String id, String title, String description) {
|
||||||
|
Map<String, String> row = new HashMap<>();
|
||||||
|
row.put("id", id);
|
||||||
|
row.put("title", title);
|
||||||
|
row.put("description", description);
|
||||||
|
return row;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
0
src/main/java/com/rit/whatsapp/handler/sedxyTiNX
Normal file
0
src/main/java/com/rit/whatsapp/handler/sedxyTiNX
Normal file
37
src/main/java/com/rit/whatsapp/model/GrievanceDraft.java
Normal file
37
src/main/java/com/rit/whatsapp/model/GrievanceDraft.java
Normal file
@@ -0,0 +1,37 @@
|
|||||||
|
package com.rit.whatsapp.model;
|
||||||
|
|
||||||
|
import java.util.Date;
|
||||||
|
|
||||||
|
public class GrievanceDraft {
|
||||||
|
private String ticketId;
|
||||||
|
private String phoneNumber;
|
||||||
|
private String studentName;
|
||||||
|
private String imsId;
|
||||||
|
private String title;
|
||||||
|
private String department;
|
||||||
|
private String description;
|
||||||
|
private String imageUrl;
|
||||||
|
private String status = "pending";
|
||||||
|
private Date createdAt = new Date();
|
||||||
|
|
||||||
|
public String getTicketId() { return ticketId; }
|
||||||
|
public void setTicketId(String ticketId) { this.ticketId = ticketId; }
|
||||||
|
public String getPhoneNumber() { return phoneNumber; }
|
||||||
|
public void setPhoneNumber(String phoneNumber) { this.phoneNumber = phoneNumber; }
|
||||||
|
public String getStudentName() { return studentName; }
|
||||||
|
public void setStudentName(String studentName) { this.studentName = studentName; }
|
||||||
|
public String getImsId() { return imsId; }
|
||||||
|
public void setImsId(String imsId) { this.imsId = imsId; }
|
||||||
|
public String getTitle() { return title; }
|
||||||
|
public void setTitle(String title) { this.title = title; }
|
||||||
|
public String getDepartment() { return department; }
|
||||||
|
public void setDepartment(String department) { this.department = department; }
|
||||||
|
public String getDescription() { return description; }
|
||||||
|
public void setDescription(String description) { this.description = description; }
|
||||||
|
public String getImageUrl() { return imageUrl; }
|
||||||
|
public void setImageUrl(String imageUrl) { this.imageUrl = imageUrl; }
|
||||||
|
public String getStatus() { return status; }
|
||||||
|
public void setStatus(String status) { this.status = status; }
|
||||||
|
public Date getCreatedAt() { return createdAt; }
|
||||||
|
public void setCreatedAt(Date createdAt) { this.createdAt = createdAt; }
|
||||||
|
}
|
||||||
@@ -6,6 +6,11 @@ public enum UserState {
|
|||||||
AWAITING_LOGIN_PASSCODE,
|
AWAITING_LOGIN_PASSCODE,
|
||||||
AWAITING_SEMESTER_SELECTION,
|
AWAITING_SEMESTER_SELECTION,
|
||||||
AWAITING_RECEIPT_SELECTION,
|
AWAITING_RECEIPT_SELECTION,
|
||||||
AWAITING_GRIEVANCE_CATEGORY,
|
AWAITING_GRIEVANCE_ACTION,
|
||||||
AWAITING_GRIEVANCE_DESC
|
AWAITING_GRIEVANCE_TITLE,
|
||||||
|
AWAITING_GRIEVANCE_DEPT,
|
||||||
|
AWAITING_GRIEVANCE_DESC,
|
||||||
|
AWAITING_GRIEVANCE_IMAGE,
|
||||||
|
AWAITING_GRIEVANCE_STATUS_SELECTION,
|
||||||
|
AWAITING_GRIEVANCE_DELETE_SELECTION
|
||||||
}
|
}
|
||||||
|
|||||||
129
src/main/java/com/rit/whatsapp/service/FirebaseService.java
Normal file
129
src/main/java/com/rit/whatsapp/service/FirebaseService.java
Normal file
@@ -0,0 +1,129 @@
|
|||||||
|
package com.rit.whatsapp.service;
|
||||||
|
|
||||||
|
import com.google.cloud.firestore.Firestore;
|
||||||
|
import com.google.cloud.firestore.QueryDocumentSnapshot;
|
||||||
|
import com.google.cloud.firestore.QuerySnapshot;
|
||||||
|
import com.google.cloud.storage.Blob;
|
||||||
|
import com.google.cloud.storage.Bucket;
|
||||||
|
import com.rit.whatsapp.model.GrievanceDraft;
|
||||||
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
|
import org.springframework.stereotype.Service;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.UUID;
|
||||||
|
import java.util.concurrent.TimeUnit;
|
||||||
|
import com.google.cloud.Timestamp;
|
||||||
|
|
||||||
|
@Service
|
||||||
|
public class FirebaseService {
|
||||||
|
|
||||||
|
private final Firestore firestore;
|
||||||
|
private final Bucket bucket;
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
public FirebaseService(@Autowired(required = false) Firestore firestore,
|
||||||
|
@Autowired(required = false) Bucket bucket) {
|
||||||
|
this.firestore = firestore;
|
||||||
|
this.bucket = bucket;
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<String> getDepartments(String org) {
|
||||||
|
List<String> deps = new ArrayList<>();
|
||||||
|
if (firestore == null) return deps;
|
||||||
|
try {
|
||||||
|
QuerySnapshot snapshot = firestore.collection("departments")
|
||||||
|
.whereEqualTo("org", org)
|
||||||
|
.get().get();
|
||||||
|
for (QueryDocumentSnapshot doc : snapshot.getDocuments()) {
|
||||||
|
Boolean isDeleted = doc.getBoolean("isDeleted");
|
||||||
|
if (isDeleted == null || !isDeleted) {
|
||||||
|
deps.add(doc.getString("name"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (Exception e) {
|
||||||
|
System.err.println("Error fetching departments: " + e.getMessage());
|
||||||
|
}
|
||||||
|
return deps;
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<Map<String, Object>> getUserGrievances(String imsId) {
|
||||||
|
List<Map<String, Object>> list = new ArrayList<>();
|
||||||
|
if (firestore == null) return list;
|
||||||
|
try {
|
||||||
|
QuerySnapshot snapshot = firestore.collection("grievances")
|
||||||
|
.whereEqualTo("studentRegNo", imsId)
|
||||||
|
.get().get();
|
||||||
|
for (QueryDocumentSnapshot doc : snapshot.getDocuments()) {
|
||||||
|
Map<String, Object> data = new HashMap<>(doc.getData());
|
||||||
|
data.put("id", doc.getId());
|
||||||
|
list.add(data);
|
||||||
|
}
|
||||||
|
} catch (Exception e) {
|
||||||
|
System.err.println("Error fetching grievances: " + e.getMessage());
|
||||||
|
}
|
||||||
|
return list;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String uploadImage(byte[] imageBytes, String mimeType, String ticketId) {
|
||||||
|
if (bucket == null || imageBytes == null) return null;
|
||||||
|
try {
|
||||||
|
String extension = mimeType != null && mimeType.contains("png") ? ".png" : ".jpg";
|
||||||
|
String blobName = "grievances/" + ticketId + "/" + UUID.randomUUID() + extension;
|
||||||
|
Blob blob = bucket.create(blobName, imageBytes, mimeType != null ? mimeType : "image/jpeg");
|
||||||
|
return blob.signUrl(7, TimeUnit.DAYS).toString();
|
||||||
|
} catch (Exception e) {
|
||||||
|
System.err.println("Failed to upload image to Firebase Storage: " + e.getMessage());
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void saveGrievance(GrievanceDraft draft) {
|
||||||
|
if (firestore == null) {
|
||||||
|
System.out.println("Firestore not configured.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
Map<String, Object> data = new HashMap<>();
|
||||||
|
data.put("userId", "whatsapp_" + draft.getPhoneNumber());
|
||||||
|
data.put("title", draft.getTitle());
|
||||||
|
data.put("description", draft.getDescription());
|
||||||
|
data.put("department", draft.getDepartment());
|
||||||
|
data.put("org", "RIT");
|
||||||
|
data.put("studentName", draft.getStudentName());
|
||||||
|
data.put("studentRegNo", draft.getImsId());
|
||||||
|
data.put("status", draft.getStatus());
|
||||||
|
data.put("createdAt", Timestamp.now());
|
||||||
|
if (draft.getImageUrl() != null) {
|
||||||
|
data.put("imageUrl", draft.getImageUrl());
|
||||||
|
}
|
||||||
|
firestore.collection("grievances").document(draft.getTicketId()).set(data).get();
|
||||||
|
} catch (Exception e) {
|
||||||
|
System.err.println("Failed to save grievance: " + e.getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void deleteGrievance(String ticketId) {
|
||||||
|
if (firestore != null) {
|
||||||
|
try {
|
||||||
|
firestore.collection("grievances").document(ticketId).delete().get();
|
||||||
|
System.out.println("Deleted Firestore document: " + ticketId);
|
||||||
|
} catch (Exception e) {
|
||||||
|
System.err.println("Failed to delete Firestore document: " + e.getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (bucket != null) {
|
||||||
|
try {
|
||||||
|
Iterable<Blob> blobs = bucket.list(com.google.cloud.storage.Storage.BlobListOption.prefix("grievances/" + ticketId + "/")).iterateAll();
|
||||||
|
for (Blob blob : blobs) {
|
||||||
|
blob.delete();
|
||||||
|
System.out.println("Deleted Storage blob: " + blob.getName());
|
||||||
|
}
|
||||||
|
} catch (Exception e) {
|
||||||
|
System.err.println("Failed to delete Storage blobs: " + e.getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -2,6 +2,7 @@ package com.rit.whatsapp.service;
|
|||||||
|
|
||||||
import com.rit.whatsapp.model.UserEntity;
|
import com.rit.whatsapp.model.UserEntity;
|
||||||
import com.rit.whatsapp.model.UserState;
|
import com.rit.whatsapp.model.UserState;
|
||||||
|
import com.rit.whatsapp.model.GrievanceDraft;
|
||||||
import com.rit.whatsapp.repository.UserRepository;
|
import com.rit.whatsapp.repository.UserRepository;
|
||||||
import com.rit.whatsapp.util.EncryptionUtils;
|
import com.rit.whatsapp.util.EncryptionUtils;
|
||||||
import org.springframework.beans.factory.annotation.Autowired;
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
@@ -137,4 +138,30 @@ public class SessionService {
|
|||||||
public void clearTempLoginId(String phoneNumber) {
|
public void clearTempLoginId(String phoneNumber) {
|
||||||
temporaryLoginIds.remove(phoneNumber);
|
temporaryLoginIds.remove(phoneNumber);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private final Map<String, GrievanceDraft> grievanceDrafts = new ConcurrentHashMap<>();
|
||||||
|
|
||||||
|
public GrievanceDraft getOrCreateGrievanceDraft(String phoneNumber) {
|
||||||
|
return grievanceDrafts.computeIfAbsent(phoneNumber, k -> {
|
||||||
|
GrievanceDraft draft = new GrievanceDraft();
|
||||||
|
draft.setPhoneNumber(phoneNumber);
|
||||||
|
return draft;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public GrievanceDraft getGrievanceDraft(String phoneNumber) {
|
||||||
|
return grievanceDrafts.get(phoneNumber);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void clearGrievanceDraft(String phoneNumber) {
|
||||||
|
grievanceDrafts.remove(phoneNumber);
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getImsId(String phoneNumber) {
|
||||||
|
Optional<UserEntity> userOpt = userRepository.findById(phoneNumber);
|
||||||
|
if (userOpt.isPresent()) {
|
||||||
|
return userOpt.get().getImsId();
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -115,4 +115,30 @@ public class WhatsAppMessageService {
|
|||||||
|
|
||||||
executePost(payload);
|
executePost(payload);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public byte[] downloadMedia(String mediaId) {
|
||||||
|
try {
|
||||||
|
String cleanToken = apiToken != null ? apiToken.trim() : "";
|
||||||
|
String metadataUrl = "https://graph.facebook.com/v19.0/" + mediaId;
|
||||||
|
|
||||||
|
org.springframework.http.HttpHeaders headers = new org.springframework.http.HttpHeaders();
|
||||||
|
headers.setBearerAuth(cleanToken);
|
||||||
|
org.springframework.http.HttpEntity<Void> request = new org.springframework.http.HttpEntity<>(headers);
|
||||||
|
|
||||||
|
org.springframework.http.ResponseEntity<String> response = restTemplate.exchange(metadataUrl, org.springframework.http.HttpMethod.GET, request, String.class);
|
||||||
|
com.fasterxml.jackson.databind.JsonNode json = new com.fasterxml.jackson.databind.ObjectMapper().readTree(response.getBody());
|
||||||
|
String directUrl = json.get("url").asText();
|
||||||
|
|
||||||
|
org.springframework.http.HttpHeaders downloadHeaders = new org.springframework.http.HttpHeaders();
|
||||||
|
downloadHeaders.setBearerAuth(cleanToken);
|
||||||
|
downloadHeaders.set("User-Agent", "curl/7.64.1");
|
||||||
|
org.springframework.http.HttpEntity<Void> downloadRequest = new org.springframework.http.HttpEntity<>(downloadHeaders);
|
||||||
|
|
||||||
|
org.springframework.http.ResponseEntity<byte[]> mediaResponse = restTemplate.exchange(directUrl, org.springframework.http.HttpMethod.GET, downloadRequest, byte[].class);
|
||||||
|
return mediaResponse.getBody();
|
||||||
|
} catch (Exception e) {
|
||||||
|
System.err.println("Failed to download media with ID " + mediaId + ": " + e.getMessage());
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -18,4 +18,4 @@ spring.jpa.hibernate.ddl-auto=update
|
|||||||
|
|
||||||
# Encryption Secret for Passcodes (Must be 16, 24, or 32 bytes)
|
# Encryption Secret for Passcodes (Must be 16, 24, or 32 bytes)
|
||||||
bot.encryption.secret=12345678901234567890123456789012
|
bot.encryption.secret=12345678901234567890123456789012
|
||||||
whatsapp.api-token=EAAc3Wjql00oBSfkmo5fYUjw1Wu8vzvG4zbeMgt40cZBKqNJNo56or7IlwV1HYV3Ue6ZB2nZAZALbE4TTtxxZB7r4WhdAcJKzgW7FVSRd4qxAm6EZAIHzZBirogyC4zXyodmsf6hXmaa9oeAiIfOeoiHkTZBZBhv43jFZAu1yJGr9CteScbRRjS1sdt1F0twZCkMYXZCyZCZBzQzptnGoAiEsJhDUIe5DtTWD8k7IkWDvmV7MvsT6DhrjRIcYPDvwIek3EWVZBGAFFsZCFtoyUA6ICgiJ2WEWzE0c
|
whatsapp.api-token=EAAc3Wjql00oBSdZAyOjO81it8Tnpiqa1mmJq9c3WFzwXmYvjZC1RaXLlJDBe14zrCQ7DA2l5pzAV5y8sinZCBmTzlp9SUXvrcMMzmMDJ7rN58KHBqP6MAIkxcyQZCpewbFqB3mp4xwUHJ89GRZB88rsLpvJFMZABWNbfsaTZAQ0ZCabRcAvL0iJ528UGJJnv98y9F9EMerZCi3KZCGYfvIZBuYBM8tL2XWmesQir8Dlj2ZCjBmtuDLGOEQ59lHZBq1XrEbhbzlWOzH6EdUZBOajYDHn8rcZBdGZC
|
||||||
|
|||||||
13
src/main/resources/firebase-service-account.json
Normal file
13
src/main/resources/firebase-service-account.json
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
{
|
||||||
|
"type": "service_account",
|
||||||
|
"project_id": "complix-r",
|
||||||
|
"private_key_id": "6369ede06c0556edcf5999de761bf8212668b4bd",
|
||||||
|
"private_key": "-----BEGIN PRIVATE KEY-----\nMIIEvAIBADANBgkqhkiG9w0BAQEFAASCBKYwggSiAgEAAoIBAQDBRshj1wH8B2hF\nGM64fBXuZXdMOgyNjmHj6mO0pSqsbauSg3nXbgDhfl5pds/reieuEV67fqE7v5Mf\nrjFLVAsw05rwJ9EcGBgfiEhguLR+Plu1WyV4HXORW2AldByO1xyYYxBFW2Ntyg0T\njZLd15snGg+9rx+jsXNq5cQqBCNYtDqe0Pphyw7e42L6s0BmeKEVr/Iw+WRJSOYh\n6tM5fjuLTmr+3QsTT+FWo6i5mLvtMk1hsULvtVlrfbIRCJnK/bY8xqTVwKl43P+h\nQws2TvyBbU/x20BzeQly4DMvUvoCT4coNitzsnx8ackHlaiHXYYNX0kH4oZ7wYPy\nwtGX4uuLAgMBAAECggEAFpd7//OXZ0rEXONH8peKhrm0hqP4aKl+dsIkkm6F/O66\n0d7tZImWr8TyKzi4mAlIzxd3r8ne9dlwTJEzcN8ribMKVs0Iq9SDKVhoQWoFqwYF\n+T5ktFQY+KIqLGaUmfu5+3QE6F6E0kX+Xk07oBO+LoVk1qK8O4mX+KRP2CtwqEMA\nvxZbDGm/wVGI9Yy93fcYLFjG5jubdBw00W4ZbrBpqSRqKCV9vtR9xP1S0Qh6HR/U\n36Ud0DblIOxw/MoiqlhzUYAR71aSoasTN7yTExCrXSw0CwEywoCq8YVqsnB5jfpd\ngBK0XW4OVjHmPaK6ma5uJkzZmP9Iyd8AUagUe9fuzQKBgQDu00/xwMhcNGJ1pSqW\nkPh9XHgH51IC7LSlIR3mUGSVfUDzS8cmzpAdnMHIttj3+V2ltpGdSwrtn79c4xVI\nqsmX+qFFiS/agzXunPcaHbGWdk0LemVIHPusKlOrUtfrpdnjBUB2TIxGIQH/sYsC\nXh3DSxij86xNk2/IUmoyNnsWlQKBgQDPLO5scKlD5jINB0dIlIUqEy6MxD+LNez3\nAr3TM8uFnua1jfg8lQL7XjaQLfLww0rnv6SJLTSYA6IQXR254C/rMnFzb8V+Q2jp\nlvFNIArP5p1LXEzsd7n8nW+RChL1aoO6RzYwqXuahQldk/ds/FE6OKBgRqkWdwFW\n0KwYtTARnwKBgF+8J1zSmb6UZS+hsOvAW0vtaFRKvw5bEqLrHpA3g8qE9idRJhkB\nTrYAI5Iw8dKdjJOkFsUF0vCkS9KRWPPTOpKIrmMwdhVS6aioFUnLw2d8LrA+NSFu\nNNiz8uxHfjSBPFGLxmZlkwl9q85yvjWJwsaJcOwv/s7uHi4gE1/jhP3tAoGAEl6m\niWD38ThRCcyyamIY6KEoyHyCMeLHLyPEFx0Jl8B+Lmz3LM/9orI43lHkr6pxUtkw\nSqWCe1QfI5pOD3Qsa4teeeDYPxjVDZk/rhoAO2z0V4FIpNL838RJ5y9gfPhD09mK\nmYf9mLbhHLYJA1XgEeyBc+fwCDjnGn6IM+yk5XsCgYBOTHX38h7cIEmFe2IcNOKk\nEq7NSJeYrGgFV5xc4XcGLBucXOBVeRou9TlYqkwCk+B1zVtV2AznICKdltOsOYb6\nsn+Se7LHnG6b2dvRZa3C82BP0XtnFs2yifKxEi+028PoGJ+OMIRGUXYRBHsWLwo9\nOV9DKpNsPx0+4I3coVzQHw==\n-----END PRIVATE KEY-----\n",
|
||||||
|
"client_email": "firebase-adminsdk-fbsvc@complix-r.iam.gserviceaccount.com",
|
||||||
|
"client_id": "100217543764222698962",
|
||||||
|
"auth_uri": "https://accounts.google.com/o/oauth2/auth",
|
||||||
|
"token_uri": "https://oauth2.googleapis.com/token",
|
||||||
|
"auth_provider_x509_cert_url": "https://www.googleapis.com/oauth2/v1/certs",
|
||||||
|
"client_x509_cert_url": "https://www.googleapis.com/robot/v1/metadata/x509/firebase-adminsdk-fbsvc%40complix-r.iam.gserviceaccount.com",
|
||||||
|
"universe_domain": "googleapis.com"
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user