made code deployable

master
Spallya Omar 2025-08-26 20:06:53 +05:30
parent 97afa9b437
commit 2c39be4302
16 changed files with 648 additions and 553 deletions

18
Dockerfile 100644
View File

@ -0,0 +1,18 @@
# Use specific platform to ensure compatibility with Linux (x86_64)
FROM --platform=linux/amd64 eclipse-temurin:17-jdk-alpine AS base
WORKDIR /app
# Build Stage
FROM base AS build
COPY .mvn/ .mvn
COPY mvnw pom.xml ./
RUN chmod +x mvnw # Ensure mvnw is executable
RUN ./mvnw dependency:resolve
COPY ./src ./src
RUN ./mvnw package -DskipTests
# Final Stage
FROM base AS final
COPY --from=build app/target/email-classifier-1.0.0.jar app.jar
ENTRYPOINT ["java", "-jar", "app.jar"]

View File

@ -1,28 +1,30 @@
package com.email.classifier; //package com.email.classifier;
//
import lombok.RequiredArgsConstructor; //import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j; //import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service; //import org.springframework.beans.factory.annotation.Autowired;
//import org.springframework.stereotype.Service;
import java.io.IOException; //
//import java.io.IOException;
@Slf4j //
@Service //@Slf4j
@RequiredArgsConstructor //@Service
public class AIProcessingService { ////@RequiredArgsConstructor
//public class AIProcessingService {
private final OpenAIClient openAIClient; //
// @Autowired
public String processEmail(String emailBody) { // private OpenAIClient openAIClient;
log.info("Sending email content to OpenAI for classification..."); //
String aiResponse = null; // public String processEmail(String emailBody) {
try { //// log.info("Sending email content to OpenAI for classification...");
aiResponse = openAIClient.sendRequest(emailBody); // String aiResponse = null;
} catch (IOException e) { // try {
throw new RuntimeException(e); // aiResponse = openAIClient.sendRequest(emailBody);
} // } catch (IOException e) {
// throw new RuntimeException(e);
log.info("AI Classification Response: {}", aiResponse); // }
return aiResponse; //
} //// log.info("AI Classification Response: {}", aiResponse);
} // return aiResponse;
// }
//}

View File

@ -1,50 +1,50 @@
package com.email.classifier; //package com.email.classifier;
//
import lombok.extern.slf4j.Slf4j; //import lombok.extern.slf4j.Slf4j;
import org.apache.commons.mail.Email; //import org.apache.commons.mail.Email;
import org.apache.commons.mail.EmailException; //import org.apache.commons.mail.EmailException;
import org.apache.commons.mail.SimpleEmail; //import org.apache.commons.mail.SimpleEmail;
import org.springframework.beans.factory.annotation.Value; //import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service; //import org.springframework.stereotype.Service;
//
@Slf4j //@Slf4j
@Service //@Service
public class CommonsEmailService { //public class CommonsEmailService {
//
@Value("${spring.mail.host}") // @Value("${spring.mail.host}")
private String smtpHost; // private String smtpHost;
//
@Value("${spring.mail.username}") // @Value("${spring.mail.username}")
private String senderEmail; // private String senderEmail;
//
@Value("${spring.mail.password}") // @Value("${spring.mail.password}")
private String smtpPassword; // private String smtpPassword;
//
@Value("${spring.mail.port}") // @Value("${spring.mail.port}")
private int smtpPort; // private int smtpPort;
//
public void sendEmail(String toEmail, String subject, String ticketUrl) { // public void sendEmail(String toEmail, String subject, String ticketUrl) {
try { // try {
Email email = new SimpleEmail(); // Email email = new SimpleEmail();
email.setHostName(smtpHost); // email.setHostName(smtpHost);
email.setSmtpPort(smtpPort); // email.setSmtpPort(smtpPort);
email.setAuthentication(senderEmail, smtpPassword); // email.setAuthentication(senderEmail, smtpPassword);
email.setSSLOnConnect(true); // email.setSSLOnConnect(true);
email.setFrom(senderEmail); // email.setFrom(senderEmail);
email.setSubject("Support Ticket Created for " + subject); // email.setSubject("Support Ticket Created for " + subject);
email.setMsg(getBody(subject, ticketUrl)); // email.setMsg(getBody(subject, ticketUrl));
email.addTo(toEmail); // email.addTo(toEmail);
email.send(); // email.send();
//
log.info("Email sent successfully to {}", toEmail); //// log.info("Email sent successfully to {}", toEmail);
} catch (EmailException e) { // } catch (EmailException e) {
log.error("Error sending email via Apache Commons Email", e); //// log.error("Error sending email via Apache Commons Email", e);
} // }
} // }
//
private String getBody(String subject, String ticketUrl) { // private String getBody(String subject, String ticketUrl) {
return "Dear User,\n\nYour support request has been logged in Jira.\n" + // return "Dear User,\n\nYour support request has been logged in Jira.\n" +
"\nThis is in regard with your email with subject: " + subject + // "\nThis is in regard with your email with subject: " + subject +
"\n\nYou can track the issue at: " + ticketUrl + "\n\nBest regards,\nSupport Team"; // "\n\nYou can track the issue at: " + ticketUrl + "\n\nBest regards,\nSupport Team";
} // }
} //}

View File

