diff --git a/pom.xml b/pom.xml
index 5376ff9..61984e2 100644
--- a/pom.xml
+++ b/pom.xml
@@ -63,6 +63,11 @@
spring-boot-starter-webmvc-test
test
+
+ com.google.firebase
+ firebase-admin
+ 9.3.0
+
diff --git a/src/main/java/com/rit/whatsapp/config/FirebaseConfig.java b/src/main/java/com/rit/whatsapp/config/FirebaseConfig.java
new file mode 100644
index 0000000..6c884ac
--- /dev/null
+++ b/src/main/java/com/rit/whatsapp/config/FirebaseConfig.java
@@ -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;
+ }
+}
diff --git a/src/main/java/com/rit/whatsapp/controller/WebhookController.java b/src/main/java/com/rit/whatsapp/controller/WebhookController.java
index 46a34e3..f9ddf80 100644
--- a/src/main/java/com/rit/whatsapp/controller/WebhookController.java
+++ b/src/main/java/com/rit/whatsapp/controller/WebhookController.java
@@ -101,7 +101,6 @@ public class WebhookController {
String body = messageNode.get("text").get("body").asText();
handleUnauthenticatedUser(fromPhoneNumber, body, currentState);
} else {
- // If they send a sticker/image/etc before logging in, force login
handleUnauthenticatedUser(fromPhoneNumber, "", currentState);
}
return ResponseEntity.ok("EVENT_RECEIVED");
@@ -111,6 +110,15 @@ public class WebhookController {
if ("text".equals(messageType)) {
String messageBody = messageNode.get("text").get("body").asText();
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)) {
JsonNode interactiveNode = messageNode.get("interactive");
String interactiveType = interactiveNode.get("type").asText();
@@ -142,7 +150,6 @@ public class WebhookController {
loginHandler.handlePasscodeInput(phoneNumber, messageBody);
break;
default:
- // Trigger the start of the login flow
loginHandler.startLoginFlow(phoneNumber);
break;
}
@@ -150,26 +157,40 @@ public class WebhookController {
private void handleAuthenticatedTextMessage(String phoneNumber, String messageBody, UserState currentState) {
switch (currentState) {
+ case AWAITING_GRIEVANCE_TITLE:
+ grievanceHandler.handleTitleInput(phoneNumber, messageBody);
+ break;
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;
case MAIN_MENU:
default:
- // Fallback to main menu
studentMenuHandler.sendMainMenu(phoneNumber);
break;
}
}
private void handleInteractiveListSelection(String phoneNumber, String selectedListId, UserState currentState) {
- if (selectedListId.startsWith("student_")) {
- // They clicked an option from the main menu
+ if (selectedListId.startsWith("grv_action_")) {
+ 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);
} else if (selectedListId.startsWith("sem_")) {
- // They clicked an option from the semester selection list
studentMenuHandler.handleSemesterSelection(phoneNumber, selectedListId);
} else if (selectedListId.startsWith("receipt_") || selectedListId.startsWith("exam_") || selectedListId.startsWith("acad_") || selectedListId.startsWith("cons_")) {
- // They clicked a receipt download option
studentMenuHandler.handleReceiptSelection(phoneNumber, selectedListId);
} else {
studentMenuHandler.sendMainMenu(phoneNumber);
@@ -223,8 +244,8 @@ public class WebhookController {
return;
}
- if (currentState == UserState.AWAITING_GRIEVANCE_CATEGORY) {
- grievanceHandler.handleCategorySelection(phoneNumber, selectedButtonId);
+ if ("grv_skip_image".equals(selectedButtonId)) {
+ grievanceHandler.handleSkipImage(phoneNumber);
} else {
studentMenuHandler.sendMainMenu(phoneNumber);
}
diff --git a/src/main/java/com/rit/whatsapp/handler/GrievanceHandler.java b/src/main/java/com/rit/whatsapp/handler/GrievanceHandler.java
index b5035ce..66c77e4 100644
--- a/src/main/java/com/rit/whatsapp/handler/GrievanceHandler.java
+++ b/src/main/java/com/rit/whatsapp/handler/GrievanceHandler.java
@@ -1,19 +1,16 @@
package com.rit.whatsapp.handler;
+import com.rit.whatsapp.model.GrievanceDraft;
import com.rit.whatsapp.model.UserState;
+import com.rit.whatsapp.service.FirebaseService;
import com.rit.whatsapp.service.SessionService;
import com.rit.whatsapp.service.WhatsAppMessageBuilder;
import com.rit.whatsapp.service.WhatsAppMessageService;
import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.context.annotation.Lazy;
import org.springframework.stereotype.Component;
-import org.springframework.context.annotation.Lazy;
-
-import java.util.ArrayList;
-import java.util.HashMap;
-import java.util.List;
-import java.util.Map;
-import java.util.UUID;
+import java.util.*;
@Component
public class GrievanceHandler {
@@ -21,54 +18,270 @@ public class GrievanceHandler {
private final WhatsAppMessageService messageService;
private final SessionService sessionService;
private final StudentMenuHandler studentMenuHandler;
+ private final FirebaseService firebaseService;
@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.sessionService = sessionService;
this.studentMenuHandler = studentMenuHandler;
+ this.firebaseService = firebaseService;
}
public void startGrievanceFlow(String phoneNumber) {
- sessionService.resetState(phoneNumber);
+ sessionService.setUserState(phoneNumber, UserState.AWAITING_GRIEVANCE_ACTION);
+
+ List