Educational Note: This class uses the {@code @SpringBootApplication} annotation, which is a + * convenience annotation that adds: - {@code @Configuration}: Tags the class as a source of bean + * definitions for the application context. - {@code @EnableAutoConfiguration}: Tells Spring Boot to + * start adding beans based on classpath settings, other beans, and various property settings. - + * {@code @ComponentScan}: Tells Spring to look for other components, configurations, and services + * in the 'com.devassistant' package, allowing it to find the controllers and processors. + */ +@SpringBootApplication +public class NotificationBotApplication { + public static void main(String[] args) { + SpringApplication.run(NotificationBotApplication.class, args); + } +} diff --git a/src/main/java/derrick/Notificationbot/rest/HelloController.java b/src/main/java/com/devassistant/Notificationbot/rest/HelloController.java similarity index 54% rename from src/main/java/derrick/Notificationbot/rest/HelloController.java rename to src/main/java/com/devassistant/Notificationbot/rest/HelloController.java index c756fdd..2871690 100644 --- a/src/main/java/derrick/Notificationbot/rest/HelloController.java +++ b/src/main/java/com/devassistant/Notificationbot/rest/HelloController.java @@ -1,4 +1,4 @@ -package derrick.Notificationbot.rest; +package com.devassistant.Notificationbot.rest; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RestController; @@ -6,8 +6,8 @@ @RestController public class HelloController { - @GetMapping("/") - public String hello() { - return "Notification Bot is running!"; - } -} \ No newline at end of file + @GetMapping("/") + public String hello() { + return "Notification Bot is running!"; + } +} diff --git a/src/main/java/com/devassistant/logging/Logger.java b/src/main/java/com/devassistant/logging/Logger.java new file mode 100644 index 0000000..04606b2 --- /dev/null +++ b/src/main/java/com/devassistant/logging/Logger.java @@ -0,0 +1,9 @@ +package com.devassistant.logging; + +public interface Logger { + void info(String message); + + void warn(String message); + + void error(String message, Throwable t); +} diff --git a/src/main/java/com/devassistant/logging/LoggerImpl.java b/src/main/java/com/devassistant/logging/LoggerImpl.java new file mode 100644 index 0000000..6788e9f --- /dev/null +++ b/src/main/java/com/devassistant/logging/LoggerImpl.java @@ -0,0 +1,24 @@ +package com.devassistant.logging; + +import org.slf4j.LoggerFactory; +import org.springframework.stereotype.Component; + +@Component +public class LoggerImpl implements Logger { + private static final org.slf4j.Logger log = LoggerFactory.getLogger(LoggerImpl.class); + + @Override + public void info(String message) { + log.info(message); + } + + @Override + public void warn(String message) { + log.warn(message); + } + + @Override + public void error(String message, Throwable t) { + log.error(message, t); + } +} diff --git a/src/main/java/com/devassistant/processor/BuildFailureEventProcessor.java b/src/main/java/com/devassistant/processor/BuildFailureEventProcessor.java new file mode 100644 index 0000000..42a8ab0 --- /dev/null +++ b/src/main/java/com/devassistant/processor/BuildFailureEventProcessor.java @@ -0,0 +1,3 @@ +package com.devassistant.processor; + +public class BuildFailureEventProcessor {} diff --git a/src/main/java/com/devassistant/processor/EventProcessor.java b/src/main/java/com/devassistant/processor/EventProcessor.java new file mode 100644 index 0000000..b6d748b --- /dev/null +++ b/src/main/java/com/devassistant/processor/EventProcessor.java @@ -0,0 +1,29 @@ +package com.devassistant.processor; + +/** + * EventProcessor defines the contract for processing different types of CI/CD events. + * + *
Educational Note: This interface is the core of the Strategy Design Pattern used in this + * platform. By defining a common interface, the main Webhook controller doesn't need to know the + * specifics of how to handle a Pull Request versus a Build Failure. It can simply iterate through a + * list of EventProcessors, ask each one if it {@code supports()} the event, and if so, call {@code + * processEvent()}. This makes the system highly extensible: to add a new event type, you simply + * create a new class implementing this interface, without modifying the core logic. + */ +public interface EventProcessor { + + /** + * Checks if this processor supports the given event type. + * + * @param eventType The event type (e.g. pull_request, build_failure) + * @return true if supported, false otherwise + */ + boolean supports(String eventType); + + /** + * Processes the given event payload. + * + * @param payload The event payload as a String (typically JSON) + */ + void processEvent(String payload); +} diff --git a/src/main/java/com/devassistant/processor/PullRequestEventProcessor.java b/src/main/java/com/devassistant/processor/PullRequestEventProcessor.java new file mode 100644 index 0000000..5f4ce6d --- /dev/null +++ b/src/main/java/com/devassistant/processor/PullRequestEventProcessor.java @@ -0,0 +1,83 @@ +package com.devassistant.processor; + +import com.devassistant.logging.Logger; +import com.devassistant.pullrequest.PullRequestService; +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectMapper; +import org.springframework.ai.chat.client.ChatClient; +import org.springframework.stereotype.Component; + +/** + * Processes incoming Pull Request events and utilizes AI to summarize the changes. + * + *
Educational Note: This class implements the {@link EventProcessor} interface. We use Spring + * AI's {@link ChatClient} to interact with the configured LLM (e.g., OpenAI). By injecting {@code + * ChatClient.Builder}, we can create a client that sends the PR title and author to the AI to + * generate a risk assessment and summary. + */ +@Component +public class PullRequestEventProcessor implements EventProcessor { + + private static final String SUPPORTED_EVENT = "pull_request"; + + private final ObjectMapper objectMapper; + private final PullRequestService pullRequestService; + private final Logger logger; + private final ChatClient chatClient; + + public PullRequestEventProcessor( + PullRequestService pullRequestService, Logger logger, ChatClient.Builder chatClientBuilder) { + this.objectMapper = new ObjectMapper(); + this.pullRequestService = pullRequestService; + this.logger = logger; + this.chatClient = chatClientBuilder.build(); + } + + @Override + public boolean supports(String eventType) { + return SUPPORTED_EVENT.equalsIgnoreCase(eventType); + } + + @Override + public void processEvent(String payload) { + try { + JsonNode root = objectMapper.readTree(payload); + JsonNode actionNode = root.path("action"); + JsonNode pullRequestNode = root.path("pull_request"); + + if (actionNode.isMissingNode() || pullRequestNode.isMissingNode()) { + logger.warn("Missing pull request action or data in payload."); + return; + } + + String action = actionNode.asText(); + String prTitle = pullRequestNode.path("title").asText(); + String prUrl = pullRequestNode.path("html_url").asText(); + String author = pullRequestNode.path("user").path("login").asText(); + + // AI Integration: Generate PR Summary + String aiPrompt = + String.format( + "Analyze this PR title and provide a 2 sentence summary and risk assessment (Low/Medium/High). Title: '%s', Author: '%s'", + prTitle, author); + + String aiSummary = "AI Summary not available"; + try { + aiSummary = chatClient.prompt().user(aiPrompt).call().content(); + } catch (Exception aiException) { + logger.error("Failed to generate AI summary for PR", aiException); + } + + pullRequestService.handlePullRequest( + action, prTitle + " - AI Analysis: " + aiSummary, prUrl, author); + + logger.info( + String.format( + "Processed pull request event: action=%s, title=%s, author=%s", + action, prTitle, author)); + + } catch (Exception e) { + logger.error("Failed to process pull request event", e); + } + } +} diff --git a/src/main/java/com/devassistant/pullrequest/PullRequestService.java b/src/main/java/com/devassistant/pullrequest/PullRequestService.java new file mode 100644 index 0000000..9449471 --- /dev/null +++ b/src/main/java/com/devassistant/pullrequest/PullRequestService.java @@ -0,0 +1,13 @@ +package com.devassistant.pullrequest; + +public interface PullRequestService { + void commentOnPullRequest(String payload); + + void validatePullRequest(String payload); + + void mergePullRequest(String payload); + + String fetchPullRequestInfo(String pullRequestId); + + void handlePullRequest(String action, String prTitle, String prUrl, String author); +} diff --git a/src/main/java/com/devassistant/pullrequest/PullRequestServiceImpl.java b/src/main/java/com/devassistant/pullrequest/PullRequestServiceImpl.java new file mode 100644 index 0000000..10b22d9 --- /dev/null +++ b/src/main/java/com/devassistant/pullrequest/PullRequestServiceImpl.java @@ -0,0 +1,31 @@ +package com.devassistant.pullrequest; + +import org.springframework.stereotype.Service; + +@Service +public class PullRequestServiceImpl implements PullRequestService { + @Override + public void commentOnPullRequest(String payload) { + // Implementation for commenting on a pull request + } + + @Override + public void validatePullRequest(String payload) { + // Implementation for validating a pull request + } + + @Override + public void mergePullRequest(String payload) { + // Implementation for merging a pull request + } + + @Override + public String fetchPullRequestInfo(String pullRequestId) { + return null; + } + + @Override + public void handlePullRequest(String action, String prTitle, String prUrl, String author) { + // Implementation for handling a pull request from webhook + } +} diff --git a/src/main/java/derrick/Notificationbot/NotificationBotApplication.java b/src/main/java/derrick/Notificationbot/NotificationBotApplication.java deleted file mode 100644 index 596bc2f..0000000 --- a/src/main/java/derrick/Notificationbot/NotificationBotApplication.java +++ /dev/null @@ -1,11 +0,0 @@ -package derrick.Notificationbot; - -import org.springframework.boot.SpringApplication; -import org.springframework.boot.autoconfigure.SpringBootApplication; - -@SpringBootApplication -public class NotificationBotApplication { - public static void main(String[] args) { - SpringApplication.run(NotificationBotApplication.class, args); - } -} \ No newline at end of file diff --git a/src/main/java/derrick/logging/Logger b/src/main/java/derrick/logging/Logger deleted file mode 100644 index 0450f6a..0000000 --- a/src/main/java/derrick/logging/Logger +++ /dev/null @@ -1,6 +0,0 @@ -package derrick.logging; - -public interface Logger { - void info(String message); - void error(String message, Throwable t); -} diff --git a/src/main/java/derrick/logging/LoggerImpl b/src/main/java/derrick/logging/LoggerImpl deleted file mode 100644 index 8cc5c5b..0000000 --- a/src/main/java/derrick/logging/LoggerImpl +++ /dev/null @@ -1,14 +0,0 @@ -package derrick.logging.impl; - -import derrick.logging.Logger; - -public class ConsoleLogger implements Logger { - public void info(String message) { - System.out.println("[INFO] " + message); // Can replace later with structured logging - } - - public void error(String message, Throwable t) { - System.err.println("[ERROR] " + message); - t.printStackTrace(System.err); - } -} diff --git a/src/main/java/derrick/processor/BuildFailureEventProcessor.java b/src/main/java/derrick/processor/BuildFailureEventProcessor.java deleted file mode 100644 index 451e9d6..0000000 --- a/src/main/java/derrick/processor/BuildFailureEventProcessor.java +++ /dev/null @@ -1,5 +0,0 @@ -package derrick.processor; - -public class BuildFailureEventProcessor { - -} diff --git a/src/main/java/derrick/processor/EventProcessor.java b/src/main/java/derrick/processor/EventProcessor.java deleted file mode 100644 index 9735318..0000000 --- a/src/main/java/derrick/processor/EventProcessor.java +++ /dev/null @@ -1,23 +0,0 @@ -package com.derick.notificationbot.processor; - -/** - * EventProcessor defines the contract for processing different types of CI/CD events. - */ -public interface EventProcessor { - - /** - * Checks if this processor supports the given event type. - * - * @param eventType The event type (e.g. pull_request, build_failure) - * @return true if supported, false otherwise - */ - boolean supports(String eventType); - - /** - * Processes the given event payload. - * - * @param payload The event payload as a String (typically JSON) - */ - void processEvent(String payload); -} -` diff --git a/src/main/java/derrick/processor/PullRequestEventProcessor b/src/main/java/derrick/processor/PullRequestEventProcessor deleted file mode 100644 index b94aad3..0000000 --- a/src/main/java/derrick/processor/PullRequestEventProcessor +++ /dev/null @@ -1,53 +0,0 @@ -package derrick.processor; - -import com.fasterxml.jackson.databind.JsonNode; -import com.fasterxml.jackson.databind.ObjectMapper; -import derrick.service.PullRequestService; -import derrick.util.Logger; - -public class PullRequestEventProcessor implements EventProcessor { - - private static final String SUPPORTED_EVENT = "pull_request"; - - private final ObjectMapper objectMapper; - private final PullRequestService pullRequestService; - private final Logger logger; - - public PullRequestEventProcessor(PullRequestService pullRequestService, Logger logger) { - this.objectMapper = new ObjectMapper(); - this.pullRequestService = pullRequestService; - this.logger = logger; - } - - @Override - public boolean supports(String eventType) { - return SUPPORTED_EVENT.equalsIgnoreCase(eventType); - } - - @Override - public void processEvent(String payload) { - try { - JsonNode root = objectMapper.readTree(payload); - JsonNode actionNode = root.path("action"); - JsonNode pullRequestNode = root.path("pull_request"); - - if (actionNode.isMissingNode() || pullRequestNode.isMissingNode()) { - logger.warn("Missing pull request action or data in payload."); - return; - } - - String action = actionNode.asText(); - String prTitle = pullRequestNode.path("title").asText(); - String prUrl = pullRequestNode.path("html_url").asText(); - String author = pullRequestNode.path("user").path("login").asText(); - - pullRequestService.handlePullRequest(action, prTitle, prUrl, author); - - logger.info(String.format("Processed pull request event: action=%s, title=%s, author=%s", - action, prTitle, author)); - - } catch (Exception e) { - logger.error("Failed to process pull request event", e); - } - } -} diff --git a/src/main/java/derrick/pullrequest/PullRequestService b/src/main/java/derrick/pullrequest/PullRequestService deleted file mode 100644 index ba8ebae..0000000 --- a/src/main/java/derrick/pullrequest/PullRequestService +++ /dev/null @@ -1,8 +0,0 @@ -package derrick.service; - -public interface PullRequestService { - void commentOnPullRequest(String payload); - void validatePullRequest(String payload); - void mergePullRequest(String payload); - String fetchPullRequestInfo(String pullRequestId); -} \ No newline at end of file diff --git a/src/main/java/derrick/pullrequest/PullRequestServiceImpl b/src/main/java/derrick/pullrequest/PullRequestServiceImpl deleted file mode 100644 index bbf7287..0000000 --- a/src/main/java/derrick/pullrequest/PullRequestServiceImpl +++ /dev/null @@ -1,34 +0,0 @@ -package derrick.service.impl; - -import derrick.service.PullRequestService; - -public class GitHubPullRequestServiceImpl implements PullRequestService { - @Override - public void commentOnPullRequest(String payload) { - // Implementation for commenting on a pull request - // This could involve using GitHub's API to post a comment - // based on the provided payload. - } - - @Override - public void validatePullRequest(String payload) { - // Implementation for validating a pull request - // This could involve checking the status of the pull request - // or ensuring it meets certain criteria. - } - - @Override - public void mergePullRequest(String payload) { - // Implementation for merging a pull request - // This could involve using GitHub's API to merge the pull request - // based on the provided payload. - } - - @Override - public String fetchPullRequestInfo(String pullRequestId) { - // Implementation for fetching pull request information - // This could involve using GitHub's API to retrieve details - // about the specified pull request. - return null; - } -}