@ -1,45 +1,49 @@
package com.email.classifier; //package com.email.classifier;
//
import lombok.RequiredArgsConstructor; //import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j; //import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service; //import org.springframework.beans.factory.annotation.Autowired;
//import org.springframework.stereotype.Service;
import java.util.Map; //
//import java.util.Map;
@Slf4j //
@Service //@Slf4j
@RequiredArgsConstructor //@Service
public class EmailProcessingService { ////@RequiredArgsConstructor
//public class EmailProcessingService {
private final AIProcessingService aiProcessingService; //
private final JiraService jiraService; // @Autowired
// private AIProcessingService aiProcessingService;
public void processEmail(String emailBody) { //
log.info("Processing email..."); // @Autowired
// private JiraService jiraService;
// Step 1: Send email content to AI for classification //
String aiResponse = aiProcessingService.processEmail(emailBody); // public void processEmail(String emailBody) {
Map<String, String> extractedData = extractKeyDetails(aiResponse); //// log.info("Processing email...");
//
String requestType = extractedData.get("requestType"); // // Step 1: Send email content to AI for classification
String subRequestType = extractedData.get("subRequestType"); // String aiResponse = aiProcessingService.processEmail(emailBody);
// Map<String, String> extractedData = extractKeyDetails(aiResponse);
log.info("Email classified as: {} -> {}", requestType, subRequestType); //
// String requestType = extractedData.get("requestType");
// Step 2: Create Jira ticket based on classification // String subRequestType = extractedData.get("subRequestType");
String summary = "New Service Request: " + requestType; //
String description = "Details: " + emailBody + "\n\nAI Classification: " + aiResponse; //// log.info("Email classified as: {} -> {}", requestType, subRequestType);
//
// jiraService.createJiraTicket(summary, description); // // Step 2: Create Jira ticket based on classification
// String summary = "New Service Request: " + requestType;
log.info("Jira ticket created successfully."); // String description = "Details: " + emailBody + "\n\nAI Classification: " + aiResponse;
} //
//// jiraService.createJiraTicket(summary, description);
private Map<String, String> extractKeyDetails(String aiResponse) { //
// Simulating AI response parsing //// log.info("Jira ticket created successfully.");
return Map.of( // }
"requestType", "Loan Modification", //
"subRequestType", "Interest Rate Change" // private Map<String, String> extractKeyDetails(String aiResponse) {
); // // Simulating AI response parsing
} // return Map.of(
} // "requestType", "Loan Modification",
// "subRequestType", "Interest Rate Change"
// );
// }
//}

View File

