feat: init development branch with permanent access token and menu handlers

This commit is contained in:
RIT Services
2026-08-29 07:22:13 +00:00
parent 7d6d019b85
commit a0fbe836c4
11 changed files with 1014 additions and 36 deletions

View File

@@ -0,0 +1,442 @@
package com.rit.whatsapp.handler;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.rit.whatsapp.model.UserState;
import com.rit.whatsapp.service.ImsApiService;
import com.rit.whatsapp.service.SessionService;
import com.rit.whatsapp.service.WhatsAppMessageBuilder;
import com.rit.whatsapp.service.WhatsAppMessageService;
import com.rit.whatsapp.util.MessageFormatter;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import java.util.*;
@Component
public class FacultyMenuHandler {
private final WhatsAppMessageService messageService;
private final SessionService sessionService;
private final ImsApiService imsApiService;
private final ObjectMapper objectMapper = new ObjectMapper();
@Autowired
public FacultyMenuHandler(WhatsAppMessageService messageService,
SessionService sessionService,
ImsApiService imsApiService) {
this.messageService = messageService;
this.sessionService = sessionService;
this.imsApiService = imsApiService;
}
public void sendMainMenu(String phoneNumber) {
sessionService.setUserState(phoneNumber, UserState.MAIN_MENU);
String token = sessionService.getImsToken(phoneNumber);
String name = sessionService.getStudentName(phoneNumber, token);
String text = "Welcome back, *" + name + "*! 👨‍🏫\n\nWhat would you like to do today?";
java.util.Map<String, Object> interactiveList = WhatsAppMessageBuilder.buildListMessage(
"RIT Digital Assistant",
text,
"Faculty Menu",
getFacultyMenuRows()
);
messageService.sendInteractiveMessage(phoneNumber, interactiveList);
}
public void sendFacultyMenuDirectly(String phoneNumber) {
sendMainMenu(phoneNumber);
}
public void handleMenuSelection(String phoneNumber, String selectedListId) {
if (selectedListId.startsWith("faculty_markatt_select_")) {
try {
int index = Integer.parseInt(selectedListId.substring("faculty_markatt_select_".length()));
handleClassSelected(phoneNumber, index);
} catch (Exception e) {
sendResponseWithMenu(phoneNumber, "❌ Invalid selection.");
}
return;
}
switch (selectedListId) {
case "faculty_timetable":
handleTimetable(phoneNumber);
break;
case "faculty_subjects":
handleSubjects(phoneNumber);
break;
case "faculty_attendance":
handleAttendanceSubjects(phoneNumber);
break;
case "faculty_logout":
sessionService.logoutUser(phoneNumber);
messageService.sendMessage(phoneNumber, "You have been logged out successfully. Type 'Hi' to log in again.");
break;
default:
sendMainMenu(phoneNumber);
break;
}
}
public void handleTimetable(String phoneNumber) {
String token = sessionService.getImsToken(phoneNumber);
if (token == null) {
messageService.sendMessage(phoneNumber, "Your session has expired. Please type 'Reset' to login again.");
return;
}
sendTimetableMenu(phoneNumber, token);
}
public void sendTimetableMenu(String phoneNumber, String token) {
String timetableJson = imsApiService.fetchFromIms("/api/faculty/timetable", token);
if (timetableJson == null) {
sendResponseWithMenu(phoneNumber, "❌ Failed to fetch timetable. Please try again later.");
return;
}
String todaySchedule = MessageFormatter.formatFacultyTimetable(timetableJson);
if (todaySchedule.length() > 1020) {
todaySchedule = todaySchedule.substring(0, 1017) + "...";
}
List<Map<String, String>> buttons = new ArrayList<>();
Map<String, String> weeklyBtn = new HashMap<>();
weeklyBtn.put("id", "faculty_weekly_timetable");
weeklyBtn.put("title", "Weekly Timetable");
buttons.add(weeklyBtn);
Map<String, String> menuBtn = new HashMap<>();
menuBtn.put("id", "faculty_show_menu");
menuBtn.put("title", "Faculty Menu");
buttons.add(menuBtn);
Map<String, Object> buttonMessage = WhatsAppMessageBuilder.buildButtonMessage(todaySchedule, buttons);
messageService.sendInteractiveMessage(phoneNumber, buttonMessage);
sendMainMenu(phoneNumber);
}
public void handleWeeklyTimetable(String phoneNumber) {
String token = sessionService.getImsToken(phoneNumber);
if (token == null) {
messageService.sendMessage(phoneNumber, "Your session has expired. Please type 'Reset' to login again.");
return;
}
String rawTimetable = imsApiService.fetchFromIms("/api/faculty/timetable", token);
String weeklyTimetable = MessageFormatter.formatFacultyWeeklyTimetable(rawTimetable);
messageService.sendMessage(phoneNumber, weeklyTimetable);
sendTimetableMenu(phoneNumber, token);
}
public void handleSubjects(String phoneNumber) {
String token = sessionService.getImsToken(phoneNumber);
String subjectsJson = imsApiService.fetchFromIms("/api/faculty/subjects", token);
if (subjectsJson != null) {
String formattedSubjects = MessageFormatter.formatFacultySubjects(subjectsJson);
sendResponseWithMenu(phoneNumber, formattedSubjects);
} else {
sendResponseWithMenu(phoneNumber, "❌ Failed to fetch subjects. Please try again later.");
}
}
public void handleAttendanceSubjects(String phoneNumber) {
sendResponseWithMenu(phoneNumber, "🚧 *Feature under development*");
}
public void handleClassSelected(String phoneNumber, int index) {
Map<String, Object> params = sessionService.getAttendanceSubjectParams(phoneNumber, index);
if (params == null) {
sendResponseWithMenu(phoneNumber, "❌ Failed to retrieve class details. Please try again.");
return;
}
sessionService.setActiveAttendanceParams(phoneNumber, params);
String subjectName = (String) params.get("subjectName");
Object periodsObj = params.get("periods");
Object classesObj = params.get("classes");
String periodStr = (periodsObj instanceof List) ? String.join(", ", (List<String>) periodsObj) : String.valueOf(periodsObj);
String classStr = (classesObj instanceof List) ? String.join(", ", (List<String>) classesObj) : String.valueOf(classesObj);
String body = "Selected Class:\n📚 *" + subjectName + "*\n📅 Period: *" + periodStr + "*\n🏫 Class: *" + classStr + "*\n\nChoose an action:";
List<Map<String, String>> buttons = new ArrayList<>();
Map<String, String> presentBtn = new HashMap<>();
presentBtn.put("id", "faculty_mark_present");
presentBtn.put("title", "Mark All Present");
buttons.add(presentBtn);
Map<String, String> absBtn = new HashMap<>();
absBtn.put("id", "faculty_mark_absentees");
absBtn.put("title", "Enter Absentees");
buttons.add(absBtn);
Map<String, Object> buttonMessage = WhatsAppMessageBuilder.buildButtonMessage(body, buttons);
messageService.sendInteractiveMessage(phoneNumber, buttonMessage);
}
public void handleButtonSelection(String phoneNumber, String selectedButtonId) {
switch (selectedButtonId) {
case "faculty_mark_present":
handleMarkAllPresent(phoneNumber);
break;
case "faculty_mark_absentees":
handleStartAbsenteesFlow(phoneNumber);
break;
case "faculty_weekly_timetable":
handleWeeklyTimetable(phoneNumber);
break;
case "faculty_show_menu":
sendMainMenu(phoneNumber);
break;
}
}
private Map<String, Object> mapToApiParams(Map<String, Object> params) {
Map<String, Object> apiParams = new HashMap<>();
apiParams.put("date", params.get("date"));
apiParams.put("period", params.get("periods"));
apiParams.put("subject", params.get("subjectId"));
apiParams.put("staff", params.get("staffId"));
apiParams.put("classes", params.get("classes"));
apiParams.put("special", params.get("specialSub") != null ? params.get("specialSub") : 0);
apiParams.put("regular", params.get("regularSub") != null ? params.get("regularSub") : 1);
apiParams.put("batch", "");
return apiParams;
}
public void handleMarkAllPresent(String phoneNumber) {
String token = sessionService.getImsToken(phoneNumber);
Map<String, Object> activeParams = sessionService.getActiveAttendanceParams(phoneNumber);
if (activeParams == null) {
sendResponseWithMenu(phoneNumber, "❌ Session expired or invalid class. Please try again.");
return;
}
messageService.sendMessage(phoneNumber, "Retrieving student list... ⏳");
String rawList = imsApiService.getStudentListForAttendance(token, mapToApiParams(activeParams));
if (rawList == null) {
sendResponseWithMenu(phoneNumber, "❌ Failed to retrieve student list from IMS.");
return;
}
try {
JsonNode root = objectMapper.readTree(rawList);
if (root.has("success") && !root.get("success").asBoolean()) {
String errorMsg = root.has("message") ? root.get("message").asText() : "Unknown IMS error";
sendResponseWithMenu(phoneNumber, "❌ IMS Error: " + errorMsg);
return;
}
} catch (Exception e) {
// Proceed to parse HTML if not JSON error
}
List<MessageFormatter.StudentAttendanceInfo> students = MessageFormatter.parseStudents(rawList);
if (students.isEmpty()) {
sendResponseWithMenu(phoneNumber, "❌ No students found in this class.");
return;
}
Map<String, Object> postData = new HashMap<>();
postData.put("date", activeParams.get("date"));
postData.put("period", activeParams.get("periods"));
postData.put("subject", activeParams.get("subjectId"));
List<Map<String, String>> studentStatusList = new ArrayList<>();
for (MessageFormatter.StudentAttendanceInfo s : students) {
Map<String, String> sStatus = new HashMap<>();
sStatus.put("id", s.id);
sStatus.put("status", "present");
studentStatusList.add(sStatus);
}
postData.put("students", studentStatusList);
messageService.sendMessage(phoneNumber, "Submitting attendance to IMS... ⏳");
String submitRes = imsApiService.submitAttendance(token, postData);
boolean isSuccess = false;
String errorMsg = "Unknown error";
try {
if (submitRes != null) {
JsonNode root = objectMapper.readTree(submitRes);
if (root.has("success") && root.get("success").asBoolean()) {
isSuccess = true;
} else if (root.has("message")) {
errorMsg = root.get("message").asText();
}
}
} catch (Exception e) {
// fallback
}
if (isSuccess) {
sendResponseWithMenu(phoneNumber, "✅ Attendance marked successfully! (100% Present)");
} else {
sendResponseWithMenu(phoneNumber, "❌ Failed to submit attendance: " + errorMsg);
}
sessionService.clearActiveAttendanceParams(phoneNumber);
}
public void handleStartAbsenteesFlow(String phoneNumber) {
String token = sessionService.getImsToken(phoneNumber);
Map<String, Object> activeParams = sessionService.getActiveAttendanceParams(phoneNumber);
if (activeParams == null) {
sendResponseWithMenu(phoneNumber, "❌ Session expired or invalid class. Please try again.");
return;
}
messageService.sendMessage(phoneNumber, "Retrieving student list... ⏳");
String rawList = imsApiService.getStudentListForAttendance(token, mapToApiParams(activeParams));
if (rawList == null) {
sendResponseWithMenu(phoneNumber, "❌ Failed to retrieve student list from IMS.");
return;
}
try {
JsonNode root = objectMapper.readTree(rawList);
if (root.has("success") && !root.get("success").asBoolean()) {
String errorMsg = root.has("message") ? root.get("message").asText() : "Unknown IMS error";
sendResponseWithMenu(phoneNumber, "❌ IMS Error: " + errorMsg);
return;
}
} catch (Exception e) {
// Proceed to parse HTML if not JSON error
}
List<MessageFormatter.StudentAttendanceInfo> students = MessageFormatter.parseStudents(rawList);
if (students.isEmpty()) {
sendResponseWithMenu(phoneNumber, "❌ No students found in this class.");
return;
}
sessionService.storeActiveStudentList(phoneNumber, students);
sessionService.setUserState(phoneNumber, UserState.AWAITING_ABSENTEES);
StringBuilder sb = new StringBuilder();
sb.append("📋 *Student List*\n");
sb.append("Please reply with the Serial Numbers of the ABSENT students separated by commas (e.g., 2, 5).\n");
sb.append("Type *'none'* if everyone is present.\n━━━━━━━━━━━━━━\n\n");
for (int i = 0; i < students.size(); i++) {
MessageFormatter.StudentAttendanceInfo s = students.get(i);
sb.append(i + 1).append(". `").append(s.regNo).append("` - ").append(s.name).append("\n");
}
messageService.sendMessage(phoneNumber, sb.toString());
}
public void handleAbsenteesInput(String phoneNumber, String messageBody) {
String token = sessionService.getImsToken(phoneNumber);
Map<String, Object> activeParams = sessionService.getActiveAttendanceParams(phoneNumber);
List<MessageFormatter.StudentAttendanceInfo> students = sessionService.getActiveStudentList(phoneNumber);
if (activeParams == null || students == null) {
sessionService.resetState(phoneNumber);
sendResponseWithMenu(phoneNumber, "❌ Session expired. Please restart marking attendance.");
return;
}
Set<Integer> absenteeIndices = new HashSet<>();
String input = messageBody.trim().toLowerCase();
if (!input.equals("none") && !input.equals("nil") && !input.equals("no one")) {
String[] parts = input.split(",");
for (String p : parts) {
try {
int val = Integer.parseInt(p.trim());
if (val >= 1 && val <= students.size()) {
absenteeIndices.add(val - 1);
}
} catch (NumberFormatException e) {
// Ignore non-numeric inputs
}
}
}
Map<String, Object> postData = new HashMap<>();
postData.put("date", activeParams.get("date"));
postData.put("period", activeParams.get("periods"));
postData.put("subject", activeParams.get("subjectId"));
List<Map<String, String>> studentStatusList = new ArrayList<>();
int absentCount = 0;
for (int i = 0; i < students.size(); i++) {
MessageFormatter.StudentAttendanceInfo s = students.get(i);
Map<String, String> sStatus = new HashMap<>();
sStatus.put("id", s.id);
if (absenteeIndices.contains(i)) {
sStatus.put("status", "absent");
absentCount++;
} else {
sStatus.put("status", "present");
}
studentStatusList.add(sStatus);
}
postData.put("students", studentStatusList);
messageService.sendMessage(phoneNumber, "Submitting attendance to IMS... ⏳");
String submitRes = imsApiService.submitAttendance(token, postData);
boolean isSuccess = false;
String errorMsg = "Unknown error";
try {
if (submitRes != null) {
JsonNode root = objectMapper.readTree(submitRes);
if (root.has("success") && root.get("success").asBoolean()) {
isSuccess = true;
} else if (root.has("message")) {
errorMsg = root.get("message").asText();
}
}
} catch (Exception e) {
// fallback
}
sessionService.resetState(phoneNumber);
sessionService.clearActiveAttendanceParams(phoneNumber);
if (isSuccess) {
sendResponseWithMenu(phoneNumber, "✅ Attendance marked successfully! (" + (students.size() - absentCount) + " Present, " + absentCount + " Absent)");
} else {
sendResponseWithMenu(phoneNumber, "❌ Failed to submit attendance: " + errorMsg);
}
}
public void sendResponseWithMenu(String phoneNumber, String bodyText) {
if (bodyText.length() > 1024) {
bodyText = bodyText.substring(0, 1021) + "...";
}
java.util.Map<String, Object> interactiveList = WhatsAppMessageBuilder.buildListMessage(
null,
bodyText,
"Faculty Menu",
getFacultyMenuRows()
);
messageService.sendInteractiveMessage(phoneNumber, interactiveList);
}
private java.util.List<java.util.Map<String, String>> getFacultyMenuRows() {
java.util.List<java.util.Map<String, String>> rows = new java.util.ArrayList<>();
rows.add(createRow("faculty_timetable", "📅 View Timetable", "Check your daily schedule"));
rows.add(createRow("faculty_subjects", "📚 View Subjects", "Subjects you are teaching"));
rows.add(createRow("faculty_attendance", "📝 Mark Attendance", "Mark student attendance"));
rows.add(createRow("faculty_logout", "🚪 Logout", "Sign out of your account"));
return rows;
}
private java.util.Map<String, String> createRow(String id, String title, String description) {
java.util.Map<String, String> row = new java.util.HashMap<>();
row.put("id", id);
row.put("title", title);
row.put("description", description);
return row;
}
}

