diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..8795245 --- /dev/null +++ b/Dockerfile @@ -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"] diff --git a/src/main/java/com/email/classifier/AIProcessingService.java b/src/main/java/com/email/classifier/AIProcessingService.java index d21e279..0b249f3 100644 --- a/src/main/java/com/email/classifier/AIProcessingService.java +++ b/src/main/java/com/email/classifier/AIProcessingService.java @@ -1,28 +1,30 @@ -package com.email.classifier; - -import lombok.RequiredArgsConstructor; -import lombok.extern.slf4j.Slf4j; -import org.springframework.stereotype.Service; - -import java.io.IOException; - -@Slf4j -@Service -@RequiredArgsConstructor -public class AIProcessingService { - - private final OpenAIClient openAIClient; - - public String processEmail(String emailBody) { - log.info("Sending email content to OpenAI for classification..."); - String aiResponse = null; - try { - aiResponse = openAIClient.sendRequest(emailBody); - } catch (IOException e) { - throw new RuntimeException(e); - } - - log.info("AI Classification Response: {}", aiResponse); - return aiResponse; - } -} \ No newline at end of file +//package com.email.classifier; +// +//import lombok.RequiredArgsConstructor; +//import lombok.extern.slf4j.Slf4j; +//import org.springframework.beans.factory.annotation.Autowired; +//import org.springframework.stereotype.Service; +// +//import java.io.IOException; +// +//@Slf4j +//@Service +////@RequiredArgsConstructor +//public class AIProcessingService { +// +// @Autowired +// private OpenAIClient openAIClient; +// +// public String processEmail(String emailBody) { +//// log.info("Sending email content to OpenAI for classification..."); +// String aiResponse = null; +// try { +// aiResponse = openAIClient.sendRequest(emailBody); +// } catch (IOException e) { +// throw new RuntimeException(e); +// } +// +//// log.info("AI Classification Response: {}", aiResponse); +// return aiResponse; +// } +//} \ No newline at end of file diff --git a/src/main/java/com/email/classifier/CommonsEmailService.java b/src/main/java/com/email/classifier/CommonsEmailService.java index fb0b427..b710233 100644 --- a/src/main/java/com/email/classifier/CommonsEmailService.java +++ b/src/main/java/com/email/classifier/CommonsEmailService.java @@ -1,50 +1,50 @@ -package com.email.classifier; - -import lombok.extern.slf4j.Slf4j; -import org.apache.commons.mail.Email; -import org.apache.commons.mail.EmailException; -import org.apache.commons.mail.SimpleEmail; -import org.springframework.beans.factory.annotation.Value; -import org.springframework.stereotype.Service; - -@Slf4j -@Service -public class CommonsEmailService { - - @Value("${spring.mail.host}") - private String smtpHost; - - @Value("${spring.mail.username}") - private String senderEmail; - - @Value("${spring.mail.password}") - private String smtpPassword; - - @Value("${spring.mail.port}") - private int smtpPort; - - public void sendEmail(String toEmail, String subject, String ticketUrl) { - try { - Email email = new SimpleEmail(); - email.setHostName(smtpHost); - email.setSmtpPort(smtpPort); - email.setAuthentication(senderEmail, smtpPassword); - email.setSSLOnConnect(true); - email.setFrom(senderEmail); - email.setSubject("Support Ticket Created for " + subject); - email.setMsg(getBody(subject, ticketUrl)); - email.addTo(toEmail); - email.send(); - - log.info("Email sent successfully to {}", toEmail); - } catch (EmailException e) { - log.error("Error sending email via Apache Commons Email", e); - } - } - - private String getBody(String subject, String ticketUrl) { - return "Dear User,\n\nYour support request has been logged in Jira.\n" + - "\nThis is in regard with your email with subject: " + subject + - "\n\nYou can track the issue at: " + ticketUrl + "\n\nBest regards,\nSupport Team"; - } -} \ No newline at end of file +//package com.email.classifier; +// +//import lombok.extern.slf4j.Slf4j; +//import org.apache.commons.mail.Email; +//import org.apache.commons.mail.EmailException; +//import org.apache.commons.mail.SimpleEmail; +//import org.springframework.beans.factory.annotation.Value; +//import org.springframework.stereotype.Service; +// +//@Slf4j +//@Service +//public class CommonsEmailService { +// +// @Value("${spring.mail.host}") +// private String smtpHost; +// +// @Value("${spring.mail.username}") +// private String senderEmail; +// +// @Value("${spring.mail.password}") +// private String smtpPassword; +// +// @Value("${spring.mail.port}") +// private int smtpPort; +// +// public void sendEmail(String toEmail, String subject, String ticketUrl) { +// try { +// Email email = new SimpleEmail(); +// email.setHostName(smtpHost); +// email.setSmtpPort(smtpPort); +// email.setAuthentication(senderEmail, smtpPassword); +// email.setSSLOnConnect(true); +// email.setFrom(senderEmail); +// email.setSubject("Support Ticket Created for " + subject); +// email.setMsg(getBody(subject, ticketUrl)); +// email.addTo(toEmail); +// email.send(); +// +//// log.info("Email sent successfully to {}", toEmail); +// } catch (EmailException e) { +//// log.error("Error sending email via Apache Commons Email", e); +// } +// } +// +// private String getBody(String subject, String ticketUrl) { +// return "Dear User,\n\nYour support request has been logged in Jira.\n" + +// "\nThis is in regard with your email with subject: " + subject + +// "\n\nYou can track the issue at: " + ticketUrl + "\n\nBest regards,\nSupport Team"; +// } +//} \ No newline at end of file diff --git a/src/main/java/com/email/classifier/EmailProcessingService.java b/src/main/java/com/email/classifier/EmailProcessingService.java index 4a032da..48a98d1 100644 --- a/src/main/java/com/email/classifier/EmailProcessingService.java +++ b/src/main/java/com/email/classifier/EmailProcessingService.java @@ -1,45 +1,49 @@ -package com.email.classifier; - -import lombok.RequiredArgsConstructor; -import lombok.extern.slf4j.Slf4j; -import org.springframework.stereotype.Service; - -import java.util.Map; - -@Slf4j -@Service -@RequiredArgsConstructor -public class EmailProcessingService { - - private final AIProcessingService aiProcessingService; - private final JiraService jiraService; - - public void processEmail(String emailBody) { - log.info("Processing email..."); - - // Step 1: Send email content to AI for classification - String aiResponse = aiProcessingService.processEmail(emailBody); - Map extractedData = extractKeyDetails(aiResponse); - - String requestType = extractedData.get("requestType"); - String subRequestType = extractedData.get("subRequestType"); - - log.info("Email classified as: {} -> {}", requestType, subRequestType); - - // Step 2: Create Jira ticket based on classification - String summary = "New Service Request: " + requestType; - String description = "Details: " + emailBody + "\n\nAI Classification: " + aiResponse; - -// jiraService.createJiraTicket(summary, description); - - log.info("Jira ticket created successfully."); - } - - private Map extractKeyDetails(String aiResponse) { - // Simulating AI response parsing - return Map.of( - "requestType", "Loan Modification", - "subRequestType", "Interest Rate Change" - ); - } -} \ No newline at end of file +//package com.email.classifier; +// +//import lombok.RequiredArgsConstructor; +//import lombok.extern.slf4j.Slf4j; +//import org.springframework.beans.factory.annotation.Autowired; +//import org.springframework.stereotype.Service; +// +//import java.util.Map; +// +//@Slf4j +//@Service +////@RequiredArgsConstructor +//public class EmailProcessingService { +// +// @Autowired +// private AIProcessingService aiProcessingService; +// +// @Autowired +// private JiraService jiraService; +// +// public void processEmail(String emailBody) { +//// log.info("Processing email..."); +// +// // Step 1: Send email content to AI for classification +// String aiResponse = aiProcessingService.processEmail(emailBody); +// Map extractedData = extractKeyDetails(aiResponse); +// +// String requestType = extractedData.get("requestType"); +// String subRequestType = extractedData.get("subRequestType"); +// +//// log.info("Email classified as: {} -> {}", requestType, subRequestType); +// +// // Step 2: Create Jira ticket based on classification +// String summary = "New Service Request: " + requestType; +// String description = "Details: " + emailBody + "\n\nAI Classification: " + aiResponse; +// +//// jiraService.createJiraTicket(summary, description); +// +//// log.info("Jira ticket created successfully."); +// } +// +// private Map extractKeyDetails(String aiResponse) { +// // Simulating AI response parsing +// return Map.of( +// "requestType", "Loan Modification", +// "subRequestType", "Interest Rate Change" +// ); +// } +//} \ No newline at end of file diff --git a/src/main/java/com/email/classifier/GmailIMAPService.java b/src/main/java/com/email/classifier/GmailIMAPService.java index df3d3bd..52b5bb5 100644 --- a/src/main/java/com/email/classifier/GmailIMAPService.java +++ b/src/main/java/com/email/classifier/GmailIMAPService.java @@ -1,151 +1,151 @@ -package com.email.classifier; - -import com.fasterxml.jackson.core.type.TypeReference; -import com.fasterxml.jackson.databind.ObjectMapper; -import lombok.RequiredArgsConstructor; -import lombok.extern.slf4j.Slf4j; -import org.springframework.beans.factory.annotation.Value; -import org.springframework.scheduling.annotation.Scheduled; -import org.springframework.stereotype.Service; - -import javax.mail.*; -import javax.mail.internet.MimeMultipart; -import javax.mail.search.FlagTerm; -import java.util.Map; -import java.util.Properties; - -@Slf4j -@Service -@RequiredArgsConstructor -public class GmailIMAPService { - - - @Value("${spring.mail.password}") - private String gmailAppPassword; - - @Value("${spring.mail.username}") - private String gmailUserName; - - private static final String HOST = "imap.gmail.com"; - private static final String LABEL_NAME = "email-classification"; // Folder name for the label - - private final AIProcessingService aiProcessingService; - private final JiraService jiraService; - private final CommonsEmailService commonsEmailService; - -// @Scheduled(fixedRate = 60000) // Check every 60 seconds - public void fetchUnreadEmails() { - log.info("Checking for new unread emails in label '{}'", LABEL_NAME); - Properties properties = new Properties(); - properties.put("mail.imap.host", HOST); - properties.put("mail.imap.port", "993"); - properties.put("mail.imap.starttls.enable", "true"); - - try { - Session session = Session.getDefaultInstance(properties); - Store store = session.getStore("imaps"); - store.connect(HOST, gmailUserName, gmailAppPassword); - - Folder labelFolder = store.getFolder(LABEL_NAME); - labelFolder.open(Folder.READ_WRITE); - - 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); - - for (Message message : messages) { - processEmail(message); - message.setFlag(Flags.Flag.SEEN, true); // Mark email as read - } - - labelFolder.close(false); - store.close(); - } catch (Exception e) { - log.error("Error fetching emails from '{}': ", LABEL_NAME, e); - } - } - - private void processEmail(Message message) throws Exception { - log.info("Processing email from: {}", message.getFrom()[0]); - log.info("Subject: {}", message.getSubject()); - - String content = getTextFromMessage(message); - log.info("Email Body: {}", content); - - // Send email text to OpenAI for classification - String classification = aiProcessingService.processEmail(content); - log.info("AI Classification: {}", classification); - Map extractedData = extractCategoryFromAIResponse(classification); - String category = extractedData.getOrDefault("category", "Unknown"); - String subCategory = extractedData.getOrDefault("subCategory", "Unknown"); - - log.info("Extracted Category: {} | Subcategory: {}", category, subCategory); - - String reporterEmail = message.getFrom()[0].toString(); - String ticketUrl = jiraService.createJiraTicket(message.getSubject(), content, category, subCategory, reporterEmail); - - if (ticketUrl != null) { - commonsEmailService.sendEmail(reporterEmail, message.getSubject(), ticketUrl); - } - - log.info("Jira ticket created and auto-reply sent for email: {}", message.getSubject()); - } - - private Map extractCategoryFromAIResponse(String aiResponse) { - try { - ObjectMapper objectMapper = new ObjectMapper(); - - Map responseMap = objectMapper.readValue(aiResponse, new TypeReference>() {}); - - String category = responseMap.getOrDefault("category", "Unknown"); - String subCategory = responseMap.getOrDefault("subCategory", "Unknown"); - - log.info("Extracted Category: {} | Subcategory: {}", category, subCategory); - return Map.of("category", category, "subCategory", subCategory); - - } catch (Exception e) { - log.error("Error extracting category from AI response", e); - return Map.of("category", "Unknown", "subCategory", "Unknown"); - } - } - - private String getTextFromMessage(Message message) throws Exception { - if (message.isMimeType("text/plain")) { - return message.getContent().toString(); - } else if (message.isMimeType("multipart/*")) { - MimeMultipart mimeMultipart = (MimeMultipart) message.getContent(); - return getTextFromMimeMultipart(mimeMultipart); - } - return ""; - } - - public void listFolders() { - try { - Properties properties = new Properties(); - properties.put("mail.imap.ssl.enable", "true"); - - Session session = Session.getInstance(properties); - Store store = session.getStore("imaps"); - store.connect("imap.gmail.com", gmailUserName, gmailAppPassword); - - Folder[] folders = store.getDefaultFolder().list(); - for (Folder folder : folders) { - System.out.println("Found folder: " + folder.getFullName()); - } - - store.close(); - } catch (Exception e) { - e.printStackTrace(); - } - } - - private String getTextFromMimeMultipart(MimeMultipart mimeMultipart) throws Exception { - StringBuilder result = new StringBuilder(); - for (int i = 0; i < mimeMultipart.getCount(); i++) { - BodyPart bodyPart = mimeMultipart.getBodyPart(i); - if (bodyPart.isMimeType("text/plain")) { - result.append(bodyPart.getContent()); - } - } - return result.toString(); - } -} \ No newline at end of file +//package com.email.classifier; +// +//import com.fasterxml.jackson.core.type.TypeReference; +//import com.fasterxml.jackson.databind.ObjectMapper; +//import lombok.RequiredArgsConstructor; +//import lombok.extern.slf4j.Slf4j; +//import org.springframework.beans.factory.annotation.Value; +//import org.springframework.scheduling.annotation.Scheduled; +//import org.springframework.stereotype.Service; +// +//import javax.mail.*; +//import javax.mail.internet.MimeMultipart; +//import javax.mail.search.FlagTerm; +//import java.util.Map; +//import java.util.Properties; +// +//@Slf4j +//@Service +//@RequiredArgsConstructor +//public class GmailIMAPService { +// +// +// @Value("${spring.mail.password}") +// private String gmailAppPassword; +// +// @Value("${spring.mail.username}") +// private String gmailUserName; +// +// private static final String HOST = "imap.gmail.com"; +// private static final String LABEL_NAME = "email-classification"; // Folder name for the label +// +// private final AIProcessingService aiProcessingService; +// private final JiraService jiraService; +// private final CommonsEmailService commonsEmailService; +// +//// @Scheduled(fixedRate = 60000) // Check every 60 seconds +// public void fetchUnreadEmails() { +// log.info("Checking for new unread emails in label '{}'", LABEL_NAME); +// Properties properties = new Properties(); +// properties.put("mail.imap.host", HOST); +// properties.put("mail.imap.port", "993"); +// properties.put("mail.imap.starttls.enable", "true"); +// +// try { +// Session session = Session.getDefaultInstance(properties); +// Store store = session.getStore("imaps"); +// store.connect(HOST, gmailUserName, gmailAppPassword); +// +// Folder labelFolder = store.getFolder(LABEL_NAME); +// labelFolder.open(Folder.READ_WRITE); +// +// 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); +// +// for (Message message : messages) { +// processEmail(message); +// message.setFlag(Flags.Flag.SEEN, true); // Mark email as read +// } +// +// labelFolder.close(false); +// store.close(); +// } catch (Exception e) { +// log.error("Error fetching emails from '{}': ", LABEL_NAME, e); +// } +// } +// +// private void processEmail(Message message) throws Exception { +// log.info("Processing email from: {}", message.getFrom()[0]); +// log.info("Subject: {}", message.getSubject()); +// +// String content = getTextFromMessage(message); +// log.info("Email Body: {}", content); +// +// // Send email text to OpenAI for classification +// String classification = aiProcessingService.processEmail(content); +// log.info("AI Classification: {}", classification); +// Map extractedData = extractCategoryFromAIResponse(classification); +// String category = extractedData.getOrDefault("category", "Unknown"); +// String subCategory = extractedData.getOrDefault("subCategory", "Unknown"); +// +// log.info("Extracted Category: {} | Subcategory: {}", category, subCategory); +// +// String reporterEmail = message.getFrom()[0].toString(); +// String ticketUrl = jiraService.createJiraTicket(message.getSubject(), content, category, subCategory, reporterEmail); +// +// if (ticketUrl != null) { +// commonsEmailService.sendEmail(reporterEmail, message.getSubject(), ticketUrl); +// } +// +// log.info("Jira ticket created and auto-reply sent for email: {}", message.getSubject()); +// } +// +// private Map extractCategoryFromAIResponse(String aiResponse) { +// try { +// ObjectMapper objectMapper = new ObjectMapper(); +// +// Map responseMap = objectMapper.readValue(aiResponse, new TypeReference>() {}); +// +// String category = responseMap.getOrDefault("category", "Unknown"); +// String subCategory = responseMap.getOrDefault("subCategory", "Unknown"); +// +// log.info("Extracted Category: {} | Subcategory: {}", category, subCategory); +// return Map.of("category", category, "subCategory", subCategory); +// +// } catch (Exception e) { +// log.error("Error extracting category from AI response", e); +// return Map.of("category", "Unknown", "subCategory", "Unknown"); +// } +// } +// +// private String getTextFromMessage(Message message) throws Exception { +// if (message.isMimeType("text/plain")) { +// return message.getContent().toString(); +// } else if (message.isMimeType("multipart/*")) { +// MimeMultipart mimeMultipart = (MimeMultipart) message.getContent(); +// return getTextFromMimeMultipart(mimeMultipart); +// } +// return ""; +// } +// +// public void listFolders() { +// try { +// Properties properties = new Properties(); +// properties.put("mail.imap.ssl.enable", "true"); +// +// Session session = Session.getInstance(properties); +// Store store = session.getStore("imaps"); +// store.connect("imap.gmail.com", gmailUserName, gmailAppPassword); +// +// Folder[] folders = store.getDefaultFolder().list(); +// for (Folder folder : folders) { +// System.out.println("Found folder: " + folder.getFullName()); +// } +// +// store.close(); +// } catch (Exception e) { +// e.printStackTrace(); +// } +// } +// +// private String getTextFromMimeMultipart(MimeMultipart mimeMultipart) throws Exception { +// StringBuilder result = new StringBuilder(); +// for (int i = 0; i < mimeMultipart.getCount(); i++) { +// BodyPart bodyPart = mimeMultipart.getBodyPart(i); +// if (bodyPart.isMimeType("text/plain")) { +// result.append(bodyPart.getContent()); +// } +// } +// return result.toString(); +// } +//} \ No newline at end of file diff --git a/src/main/java/com/email/classifier/JiraService.java b/src/main/java/com/email/classifier/JiraService.java index fcdf8f1..5197d23 100644 --- a/src/main/java/com/email/classifier/JiraService.java +++ b/src/main/java/com/email/classifier/JiraService.java @@ -1,60 +1,60 @@ -package com.email.classifier; - -import lombok.extern.slf4j.Slf4j; -import org.springframework.beans.factory.annotation.Value; -import org.springframework.http.*; -import org.springframework.stereotype.Service; -import org.springframework.web.client.RestTemplate; - -import java.util.List; -import java.util.Map; - -@Slf4j -@Service -public class JiraService { - - @Value("${jira.token}") - private String jiraToken; - - @Value("${jira.user}") - private String jiraUser; - - @Value("${jira.host}") - private String jiraHost; - - private final RestTemplate restTemplate = new RestTemplate(); - - public String createJiraTicket(String subject, String emailBody, String category, String subCategory, String reporterEmail) { - try { - HttpHeaders headers = new HttpHeaders(); - headers.setContentType(MediaType.APPLICATION_JSON); - headers.setBasicAuth(jiraUser, jiraToken); - - Map request = Map.of( - "fields", Map.of( - "project", Map.of("key", "SCRUM"), // Replace with your Jira project key - "summary", "[Support Request] " + subject, - "description", String.format( - "**Category:** %s\n**Subcategory:** %s\n**Reported By:** %s\n\n**Issue Details:**\n%s", - category, subCategory, reporterEmail, emailBody - ), - "issuetype", Map.of("name", "Task"), // Change to "Bug" or "Incident" if needed - "labels", List.of(category.replace(" ", "_"), subCategory.replace(" ", "_")), - "reporter", Map.of("accountId", "712020:2cf94bb8-d7c4-4557-8511-0bae7381f521") - ) - ); - - HttpEntity> entity = new HttpEntity<>(request, headers); - ResponseEntity response = restTemplate.postForEntity(jiraHost, entity, Map.class); - String ticketId = response.getBody().get("key").toString(); - String ticketUrl = jiraHost.replace("/rest/api/2/issue", "/browse/") + ticketId; - - log.info("Jira ticket created successfully: {}", ticketId); - return ticketUrl; - - } catch (Exception e) { - log.error("Error creating Jira ticket", e); - } - return ""; - } -} \ No newline at end of file +//package com.email.classifier; +// +//import lombok.extern.slf4j.Slf4j; +//import org.springframework.beans.factory.annotation.Value; +//import org.springframework.http.*; +//import org.springframework.stereotype.Service; +//import org.springframework.web.client.RestTemplate; +// +//import java.util.List; +//import java.util.Map; +// +//@Slf4j +//@Service +//public class JiraService { +// +// @Value("${jira.token}") +// private String jiraToken; +// +// @Value("${jira.user}") +// private String jiraUser; +// +// @Value("${jira.host}") +// private String jiraHost; +// +// private final RestTemplate restTemplate = new RestTemplate(); +// +// public String createJiraTicket(String subject, String emailBody, String category, String subCategory, String reporterEmail) { +// try { +// HttpHeaders headers = new HttpHeaders(); +// headers.setContentType(MediaType.APPLICATION_JSON); +// headers.setBasicAuth(jiraUser, jiraToken); +// +// Map request = Map.of( +// "fields", Map.of( +// "project", Map.of("key", "SCRUM"), // Replace with your Jira project key +// "summary", "[Support Request] " + subject, +// "description", String.format( +// "**Category:** %s\n**Subcategory:** %s\n**Reported By:** %s\n\n**Issue Details:**\n%s", +// category, subCategory, reporterEmail, emailBody +// ), +// "issuetype", Map.of("name", "Task"), // Change to "Bug" or "Incident" if needed +// "labels", List.of(category.replace(" ", "_"), subCategory.replace(" ", "_")), +// "reporter", Map.of("accountId", "712020:2cf94bb8-d7c4-4557-8511-0bae7381f521") +// ) +// ); +// +// HttpEntity> entity = new HttpEntity<>(request, headers); +// ResponseEntity response = restTemplate.postForEntity(jiraHost, entity, Map.class); +// String ticketId = response.getBody().get("key").toString(); +// String ticketUrl = jiraHost.replace("/rest/api/2/issue", "/browse/") + ticketId; +// +// log.info("Jira ticket created successfully: {}", ticketId); +// return ticketUrl; +// +// } catch (Exception e) { +// log.error("Error creating Jira ticket", e); +// } +// return ""; +// } +//} \ No newline at end of file diff --git a/src/main/java/com/email/classifier/LLMQueryResponse.java b/src/main/java/com/email/classifier/LLMQueryResponse.java index 3495ad6..c11ccea 100644 --- a/src/main/java/com/email/classifier/LLMQueryResponse.java +++ b/src/main/java/com/email/classifier/LLMQueryResponse.java @@ -12,4 +12,20 @@ public class LLMQueryResponse { private Boolean validSql; 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; + } } diff --git a/src/main/java/com/email/classifier/OpenAIClient.java b/src/main/java/com/email/classifier/OpenAIClient.java index 3d6a3f7..8606d66 100644 --- a/src/main/java/com/email/classifier/OpenAIClient.java +++ b/src/main/java/com/email/classifier/OpenAIClient.java @@ -18,10 +18,11 @@ import java.util.*; @Slf4j @Component -@RequiredArgsConstructor 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}") private String openAiApiKey; @@ -31,7 +32,9 @@ public class OpenAIClient { @Autowired private RestTemplate restTemplate; - private final ObjectMapper objectMapper; + + @Autowired + private ObjectMapper objectMapper; public static final Map> CATEGORY_MAP = new HashMap<>(); static { @@ -63,7 +66,7 @@ public class OpenAIClient { Map.of("role", "user", "content", "Please categorize among these mentioned category and subCategory: " + CATEGORY_MAP.toString())), "max_tokens", 500); 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(); headers.setBearerAuth(openAiApiKey); @@ -76,10 +79,10 @@ public class OpenAIClient { if (responseEntity.getStatusCode() == HttpStatus.OK) { 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(); } else { - log.error("OpenAI API Error: {}", responseEntity.getBody()); +// log.error("OpenAI API Error: {}", responseEntity.getBody()); return "Error extracting text from image."; } } diff --git a/src/main/java/com/email/classifier/QueryController.java b/src/main/java/com/email/classifier/QueryController.java index b748793..230c147 100644 --- a/src/main/java/com/email/classifier/QueryController.java +++ b/src/main/java/com/email/classifier/QueryController.java @@ -1,6 +1,7 @@ package com.email.classifier; import lombok.RequiredArgsConstructor; +import org.springframework.beans.factory.annotation.Autowired; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestBody; @@ -9,10 +10,11 @@ import org.springframework.web.bind.annotation.RestController; @RestController @RequestMapping("/api/query") -@RequiredArgsConstructor +//@RequiredArgsConstructor public class QueryController { - private final QueryService queryService; + @Autowired + private QueryService queryService; @PostMapping public ResponseEntity query(@RequestBody QueryRequest request) { diff --git a/src/main/java/com/email/classifier/QueryRequest.java b/src/main/java/com/email/classifier/QueryRequest.java index 149d6f2..8e8066b 100644 --- a/src/main/java/com/email/classifier/QueryRequest.java +++ b/src/main/java/com/email/classifier/QueryRequest.java @@ -2,7 +2,15 @@ package com.email.classifier; import lombok.Data; -@Data +//@Data public class QueryRequest { private String question; + + public String getQuestion() { + return question; + } + + public void setQuestion(String question) { + this.question = question; + } } \ No newline at end of file diff --git a/src/main/java/com/email/classifier/QueryResponse.java b/src/main/java/com/email/classifier/QueryResponse.java index 225e401..b4a2aac 100644 --- a/src/main/java/com/email/classifier/QueryResponse.java +++ b/src/main/java/com/email/classifier/QueryResponse.java @@ -17,4 +17,79 @@ public class QueryResponse { private String generatedSql; private int records_count; private List> 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> 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> 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> getResult() { + return result; + } + + public void setResult(List> result) { + this.result = result; + } + + public static Builder builder() { + return new Builder(); + } + } \ No newline at end of file diff --git a/src/main/java/com/email/classifier/QueryService.java b/src/main/java/com/email/classifier/QueryService.java index 8ddbe0e..8987d26 100644 --- a/src/main/java/com/email/classifier/QueryService.java +++ b/src/main/java/com/email/classifier/QueryService.java @@ -4,6 +4,7 @@ import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.ObjectMapper; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Autowired; import org.springframework.jdbc.core.JdbcTemplate; import org.springframework.stereotype.Service; @@ -14,14 +15,18 @@ import java.util.Map; import java.util.UUID; @Service -@RequiredArgsConstructor -@Slf4j +//@RequiredArgsConstructor +//@Slf4j public class QueryService { - private final OpenAIClient openAIClient; - private final SchemaExtractor schemaExtractor; - private final JdbcTemplate jdbcTemplate; - private final ObjectMapper objectMapper = new ObjectMapper(); + @Autowired + private OpenAIClient openAIClient; + @Autowired + private SchemaExtractor schemaExtractor; + @Autowired + private JdbcTemplate jdbcTemplate; + @Autowired + private ObjectMapper objectMapper = new ObjectMapper(); private static final int MAX_REPAIR_ATTEMPTS = 3; @@ -35,7 +40,7 @@ public class QueryService { String lastException = null; 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) if (attempt == 1) { @@ -52,7 +57,8 @@ public class QueryService { llmQueryResponse = objectMapper.readValue(sanitizedJson, LLMQueryResponse.class); } catch (JsonProcessingException e) { 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() .message("Unable to process request, please try again later or contact support! Error Id: " + errorId) .records_count(0) @@ -78,13 +84,13 @@ public class QueryService { StringWriter sw = new StringWriter(); e.printStackTrace(new PrintWriter(sw)); 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(); - 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() .message("Unable to process request, please try again later or contact support! Error Id: " + errorId) .records_count(0) diff --git a/src/main/java/com/email/classifier/SchemaExtractor.java b/src/main/java/com/email/classifier/SchemaExtractor.java index afea042..976333d 100644 --- a/src/main/java/com/email/classifier/SchemaExtractor.java +++ b/src/main/java/com/email/classifier/SchemaExtractor.java @@ -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> schemaMap = new HashMap<>(); -// private final Map> foreignKeyMap = new HashMap<>(); -// -// public Set getAllTableNames() { -// return schemaMap.keySet(); -// } -// -// public String getRequiredSchemaData(List tableNames) { -// StringBuilder schemaBuilder = new StringBuilder(); -// -// tableNames.forEach(table -> { -// schemaBuilder.append("Table: ").append(table).append("\nColumns: "); -// List columns = schemaMap.getOrDefault(table, new ArrayList<>()); -// schemaBuilder.append(String.join(", ", columns)).append("\n"); -// -// List 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 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 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; import jakarta.annotation.PostConstruct; import lombok.Data; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; import javax.sql.DataSource; @@ -91,11 +12,12 @@ import java.sql.*; import java.util.*; @Component -@RequiredArgsConstructor -@Slf4j +//@RequiredArgsConstructor +//@Slf4j public class SchemaExtractor { - private final DataSource dataSource; + @Autowired + private DataSource dataSource; private final Map schemaMap = new HashMap<>(); @@ -170,7 +92,7 @@ public class SchemaExtractor { 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) { throw new RuntimeException("Error extracting schema", e); @@ -179,7 +101,7 @@ public class SchemaExtractor { // ---------- Inner Classes ---------- - @Data + public static class TableSchema { private final String name; private final String type; // TABLE or VIEW @@ -188,6 +110,35 @@ public class SchemaExtractor { private final Map foreignKeys = new LinkedHashMap<>(); private final Map> 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 getColumns() { + return columns; + } + + public List getPrimaryKeys() { + return primaryKeys; + } + + public Map getForeignKeys() { + return foreignKeys; + } + + public Map> getIndexes() { + return indexes; + } + public String toPrettyString() { StringBuilder sb = new StringBuilder(); sb.append(type).append(": ").append(name).append("\n"); @@ -220,6 +171,29 @@ public class SchemaExtractor { private final int size; 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 public String toString() { return name + " (" + type + (size > 0 ? "(" + size + ")" : "") + (nullable ? ", NULL" : ", NOT NULL") + ")"; diff --git a/src/main/java/com/email/classifier/controller/ImageUploadController.java b/src/main/java/com/email/classifier/controller/ImageUploadController.java index 1b49cc0..672622b 100644 --- a/src/main/java/com/email/classifier/controller/ImageUploadController.java +++ b/src/main/java/com/email/classifier/controller/ImageUploadController.java @@ -1,31 +1,31 @@ -package com.email.classifier.controller; - -import com.email.classifier.service.OpenAiVisionService; -import lombok.RequiredArgsConstructor; -import lombok.extern.slf4j.Slf4j; -import org.springframework.http.ResponseEntity; -import org.springframework.web.bind.annotation.*; -import org.springframework.web.multipart.MultipartFile; - -import java.io.IOException; - -@Slf4j -@RestController -@RequestMapping("/api/v1/images") -@RequiredArgsConstructor -public class ImageUploadController { - - private final OpenAiVisionService openAiVisionService; - - @PostMapping("/extract-text") - public ResponseEntity extractTextFromImage(@RequestParam("file") MultipartFile file) { - try { - log.info("Processing image: {}", file.getOriginalFilename()); - String extractedText = openAiVisionService.extractTextFromImage(file); - return ResponseEntity.ok(extractedText); - } catch (IOException e) { - log.error("Failed to process image", e); - return ResponseEntity.status(500).body("Error processing image."); - } - } -} +//package com.email.classifier.controller; +// +//import com.email.classifier.service.OpenAiVisionService; +//import lombok.RequiredArgsConstructor; +//import lombok.extern.slf4j.Slf4j; +//import org.springframework.http.ResponseEntity; +//import org.springframework.web.bind.annotation.*; +//import org.springframework.web.multipart.MultipartFile; +// +//import java.io.IOException; +// +//@Slf4j +//@RestController +//@RequestMapping("/api/v1/images") +//@RequiredArgsConstructor +//public class ImageUploadController { +// +// private final OpenAiVisionService openAiVisionService; +// +// @PostMapping("/extract-text") +// public ResponseEntity extractTextFromImage(@RequestParam("file") MultipartFile file) { +// try { +// log.info("Processing image: {}", file.getOriginalFilename()); +// String extractedText = openAiVisionService.extractTextFromImage(file); +// return ResponseEntity.ok(extractedText); +// } catch (IOException e) { +// log.error("Failed to process image", e); +// return ResponseEntity.status(500).body("Error processing image."); +// } +// } +//} diff --git a/src/main/java/com/email/classifier/service/OpenAiVisionService.java b/src/main/java/com/email/classifier/service/OpenAiVisionService.java index 1fab957..5dc065c 100644 --- a/src/main/java/com/email/classifier/service/OpenAiVisionService.java +++ b/src/main/java/com/email/classifier/service/OpenAiVisionService.java @@ -1,70 +1,70 @@ -package com.email.classifier.service; - -import com.fasterxml.jackson.databind.JsonNode; -import com.fasterxml.jackson.databind.ObjectMapper; -import lombok.RequiredArgsConstructor; -import lombok.extern.slf4j.Slf4j; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.beans.factory.annotation.Value; -import org.springframework.core.io.ByteArrayResource; -import org.springframework.http.*; -import org.springframework.stereotype.Service; -import org.springframework.web.client.RestTemplate; -import org.springframework.web.multipart.MultipartFile; - -import java.io.IOException; -import java.util.Collections; - -@Slf4j -@Service -@RequiredArgsConstructor -public class OpenAiVisionService { - - @Value("${spring.ai.openai.api-key}") - private String openAiApiKey; - - @Autowired - private RestTemplate restTemplate; - private final ObjectMapper objectMapper; - - private static final String OPENAI_VISION_URL = "https://api.openai.com/v1/chat/completions"; - - /** - * Extracts text from an uploaded image using OpenAI Vision API. - */ - public String extractTextFromImage(MultipartFile file) throws IOException { -// byte[] imageBytes = file.getBytes(); -// String base64Image = java.util.Base64.getEncoder().encodeToString(imageBytes); - String base64Image = ""; - // JSON payload for OpenAI Vision API - String requestBody = """ - { - "model": "gpt-4.5-preview", - "messages": [{ - "role": "user", - "content": [ - { "type": "text", "text": "Extract the text from the image, and output just that" } - ] - }], - "max_tokens": 500 - }"""; - - HttpHeaders headers = new HttpHeaders(); - headers.setBearerAuth(openAiApiKey); - headers.setContentType(MediaType.APPLICATION_JSON); - - HttpEntity requestEntity = new HttpEntity<>(requestBody, headers); - - // Send request to OpenAI - ResponseEntity responseEntity = restTemplate.exchange(OPENAI_VISION_URL, HttpMethod.POST, requestEntity, String.class); - - if (responseEntity.getStatusCode() == HttpStatus.OK) { - JsonNode jsonResponse = objectMapper.readTree(responseEntity.getBody()); - log.info(responseEntity.getBody()); - return jsonResponse.get("choices").get(0).get("message").get("content").asText(); - } else { - log.error("OpenAI API Error: {}", responseEntity.getBody()); - return "Error extracting text from image."; - } - } -} +//package com.email.classifier.service; +// +//import com.fasterxml.jackson.databind.JsonNode; +//import com.fasterxml.jackson.databind.ObjectMapper; +//import lombok.RequiredArgsConstructor; +//import lombok.extern.slf4j.Slf4j; +//import org.springframework.beans.factory.annotation.Autowired; +//import org.springframework.beans.factory.annotation.Value; +//import org.springframework.core.io.ByteArrayResource; +//import org.springframework.http.*; +//import org.springframework.stereotype.Service; +//import org.springframework.web.client.RestTemplate; +//import org.springframework.web.multipart.MultipartFile; +// +//import java.io.IOException; +//import java.util.Collections; +// +//@Slf4j +//@Service +//@RequiredArgsConstructor +//public class OpenAiVisionService { +// +// @Value("${spring.ai.openai.api-key}") +// private String openAiApiKey; +// +// @Autowired +// private RestTemplate restTemplate; +// private final ObjectMapper objectMapper; +// +// private static final String OPENAI_VISION_URL = "https://api.openai.com/v1/chat/completions"; +// +// /** +// * Extracts text from an uploaded image using OpenAI Vision API. +// */ +// public String extractTextFromImage(MultipartFile file) throws IOException { +//// byte[] imageBytes = file.getBytes(); +//// String base64Image = java.util.Base64.getEncoder().encodeToString(imageBytes); +// String base64Image = ""; +// // JSON payload for OpenAI Vision API +// String requestBody = """ +// { +// "model": "gpt-4.5-preview", +// "messages": [{ +// "role": "user", +// "content": [ +// { "type": "text", "text": "Extract the text from the image, and output just that" } +// ] +// }], +// "max_tokens": 500 +// }"""; +// +// HttpHeaders headers = new HttpHeaders(); +// headers.setBearerAuth(openAiApiKey); +// headers.setContentType(MediaType.APPLICATION_JSON); +// +// HttpEntity requestEntity = new HttpEntity<>(requestBody, headers); +// +// // Send request to OpenAI +// ResponseEntity responseEntity = restTemplate.exchange(OPENAI_VISION_URL, HttpMethod.POST, requestEntity, String.class); +// +// if (responseEntity.getStatusCode() == HttpStatus.OK) { +// JsonNode jsonResponse = objectMapper.readTree(responseEntity.getBody()); +// log.info(responseEntity.getBody()); +// return jsonResponse.get("choices").get(0).get("message").get("content").asText(); +// } else { +// log.error("OpenAI API Error: {}", responseEntity.getBody()); +// return "Error extracting text from image."; +// } +// } +//} diff --git a/src/test/java/com/email/classifier/ApplicationTests.java b/src/test/java/com/email/classifier/ApplicationTests.java deleted file mode 100644 index dd42924..0000000 --- a/src/test/java/com/email/classifier/ApplicationTests.java +++ /dev/null @@ -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() { - } - -}