@ -1,151 +1,151 @@
package com.email.classifier; //package com.email.classifier;
//
import com.fasterxml.jackson.core.type.TypeReference; //import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.ObjectMapper; //import com.fasterxml.jackson.databind.ObjectMapper;
import lombok.RequiredArgsConstructor; //import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j; //import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value; //import org.springframework.beans.factory.annotation.Value;
import org.springframework.scheduling.annotation.Scheduled; //import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Service; //import org.springframework.stereotype.Service;
//
import javax.mail.*; //import javax.mail.*;
import javax.mail.internet.MimeMultipart; //import javax.mail.internet.MimeMultipart;
import javax.mail.search.FlagTerm; //import javax.mail.search.FlagTerm;
import java.util.Map; //import java.util.Map;
import java.util.Properties; //import java.util.Properties;
//
@Slf4j //@Slf4j
@Service //@Service
@RequiredArgsConstructor //@RequiredArgsConstructor
public class GmailIMAPService { //public class GmailIMAPService {
//
//
@Value("${spring.mail.password}") // @Value("${spring.mail.password}")
private String gmailAppPassword; // private String gmailAppPassword;
//
@Value("${spring.mail.username}") // @Value("${spring.mail.username}")
private String gmailUserName; // private String gmailUserName;
//
private static final String HOST = "imap.gmail.com"; // private static final String HOST = "imap.gmail.com";
private static final String LABEL_NAME = "email-classification"; // Folder name for the label // private static final String LABEL_NAME = "email-classification"; // Folder name for the label
//
private final AIProcessingService aiProcessingService; // private final AIProcessingService aiProcessingService;
private final JiraService jiraService; // private final JiraService jiraService;
private final CommonsEmailService commonsEmailService; // private final CommonsEmailService commonsEmailService;
//
// @Scheduled(fixedRate = 60000) // Check every 60 seconds //// @Scheduled(fixedRate = 60000) // Check every 60 seconds
public void fetchUnreadEmails() { // public void fetchUnreadEmails() {
log.info("Checking for new unread emails in label '{}'", LABEL_NAME); // log.info("Checking for new unread emails in label '{}'", LABEL_NAME);
Properties properties = new Properties(); // Properties properties = new Properties();
properties.put("mail.imap.host", HOST); // properties.put("mail.imap.host", HOST);
properties.put("mail.imap.port", "993"); // properties.put("mail.imap.port", "993");
properties.put("mail.imap.starttls.enable", "true"); // properties.put("mail.imap.starttls.enable", "true");
//
try { // try {
Session session = Session.getDefaultInstance(properties); // Session session = Session.getDefaultInstance(properties);
Store store = session.getStore("imaps"); // Store store = session.getStore("imaps");
store.connect(HOST, gmailUserName, gmailAppPassword); // store.connect(HOST, gmailUserName, gmailAppPassword);
//
Folder labelFolder = store.getFolder(LABEL_NAME); // Folder labelFolder = store.getFolder(LABEL_NAME);
labelFolder.open(Folder.READ_WRITE); // labelFolder.open(Folder.READ_WRITE);
//
Message[] messages = labelFolder.search(new FlagTerm(new Flags(Flags.Flag.SEEN), false)); // Fetch unread emails // Message[] messages = labelFolder.search(new FlagTerm(new Flags(Flags.Flag.SEEN), false)); // Fetch unread emails
log.info("Found {} unread emails in '{}'", messages.length, LABEL_NAME); // log.info("Found {} unread emails in '{}'", messages.length, LABEL_NAME);
//
for (Message message : messages) { // for (Message message : messages) {
processEmail(message); // processEmail(message);
message.setFlag(Flags.Flag.SEEN, true); // Mark email as read // message.setFlag(Flags.Flag.SEEN, true); // Mark email as read
} // }
//
labelFolder.close(false); // labelFolder.close(false);
store.close(); // store.close();
} catch (Exception e) { // } catch (Exception e) {
log.error("Error fetching emails from '{}': ", LABEL_NAME, e); // log.error("Error fetching emails from '{}': ", LABEL_NAME, e);
} // }
} // }
//
private void processEmail(Message message) throws Exception { // private void processEmail(Message message) throws Exception {
log.info("Processing email from: {}", message.getFrom()[0]); // log.info("Processing email from: {}", message.getFrom()[0]);
log.info("Subject: {}", message.getSubject()); // log.info("Subject: {}", message.getSubject());
//
String content = getTextFromMessage(message); // String content = getTextFromMessage(message);
log.info("Email Body: {}", content); // log.info("Email Body: {}", content);
//
// Send email text to OpenAI for classification // // Send email text to OpenAI for classification
String classification = aiProcessingService.processEmail(content); // String classification = aiProcessingService.processEmail(content);
log.info("AI Classification: {}", classification); // log.info("AI Classification: {}", classification);
Map<String, String> extractedData = extractCategoryFromAIResponse(classification); // Map<String, String> extractedData = extractCategoryFromAIResponse(classification);
String category = extractedData.getOrDefault("category", "Unknown"); // String category = extractedData.getOrDefault("category", "Unknown");
String subCategory = extractedData.getOrDefault("subCategory", "Unknown"); // String subCategory = extractedData.getOrDefault("subCategory", "Unknown");
//
log.info("Extracted Category: {} | Subcategory: {}", category, subCategory); // log.info("Extracted Category: {} | Subcategory: {}", category, subCategory);
//
String reporterEmail = message.getFrom()[0].toString(); // String reporterEmail = message.getFrom()[0].toString();
String ticketUrl = jiraService.createJiraTicket(message.getSubject(), content, category, subCategory, reporterEmail); // String ticketUrl = jiraService.createJiraTicket(message.getSubject(), content, category, subCategory, reporterEmail);
//
if (ticketUrl != null) { // if (ticketUrl != null) {
commonsEmailService.sendEmail(reporterEmail, message.getSubject(), ticketUrl); // commonsEmailService.sendEmail(reporterEmail, message.getSubject(), ticketUrl);
} // }
//
log.info("Jira ticket created and auto-reply sent for email: {}", message.getSubject()); // log.info("Jira ticket created and auto-reply sent for email: {}", message.getSubject());
} // }
//
private Map<String, String> extractCategoryFromAIResponse(String aiResponse) { // private Map<String, String> extractCategoryFromAIResponse(String aiResponse) {
try { // try {
ObjectMapper objectMapper = new ObjectMapper(); // ObjectMapper objectMapper = new ObjectMapper();
//
Map<String, String> responseMap = objectMapper.readValue(aiResponse, new TypeReference<Map<String, String>>() {}); // Map<String, String> responseMap = objectMapper.readValue(aiResponse, new TypeReference<Map<String, String>>() {});
//
String category = responseMap.getOrDefault("category", "Unknown"); // String category = responseMap.getOrDefault("category", "Unknown");
String subCategory = responseMap.getOrDefault("subCategory", "Unknown"); // String subCategory = responseMap.getOrDefault("subCategory", "Unknown");
//
log.info("Extracted Category: {} | Subcategory: {}", category, subCategory); // log.info("Extracted Category: {} | Subcategory: {}", category, subCategory);
return Map.of("category", category, "subCategory", subCategory); // return Map.of("category", category, "subCategory", subCategory);
//
} catch (Exception e) { // } catch (Exception e) {
log.error("Error extracting category from AI response", e); // log.error("Error extracting category from AI response", e);
return Map.of("category", "Unknown", "subCategory", "Unknown"); // return Map.of("category", "Unknown", "subCategory", "Unknown");
} // }
} // }
//
private String getTextFromMessage(Message message) throws Exception { // private String getTextFromMessage(Message message) throws Exception {
if (message.isMimeType("text/plain")) { // if (message.isMimeType("text/plain")) {
return message.getContent().toString(); // return message.getContent().toString();
} else if (message.isMimeType("multipart/*")) { // } else if (message.isMimeType("multipart/*")) {
MimeMultipart mimeMultipart = (MimeMultipart) message.getContent(); // MimeMultipart mimeMultipart = (MimeMultipart) message.getContent();
return getTextFromMimeMultipart(mimeMultipart); // return getTextFromMimeMultipart(mimeMultipart);
} // }
return ""; // return "";
} // }
//
public void listFolders() { // public void listFolders() {
try { // try {
Properties properties = new Properties(); // Properties properties = new Properties();
properties.put("mail.imap.ssl.enable", "true"); // properties.put("mail.imap.ssl.enable", "true");
//
Session session = Session.getInstance(properties); // Session session = Session.getInstance(properties);
Store store = session.getStore("imaps"); // Store store = session.getStore("imaps");
store.connect("imap.gmail.com", gmailUserName, gmailAppPassword); // store.connect("imap.gmail.com", gmailUserName, gmailAppPassword);
//
Folder[] folders = store.getDefaultFolder().list(); // Folder[] folders = store.getDefaultFolder().list();
for (Folder folder : folders) { // for (Folder folder : folders) {
System.out.println("Found folder: " + folder.getFullName()); // System.out.println("Found folder: " + folder.getFullName());
} // }
//
store.close(); // store.close();
} catch (Exception e) { // } catch (Exception e) {
e.printStackTrace(); // e.printStackTrace();
} // }
} // }
//
private String getTextFromMimeMultipart(MimeMultipart mimeMultipart) throws Exception { // private String getTextFromMimeMultipart(MimeMultipart mimeMultipart) throws Exception {
StringBuilder result = new StringBuilder(); // StringBuilder result = new StringBuilder();
for (int i = 0; i < mimeMultipart.getCount(); i++) { // for (int i = 0; i < mimeMultipart.getCount(); i++) {
BodyPart bodyPart = mimeMultipart.getBodyPart(i); // BodyPart bodyPart = mimeMultipart.getBodyPart(i);
if (bodyPart.isMimeType("text/plain")) { // if (bodyPart.isMimeType("text/plain")) {
result.append(bodyPart.getContent()); // result.append(bodyPart.getContent());
} // }
} // }
return result.toString(); // return result.toString();
} // }
} //}