View File

@@ -15,15 +15,19 @@ public class LoginHandler {
private final ImsApiService imsApiService;
private final StudentMenuHandler studentMenuHandler;
private final FacultyMenuHandler facultyMenuHandler;
@Autowired
public LoginHandler(WhatsAppMessageService messageService,
SessionService sessionService,
ImsApiService imsApiService,
StudentMenuHandler studentMenuHandler) {
StudentMenuHandler studentMenuHandler,
FacultyMenuHandler facultyMenuHandler) {
this.messageService = messageService;
this.sessionService = sessionService;
this.imsApiService = imsApiService;
this.studentMenuHandler = studentMenuHandler;
this.facultyMenuHandler = facultyMenuHandler;
}
public void startLoginFlow(String phoneNumber) {
@@ -42,18 +46,22 @@ public class LoginHandler {
messageService.sendMessage(phoneNumber, "Authenticating with IMS... ⏳");
String imsSessionToken = imsApiService.authenticate(imsId, passcode);
ImsApiService.AuthResult result = imsApiService.authenticate(imsId, passcode);
if (imsSessionToken != null) {
if (result != null && result.token != null) {
// Authentication successful
sessionService.authenticateUser(phoneNumber, imsId, passcode, imsSessionToken);
sessionService.authenticateUser(phoneNumber, imsId, passcode, result);
sessionService.clearTempLoginId(phoneNumber);
sessionService.resetState(phoneNumber); // Reset the flow state
messageService.sendMessage(phoneNumber, "✅ Login Successful!");
// Show the main menu
studentMenuHandler.sendMainMenu(phoneNumber);
if (result.isFaculty) {
facultyMenuHandler.sendMainMenu(phoneNumber);
} else {
studentMenuHandler.sendMainMenu(phoneNumber);
}
} else {
// Authentication failed
sessionService.clearTempLoginId(phoneNumber);