View File

@ -1,60 +1,60 @@
package com.email.classifier; //package com.email.classifier;
//
import lombok.extern.slf4j.Slf4j; //import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value; //import org.springframework.beans.factory.annotation.Value;
import org.springframework.http.*; //import org.springframework.http.*;
import org.springframework.stereotype.Service; //import org.springframework.stereotype.Service;
import org.springframework.web.client.RestTemplate; //import org.springframework.web.client.RestTemplate;
//
import java.util.List; //import java.util.List;
import java.util.Map; //import java.util.Map;
//
@Slf4j //@Slf4j
@Service //@Service
public class JiraService { //public class JiraService {
//
@Value("${jira.token}") // @Value("${jira.token}")
private String jiraToken; // private String jiraToken;
//
@Value("${jira.user}") // @Value("${jira.user}")
private String jiraUser; // private String jiraUser;
//
@Value("${jira.host}") // @Value("${jira.host}")
private String jiraHost; // private String jiraHost;
//
private final RestTemplate restTemplate = new RestTemplate(); // private final RestTemplate restTemplate = new RestTemplate();
//
public String createJiraTicket(String subject, String emailBody, String category, String subCategory, String reporterEmail) { // public String createJiraTicket(String subject, String emailBody, String category, String subCategory, String reporterEmail) {
try { // try {
HttpHeaders headers = new HttpHeaders(); // HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.APPLICATION_JSON); // headers.setContentType(MediaType.APPLICATION_JSON);
headers.setBasicAuth(jiraUser, jiraToken); // headers.setBasicAuth(jiraUser, jiraToken);
//
Map<String, Object> request = Map.of( // Map<String, Object> request = Map.of(
"fields", Map.of( // "fields", Map.of(
"project", Map.of("key", "SCRUM"), // Replace with your Jira project key // "project", Map.of("key", "SCRUM"), // Replace with your Jira project key
"summary", "[Support Request] " + subject, // "summary", "[Support Request] " + subject,
"description", String.format( // "description", String.format(
"**Category:** %s\n**Subcategory:** %s\n**Reported By:** %s\n\n**Issue Details:**\n%s", // "**Category:** %s\n**Subcategory:** %s\n**Reported By:** %s\n\n**Issue Details:**\n%s",
category, subCategory, reporterEmail, emailBody // category, subCategory, reporterEmail, emailBody
), // ),
"issuetype", Map.of("name", "Task"), // Change to "Bug" or "Incident" if needed // "issuetype", Map.of("name", "Task"), // Change to "Bug" or "Incident" if needed
"labels", List.of(category.replace(" ", "_"), subCategory.replace(" ", "_")), // "labels", List.of(category.replace(" ", "_"), subCategory.replace(" ", "_")),
"reporter", Map.of("accountId", "712020:2cf94bb8-d7c4-4557-8511-0bae7381f521") // "reporter", Map.of("accountId", "712020:2cf94bb8-d7c4-4557-8511-0bae7381f521")
) // )
); // );
//
HttpEntity<Map<String, Object>> entity = new HttpEntity<>(request, headers); // HttpEntity<Map<String, Object>> entity = new HttpEntity<>(request, headers);
ResponseEntity<Map> response = restTemplate.postForEntity(jiraHost, entity, Map.class); // ResponseEntity<Map> response = restTemplate.postForEntity(jiraHost, entity, Map.class);
String ticketId = response.getBody().get("key").toString(); // String ticketId = response.getBody().get("key").toString();
String ticketUrl = jiraHost.replace("/rest/api/2/issue", "/browse/") + ticketId; // String ticketUrl = jiraHost.replace("/rest/api/2/issue", "/browse/") + ticketId;
//
log.info("Jira ticket created successfully: {}", ticketId); // log.info("Jira ticket created successfully: {}", ticketId);
return ticketUrl; // return ticketUrl;
//
} catch (Exception e) { // } catch (Exception e) {
log.error("Error creating Jira ticket", e); // log.error("Error creating Jira ticket", e);
} // }
return ""; // return "";
} // }
} //}

View File

@ -12,4 +12,20 @@ public class LLMQueryResponse {
private Boolean validSql; private Boolean validSql;
private String sql; private String sql;
public Boolean getValidSql() {
return validSql;
}
public void setValidSql(Boolean validSql) {
this.validSql = validSql;
}
public String getSql() {
return sql;
}
public void setSql(String sql) {
this.sql = sql;
}
} }

View File

@ -18,10 +18,11 @@ import java.util.*;
@Slf4j @Slf4j
@Component @Component
@RequiredArgsConstructor
public class OpenAIClient { public class OpenAIClient {
@Qualifier("openAiClient") // if you used a named bean
private final WebClient webClient; @Qualifier("openAiClient")
@Autowired
private WebClient webClient;
@Value("${spring.ai.openai.api-key}") @Value("${spring.ai.openai.api-key}")
private String openAiApiKey; private String openAiApiKey;
@ -31,7 +32,9 @@ public class OpenAIClient {
@Autowired @Autowired
private RestTemplate restTemplate; private RestTemplate restTemplate;
private final ObjectMapper objectMapper;
@Autowired
private ObjectMapper objectMapper;
public static final Map<String, List<String>> CATEGORY_MAP = new HashMap<>(); public static final Map<String, List<String>> CATEGORY_MAP = new HashMap<>();
static { static {
@ -63,7 +66,7 @@ public class OpenAIClient {
Map.of("role", "user", "content", "Please categorize among these mentioned category and subCategory: " + CATEGORY_MAP.toString())), Map.of("role", "user", "content", "Please categorize among these mentioned category and subCategory: " + CATEGORY_MAP.toString())),
"max_tokens", 500); "max_tokens", 500);
String requestBody = objectMapper.writeValueAsString(request); String requestBody = objectMapper.writeValueAsString(request);
log.info("Sending request to OpenAI: {}", requestBody); // ✅ Log the request JSON for debugging // log.info("Sending request to OpenAI: {}", requestBody); // ✅ Log the request JSON for debugging
HttpHeaders headers = new HttpHeaders(); HttpHeaders headers = new HttpHeaders();
headers.setBearerAuth(openAiApiKey); headers.setBearerAuth(openAiApiKey);
@ -76,10 +79,10 @@ public class OpenAIClient {
if (responseEntity.getStatusCode() == HttpStatus.OK) { if (responseEntity.getStatusCode() == HttpStatus.OK) {
JsonNode jsonResponse = objectMapper.readTree(responseEntity.getBody()); JsonNode jsonResponse = objectMapper.readTree(responseEntity.getBody());
log.info(responseEntity.getBody()); // log.info(responseEntity.getBody());
return jsonResponse.get("choices").get(0).get("message").get("content").asText(); return jsonResponse.get("choices").get(0).get("message").get("content").asText();
} else { } else {
log.error("OpenAI API Error: {}", responseEntity.getBody()); // log.error("OpenAI API Error: {}", responseEntity.getBody());
return "Error extracting text from image."; return "Error extracting text from image.";
} }
} }

View File

@ -1,6 +1,7 @@
package com.email.classifier; package com.email.classifier;
import lombok.RequiredArgsConstructor; import lombok.RequiredArgsConstructor;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.ResponseEntity; import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestBody;
@ -9,10 +10,11 @@ import org.springframework.web.bind.annotation.RestController;
@RestController @RestController
@RequestMapping("/api/query") @RequestMapping("/api/query")
@RequiredArgsConstructor //@RequiredArgsConstructor
public class QueryController { public class QueryController {
private final QueryService queryService; @Autowired
private QueryService queryService;
@PostMapping @PostMapping
public ResponseEntity<QueryResponse> query(@RequestBody QueryRequest request) { public ResponseEntity<QueryResponse> query(@RequestBody QueryRequest request) {

View File

@ -2,7 +2,15 @@ package com.email.classifier;
import lombok.Data; import lombok.Data;
@Data //@Data
public class QueryRequest { public class QueryRequest {
private String question; private String question;
public String getQuestion() {
return question;
}
public void setQuestion(String question) {
this.question = question;
}
} }

View File

@ -17,4 +17,79 @@ public class QueryResponse {
private String generatedSql; private String generatedSql;
private int records_count; private int records_count;
private List<Map<String, Object>> result; private List<Map<String, Object>> result;
private QueryResponse(Builder builder) {
this.message = builder.message;
this.generatedSql = builder.generatedSql;
this.records_count = builder.records_count;
this.result = builder.result;
}
public static class Builder {
private String message;
private String generatedSql;
private int records_count;
private List<Map<String, Object>> result;
public Builder message(String message) {
this.message = message;
return this;
}
public Builder generatedSql(String generatedSql) {
this.generatedSql = generatedSql;
return this;
}
public Builder records_count(int records_count) {
this.records_count = records_count;
return this;
}
public Builder result(List<Map<String, Object>> result) {
this.result = result;
return this;
}
public QueryResponse build() {
return new QueryResponse(this);
}
}
public String getMessage() {
return message;
}
public void setMessage(String message) {
this.message = message;
}
public String getGeneratedSql() {
return generatedSql;
}
public void setGeneratedSql(String generatedSql) {
this.generatedSql = generatedSql;
}
public int getRecords_count() {
return records_count;
}
public void setRecords_count(int records_count) {
this.records_count = records_count;
}
public List<Map<String, Object>> getResult() {
return result;
}
public void setResult(List<Map<String, Object>> result) {
this.result = result;
}
public static Builder builder() {
return new Builder();
}
} }

View File

@ -4,6 +4,7 @@ import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.ObjectMapper;
import lombok.RequiredArgsConstructor; import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jdbc.core.JdbcTemplate; import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
@ -14,14 +15,18 @@ import java.util.Map;
import java.util.UUID; import java.util.UUID;
@Service @Service
@RequiredArgsConstructor //@RequiredArgsConstructor
@Slf4j //@Slf4j
public class QueryService { public class QueryService {
private final OpenAIClient openAIClient; @Autowired
private final SchemaExtractor schemaExtractor; private OpenAIClient openAIClient;
private final JdbcTemplate jdbcTemplate; @Autowired
private final ObjectMapper objectMapper = new ObjectMapper(); private SchemaExtractor schemaExtractor;
@Autowired
private JdbcTemplate jdbcTemplate;
@Autowired
private ObjectMapper objectMapper = new ObjectMapper();
private static final int MAX_REPAIR_ATTEMPTS = 3; private static final int MAX_REPAIR_ATTEMPTS = 3;
@ -35,7 +40,7 @@ public class QueryService {
String lastException = null; String lastException = null;
for (int attempt = 1; attempt <= MAX_REPAIR_ATTEMPTS; attempt++) { for (int attempt = 1; attempt <= MAX_REPAIR_ATTEMPTS; attempt++) {
log.info("Attempt {} to generate SQL for question: {}", attempt, question); // log.info("Attempt {} to generate SQL for question: {}", attempt, question);
// Get SQL from LLM (first attempt = direct, retries = repair) // Get SQL from LLM (first attempt = direct, retries = repair)
if (attempt == 1) { if (attempt == 1) {
@ -52,7 +57,8 @@ public class QueryService {
llmQueryResponse = objectMapper.readValue(sanitizedJson, LLMQueryResponse.class); llmQueryResponse = objectMapper.readValue(sanitizedJson, LLMQueryResponse.class);
} catch (JsonProcessingException e) { } catch (JsonProcessingException e) {
String errorId = UUID.randomUUID().toString(); String errorId = UUID.randomUUID().toString();
log.error("Error id: " + errorId + " Failed to parse LLM response: {}", e.getMessage()); // log.error("Error id: " + errorId + " Failed to parse LLM response: {}", e.getMessage());
return QueryResponse.builder() return QueryResponse.builder()
.message("Unable to process request, please try again later or contact support! Error Id: " + errorId) .message("Unable to process request, please try again later or contact support! Error Id: " + errorId)
.records_count(0) .records_count(0)
@ -78,13 +84,13 @@ public class QueryService {
StringWriter sw = new StringWriter(); StringWriter sw = new StringWriter();
e.printStackTrace(new PrintWriter(sw)); e.printStackTrace(new PrintWriter(sw));
lastException = sw.toString(); lastException = sw.toString();
log.error("SQL execution failed at attempt {}: {}", attempt, e.getMessage()); // log.error("SQL execution failed at attempt {}: {}", attempt, e.getMessage());
} }
} }
String errorId = UUID.randomUUID().toString(); String errorId = UUID.randomUUID().toString();
log.error("Error id: " + errorId + " Failed to parse LLM response: {}", lastException); // log.error("Error id: " + errorId + " Failed to parse LLM response: {}", lastException);
return QueryResponse.builder() return QueryResponse.builder()
.message("Unable to process request, please try again later or contact support! Error Id: " + errorId) .message("Unable to process request, please try again later or contact support! Error Id: " + errorId)
.records_count(0) .records_count(0)

View File

@ -1,89 +1,10 @@
//package com.email.classifier;
//
//import jakarta.annotation.PostConstruct;
//import lombok.RequiredArgsConstructor;
//import org.springframework.stereotype.Component;
//
//import javax.sql.DataSource;
//import java.sql.*;
//import java.util.*;
//
//@Component
//@RequiredArgsConstructor
//public class SchemaExtractor {
//
// private final DataSource dataSource;
//
// private final Map<String, List<String>> schemaMap = new HashMap<>();
// private final Map<String, List<String>> foreignKeyMap = new HashMap<>();
//
// public Set<String> getAllTableNames() {
// return schemaMap.keySet();
// }
//
// public String getRequiredSchemaData(List<String> tableNames) {
// StringBuilder schemaBuilder = new StringBuilder();
//
// tableNames.forEach(table -> {
// schemaBuilder.append("Table: ").append(table).append("\nColumns: ");
// List<String> columns = schemaMap.getOrDefault(table, new ArrayList<>());
// schemaBuilder.append(String.join(", ", columns)).append("\n");
//
// List<String> fks = foreignKeyMap.getOrDefault(table, new ArrayList<>());
// if (!fks.isEmpty()) {
// schemaBuilder.append("Foreign Keys: ").append(String.join(", ", fks)).append("\n");
// }
//
// schemaBuilder.append("\n");
// });
//
// return schemaBuilder.toString();
// }
//
// @PostConstruct
// public void extractSchema() {
// try (Connection conn = dataSource.getConnection()) {
// DatabaseMetaData metaData = conn.getMetaData();
//
// // ✅ Extract Tables & Columns
// ResultSet tables = metaData.getTables(conn.getCatalog(), null, "%", new String[]{"TABLE"});
// while (tables.next()) {
// String tableName = tables.getString("TABLE_NAME");
//
// ResultSet columns = metaData.getColumns(conn.getCatalog(), null, tableName, "%");
// List<String> columnNames = new ArrayList<>();
// while (columns.next()) {
// columnNames.add(columns.getString("COLUMN_NAME"));
// }
// schemaMap.put(tableName, columnNames);
// }
//
// // ✅ Extract Foreign Keys
// for (String tableName : schemaMap.keySet()) {
// ResultSet fks = metaData.getImportedKeys(conn.getCatalog(), null, tableName);
// List<String> fkList = new ArrayList<>();
// while (fks.next()) {
// String fkColumn = fks.getString("FKCOLUMN_NAME");
// String pkTable = fks.getString("PKTABLE_NAME");
// String pkColumn = fks.getString("PKCOLUMN_NAME");
// fkList.add(fkColumn + " -> " + pkTable + "." + pkColumn);
// }
// foreignKeyMap.put(tableName, fkList);
// }
//
// } catch (SQLException e) {
// throw new RuntimeException("Error extracting schema", e);
// }
// }
//}
package com.email.classifier; package com.email.classifier;
import jakarta.annotation.PostConstruct; import jakarta.annotation.PostConstruct;
import lombok.Data; import lombok.Data;
import lombok.RequiredArgsConstructor; import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component; import org.springframework.stereotype.Component;
import javax.sql.DataSource; import javax.sql.DataSource;
@ -91,11 +12,12 @@ import java.sql.*;
import java.util.*; import java.util.*;
@Component @Component
@RequiredArgsConstructor //@RequiredArgsConstructor
@Slf4j //@Slf4j
public class SchemaExtractor { public class SchemaExtractor {
private final DataSource dataSource; @Autowired
private DataSource dataSource;
private final Map<String, TableSchema> schemaMap = new HashMap<>(); private final Map<String, TableSchema> schemaMap = new HashMap<>();
@ -170,7 +92,7 @@ public class SchemaExtractor {
schemaMap.put(tableName, tableSchema); schemaMap.put(tableName, tableSchema);
} }
log.info("✅ Schema extracted for {} tables/views", schemaMap.size()); // log.info("✅ Schema extracted for {} tables/views", schemaMap.size());
} catch (SQLException e) { } catch (SQLException e) {
throw new RuntimeException("Error extracting schema", e); throw new RuntimeException("Error extracting schema", e);
@ -179,7 +101,7 @@ public class SchemaExtractor {
// ---------- Inner Classes ---------- // ---------- Inner Classes ----------
@Data
public static class TableSchema { public static class TableSchema {
private final String name; private final String name;
private final String type; // TABLE or VIEW private final String type; // TABLE or VIEW
@ -188,6 +110,35 @@ public class SchemaExtractor {
private final Map<String, String> foreignKeys = new LinkedHashMap<>(); private final Map<String, String> foreignKeys = new LinkedHashMap<>();
private final Map<String, List<String>> indexes = new LinkedHashMap<>(); private final Map<String, List<String>> indexes = new LinkedHashMap<>();
public TableSchema(String name, String type) {
this.name = name;
this.type = type;
}
public String getName() {
return name;
}
public String getType() {
return type;
}
public List<ColumnSchema> getColumns() {
return columns;
}
public List<String> getPrimaryKeys() {
return primaryKeys;
}
public Map<String, String> getForeignKeys() {
return foreignKeys;
}
public Map<String, List<String>> getIndexes() {
return indexes;
}
public String toPrettyString() { public String toPrettyString() {
StringBuilder sb = new StringBuilder(); StringBuilder sb = new StringBuilder();
sb.append(type).append(": ").append(name).append("\n"); sb.append(type).append(": ").append(name).append("\n");
@ -220,6 +171,29 @@ public class SchemaExtractor {
private final int size; private final int size;
private final boolean nullable; private final boolean nullable;
public ColumnSchema(String name, String type, int size, boolean nullable) {
this.name = name;
this.type = type;
this.size = size;
this.nullable = nullable;
}
public String getName() {
return name;
}
public String getType() {
return type;
}
public int getSize() {
return size;
}
public boolean isNullable() {
return nullable;
}
@Override @Override
public String toString() { public String toString() {
return name + " (" + type + (size > 0 ? "(" + size + ")" : "") + (nullable ? ", NULL" : ", NOT NULL") + ")"; return name + " (" + type + (size > 0 ? "(" + size + ")" : "") + (nullable ? ", NULL" : ", NOT NULL") + ")";

View File

@ -1,31 +1,31 @@
package com.email.classifier.controller; //package com.email.classifier.controller;
//
import com.email.classifier.service.OpenAiVisionService; //import com.email.classifier.service.OpenAiVisionService;
import lombok.RequiredArgsConstructor; //import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j; //import lombok.extern.slf4j.Slf4j;
import org.springframework.http.ResponseEntity; //import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*; //import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile; //import org.springframework.web.multipart.MultipartFile;
//
import java.io.IOException; //import java.io.IOException;
//
@Slf4j //@Slf4j
@RestController //@RestController
@RequestMapping("/api/v1/images") //@RequestMapping("/api/v1/images")
@RequiredArgsConstructor //@RequiredArgsConstructor
public class ImageUploadController { //public class ImageUploadController {
//
private final OpenAiVisionService openAiVisionService; // private final OpenAiVisionService openAiVisionService;
//
@PostMapping("/extract-text") // @PostMapping("/extract-text")
public ResponseEntity<String> extractTextFromImage(@RequestParam("file") MultipartFile file) { // public ResponseEntity<String> extractTextFromImage(@RequestParam("file") MultipartFile file) {
try { // try {
log.info("Processing image: {}", file.getOriginalFilename()); // log.info("Processing image: {}", file.getOriginalFilename());
String extractedText = openAiVisionService.extractTextFromImage(file); // String extractedText = openAiVisionService.extractTextFromImage(file);
return ResponseEntity.ok(extractedText); // return ResponseEntity.ok(extractedText);
} catch (IOException e) { // } catch (IOException e) {
log.error("Failed to process image", e); // log.error("Failed to process image", e);
return ResponseEntity.status(500).body("Error processing image."); // return ResponseEntity.status(500).body("Error processing image.");
} // }
} // }
} //}

View File

@ -1,70 +1,70 @@
package com.email.classifier.service; //package com.email.classifier.service;
//
import com.fasterxml.jackson.databind.JsonNode; //import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper; //import com.fasterxml.jackson.databind.ObjectMapper;
import lombok.RequiredArgsConstructor; //import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j; //import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired; //import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value; //import org.springframework.beans.factory.annotation.Value;
import org.springframework.core.io.ByteArrayResource; //import org.springframework.core.io.ByteArrayResource;
import org.springframework.http.*; //import org.springframework.http.*;
import org.springframework.stereotype.Service; //import org.springframework.stereotype.Service;
import org.springframework.web.client.RestTemplate; //import org.springframework.web.client.RestTemplate;
import org.springframework.web.multipart.MultipartFile; //import org.springframework.web.multipart.MultipartFile;
//
import java.io.IOException; //import java.io.IOException;
import java.util.Collections; //import java.util.Collections;
//
@Slf4j //@Slf4j
@Service //@Service
@RequiredArgsConstructor //@RequiredArgsConstructor
public class OpenAiVisionService { //public class OpenAiVisionService {
//
@Value("${spring.ai.openai.api-key}") // @Value("${spring.ai.openai.api-key}")
private String openAiApiKey; // private String openAiApiKey;
//
@Autowired // @Autowired
private RestTemplate restTemplate; // private RestTemplate restTemplate;
private final ObjectMapper objectMapper; // private final ObjectMapper objectMapper;
//
private static final String OPENAI_VISION_URL = "https://api.openai.com/v1/chat/completions"; // private static final String OPENAI_VISION_URL = "https://api.openai.com/v1/chat/completions";
//
/** // /**
* Extracts text from an uploaded image using OpenAI Vision API. // * Extracts text from an uploaded image using OpenAI Vision API.
*/ // */
public String extractTextFromImage(MultipartFile file) throws IOException { // public String extractTextFromImage(MultipartFile file) throws IOException {
// byte[] imageBytes = file.getBytes(); //// byte[] imageBytes = file.getBytes();
// String base64Image = java.util.Base64.getEncoder().encodeToString(imageBytes); //// String base64Image = java.util.Base64.getEncoder().encodeToString(imageBytes);
String base64Image = ""; // String base64Image = "";
// JSON payload for OpenAI Vision API // // JSON payload for OpenAI Vision API
String requestBody = """ // String requestBody = """
{ // {
"model": "gpt-4.5-preview", // "model": "gpt-4.5-preview",
"messages": [{ // "messages": [{
"role": "user", // "role": "user",
"content": [ // "content": [
{ "type": "text", "text": "Extract the text from the image, and output just that" } // { "type": "text", "text": "Extract the text from the image, and output just that" }
] // ]
}], // }],
"max_tokens": 500 // "max_tokens": 500
}"""; // }""";
//
HttpHeaders headers = new HttpHeaders(); // HttpHeaders headers = new HttpHeaders();
headers.setBearerAuth(openAiApiKey); // headers.setBearerAuth(openAiApiKey);
headers.setContentType(MediaType.APPLICATION_JSON); // headers.setContentType(MediaType.APPLICATION_JSON);
//
HttpEntity<String> requestEntity = new HttpEntity<>(requestBody, headers); // HttpEntity<String> requestEntity = new HttpEntity<>(requestBody, headers);
//
// Send request to OpenAI // // Send request to OpenAI
ResponseEntity<String> responseEntity = restTemplate.exchange(OPENAI_VISION_URL, HttpMethod.POST, requestEntity, String.class); // ResponseEntity<String> responseEntity = restTemplate.exchange(OPENAI_VISION_URL, HttpMethod.POST, requestEntity, String.class);
//
if (responseEntity.getStatusCode() == HttpStatus.OK) { // if (responseEntity.getStatusCode() == HttpStatus.OK) {
JsonNode jsonResponse = objectMapper.readTree(responseEntity.getBody()); // JsonNode jsonResponse = objectMapper.readTree(responseEntity.getBody());
log.info(responseEntity.getBody()); // log.info(responseEntity.getBody());
return jsonResponse.get("choices").get(0).get("message").get("content").asText(); // return jsonResponse.get("choices").get(0).get("message").get("content").asText();
} else { // } else {
log.error("OpenAI API Error: {}", responseEntity.getBody()); // log.error("OpenAI API Error: {}", responseEntity.getBody());
return "Error extracting text from image."; // return "Error extracting text from image.";
} // }
} // }
} //}

View File

@ -1,13 +0,0 @@
package com.email.classifier;
import org.junit.jupiter.api.Test;
import org.springframework.boot.test.context.SpringBootTest;
@SpringBootTest
class ApplicationTests {
@Test
void contextLoads() {
}
}