diff --git a/java-ecosystem/libs/analysis-engine/src/main/java/module-info.java b/java-ecosystem/libs/analysis-engine/src/main/java/module-info.java index c58ee723..d2fd51b7 100644 --- a/java-ecosystem/libs/analysis-engine/src/main/java/module-info.java +++ b/java-ecosystem/libs/analysis-engine/src/main/java/module-info.java @@ -17,7 +17,7 @@ requires jakarta.persistence; requires kotlin.stdlib; - exports org.rostilos.codecrow.analysisengine.client; + exports org.rostilos.codecrow.analysisengine.aiclient; exports org.rostilos.codecrow.analysisengine.config; exports org.rostilos.codecrow.analysisengine.dto.request.ai; exports org.rostilos.codecrow.analysisengine.dto.request.processor; @@ -30,7 +30,7 @@ exports org.rostilos.codecrow.analysisengine.service.vcs; exports org.rostilos.codecrow.analysisengine.util; - opens org.rostilos.codecrow.analysisengine.client to spring.core, spring.beans, spring.context; + opens org.rostilos.codecrow.analysisengine.aiclient to spring.core, spring.beans, spring.context; opens org.rostilos.codecrow.analysisengine.config to spring.core, spring.beans, spring.context; opens org.rostilos.codecrow.analysisengine.processor to spring.core, spring.beans, spring.context; opens org.rostilos.codecrow.analysisengine.processor.analysis to spring.core, spring.beans, spring.context; diff --git a/java-ecosystem/libs/analysis-engine/src/main/java/org/rostilos/codecrow/analysisengine/client/AiAnalysisClient.java b/java-ecosystem/libs/analysis-engine/src/main/java/org/rostilos/codecrow/analysisengine/aiclient/AiAnalysisClient.java similarity index 99% rename from java-ecosystem/libs/analysis-engine/src/main/java/org/rostilos/codecrow/analysisengine/client/AiAnalysisClient.java rename to java-ecosystem/libs/analysis-engine/src/main/java/org/rostilos/codecrow/analysisengine/aiclient/AiAnalysisClient.java index 5cd49226..d31b136a 100644 --- a/java-ecosystem/libs/analysis-engine/src/main/java/org/rostilos/codecrow/analysisengine/client/AiAnalysisClient.java +++ b/java-ecosystem/libs/analysis-engine/src/main/java/org/rostilos/codecrow/analysisengine/aiclient/AiAnalysisClient.java @@ -1,4 +1,4 @@ -package org.rostilos.codecrow.analysisengine.client; +package org.rostilos.codecrow.analysisengine.aiclient; import org.rostilos.codecrow.analysisengine.dto.request.ai.AiAnalysisRequest; import org.slf4j.Logger; diff --git a/java-ecosystem/libs/analysis-engine/src/main/java/org/rostilos/codecrow/analysisengine/client/AiCommandClient.java b/java-ecosystem/libs/analysis-engine/src/main/java/org/rostilos/codecrow/analysisengine/aiclient/AiCommandClient.java similarity index 99% rename from java-ecosystem/libs/analysis-engine/src/main/java/org/rostilos/codecrow/analysisengine/client/AiCommandClient.java rename to java-ecosystem/libs/analysis-engine/src/main/java/org/rostilos/codecrow/analysisengine/aiclient/AiCommandClient.java index 28494ddf..0f6537a9 100644 --- a/java-ecosystem/libs/analysis-engine/src/main/java/org/rostilos/codecrow/analysisengine/client/AiCommandClient.java +++ b/java-ecosystem/libs/analysis-engine/src/main/java/org/rostilos/codecrow/analysisengine/aiclient/AiCommandClient.java @@ -1,4 +1,4 @@ -package org.rostilos.codecrow.analysisengine.client; +package org.rostilos.codecrow.analysisengine.aiclient; import com.fasterxml.jackson.databind.ObjectMapper; import org.slf4j.Logger; diff --git a/java-ecosystem/libs/analysis-engine/src/main/java/org/rostilos/codecrow/analysisengine/processor/analysis/BranchAnalysisProcessor.java b/java-ecosystem/libs/analysis-engine/src/main/java/org/rostilos/codecrow/analysisengine/processor/analysis/BranchAnalysisProcessor.java index 2a9238d0..2bf04c17 100644 --- a/java-ecosystem/libs/analysis-engine/src/main/java/org/rostilos/codecrow/analysisengine/processor/analysis/BranchAnalysisProcessor.java +++ b/java-ecosystem/libs/analysis-engine/src/main/java/org/rostilos/codecrow/analysisengine/processor/analysis/BranchAnalysisProcessor.java @@ -10,7 +10,6 @@ import org.rostilos.codecrow.core.model.branch.BranchIssue; import org.rostilos.codecrow.core.model.project.Project; import org.rostilos.codecrow.core.model.vcs.VcsConnection; -import org.rostilos.codecrow.core.model.vcs.VcsRepoBinding; import org.rostilos.codecrow.core.persistence.repository.branch.BranchIssueRepository; import org.rostilos.codecrow.core.persistence.repository.codeanalysis.CodeAnalysisIssueRepository; import org.rostilos.codecrow.core.persistence.repository.branch.BranchRepository; @@ -24,7 +23,7 @@ import org.rostilos.codecrow.analysisengine.service.vcs.VcsAiClientService; import org.rostilos.codecrow.analysisengine.service.vcs.VcsOperationsService; import org.rostilos.codecrow.analysisengine.service.vcs.VcsServiceFactory; -import org.rostilos.codecrow.analysisengine.client.AiAnalysisClient; +import org.rostilos.codecrow.analysisengine.aiclient.AiAnalysisClient; import org.rostilos.codecrow.vcsclient.VcsClientProvider; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -87,28 +86,21 @@ public BranchAnalysisProcessor( } /** - * Helper record to hold VCS info from either ProjectVcsConnectionBinding or VcsRepoBinding. + * Helper record to hold VCS info. */ public record VcsInfo(VcsConnection vcsConnection, String workspace, String repoSlug) {} /** - * Get VCS info from project, preferring ProjectVcsConnectionBinding but falling back to VcsRepoBinding. + * Get VCS info from project using the unified accessor. */ public VcsInfo getVcsInfo(Project project) { - if (project.getVcsBinding() != null) { + var vcsInfo = project.getEffectiveVcsRepoInfo(); + if (vcsInfo != null && vcsInfo.getVcsConnection() != null) { return new VcsInfo( - project.getVcsBinding().getVcsConnection(), - project.getVcsBinding().getWorkspace(), - project.getVcsBinding().getRepoSlug() - ); - } - VcsRepoBinding repoBinding = project.getVcsRepoBinding(); - if (repoBinding != null && repoBinding.getVcsConnection() != null) { - return new VcsInfo( - repoBinding.getVcsConnection(), - repoBinding.getExternalNamespace(), - repoBinding.getExternalRepoSlug() + vcsInfo.getVcsConnection(), + vcsInfo.getRepoWorkspace(), + vcsInfo.getRepoSlug() ); } throw new IllegalStateException("No VCS connection configured for project: " + project.getId()); diff --git a/java-ecosystem/libs/analysis-engine/src/main/java/org/rostilos/codecrow/analysisengine/processor/analysis/PullRequestAnalysisProcessor.java b/java-ecosystem/libs/analysis-engine/src/main/java/org/rostilos/codecrow/analysisengine/processor/analysis/PullRequestAnalysisProcessor.java index a4c29c06..815c6272 100644 --- a/java-ecosystem/libs/analysis-engine/src/main/java/org/rostilos/codecrow/analysisengine/processor/analysis/PullRequestAnalysisProcessor.java +++ b/java-ecosystem/libs/analysis-engine/src/main/java/org/rostilos/codecrow/analysisengine/processor/analysis/PullRequestAnalysisProcessor.java @@ -14,7 +14,7 @@ import org.rostilos.codecrow.analysisengine.service.vcs.VcsAiClientService; import org.rostilos.codecrow.analysisengine.service.vcs.VcsReportingService; import org.rostilos.codecrow.analysisengine.service.vcs.VcsServiceFactory; -import org.rostilos.codecrow.analysisengine.client.AiAnalysisClient; +import org.rostilos.codecrow.analysisengine.aiclient.AiAnalysisClient; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.stereotype.Service; @@ -57,11 +57,10 @@ public interface EventConsumer { } private EVcsProvider getVcsProvider(Project project) { - if (project.getVcsBinding() != null && project.getVcsBinding().getVcsConnection() != null) { - return project.getVcsBinding().getVcsConnection().getProviderType(); - } - if (project.getVcsRepoBinding() != null && project.getVcsRepoBinding().getVcsConnection() != null) { - return project.getVcsRepoBinding().getVcsConnection().getProviderType(); + // Use unified method to get effective VCS connection + var vcsConnection = project.getEffectiveVcsConnection(); + if (vcsConnection != null) { + return vcsConnection.getProviderType(); } throw new IllegalStateException("No VCS connection configured for project: " + project.getId()); } diff --git a/java-ecosystem/libs/analysis-engine/src/main/java/org/rostilos/codecrow/analysisengine/service/ProjectService.java b/java-ecosystem/libs/analysis-engine/src/main/java/org/rostilos/codecrow/analysisengine/service/ProjectService.java index f12fe39a..8fda91c0 100644 --- a/java-ecosystem/libs/analysis-engine/src/main/java/org/rostilos/codecrow/analysisengine/service/ProjectService.java +++ b/java-ecosystem/libs/analysis-engine/src/main/java/org/rostilos/codecrow/analysisengine/service/ProjectService.java @@ -38,11 +38,8 @@ public Project getProjectWithConnections(Long projectId) throws IOException { } private void validateProjectConnections(Project project) throws IOException { - // Check for VCS connection: either legacy ProjectVcsConnectionBinding or new VcsRepoBinding - boolean hasVcsConnection = project.getVcsBinding() != null || - (project.getVcsRepoBinding() != null && project.getVcsRepoBinding().getVcsConnection() != null); - - if (!hasVcsConnection) { + // Use unified hasVcsBinding() method that checks both bindings + if (!project.hasVcsBinding()) { throw new IOException("VCS connection is not configured for project: " + project.getId()); } diff --git a/java-ecosystem/services/pipeline-agent/src/main/java/org/rostilos/codecrow/pipelineagent/generic/service/PromptSanitizationService.java b/java-ecosystem/libs/analysis-engine/src/main/java/org/rostilos/codecrow/analysisengine/service/PromptSanitizationService.java similarity index 99% rename from java-ecosystem/services/pipeline-agent/src/main/java/org/rostilos/codecrow/pipelineagent/generic/service/PromptSanitizationService.java rename to java-ecosystem/libs/analysis-engine/src/main/java/org/rostilos/codecrow/analysisengine/service/PromptSanitizationService.java index 666a133f..4ed5ab7c 100644 --- a/java-ecosystem/services/pipeline-agent/src/main/java/org/rostilos/codecrow/pipelineagent/generic/service/PromptSanitizationService.java +++ b/java-ecosystem/libs/analysis-engine/src/main/java/org/rostilos/codecrow/analysisengine/service/PromptSanitizationService.java @@ -1,4 +1,4 @@ -package org.rostilos.codecrow.pipelineagent.generic.service; +package org.rostilos.codecrow.analysisengine.service; import org.slf4j.Logger; import org.slf4j.LoggerFactory; diff --git a/java-ecosystem/libs/analysis-engine/src/main/java/org/rostilos/codecrow/analysisengine/service/job/AnalysisJobService.java b/java-ecosystem/libs/analysis-engine/src/main/java/org/rostilos/codecrow/analysisengine/service/job/AnalysisJobService.java deleted file mode 100644 index f72aad15..00000000 --- a/java-ecosystem/libs/analysis-engine/src/main/java/org/rostilos/codecrow/analysisengine/service/job/AnalysisJobService.java +++ /dev/null @@ -1,103 +0,0 @@ -package org.rostilos.codecrow.analysisengine.service.job; - -import org.rostilos.codecrow.core.model.job.Job; -import org.rostilos.codecrow.core.model.job.JobLogLevel; -import org.rostilos.codecrow.core.model.job.JobTriggerSource; -import org.rostilos.codecrow.core.model.project.Project; -import org.rostilos.codecrow.core.model.user.User; - -import java.util.Map; - -/** - * Interface for job management operations used by analysis-engine components. - * This abstraction allows different implementations (pipeline, IDE, CLI) to provide - * their own job tracking mechanism. - */ -public interface AnalysisJobService { - - /** - * Create a job for RAG initial indexing. - * @param project The project to index - * @param triggeredBy The user who triggered the job (can be null for automated jobs) - * @return The created job - */ - Job createRagIndexJob(Project project, User triggeredBy); - - /** - * Create a job for RAG indexing with configurable parameters. - * @param project The project to index - * @param isInitial true for initial indexing, false for incremental update - * @param triggerSource The source that triggered the job - * @return The created job - */ - Job createRagIndexJob(Project project, boolean isInitial, JobTriggerSource triggerSource); - - /** - * Start a job. - * @param job The job to start - */ - void startJob(Job job); - - /** - * Log a message to a job. - * @param job The job to log to - * @param level The log level - * @param state The current state/phase - * @param message The log message - */ - void logToJob(Job job, JobLogLevel level, String state, String message); - - /** - * Log a message with metadata to a job. - * @param job The job to log to - * @param level The log level - * @param state The current state/phase - * @param message The log message - * @param metadata Additional metadata - */ - void logToJob(Job job, JobLogLevel level, String state, String message, Map metadata); - - /** - * Complete a job successfully. - * @param job The job to complete - * @param result Optional result data - */ - void completeJob(Job job, Map result); - - /** - * Fail a job with an error message. - * @param job The job to fail - * @param errorMessage The error message - */ - void failJob(Job job, String errorMessage); - - /** - * Log an INFO level message to a job. - * @param job The job to log to - * @param state The current state/phase - * @param message The log message - */ - default void info(Job job, String state, String message) { - logToJob(job, JobLogLevel.INFO, state, message); - } - - /** - * Log a WARN level message to a job. - * @param job The job to log to - * @param state The current state/phase - * @param message The log message - */ - default void warn(Job job, String state, String message) { - logToJob(job, JobLogLevel.WARN, state, message); - } - - /** - * Log an ERROR level message to a job. - * @param job The job to log to - * @param state The current state/phase - * @param message The log message - */ - default void error(Job job, String state, String message) { - logToJob(job, JobLogLevel.ERROR, state, message); - } -} diff --git a/java-ecosystem/libs/core/src/main/java/org/rostilos/codecrow/core/dto/project/ProjectDTO.java b/java-ecosystem/libs/core/src/main/java/org/rostilos/codecrow/core/dto/project/ProjectDTO.java index cb8f9460..6a8967e0 100644 --- a/java-ecosystem/libs/core/src/main/java/org/rostilos/codecrow/core/dto/project/ProjectDTO.java +++ b/java-ecosystem/libs/core/src/main/java/org/rostilos/codecrow/core/dto/project/ProjectDTO.java @@ -3,6 +3,8 @@ import org.rostilos.codecrow.core.model.branch.Branch; import org.rostilos.codecrow.core.model.project.Project; import org.rostilos.codecrow.core.model.project.config.ProjectConfig; +import org.rostilos.codecrow.core.model.vcs.VcsConnection; +import org.rostilos.codecrow.core.model.vcs.VcsRepoInfo; import java.util.List; @@ -35,31 +37,21 @@ public static ProjectDTO fromProject(Project project) { String vcsWorkspace = null; String repoSlug = null; - // Check vcsBinding first (manual OAuth connection) - if (project.getVcsBinding() != null && project.getVcsBinding().getVcsConnection() != null) { - vcsConnectionId = project.getVcsBinding().getVcsConnection().getId(); - vcsWorkspace = project.getVcsBinding().getWorkspace(); - repoSlug = project.getVcsBinding().getRepoSlug(); - if (project.getVcsBinding().getVcsConnection().getConnectionType() != null) { - vcsConnectionType = project.getVcsBinding().getVcsConnection().getConnectionType().name(); - } - if (project.getVcsBinding().getVcsConnection().getProviderType() != null) { - vcsProvider = project.getVcsBinding().getVcsConnection().getProviderType().name(); - } - } - // Fallback to vcsRepoBinding (App-based connection) - else if (project.getVcsRepoBinding() != null) { - if (project.getVcsRepoBinding().getVcsConnection() != null) { - vcsConnectionId = project.getVcsRepoBinding().getVcsConnection().getId(); - if (project.getVcsRepoBinding().getVcsConnection().getConnectionType() != null) { - vcsConnectionType = project.getVcsRepoBinding().getVcsConnection().getConnectionType().name(); + // Use unified method to get VCS info (prefers VcsRepoBinding over legacy vcsBinding) + VcsRepoInfo vcsInfo = project.getEffectiveVcsRepoInfo(); + if (vcsInfo != null) { + VcsConnection conn = vcsInfo.getVcsConnection(); + if (conn != null) { + vcsConnectionId = conn.getId(); + if (conn.getConnectionType() != null) { + vcsConnectionType = conn.getConnectionType().name(); } - if (project.getVcsRepoBinding().getVcsConnection().getProviderType() != null) { - vcsProvider = project.getVcsRepoBinding().getVcsConnection().getProviderType().name(); + if (conn.getProviderType() != null) { + vcsProvider = conn.getProviderType().name(); } } - vcsWorkspace = project.getVcsRepoBinding().getExternalNamespace(); - repoSlug = project.getVcsRepoBinding().getExternalRepoSlug(); + vcsWorkspace = vcsInfo.getRepoWorkspace(); + repoSlug = vcsInfo.getRepoSlug(); } Long aiConnectionId = null; diff --git a/java-ecosystem/libs/core/src/main/java/org/rostilos/codecrow/core/model/project/Project.java b/java-ecosystem/libs/core/src/main/java/org/rostilos/codecrow/core/model/project/Project.java index 8da25afb..40feaf84 100644 --- a/java-ecosystem/libs/core/src/main/java/org/rostilos/codecrow/core/model/project/Project.java +++ b/java-ecosystem/libs/core/src/main/java/org/rostilos/codecrow/core/model/project/Project.java @@ -19,7 +19,9 @@ import org.hibernate.annotations.JdbcTypeCode; import org.hibernate.type.SqlTypes; import org.rostilos.codecrow.core.model.project.config.ProjectConfig; +import org.rostilos.codecrow.core.model.vcs.VcsConnection; import org.rostilos.codecrow.core.model.vcs.VcsRepoBinding; +import org.rostilos.codecrow.core.model.vcs.VcsRepoInfo; import org.rostilos.codecrow.core.model.workspace.Workspace; @Entity @@ -154,10 +156,52 @@ public void setVcsBinding(ProjectVcsConnectionBinding projectVcsConnectionBindin this.vcsBinding = projectVcsConnectionBinding; } + /** + * @deprecated Use {@link #getEffectiveVcsRepoInfo()} or {@link #getVcsRepoBinding()} instead. + * This method returns the legacy Bitbucket-specific binding. + */ + @Deprecated(since = "2.0", forRemoval = true) public ProjectVcsConnectionBinding getVcsBinding() { return this.vcsBinding; } + /** + * Returns the effective VCS repository information for this project. + *

+ * This method provides a unified way to access VCS repository info regardless + * of whether the project uses the legacy {@link ProjectVcsConnectionBinding} + * or the new provider-agnostic {@link VcsRepoBinding}. + *

+ * Priority: VcsRepoBinding (new) > ProjectVcsConnectionBinding (legacy) + * + * @return VcsRepoInfo if any binding exists, null otherwise + */ + public VcsRepoInfo getEffectiveVcsRepoInfo() { + if (vcsRepoBinding != null) { + return vcsRepoBinding; + } + return vcsBinding; + } + + /** + * Returns the VCS connection for this project, checking both new and legacy bindings. + * + * @return VcsConnection if any binding exists, null otherwise + */ + public VcsConnection getEffectiveVcsConnection() { + VcsRepoInfo info = getEffectiveVcsRepoInfo(); + return info != null ? info.getVcsConnection() : null; + } + + /** + * Checks if this project has any VCS binding configured. + * + * @return true if either vcsRepoBinding or vcsBinding is present + */ + public boolean hasVcsBinding() { + return vcsRepoBinding != null || vcsBinding != null; + } + public void setAiConnectionBinding(ProjectAiConnectionBinding projectAiConnectionBinding) { this.aiBinding = projectAiConnectionBinding; } diff --git a/java-ecosystem/libs/core/src/main/java/org/rostilos/codecrow/core/model/project/ProjectVcsConnectionBinding.java b/java-ecosystem/libs/core/src/main/java/org/rostilos/codecrow/core/model/project/ProjectVcsConnectionBinding.java index 7f3443a0..eaec2397 100644 --- a/java-ecosystem/libs/core/src/main/java/org/rostilos/codecrow/core/model/project/ProjectVcsConnectionBinding.java +++ b/java-ecosystem/libs/core/src/main/java/org/rostilos/codecrow/core/model/project/ProjectVcsConnectionBinding.java @@ -4,10 +4,19 @@ import jakarta.persistence.*; import org.rostilos.codecrow.core.model.vcs.EVcsProvider; import org.rostilos.codecrow.core.model.vcs.VcsConnection; +import org.rostilos.codecrow.core.model.vcs.VcsRepoBinding; import org.rostilos.codecrow.core.model.vcs.VcsRepoInfo; import java.util.UUID; +/** + * @deprecated Use {@link VcsRepoBinding} instead. This class is Bitbucket-specific and lacks + * webhook tracking, timestamps, and provider-agnostic repository ID support. + *

+ * Migration: Use {@link Project#getEffectiveVcsRepoInfo()} which returns {@link VcsRepoInfo} + * and handles both legacy and new bindings transparently. + */ +@Deprecated(since = "2.0", forRemoval = true) @Entity @Table(name = "project_vcs_connection", uniqueConstraints = { @UniqueConstraint(name = "uq_repo_unique", columnNames = {"repository_id"}) diff --git a/java-ecosystem/libs/core/src/main/java/org/rostilos/codecrow/core/util/VcsBindingHelper.java b/java-ecosystem/libs/core/src/main/java/org/rostilos/codecrow/core/util/VcsBindingHelper.java new file mode 100644 index 00000000..af4f7bf8 --- /dev/null +++ b/java-ecosystem/libs/core/src/main/java/org/rostilos/codecrow/core/util/VcsBindingHelper.java @@ -0,0 +1,106 @@ +package org.rostilos.codecrow.core.util; + +import org.rostilos.codecrow.core.model.project.Project; +import org.rostilos.codecrow.core.model.vcs.EVcsProvider; +import org.rostilos.codecrow.core.model.vcs.VcsConnection; +import org.rostilos.codecrow.core.model.vcs.VcsRepoBinding; +import org.rostilos.codecrow.core.model.vcs.VcsRepoInfo; + +/** + * Utility class for working with VCS bindings in projects. + *

+ * This helper provides methods to extract VCS information from a project, + * handling both the new {@link VcsRepoBinding} and the legacy + * {@link org.rostilos.codecrow.core.model.project.ProjectVcsConnectionBinding}. + */ +public final class VcsBindingHelper { + + private VcsBindingHelper() { + // Utility class + } + + /** + * Gets the effective VCS repository info from a project. + * Prefers VcsRepoBinding over the legacy ProjectVcsConnectionBinding. + * + * @param project the project to extract VCS info from + * @return VcsRepoInfo if any binding exists, null otherwise + */ + public static VcsRepoInfo getEffectiveVcsRepoInfo(Project project) { + if (project == null) { + return null; + } + return project.getEffectiveVcsRepoInfo(); + } + + /** + * Gets the VCS connection from a project. + * + * @param project the project to extract connection from + * @return VcsConnection if any binding exists, null otherwise + */ + public static VcsConnection getVcsConnection(Project project) { + if (project == null) { + return null; + } + return project.getEffectiveVcsConnection(); + } + + /** + * Gets the repository workspace/namespace from a project. + * + * @param project the project to extract workspace from + * @return workspace string if any binding exists, null otherwise + */ + public static String getRepoWorkspace(Project project) { + VcsRepoInfo info = getEffectiveVcsRepoInfo(project); + return info != null ? info.getRepoWorkspace() : null; + } + + /** + * Gets the repository slug from a project. + * + * @param project the project to extract slug from + * @return repo slug if any binding exists, null otherwise + */ + public static String getRepoSlug(Project project) { + VcsRepoInfo info = getEffectiveVcsRepoInfo(project); + return info != null ? info.getRepoSlug() : null; + } + + /** + * Gets the VCS provider from a project's VCS connection. + * + * @param project the project to extract provider from + * @return EVcsProvider if connection exists, null otherwise + */ + public static EVcsProvider getVcsProvider(Project project) { + VcsConnection connection = getVcsConnection(project); + return connection != null ? connection.getProviderType() : null; + } + + /** + * Checks if the project has a valid VCS binding with connection. + * + * @param project the project to check + * @return true if project has a binding with a non-null VCS connection + */ + public static boolean hasValidVcsBinding(Project project) { + return getVcsConnection(project) != null; + } + + /** + * Gets the full repository path (workspace/slug) from a project. + * + * @param project the project to extract path from + * @return full repo path in format "workspace/slug", or null if unavailable + */ + public static String getFullRepoPath(Project project) { + String workspace = getRepoWorkspace(project); + String slug = getRepoSlug(project); + if (workspace != null && slug != null) { + return workspace + "/" + slug; + } + return null; + } +} diff --git a/java-ecosystem/libs/rag-engine/src/main/java/org/rostilos/codecrow/ragengine/service/VcsRagIndexingService.java b/java-ecosystem/libs/rag-engine/src/main/java/org/rostilos/codecrow/ragengine/service/VcsRagIndexingService.java index fcb19a2f..a0039e70 100644 --- a/java-ecosystem/libs/rag-engine/src/main/java/org/rostilos/codecrow/ragengine/service/VcsRagIndexingService.java +++ b/java-ecosystem/libs/rag-engine/src/main/java/org/rostilos/codecrow/ragengine/service/VcsRagIndexingService.java @@ -8,7 +8,6 @@ import org.rostilos.codecrow.core.model.project.Project; import org.rostilos.codecrow.core.model.project.config.ProjectConfig; import org.rostilos.codecrow.core.model.vcs.VcsConnection; -import org.rostilos.codecrow.core.model.vcs.VcsRepoBinding; import org.rostilos.codecrow.core.persistence.repository.project.ProjectRepository; import org.rostilos.codecrow.analysisengine.service.AnalysisLockService; import org.rostilos.codecrow.core.service.AnalysisJobService; @@ -86,15 +85,12 @@ public Map indexProjectFromVcs( String workspaceSlug; String repoSlug; - if (project.getVcsBinding() != null && project.getVcsBinding().getVcsConnection() != null) { - vcsConnection = project.getVcsBinding().getVcsConnection(); - workspaceSlug = project.getVcsBinding().getWorkspace(); - repoSlug = project.getVcsBinding().getRepoSlug(); - } else if (project.getVcsRepoBinding() != null && project.getVcsRepoBinding().getVcsConnection() != null) { - VcsRepoBinding repoBinding = project.getVcsRepoBinding(); - vcsConnection = repoBinding.getVcsConnection(); - workspaceSlug = repoBinding.getExternalNamespace(); - repoSlug = repoBinding.getExternalRepoSlug(); + // Use unified method to get VCS info + var vcsInfo = project.getEffectiveVcsRepoInfo(); + if (vcsInfo != null && vcsInfo.getVcsConnection() != null) { + vcsConnection = vcsInfo.getVcsConnection(); + workspaceSlug = vcsInfo.getRepoWorkspace(); + repoSlug = vcsInfo.getRepoSlug(); } else { log.warn("Project {} has no VCS binding", project.getName()); return Map.of("status", "error", "message", "Project has no VCS connection"); @@ -315,6 +311,7 @@ public boolean shouldAutoIndex(Project project) { return false; } - return project.getVcsBinding() != null && project.getVcsBinding().getVcsConnection() != null; + // Use unified hasVcsBinding() check + return project.hasVcsBinding(); } } diff --git a/java-ecosystem/libs/vcs-client/src/main/java/module-info.java b/java-ecosystem/libs/vcs-client/src/main/java/module-info.java index 284f1a62..0facfad3 100644 --- a/java-ecosystem/libs/vcs-client/src/main/java/module-info.java +++ b/java-ecosystem/libs/vcs-client/src/main/java/module-info.java @@ -27,4 +27,5 @@ exports org.rostilos.codecrow.vcsclient.gitlab; exports org.rostilos.codecrow.vcsclient.gitlab.actions; exports org.rostilos.codecrow.vcsclient.gitlab.dto.response; + exports org.rostilos.codecrow.vcsclient.utils; } \ No newline at end of file diff --git a/java-ecosystem/libs/vcs-client/src/main/java/org/rostilos/codecrow/vcsclient/VcsClientProvider.java b/java-ecosystem/libs/vcs-client/src/main/java/org/rostilos/codecrow/vcsclient/VcsClientProvider.java index a68c3ebd..1a6ad7de 100644 --- a/java-ecosystem/libs/vcs-client/src/main/java/org/rostilos/codecrow/vcsclient/VcsClientProvider.java +++ b/java-ecosystem/libs/vcs-client/src/main/java/org/rostilos/codecrow/vcsclient/VcsClientProvider.java @@ -13,12 +13,13 @@ import org.rostilos.codecrow.core.model.vcs.EVcsConnectionType; import org.rostilos.codecrow.core.model.vcs.EVcsProvider; import org.rostilos.codecrow.core.model.vcs.VcsConnection; -import org.rostilos.codecrow.core.model.vcs.config.cloud.BitbucketCloudConfig; import org.rostilos.codecrow.core.persistence.repository.vcs.BitbucketConnectInstallationRepository; import org.rostilos.codecrow.core.persistence.repository.vcs.VcsConnectionRepository; import org.rostilos.codecrow.security.oauth.TokenEncryptionService; import org.rostilos.codecrow.vcsclient.bitbucket.cloud.BitbucketCloudClient; import org.rostilos.codecrow.vcsclient.github.GitHubClient; +import org.rostilos.codecrow.vcsclient.utils.VcsConnectionCredentialsExtractor; +import org.rostilos.codecrow.vcsclient.utils.VcsConnectionCredentialsExtractor.VcsConnectionCredentials; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Value; @@ -61,6 +62,7 @@ public class VcsClientProvider { private final BitbucketConnectInstallationRepository connectInstallationRepository; private final TokenEncryptionService encryptionService; private final HttpAuthorizedClientFactory httpClientFactory; + private final VcsConnectionCredentialsExtractor credentialsExtractor; @Value("${codecrow.bitbucket.app.client-id:}") private String bitbucketAppClientId; @@ -93,38 +95,10 @@ public VcsClientProvider( this.connectInstallationRepository = connectInstallationRepository; this.encryptionService = encryptionService; this.httpClientFactory = httpClientFactory; + this.credentialsExtractor = new VcsConnectionCredentialsExtractor(encryptionService); } - - /** - * Get an authorized VCS client for the given connection ID. - * - * @param connectionId the VCS connection ID - * @return authorized VcsClient - * @throws IllegalArgumentException if connection not found - * @throws VcsClientException if client creation fails - */ - public VcsClient getClient(Long connectionId) { - VcsConnection connection = connectionRepository.findById(connectionId) - .orElseThrow(() -> new IllegalArgumentException("VCS connection not found: " + connectionId)); - return getClient(connection); - } - - /** - * Get an authorized VCS client for the given connection ID within a workspace. - * - * @param workspaceId the workspace ID - * @param connectionId the VCS connection ID - * @return authorized VcsClient - * @throws IllegalArgumentException if connection not found or doesn't belong to workspace - * @throws VcsClientException if client creation fails - */ - public VcsClient getClient(Long workspaceId, Long connectionId) { - VcsConnection connection = connectionRepository.findByWorkspace_IdAndId(workspaceId, connectionId) - .orElseThrow(() -> new IllegalArgumentException( - "VCS connection not found: " + connectionId + " in workspace: " + workspaceId)); - return getClient(connection); - } - + + /** * Get an authorized VCS client for the given connection entity. * Automatically refreshes token if needed for APP connections. @@ -143,36 +117,7 @@ public VcsClient getClient(VcsConnection connection) { throw new VcsClientException("Failed to decrypt credentials for connection: " + connection.getId(), e); } } - - /** - * Get an authorized OkHttpClient for the given connection. - * Automatically refreshes token if needed for APP connections. - * Use this for low-level HTTP operations. - * - * @param connectionId the VCS connection ID - * @return authorized OkHttpClient - */ - public OkHttpClient getHttpClient(Long connectionId) { - VcsConnection connection = connectionRepository.findById(connectionId) - .orElseThrow(() -> new IllegalArgumentException("VCS connection not found: " + connectionId)); - return getHttpClient(connection); - } - - /** - * Get an authorized OkHttpClient for the given connection within a workspace. - * Automatically refreshes token if needed for APP connections. - * - * @param workspaceId the workspace ID - * @param connectionId the VCS connection ID - * @return authorized OkHttpClient - */ - public OkHttpClient getHttpClient(Long workspaceId, Long connectionId) { - VcsConnection connection = connectionRepository.findByWorkspace_IdAndId(workspaceId, connectionId) - .orElseThrow(() -> new IllegalArgumentException( - "VCS connection not found: " + connectionId + " in workspace: " + workspaceId)); - return getHttpClient(connection); - } - + /** * Get an authorized OkHttpClient for the given connection entity. * Automatically refreshes token if needed for APP connections. @@ -605,39 +550,32 @@ private OkHttpClient createAppHttpClient(VcsConnection connection) throws Genera * Uses OAuth Consumer key/secret (OAuth 1.0 style or OAuth 2.0 client credentials). */ private OkHttpClient createOAuthManualHttpClient(VcsConnection connection) throws GeneralSecurityException { - // For legacy connections, credentials are stored in the configuration JSON - if (connection.getConfiguration() == null) { - throw new VcsClientException("No configuration found for OAUTH_MANUAL connection: " + connection.getId()); + VcsConnectionCredentials credentials = credentialsExtractor.extractCredentials(connection); + + if (VcsConnectionCredentialsExtractor.hasOAuthCredentials(credentials)) { + return httpClientFactory.createClient( + credentials.oAuthClient(), + credentials.oAuthSecret(), + connection.getProviderType().getId() + ); } - if (connection.getConfiguration() instanceof BitbucketCloudConfig config) { - String clientId = encryptionService.decrypt(config.oAuthKey()); - String clientSecret = encryptionService.decrypt(config.oAuthToken()); - - return httpClientFactory.createClient(clientId, clientSecret, - connection.getProviderType().getId()); - } - - throw new VcsClientException("Unsupported configuration type for connection: " + connection.getId()); + throw new VcsClientException("No OAuth credentials found for OAUTH_MANUAL connection: " + connection.getId()); } /** - * Create HTTP client for PERSONAL_TOKEN connections. - * Uses personal access token (bearer token authentication). + * Create HTTP client for PERSONAL_TOKEN and REPOSITORY_TOKEN connections. + * Uses personal/repository access token (bearer token authentication). */ private OkHttpClient createPersonalTokenHttpClient(VcsConnection connection) throws GeneralSecurityException { - String accessToken; - - // Get access token - either from direct field or from configuration - if (connection.getAccessToken() != null && !connection.getAccessToken().isBlank()) { - accessToken = encryptionService.decrypt(connection.getAccessToken()); - } else if (connection.getConfiguration() != null) { - // Extract token from config for GitHub/GitLab - accessToken = extractTokenFromConfig(connection); - } else { - throw new VcsClientException("No access token found for PERSONAL_TOKEN connection: " + connection.getId()); + VcsConnectionCredentials credentials = credentialsExtractor.extractCredentials(connection); + + if (!VcsConnectionCredentialsExtractor.hasAccessToken(credentials)) { + throw new VcsClientException("No access token found for connection: " + connection.getId()); } + String accessToken = credentials.accessToken(); + // Use provider-specific client factory for proper headers return switch (connection.getProviderType()) { case GITHUB -> httpClientFactory.createGitHubClient(accessToken); @@ -646,16 +584,6 @@ private OkHttpClient createPersonalTokenHttpClient(VcsConnection connection) thr }; } - private String extractTokenFromConfig(VcsConnection connection) throws GeneralSecurityException { - if (connection.getConfiguration() instanceof org.rostilos.codecrow.core.model.vcs.config.github.GitHubConfig config) { - return config.accessToken(); // GitHub config stores token in plain text - } - if (connection.getConfiguration() instanceof org.rostilos.codecrow.core.model.vcs.config.gitlab.GitLabConfig config) { - return config.accessToken(); // GitLab config stores token in plain text - } - throw new VcsClientException("Cannot extract token from config for connection: " + connection.getId()); - } - /** * Create a VcsClient for the given provider with the authorized HTTP client. */ diff --git a/java-ecosystem/libs/vcs-client/src/main/java/org/rostilos/codecrow/vcsclient/utils/VcsConnectionCredentialsExtractor.java b/java-ecosystem/libs/vcs-client/src/main/java/org/rostilos/codecrow/vcsclient/utils/VcsConnectionCredentialsExtractor.java new file mode 100644 index 00000000..a15df969 --- /dev/null +++ b/java-ecosystem/libs/vcs-client/src/main/java/org/rostilos/codecrow/vcsclient/utils/VcsConnectionCredentialsExtractor.java @@ -0,0 +1,257 @@ +package org.rostilos.codecrow.vcsclient.utils; + +import org.rostilos.codecrow.core.model.vcs.EVcsConnectionType; +import org.rostilos.codecrow.core.model.vcs.EVcsProvider; +import org.rostilos.codecrow.core.model.vcs.VcsConnection; +import org.rostilos.codecrow.core.model.vcs.config.cloud.BitbucketCloudConfig; +import org.rostilos.codecrow.core.model.vcs.config.github.GitHubConfig; +import org.rostilos.codecrow.core.model.vcs.config.gitlab.GitLabConfig; +import org.rostilos.codecrow.security.oauth.TokenEncryptionService; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.security.GeneralSecurityException; + +/** + * Single source of truth for extracting VCS connection credentials. + * Handles all provider types (GitHub, GitLab, Bitbucket) and all connection types + * (OAUTH_MANUAL, APP, CONNECT_APP, GITHUB_APP, PERSONAL_TOKEN, REPOSITORY_TOKEN, APPLICATION). + */ +public class VcsConnectionCredentialsExtractor { + + private static final Logger log = LoggerFactory.getLogger(VcsConnectionCredentialsExtractor.class); + + private final TokenEncryptionService tokenEncryptionService; + + public VcsConnectionCredentialsExtractor(TokenEncryptionService tokenEncryptionService) { + this.tokenEncryptionService = tokenEncryptionService; + } + + /** + * Extract credentials from a VcsConnection. + * This is the main method that handles all provider and connection type combinations. + * + * @param vcsConnection The VcsConnection to extract credentials from + * @return VcsConnectionCredentials containing the extracted credentials + * @throws GeneralSecurityException if decryption fails + */ + public VcsConnectionCredentials extractCredentials(VcsConnection vcsConnection) throws GeneralSecurityException { + if (vcsConnection == null) { + throw new IllegalArgumentException("VcsConnection cannot be null"); + } + + EVcsProvider provider = vcsConnection.getProviderType(); + EVcsConnectionType connectionType = vcsConnection.getConnectionType(); + + if (provider == null) { + throw new IllegalArgumentException("VcsConnection provider type cannot be null"); + } + + if (connectionType == null) { + throw new IllegalArgumentException("VcsConnection type cannot be null"); + } + + log.debug("Extracting credentials for provider={}, connectionType={}", provider, connectionType); + + String oAuthClient = null; + String oAuthSecret = null; + String accessToken = null; + + switch (connectionType) { + case OAUTH_MANUAL -> { + // Bitbucket Cloud OAuth consumer credentials + if (provider == EVcsProvider.BITBUCKET_CLOUD && + vcsConnection.getConfiguration() instanceof BitbucketCloudConfig config) { + oAuthClient = decryptIfNeeded(config.oAuthKey()); + oAuthSecret = decryptIfNeeded(config.oAuthToken()); + } else { + log.warn("OAUTH_MANUAL connection type but no BitbucketCloudConfig found for provider {}", provider); + } + } + + case APP, CONNECT_APP -> { + // Bitbucket App/Connect App - uses encrypted accessToken on VcsConnection + accessToken = decryptStoredAccessToken(vcsConnection); + } + + case GITHUB_APP -> { + // GitHub App installation - uses encrypted accessToken on VcsConnection + accessToken = decryptStoredAccessToken(vcsConnection); + } + + case APPLICATION -> { + // GitLab OAuth Application - uses encrypted accessToken on VcsConnection + accessToken = decryptStoredAccessToken(vcsConnection); + } + + case PERSONAL_TOKEN -> { + // Personal Access Token - stored in config (GitHub or GitLab) + accessToken = extractTokenFromConfig(vcsConnection); + } + + case REPOSITORY_TOKEN -> { + // Repository-scoped token (GitLab Project Access Token, etc.) - stored in config + accessToken = extractTokenFromConfig(vcsConnection); + } + + case ACCESS_TOKEN -> { + // Generic access token (e.g., Bitbucket Server) - uses stored accessToken + accessToken = decryptStoredAccessToken(vcsConnection); + } + + default -> { + log.warn("Unknown connection type: {}. Attempting to extract from stored accessToken.", connectionType); + accessToken = decryptStoredAccessToken(vcsConnection); + } + } + + String vcsProviderString = getVcsProviderString(provider); + + return new VcsConnectionCredentials(oAuthClient, oAuthSecret, accessToken, vcsProviderString, provider, connectionType); + } + + /** + * Convenience method - same as extractCredentials but with backwards compatible name. + */ + public VcsConnectionCredentials extractCredentialsFromConnection(VcsConnection vcsConnection) throws GeneralSecurityException { + return extractCredentials(vcsConnection); + } + + /** + * Decrypt the stored accessToken from VcsConnection. + */ + private String decryptStoredAccessToken(VcsConnection vcsConnection) throws GeneralSecurityException { + String encryptedToken = vcsConnection.getAccessToken(); + if (encryptedToken == null || encryptedToken.isBlank()) { + log.warn("No stored accessToken found for connection id={}, type={}", + vcsConnection.getId(), vcsConnection.getConnectionType()); + return null; + } + return tokenEncryptionService.decrypt(encryptedToken); + } + + /** + * Extract access token from the connection's configuration object. + * Supports GitHubConfig and GitLabConfig. + */ + private String extractTokenFromConfig(VcsConnection vcsConnection) { + Object config = vcsConnection.getConfiguration(); + + if (config instanceof GitLabConfig gitLabConfig) { + return gitLabConfig.accessToken(); + } + + if (config instanceof GitHubConfig gitHubConfig) { + return gitHubConfig.accessToken(); + } + + // Fallback: try to get from stored accessToken (some implementations store it there) + if (vcsConnection.getAccessToken() != null && !vcsConnection.getAccessToken().isBlank()) { + try { + return tokenEncryptionService.decrypt(vcsConnection.getAccessToken()); + } catch (GeneralSecurityException e) { + log.warn("Failed to decrypt stored accessToken for connection id={}: {}", + vcsConnection.getId(), e.getMessage()); + } + } + + log.warn("Could not extract token from config for connection id={}, configType={}", + vcsConnection.getId(), config != null ? config.getClass().getSimpleName() : "null"); + return null; + } + + /** + * Decrypt a value if tokenEncryptionService is available. + */ + private String decryptIfNeeded(String value) throws GeneralSecurityException { + if (value == null || value.isBlank()) { + return null; + } + return tokenEncryptionService.decrypt(value); + } + + /** + * Get the VCS provider string used for MCP client selection. + * This is the canonical mapping from EVcsProvider to the string identifier used throughout the system. + * + * @param provider The EVcsProvider enum value + * @return The string identifier (e.g., "github", "gitlab", "bitbucket_cloud") + */ + public static String getVcsProviderString(EVcsProvider provider) { + if (provider == null) { + return "bitbucket_cloud"; // Default fallback + } + + return switch (provider) { + case GITHUB -> "github"; + case GITLAB -> "gitlab"; + case BITBUCKET_CLOUD -> "bitbucket_cloud"; + case BITBUCKET_SERVER -> "bitbucket_server"; + }; + } + + /** + * Check if the credentials include OAuth client credentials (for OAuth flow). + */ + public static boolean hasOAuthCredentials(VcsConnectionCredentials credentials) { + return credentials != null && + credentials.oAuthClient() != null && !credentials.oAuthClient().isBlank() && + credentials.oAuthSecret() != null && !credentials.oAuthSecret().isBlank(); + } + + /** + * Check if the credentials include an access token. + */ + public static boolean hasAccessToken(VcsConnectionCredentials credentials) { + return credentials != null && + credentials.accessToken() != null && !credentials.accessToken().isBlank(); + } + + /** + * Check if any valid credentials are available. + */ + public static boolean hasValidCredentials(VcsConnectionCredentials credentials) { + return hasOAuthCredentials(credentials) || hasAccessToken(credentials); + } + + /** + * Record containing extracted VCS connection credentials. + * Includes all possible credential types plus metadata about the connection. + */ + public record VcsConnectionCredentials( + String oAuthClient, + String oAuthSecret, + String accessToken, + String vcsProviderString, + EVcsProvider provider, + EVcsConnectionType connectionType + ) { + /** + * Backwards compatible constructor without provider info. + */ + public VcsConnectionCredentials(String oAuthClient, String oAuthSecret, String accessToken) { + this(oAuthClient, oAuthSecret, accessToken, null, null, null); + } + + /** + * Check if this credentials object has OAuth client credentials. + */ + public boolean hasOAuthCredentials() { + return VcsConnectionCredentialsExtractor.hasOAuthCredentials(this); + } + + /** + * Check if this credentials object has an access token. + */ + public boolean hasAccessToken() { + return VcsConnectionCredentialsExtractor.hasAccessToken(this); + } + + /** + * Check if any valid credentials are available. + */ + public boolean hasValidCredentials() { + return VcsConnectionCredentialsExtractor.hasValidCredentials(this); + } + } +} diff --git a/java-ecosystem/services/pipeline-agent/src/main/java/org/rostilos/codecrow/pipelineagent/bitbucket/dto/request/ai/AiBranchAnalysisRequest.java b/java-ecosystem/services/pipeline-agent/src/main/java/org/rostilos/codecrow/pipelineagent/bitbucket/dto/request/ai/AiBranchAnalysisRequest.java deleted file mode 100644 index 0539821b..00000000 --- a/java-ecosystem/services/pipeline-agent/src/main/java/org/rostilos/codecrow/pipelineagent/bitbucket/dto/request/ai/AiBranchAnalysisRequest.java +++ /dev/null @@ -1,59 +0,0 @@ -package org.rostilos.codecrow.pipelineagent.bitbucket.dto.request.ai; - -import org.rostilos.codecrow.analysisengine.dto.request.ai.AiAnalysisRequestImpl; - -/** - * Bitbucket-specific branch analysis request. - * Extends the generic AiAnalysisRequestImpl with branch-specific fields. - */ -public class AiBranchAnalysisRequest extends AiAnalysisRequestImpl { - protected final String branch; - protected final String commitHash; - - protected AiBranchAnalysisRequest(Builder builder) { - super(builder); - this.branch = builder.branch; - this.commitHash = builder.commitHash; - } - - public String getBranch() { - return branch; - } - - public String getCommitHash() { - return commitHash; - } - - public static Builder builder() { - return new Builder(); - } - - public static class Builder extends AiAnalysisRequestImpl.Builder { - protected String branch; - protected String commitHash; - - protected Builder() { - super(); - } - - @Override - protected Builder self() { - return this; - } - - public Builder withBranch(String branch) { - this.branch = branch; - return this; - } - - public Builder withCommitHash(String commitHash) { - this.commitHash = commitHash; - return this; - } - - @Override - public AiBranchAnalysisRequest build() { - return new AiBranchAnalysisRequest(this); - } - } -} diff --git a/java-ecosystem/services/pipeline-agent/src/main/java/org/rostilos/codecrow/pipelineagent/bitbucket/dto/request/ai/AiPullRequestAnalysisRequest.java b/java-ecosystem/services/pipeline-agent/src/main/java/org/rostilos/codecrow/pipelineagent/bitbucket/dto/request/ai/AiPullRequestAnalysisRequest.java deleted file mode 100644 index 418d4ae3..00000000 --- a/java-ecosystem/services/pipeline-agent/src/main/java/org/rostilos/codecrow/pipelineagent/bitbucket/dto/request/ai/AiPullRequestAnalysisRequest.java +++ /dev/null @@ -1,35 +0,0 @@ -package org.rostilos.codecrow.pipelineagent.bitbucket.dto.request.ai; - -import org.rostilos.codecrow.analysisengine.dto.request.ai.AiAnalysisRequestImpl; - -/** - * Bitbucket-specific PR analysis request. - * Extends the generic AiAnalysisRequestImpl with the same structure. - */ -public class AiPullRequestAnalysisRequest extends AiAnalysisRequestImpl { - - protected AiPullRequestAnalysisRequest(Builder builder) { - super(builder); - } - - public static Builder builder() { - return new Builder(); - } - - public static class Builder extends AiAnalysisRequestImpl.Builder { - - protected Builder() { - super(); - } - - @Override - protected Builder self() { - return this; - } - - @Override - public AiPullRequestAnalysisRequest build() { - return new AiPullRequestAnalysisRequest(this); - } - } -} diff --git a/java-ecosystem/services/pipeline-agent/src/main/java/org/rostilos/codecrow/pipelineagent/generic/controller/ProviderPipelineActionController.java b/java-ecosystem/services/pipeline-agent/src/main/java/org/rostilos/codecrow/pipelineagent/controller/ProviderPipelineActionController.java similarity index 94% rename from java-ecosystem/services/pipeline-agent/src/main/java/org/rostilos/codecrow/pipelineagent/generic/controller/ProviderPipelineActionController.java rename to java-ecosystem/services/pipeline-agent/src/main/java/org/rostilos/codecrow/pipelineagent/controller/ProviderPipelineActionController.java index fa3e56b0..e26ca5a0 100644 --- a/java-ecosystem/services/pipeline-agent/src/main/java/org/rostilos/codecrow/pipelineagent/generic/controller/ProviderPipelineActionController.java +++ b/java-ecosystem/services/pipeline-agent/src/main/java/org/rostilos/codecrow/pipelineagent/controller/ProviderPipelineActionController.java @@ -1,4 +1,4 @@ -package org.rostilos.codecrow.pipelineagent.generic.controller; +package org.rostilos.codecrow.pipelineagent.controller; import jakarta.servlet.http.HttpServletResponse; import jakarta.validation.Valid; @@ -7,8 +7,8 @@ import org.rostilos.codecrow.analysisengine.dto.request.processor.AnalysisProcessRequest; import org.rostilos.codecrow.analysisengine.dto.request.processor.BranchProcessRequest; import org.rostilos.codecrow.analysisengine.dto.request.processor.PrProcessRequest; -import org.rostilos.codecrow.analysisengine.processor.WebhookProcessor; -import org.rostilos.codecrow.pipelineagent.generic.service.PipelineJobService; +import org.rostilos.codecrow.pipelineagent.processor.PipelineActionProcessor; +import org.rostilos.codecrow.pipelineagent.service.PipelineJobService; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.http.ResponseEntity; @@ -41,16 +41,16 @@ public class ProviderPipelineActionController { private static final Logger log = LoggerFactory.getLogger(ProviderPipelineActionController.class); private static final String EOF_MARKER = "__EOF__"; - private final WebhookProcessor webhookProcessor; + private final PipelineActionProcessor pipelineActionProcessor; private final PipelineJobService pipelineJobService; private final ObjectMapper objectMapper; public ProviderPipelineActionController( - WebhookProcessor webhookProcessor, + PipelineActionProcessor pipelineActionProcessor, PipelineJobService pipelineJobService, ObjectMapper objectMapper ) { - this.webhookProcessor = webhookProcessor; + this.pipelineActionProcessor = pipelineActionProcessor; this.pipelineJobService = pipelineJobService; this.objectMapper = objectMapper; } @@ -67,10 +67,10 @@ public ResponseEntity handlePrWebhook( payload, job, (consumer, jobRef) -> { - WebhookProcessor.EventConsumer dualConsumer = + PipelineActionProcessor.EventConsumer dualConsumer = pipelineJobService.createDualConsumer(jobRef, consumer); try { - return webhookProcessor.processWebhookWithConsumer(payload, dualConsumer); + return pipelineActionProcessor.processPipelineActionWithConsumer(payload, dualConsumer); } catch (org.rostilos.codecrow.analysisengine.exception.AnalysisLockedException e) { log.warn("Analysis locked: {}", e.getMessage()); dualConsumer.accept(Map.of( @@ -122,10 +122,10 @@ public ResponseEntity handleBranchWebhook( payload, job, (consumer, jobRef) -> { - WebhookProcessor.EventConsumer dualConsumer = + PipelineActionProcessor.EventConsumer dualConsumer = pipelineJobService.createDualConsumer(jobRef, consumer); try { - return webhookProcessor.processWebhookWithConsumer(payload, dualConsumer); + return pipelineActionProcessor.processPipelineActionWithConsumer(payload, dualConsumer); } catch (org.rostilos.codecrow.analysisengine.exception.AnalysisLockedException e) { log.warn("Analysis locked: {}", e.getMessage()); dualConsumer.accept(Map.of( @@ -154,7 +154,7 @@ public ResponseEntity handleBranchWebhook( @FunctionalInterface private interface JobAwareWebhookProcessor { - Map process(WebhookProcessor.EventConsumer consumer, Job job); + Map process(PipelineActionProcessor.EventConsumer consumer, Job job); } private ResponseEntity processWebhookWithJob( @@ -171,7 +171,7 @@ private ResponseEntity processWebhookWithJob( } LinkedBlockingQueue queue = new LinkedBlockingQueue<>(); - WebhookProcessor.EventConsumer consumer = createEventConsumer(queue); + PipelineActionProcessor.EventConsumer consumer = createEventConsumer(queue); CompletableFuture> processingFuture = CompletableFuture.supplyAsync(() -> { try { @@ -277,7 +277,7 @@ private void validateAuthentication(ProjectDTO authenticationPrincipal, Analysis } } - private WebhookProcessor.EventConsumer createEventConsumer(LinkedBlockingQueue queue) { + private PipelineActionProcessor.EventConsumer createEventConsumer(LinkedBlockingQueue queue) { return event -> { try { log.debug("Event consumer received event: type={}", event.get("type")); diff --git a/java-ecosystem/services/pipeline-agent/src/main/java/org/rostilos/codecrow/pipelineagent/generic/controller/ProviderWebhookController.java b/java-ecosystem/services/pipeline-agent/src/main/java/org/rostilos/codecrow/pipelineagent/controller/ProviderWebhookController.java similarity index 95% rename from java-ecosystem/services/pipeline-agent/src/main/java/org/rostilos/codecrow/pipelineagent/generic/controller/ProviderWebhookController.java rename to java-ecosystem/services/pipeline-agent/src/main/java/org/rostilos/codecrow/pipelineagent/controller/ProviderWebhookController.java index 90b7f01b..29e66c1c 100644 --- a/java-ecosystem/services/pipeline-agent/src/main/java/org/rostilos/codecrow/pipelineagent/generic/controller/ProviderWebhookController.java +++ b/java-ecosystem/services/pipeline-agent/src/main/java/org/rostilos/codecrow/pipelineagent/controller/ProviderWebhookController.java @@ -1,4 +1,4 @@ -package org.rostilos.codecrow.pipelineagent.generic.controller; +package org.rostilos.codecrow.pipelineagent.controller; import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.ObjectMapper; @@ -9,14 +9,14 @@ import org.rostilos.codecrow.core.model.project.Project; import org.rostilos.codecrow.core.model.vcs.EVcsProvider; import org.rostilos.codecrow.core.service.JobService; -import org.rostilos.codecrow.pipelineagent.bitbucket.webhook.BitbucketCloudWebhookParser; -import org.rostilos.codecrow.pipelineagent.github.webhook.GitHubWebhookParser; -import org.rostilos.codecrow.pipelineagent.gitlab.webhook.GitLabWebhookParser; -import org.rostilos.codecrow.pipelineagent.generic.webhook.WebhookPayload; -import org.rostilos.codecrow.pipelineagent.generic.webhook.WebhookProjectResolver; -import org.rostilos.codecrow.pipelineagent.generic.webhook.handler.WebhookHandler; -import org.rostilos.codecrow.pipelineagent.generic.webhook.handler.WebhookHandlerFactory; -import org.rostilos.codecrow.pipelineagent.generic.service.WebhookAsyncProcessor; +import org.rostilos.codecrow.pipelineagent.webhookhandler.bitbucket.BitbucketCloudWebhookParser; +import org.rostilos.codecrow.pipelineagent.webhookhandler.github.GitHubWebhookParser; +import org.rostilos.codecrow.pipelineagent.webhookhandler.gitlab.GitLabWebhookParser; +import org.rostilos.codecrow.pipelineagent.dto.webhook.WebhookPayload; +import org.rostilos.codecrow.pipelineagent.webhookhandler.WebhookProjectResolver; +import org.rostilos.codecrow.pipelineagent.webhookhandler.WebhookHandler; +import org.rostilos.codecrow.pipelineagent.webhookhandler.WebhookHandlerFactory; +import org.rostilos.codecrow.pipelineagent.processor.WebhookAsyncProcessor; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Value; diff --git a/java-ecosystem/services/pipeline-agent/src/main/java/org/rostilos/codecrow/pipelineagent/generic/controller/RagIndexingController.java b/java-ecosystem/services/pipeline-agent/src/main/java/org/rostilos/codecrow/pipelineagent/controller/RagIndexingController.java similarity index 98% rename from java-ecosystem/services/pipeline-agent/src/main/java/org/rostilos/codecrow/pipelineagent/generic/controller/RagIndexingController.java rename to java-ecosystem/services/pipeline-agent/src/main/java/org/rostilos/codecrow/pipelineagent/controller/RagIndexingController.java index 4a87510f..634e4318 100644 --- a/java-ecosystem/services/pipeline-agent/src/main/java/org/rostilos/codecrow/pipelineagent/generic/controller/RagIndexingController.java +++ b/java-ecosystem/services/pipeline-agent/src/main/java/org/rostilos/codecrow/pipelineagent/controller/RagIndexingController.java @@ -1,4 +1,4 @@ -package org.rostilos.codecrow.pipelineagent.generic.controller; +package org.rostilos.codecrow.pipelineagent.controller; import com.fasterxml.jackson.databind.ObjectMapper; import org.rostilos.codecrow.core.dto.project.ProjectDTO; diff --git a/java-ecosystem/services/pipeline-agent/src/main/java/org/rostilos/codecrow/pipelineagent/generic/controller/helpers/HealthCheckController.java b/java-ecosystem/services/pipeline-agent/src/main/java/org/rostilos/codecrow/pipelineagent/controller/helpers/HealthCheckController.java similarity index 86% rename from java-ecosystem/services/pipeline-agent/src/main/java/org/rostilos/codecrow/pipelineagent/generic/controller/helpers/HealthCheckController.java rename to java-ecosystem/services/pipeline-agent/src/main/java/org/rostilos/codecrow/pipelineagent/controller/helpers/HealthCheckController.java index 40d6e88b..f16f8366 100644 --- a/java-ecosystem/services/pipeline-agent/src/main/java/org/rostilos/codecrow/pipelineagent/generic/controller/helpers/HealthCheckController.java +++ b/java-ecosystem/services/pipeline-agent/src/main/java/org/rostilos/codecrow/pipelineagent/controller/helpers/HealthCheckController.java @@ -1,4 +1,4 @@ -package org.rostilos.codecrow.pipelineagent.generic.controller.helpers; +package org.rostilos.codecrow.pipelineagent.controller.helpers; import org.springframework.web.bind.annotation.CrossOrigin; import org.springframework.web.bind.annotation.GetMapping; diff --git a/java-ecosystem/services/pipeline-agent/src/main/java/org/rostilos/codecrow/pipelineagent/generic/webhook/WebhookPayload.java b/java-ecosystem/services/pipeline-agent/src/main/java/org/rostilos/codecrow/pipelineagent/dto/webhook/WebhookPayload.java similarity index 99% rename from java-ecosystem/services/pipeline-agent/src/main/java/org/rostilos/codecrow/pipelineagent/generic/webhook/WebhookPayload.java rename to java-ecosystem/services/pipeline-agent/src/main/java/org/rostilos/codecrow/pipelineagent/dto/webhook/WebhookPayload.java index ef5e51fd..5006bf3e 100644 --- a/java-ecosystem/services/pipeline-agent/src/main/java/org/rostilos/codecrow/pipelineagent/generic/webhook/WebhookPayload.java +++ b/java-ecosystem/services/pipeline-agent/src/main/java/org/rostilos/codecrow/pipelineagent/dto/webhook/WebhookPayload.java @@ -1,4 +1,4 @@ -package org.rostilos.codecrow.pipelineagent.generic.webhook; +package org.rostilos.codecrow.pipelineagent.dto.webhook; import org.rostilos.codecrow.core.model.vcs.EVcsProvider; diff --git a/java-ecosystem/libs/analysis-engine/src/main/java/org/rostilos/codecrow/analysisengine/processor/WebhookProcessor.java b/java-ecosystem/services/pipeline-agent/src/main/java/org/rostilos/codecrow/pipelineagent/processor/PipelineActionProcessor.java similarity index 91% rename from java-ecosystem/libs/analysis-engine/src/main/java/org/rostilos/codecrow/analysisengine/processor/WebhookProcessor.java rename to java-ecosystem/services/pipeline-agent/src/main/java/org/rostilos/codecrow/pipelineagent/processor/PipelineActionProcessor.java index ef98c810..6ff1dc1d 100644 --- a/java-ecosystem/libs/analysis-engine/src/main/java/org/rostilos/codecrow/analysisengine/processor/WebhookProcessor.java +++ b/java-ecosystem/services/pipeline-agent/src/main/java/org/rostilos/codecrow/pipelineagent/processor/PipelineActionProcessor.java @@ -1,4 +1,4 @@ -package org.rostilos.codecrow.analysisengine.processor; +package org.rostilos.codecrow.pipelineagent.processor; import jakarta.validation.Valid; import org.rostilos.codecrow.core.model.codeanalysis.AnalysisType; @@ -24,14 +24,14 @@ * Orchestrates code analysis workflow including caching, AI analysis, and result posting. */ @Service -public class WebhookProcessor { - private static final Logger log = LoggerFactory.getLogger(WebhookProcessor.class); +public class PipelineActionProcessor { + private static final Logger log = LoggerFactory.getLogger(PipelineActionProcessor.class); private final ProjectService projectService; private final PullRequestAnalysisProcessor pullRequestAnalysisProcessor; private final BranchAnalysisProcessor branchAnalysisProcessor; - public WebhookProcessor( + public PipelineActionProcessor( ProjectService projectService, PullRequestAnalysisProcessor pullRequestAnalysisProcessor, BranchAnalysisProcessor branchAnalysisProcessor @@ -52,7 +52,7 @@ public interface EventConsumer { * @param consumer event consumer invoked for each streamed event (ndjson objects) * @return AI response map (final result) */ - public Map processWebhookWithConsumer( + public Map processPipelineActionWithConsumer( @Valid @RequestBody AnalysisProcessRequest request, EventConsumer consumer ) throws GeneralSecurityException { diff --git a/java-ecosystem/services/pipeline-agent/src/main/java/org/rostilos/codecrow/pipelineagent/generic/service/WebhookAsyncProcessor.java b/java-ecosystem/services/pipeline-agent/src/main/java/org/rostilos/codecrow/pipelineagent/processor/WebhookAsyncProcessor.java similarity index 97% rename from java-ecosystem/services/pipeline-agent/src/main/java/org/rostilos/codecrow/pipelineagent/generic/service/WebhookAsyncProcessor.java rename to java-ecosystem/services/pipeline-agent/src/main/java/org/rostilos/codecrow/pipelineagent/processor/WebhookAsyncProcessor.java index 25ca9209..bc009ec0 100644 --- a/java-ecosystem/services/pipeline-agent/src/main/java/org/rostilos/codecrow/pipelineagent/generic/service/WebhookAsyncProcessor.java +++ b/java-ecosystem/services/pipeline-agent/src/main/java/org/rostilos/codecrow/pipelineagent/processor/WebhookAsyncProcessor.java @@ -1,12 +1,12 @@ -package org.rostilos.codecrow.pipelineagent.generic.service; +package org.rostilos.codecrow.pipelineagent.processor; import org.rostilos.codecrow.core.model.job.Job; import org.rostilos.codecrow.core.model.project.Project; import org.rostilos.codecrow.core.model.vcs.EVcsProvider; import org.rostilos.codecrow.core.persistence.repository.project.ProjectRepository; import org.rostilos.codecrow.core.service.JobService; -import org.rostilos.codecrow.pipelineagent.generic.webhook.WebhookPayload; -import org.rostilos.codecrow.pipelineagent.generic.webhook.handler.WebhookHandler; +import org.rostilos.codecrow.pipelineagent.dto.webhook.WebhookPayload; +import org.rostilos.codecrow.pipelineagent.webhookhandler.WebhookHandler; import org.rostilos.codecrow.analysisengine.service.vcs.VcsReportingService; import org.rostilos.codecrow.analysisengine.service.vcs.VcsServiceFactory; import org.slf4j.Logger; @@ -173,22 +173,16 @@ public void processWebhookAsync( * Initialize lazy associations that will be needed for VCS operations. */ private void initializeProjectAssociations(Project project) { - // Force initialization of VCS connections - if (project.getVcsBinding() != null) { - var vcsConn = project.getVcsBinding().getVcsConnection(); + // Force initialization of VCS connections using unified accessor + var vcsInfo = project.getEffectiveVcsRepoInfo(); + if (vcsInfo != null) { + var vcsConn = vcsInfo.getVcsConnection(); if (vcsConn != null) { // Touch to initialize vcsConn.getConnectionType(); vcsConn.getProviderType(); } } - if (project.getVcsRepoBinding() != null) { - var vcsConn = project.getVcsRepoBinding().getVcsConnection(); - if (vcsConn != null) { - vcsConn.getConnectionType(); - vcsConn.getProviderType(); - } - } } /** diff --git a/java-ecosystem/services/pipeline-agent/src/main/java/org/rostilos/codecrow/pipelineagent/generic/handler/processor/AskCommandProcessor.java b/java-ecosystem/services/pipeline-agent/src/main/java/org/rostilos/codecrow/pipelineagent/processor/command/AskCommandProcessor.java similarity index 86% rename from java-ecosystem/services/pipeline-agent/src/main/java/org/rostilos/codecrow/pipelineagent/generic/handler/processor/AskCommandProcessor.java rename to java-ecosystem/services/pipeline-agent/src/main/java/org/rostilos/codecrow/pipelineagent/processor/command/AskCommandProcessor.java index 68b90be2..8dc332ac 100644 --- a/java-ecosystem/services/pipeline-agent/src/main/java/org/rostilos/codecrow/pipelineagent/generic/handler/processor/AskCommandProcessor.java +++ b/java-ecosystem/services/pipeline-agent/src/main/java/org/rostilos/codecrow/pipelineagent/processor/command/AskCommandProcessor.java @@ -1,23 +1,20 @@ -package org.rostilos.codecrow.pipelineagent.generic.handler.processor; +package org.rostilos.codecrow.pipelineagent.processor.command; -import org.rostilos.codecrow.analysisengine.client.AiCommandClient; -import org.rostilos.codecrow.analysisengine.client.AiCommandClient.AskRequest; -import org.rostilos.codecrow.analysisengine.client.AiCommandClient.AskResult; +import org.rostilos.codecrow.analysisengine.aiclient.AiCommandClient; +import org.rostilos.codecrow.analysisengine.aiclient.AiCommandClient.AskRequest; +import org.rostilos.codecrow.analysisengine.aiclient.AiCommandClient.AskResult; import org.rostilos.codecrow.core.model.ai.AIConnection; import org.rostilos.codecrow.core.model.codeanalysis.CodeAnalysis; import org.rostilos.codecrow.core.model.project.Project; -import org.rostilos.codecrow.core.model.project.ProjectVcsConnectionBinding; -import org.rostilos.codecrow.core.model.vcs.EVcsConnectionType; -import org.rostilos.codecrow.core.model.vcs.EVcsProvider; import org.rostilos.codecrow.core.model.vcs.VcsConnection; -import org.rostilos.codecrow.core.model.vcs.VcsRepoBinding; -import org.rostilos.codecrow.core.model.vcs.config.cloud.BitbucketCloudConfig; import org.rostilos.codecrow.core.service.CodeAnalysisService; -import org.rostilos.codecrow.pipelineagent.generic.handler.CommentCommandWebhookHandler.CommentCommandProcessor; -import org.rostilos.codecrow.pipelineagent.generic.service.PromptSanitizationService; -import org.rostilos.codecrow.pipelineagent.generic.webhook.WebhookPayload; -import org.rostilos.codecrow.pipelineagent.generic.webhook.handler.WebhookHandler.WebhookResult; +import org.rostilos.codecrow.pipelineagent.webhookhandler.CommentCommandWebhookHandler.CommentCommandProcessor; +import org.rostilos.codecrow.analysisengine.service.PromptSanitizationService; +import org.rostilos.codecrow.pipelineagent.dto.webhook.WebhookPayload; +import org.rostilos.codecrow.pipelineagent.webhookhandler.WebhookHandler.WebhookResult; import org.rostilos.codecrow.security.oauth.TokenEncryptionService; +import org.rostilos.codecrow.vcsclient.utils.VcsConnectionCredentialsExtractor; +import org.rostilos.codecrow.vcsclient.utils.VcsConnectionCredentialsExtractor.VcsConnectionCredentials; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.stereotype.Component; @@ -55,6 +52,7 @@ public class AskCommandProcessor implements CommentCommandProcessor { private final PromptSanitizationService sanitizationService; private final AiCommandClient aiCommandClient; private final TokenEncryptionService tokenEncryptionService; + private final VcsConnectionCredentialsExtractor credentialsExtractor; public AskCommandProcessor( CodeAnalysisService codeAnalysisService, @@ -66,6 +64,7 @@ public AskCommandProcessor( this.sanitizationService = sanitizationService; this.aiCommandClient = aiCommandClient; this.tokenEncryptionService = tokenEncryptionService; + this.credentialsExtractor = new VcsConnectionCredentialsExtractor(tokenEncryptionService); } @Override @@ -347,34 +346,9 @@ private AskRequest buildAskRequest( AIConnection aiConnection = project.getAiBinding().getAiConnection(); String decryptedApiKey = tokenEncryptionService.decrypt(aiConnection.getApiKeyEncrypted()); - // Get VCS credentials + // Get VCS credentials using centralized extractor VcsConnection vcsConnection = vcsInfo.vcsConnection(); - String oAuthClient = null; - String oAuthSecret = null; - String accessToken = null; - - if (vcsConnection.getConnectionType() == EVcsConnectionType.OAUTH_MANUAL) { - if (vcsConnection.getProviderType() == EVcsProvider.BITBUCKET_CLOUD) { - BitbucketCloudConfig config = (BitbucketCloudConfig) vcsConnection.getConfiguration(); - oAuthClient = tokenEncryptionService.decrypt(config.oAuthKey()); - oAuthSecret = tokenEncryptionService.decrypt(config.oAuthToken()); - } - } else if (vcsConnection.getConnectionType() == EVcsConnectionType.APP) { - accessToken = tokenEncryptionService.decrypt(vcsConnection.getAccessToken()); - } else if (vcsConnection.getConnectionType() == EVcsConnectionType.PERSONAL_TOKEN || - vcsConnection.getConnectionType() == EVcsConnectionType.REPOSITORY_TOKEN) { - // For GitLab personal/repository tokens, get the token from config - if (vcsConnection.getConfiguration() instanceof org.rostilos.codecrow.core.model.vcs.config.gitlab.GitLabConfig gitLabConfig) { - accessToken = gitLabConfig.accessToken(); - } - } - - // Determine VCS provider string - String vcsProvider = switch (vcsConnection.getProviderType()) { - case GITHUB -> "github"; - case GITLAB -> "gitlab"; - default -> "bitbucket_cloud"; - }; + VcsConnectionCredentials credentials = credentialsExtractor.extractCredentials(vcsConnection); // Build analysis context string String analysisContext = contextData.analysisInfo(); @@ -398,11 +372,11 @@ private AskRequest buildAskRequest( question, prId, payload.commitHash(), - oAuthClient, - oAuthSecret, - accessToken, + credentials.oAuthClient(), + credentials.oAuthSecret(), + credentials.accessToken(), aiConnection.getTokenLimitation(), - vcsProvider, + credentials.vcsProviderString(), analysisContext, context.issueReferences() ); @@ -419,19 +393,13 @@ private AskRequest buildAskRequest( private record VcsInfo(VcsConnection vcsConnection, String workspace, String repoSlug) {} /** - * Get VCS info from the project. + * Get VCS info from the project using unified accessor. */ private VcsInfo getVcsInfo(Project project) { - ProjectVcsConnectionBinding vcsBinding = project.getVcsBinding(); - if (vcsBinding != null && vcsBinding.getVcsConnection() != null) { - return new VcsInfo(vcsBinding.getVcsConnection(), vcsBinding.getWorkspace(), vcsBinding.getRepoSlug()); - } - - VcsRepoBinding repoBinding = project.getVcsRepoBinding(); - if (repoBinding != null && repoBinding.getVcsConnection() != null) { - return new VcsInfo(repoBinding.getVcsConnection(), repoBinding.getExternalNamespace(), repoBinding.getExternalRepoSlug()); + var vcsInfo = project.getEffectiveVcsRepoInfo(); + if (vcsInfo != null && vcsInfo.getVcsConnection() != null) { + return new VcsInfo(vcsInfo.getVcsConnection(), vcsInfo.getRepoWorkspace(), vcsInfo.getRepoSlug()); } - return null; } diff --git a/java-ecosystem/services/pipeline-agent/src/main/java/org/rostilos/codecrow/pipelineagent/generic/handler/processor/ReviewCommandProcessor.java b/java-ecosystem/services/pipeline-agent/src/main/java/org/rostilos/codecrow/pipelineagent/processor/command/ReviewCommandProcessor.java similarity index 67% rename from java-ecosystem/services/pipeline-agent/src/main/java/org/rostilos/codecrow/pipelineagent/generic/handler/processor/ReviewCommandProcessor.java rename to java-ecosystem/services/pipeline-agent/src/main/java/org/rostilos/codecrow/pipelineagent/processor/command/ReviewCommandProcessor.java index 85f1582d..c80079a5 100644 --- a/java-ecosystem/services/pipeline-agent/src/main/java/org/rostilos/codecrow/pipelineagent/generic/handler/processor/ReviewCommandProcessor.java +++ b/java-ecosystem/services/pipeline-agent/src/main/java/org/rostilos/codecrow/pipelineagent/processor/command/ReviewCommandProcessor.java @@ -1,20 +1,17 @@ -package org.rostilos.codecrow.pipelineagent.generic.handler.processor; +package org.rostilos.codecrow.pipelineagent.processor.command; -import org.rostilos.codecrow.analysisengine.client.AiCommandClient; -import org.rostilos.codecrow.analysisengine.client.AiCommandClient.ReviewRequest; -import org.rostilos.codecrow.analysisengine.client.AiCommandClient.ReviewResult; +import org.rostilos.codecrow.analysisengine.aiclient.AiCommandClient; +import org.rostilos.codecrow.analysisengine.aiclient.AiCommandClient.ReviewRequest; +import org.rostilos.codecrow.analysisengine.aiclient.AiCommandClient.ReviewResult; import org.rostilos.codecrow.core.model.ai.AIConnection; import org.rostilos.codecrow.core.model.project.Project; -import org.rostilos.codecrow.core.model.project.ProjectVcsConnectionBinding; -import org.rostilos.codecrow.core.model.vcs.EVcsConnectionType; -import org.rostilos.codecrow.core.model.vcs.EVcsProvider; import org.rostilos.codecrow.core.model.vcs.VcsConnection; -import org.rostilos.codecrow.core.model.vcs.VcsRepoBinding; -import org.rostilos.codecrow.core.model.vcs.config.cloud.BitbucketCloudConfig; -import org.rostilos.codecrow.pipelineagent.generic.handler.CommentCommandWebhookHandler.CommentCommandProcessor; -import org.rostilos.codecrow.pipelineagent.generic.webhook.WebhookPayload; -import org.rostilos.codecrow.pipelineagent.generic.webhook.handler.WebhookHandler.WebhookResult; +import org.rostilos.codecrow.pipelineagent.webhookhandler.CommentCommandWebhookHandler.CommentCommandProcessor; +import org.rostilos.codecrow.pipelineagent.dto.webhook.WebhookPayload; +import org.rostilos.codecrow.pipelineagent.webhookhandler.WebhookHandler.WebhookResult; import org.rostilos.codecrow.security.oauth.TokenEncryptionService; +import org.rostilos.codecrow.vcsclient.utils.VcsConnectionCredentialsExtractor; +import org.rostilos.codecrow.vcsclient.utils.VcsConnectionCredentialsExtractor.VcsConnectionCredentials; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.stereotype.Component; @@ -44,6 +41,7 @@ public class ReviewCommandProcessor implements CommentCommandProcessor { private final AiCommandClient aiCommandClient; private final TokenEncryptionService tokenEncryptionService; + private final VcsConnectionCredentialsExtractor credentialsExtractor; public ReviewCommandProcessor( AiCommandClient aiCommandClient, @@ -51,6 +49,7 @@ public ReviewCommandProcessor( ) { this.aiCommandClient = aiCommandClient; this.tokenEncryptionService = tokenEncryptionService; + this.credentialsExtractor = new VcsConnectionCredentialsExtractor(tokenEncryptionService); } @Override @@ -157,34 +156,9 @@ private ReviewRequest buildReviewRequest(Project project, WebhookPayload payload AIConnection aiConnection = project.getAiBinding().getAiConnection(); String decryptedApiKey = tokenEncryptionService.decrypt(aiConnection.getApiKeyEncrypted()); - // Get VCS credentials + // Get VCS credentials using centralized extractor VcsConnection vcsConnection = vcsInfo.vcsConnection(); - String oAuthClient = null; - String oAuthSecret = null; - String accessToken = null; - - if (vcsConnection.getConnectionType() == EVcsConnectionType.OAUTH_MANUAL) { - if (vcsConnection.getProviderType() == EVcsProvider.BITBUCKET_CLOUD) { - BitbucketCloudConfig config = (BitbucketCloudConfig) vcsConnection.getConfiguration(); - oAuthClient = tokenEncryptionService.decrypt(config.oAuthKey()); - oAuthSecret = tokenEncryptionService.decrypt(config.oAuthToken()); - } - } else if (vcsConnection.getConnectionType() == EVcsConnectionType.APP) { - accessToken = tokenEncryptionService.decrypt(vcsConnection.getAccessToken()); - } else if (vcsConnection.getConnectionType() == EVcsConnectionType.PERSONAL_TOKEN || - vcsConnection.getConnectionType() == EVcsConnectionType.REPOSITORY_TOKEN) { - // For GitLab personal/repository tokens, get the token from config - if (vcsConnection.getConfiguration() instanceof org.rostilos.codecrow.core.model.vcs.config.gitlab.GitLabConfig gitLabConfig) { - accessToken = gitLabConfig.accessToken(); - } - } - - // Determine VCS provider string - String vcsProvider = switch (vcsConnection.getProviderType()) { - case GITHUB -> "github"; - case GITLAB -> "gitlab"; - default -> "bitbucket_cloud"; - }; + VcsConnectionCredentials credentials = credentialsExtractor.extractCredentials(vcsConnection); Long prId = payload.pullRequestId() != null ? Long.parseLong(payload.pullRequestId()) @@ -203,11 +177,11 @@ private ReviewRequest buildReviewRequest(Project project, WebhookPayload payload payload.sourceBranch(), payload.targetBranch(), payload.commitHash(), - oAuthClient, - oAuthSecret, - accessToken, + credentials.oAuthClient(), + credentials.oAuthSecret(), + credentials.accessToken(), aiConnection.getTokenLimitation(), - vcsProvider + credentials.vcsProviderString() ); } catch (GeneralSecurityException e) { @@ -222,21 +196,13 @@ private ReviewRequest buildReviewRequest(Project project, WebhookPayload payload private record VcsInfo(VcsConnection vcsConnection, String workspace, String repoSlug) {} /** - * Get VCS connection info from project. + * Get VCS connection info from project using unified accessor. */ private VcsInfo getVcsInfo(Project project) { - // Try VcsBinding first (legacy) - ProjectVcsConnectionBinding vcsBinding = project.getVcsBinding(); - if (vcsBinding != null && vcsBinding.getVcsConnection() != null) { - return new VcsInfo(vcsBinding.getVcsConnection(), vcsBinding.getWorkspace(), vcsBinding.getRepoSlug()); + var vcsInfo = project.getEffectiveVcsRepoInfo(); + if (vcsInfo != null && vcsInfo.getVcsConnection() != null) { + return new VcsInfo(vcsInfo.getVcsConnection(), vcsInfo.getRepoWorkspace(), vcsInfo.getRepoSlug()); } - - // Try VcsRepoBinding (new style) - VcsRepoBinding repoBinding = project.getVcsRepoBinding(); - if (repoBinding != null && repoBinding.getVcsConnection() != null) { - return new VcsInfo(repoBinding.getVcsConnection(), repoBinding.getExternalNamespace(), repoBinding.getExternalRepoSlug()); - } - return null; } } diff --git a/java-ecosystem/services/pipeline-agent/src/main/java/org/rostilos/codecrow/pipelineagent/generic/handler/processor/SummarizeCommandProcessor.java b/java-ecosystem/services/pipeline-agent/src/main/java/org/rostilos/codecrow/pipelineagent/processor/command/SummarizeCommandProcessor.java similarity index 83% rename from java-ecosystem/services/pipeline-agent/src/main/java/org/rostilos/codecrow/pipelineagent/generic/handler/processor/SummarizeCommandProcessor.java rename to java-ecosystem/services/pipeline-agent/src/main/java/org/rostilos/codecrow/pipelineagent/processor/command/SummarizeCommandProcessor.java index 1b8a7ef2..815141b6 100644 --- a/java-ecosystem/services/pipeline-agent/src/main/java/org/rostilos/codecrow/pipelineagent/generic/handler/processor/SummarizeCommandProcessor.java +++ b/java-ecosystem/services/pipeline-agent/src/main/java/org/rostilos/codecrow/pipelineagent/processor/command/SummarizeCommandProcessor.java @@ -1,24 +1,22 @@ -package org.rostilos.codecrow.pipelineagent.generic.handler.processor; +package org.rostilos.codecrow.pipelineagent.processor.command; -import org.rostilos.codecrow.analysisengine.client.AiCommandClient; -import org.rostilos.codecrow.analysisengine.client.AiCommandClient.SummarizeRequest; -import org.rostilos.codecrow.analysisengine.client.AiCommandClient.SummarizeResult; +import org.rostilos.codecrow.analysisengine.aiclient.AiCommandClient; +import org.rostilos.codecrow.analysisengine.aiclient.AiCommandClient.SummarizeRequest; +import org.rostilos.codecrow.analysisengine.aiclient.AiCommandClient.SummarizeResult; import org.rostilos.codecrow.core.model.ai.AIConnection; import org.rostilos.codecrow.core.model.codeanalysis.PrSummarizeCache; import org.rostilos.codecrow.core.model.project.Project; -import org.rostilos.codecrow.core.model.project.ProjectVcsConnectionBinding; -import org.rostilos.codecrow.core.model.vcs.EVcsConnectionType; import org.rostilos.codecrow.core.model.vcs.EVcsProvider; import org.rostilos.codecrow.core.model.vcs.VcsConnection; -import org.rostilos.codecrow.core.model.vcs.VcsRepoBinding; -import org.rostilos.codecrow.core.model.vcs.config.cloud.BitbucketCloudConfig; import org.rostilos.codecrow.core.persistence.repository.codeanalysis.PrSummarizeCacheRepository; -import org.rostilos.codecrow.pipelineagent.generic.handler.CommentCommandWebhookHandler.CommentCommandProcessor; -import org.rostilos.codecrow.pipelineagent.generic.webhook.WebhookPayload; -import org.rostilos.codecrow.pipelineagent.generic.webhook.handler.WebhookHandler.WebhookResult; +import org.rostilos.codecrow.pipelineagent.webhookhandler.CommentCommandWebhookHandler.CommentCommandProcessor; +import org.rostilos.codecrow.pipelineagent.dto.webhook.WebhookPayload; +import org.rostilos.codecrow.pipelineagent.webhookhandler.WebhookHandler.WebhookResult; import org.rostilos.codecrow.analysisengine.service.vcs.VcsReportingService; import org.rostilos.codecrow.analysisengine.service.vcs.VcsServiceFactory; import org.rostilos.codecrow.security.oauth.TokenEncryptionService; +import org.rostilos.codecrow.vcsclient.utils.VcsConnectionCredentialsExtractor; +import org.rostilos.codecrow.vcsclient.utils.VcsConnectionCredentialsExtractor.VcsConnectionCredentials; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.stereotype.Component; @@ -54,6 +52,7 @@ public class SummarizeCommandProcessor implements CommentCommandProcessor { private final PrSummarizeCacheRepository summarizeCacheRepository; private final AiCommandClient aiCommandClient; private final TokenEncryptionService tokenEncryptionService; + private final VcsConnectionCredentialsExtractor credentialsExtractor; public SummarizeCommandProcessor( VcsServiceFactory vcsServiceFactory, @@ -65,6 +64,7 @@ public SummarizeCommandProcessor( this.summarizeCacheRepository = summarizeCacheRepository; this.aiCommandClient = aiCommandClient; this.tokenEncryptionService = tokenEncryptionService; + this.credentialsExtractor = new VcsConnectionCredentialsExtractor(tokenEncryptionService); } @Override @@ -163,11 +163,10 @@ public WebhookResult process( } private EVcsProvider getVcsProvider(Project project) { - if (project.getVcsBinding() != null && project.getVcsBinding().getVcsConnection() != null) { - return project.getVcsBinding().getVcsConnection().getProviderType(); - } - if (project.getVcsRepoBinding() != null && project.getVcsRepoBinding().getVcsConnection() != null) { - return project.getVcsRepoBinding().getVcsConnection().getProviderType(); + // Use unified method to get effective VCS connection + var vcsConnection = project.getEffectiveVcsConnection(); + if (vcsConnection != null) { + return vcsConnection.getProviderType(); } throw new IllegalStateException("No VCS connection configured for project: " + project.getId()); } @@ -272,34 +271,9 @@ private SummarizeRequest buildSummarizeRequest( AIConnection aiConnection = project.getAiBinding().getAiConnection(); String decryptedApiKey = tokenEncryptionService.decrypt(aiConnection.getApiKeyEncrypted()); - // Get VCS credentials + // Get VCS credentials using centralized extractor VcsConnection vcsConnection = vcsInfo.vcsConnection(); - String oAuthClient = null; - String oAuthSecret = null; - String accessToken = null; - - if (vcsConnection.getConnectionType() == EVcsConnectionType.OAUTH_MANUAL) { - if (vcsConnection.getProviderType() == EVcsProvider.BITBUCKET_CLOUD) { - BitbucketCloudConfig config = (BitbucketCloudConfig) vcsConnection.getConfiguration(); - oAuthClient = tokenEncryptionService.decrypt(config.oAuthKey()); - oAuthSecret = tokenEncryptionService.decrypt(config.oAuthToken()); - } - } else if (vcsConnection.getConnectionType() == EVcsConnectionType.APP) { - accessToken = tokenEncryptionService.decrypt(vcsConnection.getAccessToken()); - } else if (vcsConnection.getConnectionType() == EVcsConnectionType.PERSONAL_TOKEN || - vcsConnection.getConnectionType() == EVcsConnectionType.REPOSITORY_TOKEN) { - // For GitLab personal/repository tokens, get the token from config - if (vcsConnection.getConfiguration() instanceof org.rostilos.codecrow.core.model.vcs.config.gitlab.GitLabConfig gitLabConfig) { - accessToken = gitLabConfig.accessToken(); - } - } - - // Determine VCS provider string - String vcsProvider = switch (vcsConnection.getProviderType()) { - case GITHUB -> "github"; - case GITLAB -> "gitlab"; - default -> "bitbucket_cloud"; - }; + VcsConnectionCredentials credentials = credentialsExtractor.extractCredentials(vcsConnection); return new SummarizeRequest( project.getId(), @@ -314,12 +288,12 @@ private SummarizeRequest buildSummarizeRequest( payload.sourceBranch(), payload.targetBranch(), payload.commitHash(), - oAuthClient, - oAuthSecret, - accessToken, + credentials.oAuthClient(), + credentials.oAuthSecret(), + credentials.accessToken(), diagramType == PrSummarizeCache.DiagramType.MERMAID, aiConnection.getTokenLimitation(), - vcsProvider + credentials.vcsProviderString() ); } catch (GeneralSecurityException e) { @@ -334,19 +308,13 @@ private SummarizeRequest buildSummarizeRequest( private record VcsInfo(VcsConnection vcsConnection, String workspace, String repoSlug) {} /** - * Get VCS info from the project. + * Get VCS info from the project using unified accessor. */ private VcsInfo getVcsInfo(Project project) { - ProjectVcsConnectionBinding vcsBinding = project.getVcsBinding(); - if (vcsBinding != null && vcsBinding.getVcsConnection() != null) { - return new VcsInfo(vcsBinding.getVcsConnection(), vcsBinding.getWorkspace(), vcsBinding.getRepoSlug()); + var vcsInfo = project.getEffectiveVcsRepoInfo(); + if (vcsInfo != null && vcsInfo.getVcsConnection() != null) { + return new VcsInfo(vcsInfo.getVcsConnection(), vcsInfo.getRepoWorkspace(), vcsInfo.getRepoSlug()); } - - VcsRepoBinding repoBinding = project.getVcsRepoBinding(); - if (repoBinding != null && repoBinding.getVcsConnection() != null) { - return new VcsInfo(repoBinding.getVcsConnection(), repoBinding.getExternalNamespace(), repoBinding.getExternalRepoSlug()); - } - return null; } diff --git a/java-ecosystem/services/pipeline-agent/src/main/java/org/rostilos/codecrow/pipelineagent/generic/service/CommandAuthorizationService.java b/java-ecosystem/services/pipeline-agent/src/main/java/org/rostilos/codecrow/pipelineagent/service/CommandAuthorizationService.java similarity index 94% rename from java-ecosystem/services/pipeline-agent/src/main/java/org/rostilos/codecrow/pipelineagent/generic/service/CommandAuthorizationService.java rename to java-ecosystem/services/pipeline-agent/src/main/java/org/rostilos/codecrow/pipelineagent/service/CommandAuthorizationService.java index 27dbe56a..143f7573 100644 --- a/java-ecosystem/services/pipeline-agent/src/main/java/org/rostilos/codecrow/pipelineagent/generic/service/CommandAuthorizationService.java +++ b/java-ecosystem/services/pipeline-agent/src/main/java/org/rostilos/codecrow/pipelineagent/service/CommandAuthorizationService.java @@ -1,4 +1,4 @@ -package org.rostilos.codecrow.pipelineagent.generic.service; +package org.rostilos.codecrow.pipelineagent.service; import org.rostilos.codecrow.core.model.project.AllowedCommandUser; import org.rostilos.codecrow.core.model.project.Project; @@ -7,7 +7,7 @@ import org.rostilos.codecrow.core.model.project.config.ProjectConfig.CommentCommandsConfig; import org.rostilos.codecrow.core.model.vcs.EVcsProvider; import org.rostilos.codecrow.core.persistence.repository.project.AllowedCommandUserRepository; -import org.rostilos.codecrow.pipelineagent.generic.webhook.WebhookPayload; +import org.rostilos.codecrow.pipelineagent.dto.webhook.WebhookPayload; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.stereotype.Service; @@ -196,15 +196,9 @@ public AllowedCommandUser setUserEnabled(Long projectId, String vcsUserId, boole * Get the VCS provider for a project. */ private EVcsProvider getVcsProvider(Project project) { - if (project.getVcsBinding() != null) { - if (project.getVcsBinding().getVcsProvider() != null) { - return project.getVcsBinding().getVcsProvider(); - } - if (project.getVcsBinding().getVcsConnection() != null) { - return project.getVcsBinding().getVcsConnection().getProviderType(); - } - } - return null; + // Use unified method to get effective VCS connection + var vcsConnection = project.getEffectiveVcsConnection(); + return vcsConnection != null ? vcsConnection.getProviderType() : null; } public record AuthorizationResult(boolean authorized, String reason) { diff --git a/java-ecosystem/services/pipeline-agent/src/main/java/org/rostilos/codecrow/pipelineagent/generic/service/CommentCommandRateLimitService.java b/java-ecosystem/services/pipeline-agent/src/main/java/org/rostilos/codecrow/pipelineagent/service/CommentCommandRateLimitService.java similarity index 99% rename from java-ecosystem/services/pipeline-agent/src/main/java/org/rostilos/codecrow/pipelineagent/generic/service/CommentCommandRateLimitService.java rename to java-ecosystem/services/pipeline-agent/src/main/java/org/rostilos/codecrow/pipelineagent/service/CommentCommandRateLimitService.java index 82aff12d..0f6ebaed 100644 --- a/java-ecosystem/services/pipeline-agent/src/main/java/org/rostilos/codecrow/pipelineagent/generic/service/CommentCommandRateLimitService.java +++ b/java-ecosystem/services/pipeline-agent/src/main/java/org/rostilos/codecrow/pipelineagent/service/CommentCommandRateLimitService.java @@ -1,4 +1,4 @@ -package org.rostilos.codecrow.pipelineagent.generic.service; +package org.rostilos.codecrow.pipelineagent.service; import org.rostilos.codecrow.core.model.analysis.CommentCommandRateLimit; import org.rostilos.codecrow.core.model.project.Project; diff --git a/java-ecosystem/services/pipeline-agent/src/main/java/org/rostilos/codecrow/pipelineagent/generic/service/PipelineJobService.java b/java-ecosystem/services/pipeline-agent/src/main/java/org/rostilos/codecrow/pipelineagent/service/PipelineJobService.java similarity index 98% rename from java-ecosystem/services/pipeline-agent/src/main/java/org/rostilos/codecrow/pipelineagent/generic/service/PipelineJobService.java rename to java-ecosystem/services/pipeline-agent/src/main/java/org/rostilos/codecrow/pipelineagent/service/PipelineJobService.java index c7e5bc86..95ad1e25 100644 --- a/java-ecosystem/services/pipeline-agent/src/main/java/org/rostilos/codecrow/pipelineagent/generic/service/PipelineJobService.java +++ b/java-ecosystem/services/pipeline-agent/src/main/java/org/rostilos/codecrow/pipelineagent/service/PipelineJobService.java @@ -1,4 +1,4 @@ -package org.rostilos.codecrow.pipelineagent.generic.service; +package org.rostilos.codecrow.pipelineagent.service; import org.rostilos.codecrow.core.model.job.Job; import org.rostilos.codecrow.core.model.job.JobLogLevel; @@ -7,7 +7,7 @@ import org.rostilos.codecrow.core.model.user.User; import org.rostilos.codecrow.core.persistence.repository.project.ProjectRepository; import org.rostilos.codecrow.core.service.JobService; -import org.rostilos.codecrow.analysisengine.processor.WebhookProcessor; +import org.rostilos.codecrow.pipelineagent.processor.PipelineActionProcessor; import org.rostilos.codecrow.analysisengine.dto.request.processor.BranchProcessRequest; import org.rostilos.codecrow.analysisengine.dto.request.processor.PrProcessRequest; import org.rostilos.codecrow.core.service.AnalysisJobService; @@ -168,9 +168,9 @@ public void logToJob(Job job, JobLogLevel level, String state, String message, M * Create an EventConsumer that logs to both the streaming response and the job. * Uses the generic WebhookProcessor.EventConsumer interface. */ - public WebhookProcessor.EventConsumer createDualConsumer( + public PipelineActionProcessor.EventConsumer createDualConsumer( Job job, - WebhookProcessor.EventConsumer streamConsumer + PipelineActionProcessor.EventConsumer streamConsumer ) { return event -> { // Forward to stream consumer diff --git a/java-ecosystem/services/pipeline-agent/src/main/java/org/rostilos/codecrow/pipelineagent/bitbucket/service/BitbucketAiClientService.java b/java-ecosystem/services/pipeline-agent/src/main/java/org/rostilos/codecrow/pipelineagent/service/bitbucket/BitbucketAiClientService.java similarity index 81% rename from java-ecosystem/services/pipeline-agent/src/main/java/org/rostilos/codecrow/pipelineagent/bitbucket/service/BitbucketAiClientService.java rename to java-ecosystem/services/pipeline-agent/src/main/java/org/rostilos/codecrow/pipelineagent/service/bitbucket/BitbucketAiClientService.java index cd648813..297eeaf4 100644 --- a/java-ecosystem/services/pipeline-agent/src/main/java/org/rostilos/codecrow/pipelineagent/bitbucket/service/BitbucketAiClientService.java +++ b/java-ecosystem/services/pipeline-agent/src/main/java/org/rostilos/codecrow/pipelineagent/service/bitbucket/BitbucketAiClientService.java @@ -1,4 +1,4 @@ -package org.rostilos.codecrow.pipelineagent.bitbucket.service; +package org.rostilos.codecrow.pipelineagent.service.bitbucket; import okhttp3.OkHttpClient; import org.rostilos.codecrow.core.model.ai.AIConnection; @@ -6,14 +6,9 @@ import org.rostilos.codecrow.core.model.codeanalysis.AnalysisType; import org.rostilos.codecrow.core.model.codeanalysis.CodeAnalysis; import org.rostilos.codecrow.core.model.project.Project; -import org.rostilos.codecrow.core.model.project.ProjectVcsConnectionBinding; -import org.rostilos.codecrow.core.model.vcs.EVcsConnectionType; import org.rostilos.codecrow.core.model.vcs.EVcsProvider; import org.rostilos.codecrow.core.model.vcs.VcsConnection; -import org.rostilos.codecrow.core.model.vcs.VcsRepoBinding; -import org.rostilos.codecrow.core.model.vcs.config.cloud.BitbucketCloudConfig; -import org.rostilos.codecrow.pipelineagent.bitbucket.dto.request.ai.AiBranchAnalysisRequest; -import org.rostilos.codecrow.pipelineagent.bitbucket.dto.request.ai.AiPullRequestAnalysisRequest; +import org.rostilos.codecrow.analysisengine.dto.request.ai.AiAnalysisRequestImpl; import org.rostilos.codecrow.analysisengine.dto.request.processor.AnalysisProcessRequest; import org.rostilos.codecrow.analysisengine.dto.request.processor.BranchProcessRequest; import org.rostilos.codecrow.analysisengine.dto.request.processor.PrProcessRequest; @@ -26,6 +21,8 @@ import org.rostilos.codecrow.vcsclient.bitbucket.cloud.actions.GetCommitRangeDiffAction; import org.rostilos.codecrow.vcsclient.bitbucket.cloud.actions.GetPullRequestAction; import org.rostilos.codecrow.vcsclient.bitbucket.cloud.actions.GetPullRequestDiffAction; +import org.rostilos.codecrow.vcsclient.utils.VcsConnectionCredentialsExtractor; +import org.rostilos.codecrow.vcsclient.utils.VcsConnectionCredentialsExtractor.VcsConnectionCredentials; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.stereotype.Service; @@ -54,6 +51,7 @@ public class BitbucketAiClientService implements VcsAiClientService { private final TokenEncryptionService tokenEncryptionService; private final VcsClientProvider vcsClientProvider; + private final VcsConnectionCredentialsExtractor credentialsExtractor; public BitbucketAiClientService( TokenEncryptionService tokenEncryptionService, @@ -61,6 +59,7 @@ public BitbucketAiClientService( ) { this.tokenEncryptionService = tokenEncryptionService; this.vcsClientProvider = vcsClientProvider; + this.credentialsExtractor = new VcsConnectionCredentialsExtractor(tokenEncryptionService); } @Override @@ -69,23 +68,18 @@ public EVcsProvider getProvider() { } /** - * Helper class to hold VCS connection info from either ProjectVcsConnectionBinding or VcsRepoBinding. + * Helper class to hold VCS connection info. */ private record VcsInfo(VcsConnection vcsConnection, String workspace, String repoSlug) {} /** - * Get VCS info from the project, using VcsRepoBinding as fallback if ProjectVcsConnectionBinding is null. + * Get VCS info from the project using the unified accessor. */ private VcsInfo getVcsInfo(Project project) { - ProjectVcsConnectionBinding vcsBinding = project.getVcsBinding(); - if (vcsBinding != null && vcsBinding.getVcsConnection() != null) { - return new VcsInfo(vcsBinding.getVcsConnection(), vcsBinding.getWorkspace(), vcsBinding.getRepoSlug()); - } - - VcsRepoBinding repoBinding = project.getVcsRepoBinding(); - if (repoBinding != null && repoBinding.getVcsConnection() != null) { - log.debug("Using VcsRepoBinding for project {} as fallback", project.getId()); - return new VcsInfo(repoBinding.getVcsConnection(), repoBinding.getExternalNamespace(), repoBinding.getExternalRepoSlug()); + // Use unified method that prefers VcsRepoBinding over legacy vcsBinding + var vcsInfo = project.getEffectiveVcsRepoInfo(); + if (vcsInfo != null && vcsInfo.getVcsConnection() != null) { + return new VcsInfo(vcsInfo.getVcsConnection(), vcsInfo.getRepoWorkspace(), vcsInfo.getRepoSlug()); } throw new IllegalStateException("No VCS connection configured for project: " + project.getId()); @@ -215,7 +209,7 @@ public AiAnalysisRequest buildPrAnalysisRequest( // Continue without metadata - RAG will use fallback } - var builder = AiPullRequestAnalysisRequest.builder() + var builder = AiAnalysisRequestImpl.builder() .withProjectId(project.getId()) .withPullRequestId(request.getPullRequestId()) .withProjectAiConnection(aiConnection) @@ -286,7 +280,7 @@ public AiAnalysisRequest buildBranchAnalysisRequest( AIConnection aiConnection = project.getAiBinding().getAiConnection(); AIConnection projectAiConnection = project.getAiBinding().getAiConnection(); - var builder = AiBranchAnalysisRequest.builder() + var builder = AiAnalysisRequestImpl.builder() .withProjectId(project.getId()) .withPullRequestId(null) .withProjectAiConnection(aiConnection) @@ -296,8 +290,8 @@ public AiAnalysisRequest buildBranchAnalysisRequest( .withPreviousAnalysisData(previousAnalysis) .withMaxAllowedTokens(aiConnection.getTokenLimitation()) .withAnalysisType(request.getAnalysisType()) - .withBranch(request.getTargetBranchName()) - .withCommitHash(request.getCommitHash()) + .withTargetBranchName(request.getTargetBranchName()) + .withCurrentCommitHash(request.getCommitHash()) .withProjectMetadata(project.getWorkspace().getName(), project.getNamespace()) .withVcsProvider("bitbucket_cloud"); @@ -311,33 +305,18 @@ public AiAnalysisRequest buildBranchAnalysisRequest( * For OAUTH_MANUAL: uses OAuth consumer key/secret from config * For APP: uses bearer token directly via accessToken field */ - private void addVcsCredentials(AiPullRequestAnalysisRequest.Builder builder, VcsConnection connection) - throws GeneralSecurityException { - if (connection.getConnectionType() == EVcsConnectionType.APP && connection.getAccessToken() != null) { - String accessToken = tokenEncryptionService.decrypt(connection.getAccessToken()); - builder.withAccessToken(accessToken); - } else if (connection.getConfiguration() instanceof BitbucketCloudConfig config) { - builder.withProjectVcsConnectionCredentials( - tokenEncryptionService.decrypt(config.oAuthKey()), - tokenEncryptionService.decrypt(config.oAuthToken()) - ); - } else { - log.warn("Unknown connection type for VCS credentials: {}", connection.getConnectionType()); - } - } - - private void addVcsCredentials(AiBranchAnalysisRequest.Builder builder, VcsConnection connection) + private void addVcsCredentials(AiAnalysisRequestImpl.Builder builder, VcsConnection connection) throws GeneralSecurityException { - if (connection.getConnectionType() == EVcsConnectionType.APP && connection.getAccessToken() != null) { - String accessToken = tokenEncryptionService.decrypt(connection.getAccessToken()); - builder.withAccessToken(accessToken); - } else if (connection.getConfiguration() instanceof BitbucketCloudConfig config) { + VcsConnectionCredentials credentials = credentialsExtractor.extractCredentials(connection); + if (VcsConnectionCredentialsExtractor.hasAccessToken(credentials)) { + builder.withAccessToken(credentials.accessToken()); + } else if (VcsConnectionCredentialsExtractor.hasOAuthCredentials(credentials)) { builder.withProjectVcsConnectionCredentials( - tokenEncryptionService.decrypt(config.oAuthKey()), - tokenEncryptionService.decrypt(config.oAuthToken()) + credentials.oAuthClient(), + credentials.oAuthSecret() ); } else { - log.warn("Unknown connection type for VCS credentials: {}", connection.getConnectionType()); + log.warn("No credentials available for VCS connection type: {}", connection.getConnectionType()); } } } diff --git a/java-ecosystem/services/pipeline-agent/src/main/java/org/rostilos/codecrow/pipelineagent/bitbucket/service/BitbucketOperationsService.java b/java-ecosystem/services/pipeline-agent/src/main/java/org/rostilos/codecrow/pipelineagent/service/bitbucket/BitbucketOperationsService.java similarity index 97% rename from java-ecosystem/services/pipeline-agent/src/main/java/org/rostilos/codecrow/pipelineagent/bitbucket/service/BitbucketOperationsService.java rename to java-ecosystem/services/pipeline-agent/src/main/java/org/rostilos/codecrow/pipelineagent/service/bitbucket/BitbucketOperationsService.java index ffdeb57b..eafb4ec1 100644 --- a/java-ecosystem/services/pipeline-agent/src/main/java/org/rostilos/codecrow/pipelineagent/bitbucket/service/BitbucketOperationsService.java +++ b/java-ecosystem/services/pipeline-agent/src/main/java/org/rostilos/codecrow/pipelineagent/service/bitbucket/BitbucketOperationsService.java @@ -1,4 +1,4 @@ -package org.rostilos.codecrow.pipelineagent.bitbucket.service; +package org.rostilos.codecrow.pipelineagent.service.bitbucket; import okhttp3.OkHttpClient; import org.rostilos.codecrow.core.model.vcs.EVcsProvider; diff --git a/java-ecosystem/services/pipeline-agent/src/main/java/org/rostilos/codecrow/pipelineagent/bitbucket/service/BitbucketReportingService.java b/java-ecosystem/services/pipeline-agent/src/main/java/org/rostilos/codecrow/pipelineagent/service/bitbucket/BitbucketReportingService.java similarity index 96% rename from java-ecosystem/services/pipeline-agent/src/main/java/org/rostilos/codecrow/pipelineagent/bitbucket/service/BitbucketReportingService.java rename to java-ecosystem/services/pipeline-agent/src/main/java/org/rostilos/codecrow/pipelineagent/service/bitbucket/BitbucketReportingService.java index 9427b405..a88f0099 100644 --- a/java-ecosystem/services/pipeline-agent/src/main/java/org/rostilos/codecrow/pipelineagent/bitbucket/service/BitbucketReportingService.java +++ b/java-ecosystem/services/pipeline-agent/src/main/java/org/rostilos/codecrow/pipelineagent/service/bitbucket/BitbucketReportingService.java @@ -1,4 +1,4 @@ -package org.rostilos.codecrow.pipelineagent.bitbucket.service; +package org.rostilos.codecrow.pipelineagent.service.bitbucket; import okhttp3.OkHttpClient; import org.rostilos.codecrow.core.model.codeanalysis.CodeAnalysis; @@ -54,23 +54,23 @@ public EVcsProvider getProvider() { } /** - * Gets VcsRepoInfo from project, trying ProjectVcsConnectionBinding first, - * then falling back to VcsRepoBinding if not available. + * Gets VcsRepoInfo from project using the unified accessor. */ private VcsRepoInfo getVcsRepoInfo(Project project) { - // Try ProjectVcsConnectionBinding first (legacy path) - if (project.getVcsBinding() != null) { - return project.getVcsBinding(); + // Use unified method that prefers VcsRepoBinding over legacy vcsBinding + VcsRepoInfo vcsInfo = project.getEffectiveVcsRepoInfo(); + if (vcsInfo != null) { + return vcsInfo; } - // Fallback to VcsRepoBinding (APP-created projects) + // Fallback to repository lookup (shouldn't be needed normally) VcsRepoBinding vcsRepoBinding = vcsRepoBindingRepository.findByProject_Id(project.getId()) .orElseThrow(() -> new IllegalStateException( "No VCS binding found for project " + project.getId() + ". Neither ProjectVcsConnectionBinding nor VcsRepoBinding is configured." )); - log.debug("Using VcsRepoBinding fallback for project {}: {}/{}", + log.debug("Using VcsRepoBinding repository fallback for project {}: {}/{}", project.getId(), vcsRepoBinding.getRepoWorkspace(), vcsRepoBinding.getRepoSlug()); return vcsRepoBinding; diff --git a/java-ecosystem/services/pipeline-agent/src/main/java/org/rostilos/codecrow/pipelineagent/github/service/GitHubAiClientService.java b/java-ecosystem/services/pipeline-agent/src/main/java/org/rostilos/codecrow/pipelineagent/service/github/GitHubAiClientService.java similarity index 89% rename from java-ecosystem/services/pipeline-agent/src/main/java/org/rostilos/codecrow/pipelineagent/github/service/GitHubAiClientService.java rename to java-ecosystem/services/pipeline-agent/src/main/java/org/rostilos/codecrow/pipelineagent/service/github/GitHubAiClientService.java index b72e740e..0cfa2475 100644 --- a/java-ecosystem/services/pipeline-agent/src/main/java/org/rostilos/codecrow/pipelineagent/github/service/GitHubAiClientService.java +++ b/java-ecosystem/services/pipeline-agent/src/main/java/org/rostilos/codecrow/pipelineagent/service/github/GitHubAiClientService.java @@ -1,4 +1,4 @@ -package org.rostilos.codecrow.pipelineagent.github.service; +package org.rostilos.codecrow.pipelineagent.service.github; import com.fasterxml.jackson.databind.JsonNode; import okhttp3.OkHttpClient; @@ -6,12 +6,8 @@ import org.rostilos.codecrow.core.model.codeanalysis.AnalysisMode; import org.rostilos.codecrow.core.model.codeanalysis.CodeAnalysis; import org.rostilos.codecrow.core.model.project.Project; -import org.rostilos.codecrow.core.model.project.ProjectVcsConnectionBinding; -import org.rostilos.codecrow.core.model.vcs.EVcsConnectionType; import org.rostilos.codecrow.core.model.vcs.EVcsProvider; import org.rostilos.codecrow.core.model.vcs.VcsConnection; -import org.rostilos.codecrow.core.model.vcs.VcsRepoBinding; -import org.rostilos.codecrow.core.model.vcs.config.github.GitHubConfig; import org.rostilos.codecrow.analysisengine.dto.request.ai.AiAnalysisRequest; import org.rostilos.codecrow.analysisengine.dto.request.ai.AiAnalysisRequestImpl; import org.rostilos.codecrow.analysisengine.dto.request.processor.AnalysisProcessRequest; @@ -25,6 +21,8 @@ import org.rostilos.codecrow.vcsclient.github.actions.GetCommitRangeDiffAction; import org.rostilos.codecrow.vcsclient.github.actions.GetPullRequestAction; import org.rostilos.codecrow.vcsclient.github.actions.GetPullRequestDiffAction; +import org.rostilos.codecrow.vcsclient.utils.VcsConnectionCredentialsExtractor; +import org.rostilos.codecrow.vcsclient.utils.VcsConnectionCredentialsExtractor.VcsConnectionCredentials; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.stereotype.Service; @@ -52,6 +50,7 @@ public class GitHubAiClientService implements VcsAiClientService { private final TokenEncryptionService tokenEncryptionService; private final VcsClientProvider vcsClientProvider; + private final VcsConnectionCredentialsExtractor credentialsExtractor; public GitHubAiClientService( TokenEncryptionService tokenEncryptionService, @@ -59,6 +58,7 @@ public GitHubAiClientService( ) { this.tokenEncryptionService = tokenEncryptionService; this.vcsClientProvider = vcsClientProvider; + this.credentialsExtractor = new VcsConnectionCredentialsExtractor(tokenEncryptionService); } @Override @@ -69,15 +69,10 @@ public EVcsProvider getProvider() { private record VcsInfo(VcsConnection vcsConnection, String owner, String repoSlug) {} private VcsInfo getVcsInfo(Project project) { - ProjectVcsConnectionBinding vcsBinding = project.getVcsBinding(); - if (vcsBinding != null && vcsBinding.getVcsConnection() != null) { - return new VcsInfo(vcsBinding.getVcsConnection(), vcsBinding.getWorkspace(), vcsBinding.getRepoSlug()); - } - - VcsRepoBinding repoBinding = project.getVcsRepoBinding(); - if (repoBinding != null && repoBinding.getVcsConnection() != null) { - log.debug("Using VcsRepoBinding for project {} as fallback", project.getId()); - return new VcsInfo(repoBinding.getVcsConnection(), repoBinding.getExternalNamespace(), repoBinding.getExternalRepoSlug()); + // Use unified method that prefers VcsRepoBinding over legacy vcsBinding + var vcsInfo = project.getEffectiveVcsRepoInfo(); + if (vcsInfo != null && vcsInfo.getVcsConnection() != null) { + return new VcsInfo(vcsInfo.getVcsConnection(), vcsInfo.getRepoWorkspace(), vcsInfo.getRepoSlug()); } throw new IllegalStateException("No VCS connection configured for project: " + project.getId()); @@ -291,14 +286,11 @@ private AiAnalysisRequest buildBranchAnalysisRequest( private void addVcsCredentials(AiAnalysisRequestImpl.Builder builder, VcsConnection connection) throws GeneralSecurityException { - if (connection.getConnectionType() == EVcsConnectionType.APP && connection.getAccessToken() != null) { - String accessToken = tokenEncryptionService.decrypt(connection.getAccessToken()); - builder.withAccessToken(accessToken); - } else if (connection.getConnectionType() == EVcsConnectionType.PERSONAL_TOKEN && - connection.getConfiguration() instanceof GitHubConfig config) { - builder.withAccessToken(config.accessToken()); + VcsConnectionCredentials credentials = credentialsExtractor.extractCredentials(connection); + if (VcsConnectionCredentialsExtractor.hasAccessToken(credentials)) { + builder.withAccessToken(credentials.accessToken()); } else { - log.warn("Unknown connection type for VCS credentials: {}", connection.getConnectionType()); + log.warn("No access token available for VCS connection type: {}", connection.getConnectionType()); } } } diff --git a/java-ecosystem/services/pipeline-agent/src/main/java/org/rostilos/codecrow/pipelineagent/github/service/GitHubOperationsService.java b/java-ecosystem/services/pipeline-agent/src/main/java/org/rostilos/codecrow/pipelineagent/service/github/GitHubOperationsService.java similarity index 97% rename from java-ecosystem/services/pipeline-agent/src/main/java/org/rostilos/codecrow/pipelineagent/github/service/GitHubOperationsService.java rename to java-ecosystem/services/pipeline-agent/src/main/java/org/rostilos/codecrow/pipelineagent/service/github/GitHubOperationsService.java index 2084bbb3..5a578c73 100644 --- a/java-ecosystem/services/pipeline-agent/src/main/java/org/rostilos/codecrow/pipelineagent/github/service/GitHubOperationsService.java +++ b/java-ecosystem/services/pipeline-agent/src/main/java/org/rostilos/codecrow/pipelineagent/service/github/GitHubOperationsService.java @@ -1,4 +1,4 @@ -package org.rostilos.codecrow.pipelineagent.github.service; +package org.rostilos.codecrow.pipelineagent.service.github; import okhttp3.OkHttpClient; import org.rostilos.codecrow.core.model.vcs.EVcsProvider; diff --git a/java-ecosystem/services/pipeline-agent/src/main/java/org/rostilos/codecrow/pipelineagent/github/service/GitHubReportingService.java b/java-ecosystem/services/pipeline-agent/src/main/java/org/rostilos/codecrow/pipelineagent/service/github/GitHubReportingService.java similarity index 96% rename from java-ecosystem/services/pipeline-agent/src/main/java/org/rostilos/codecrow/pipelineagent/github/service/GitHubReportingService.java rename to java-ecosystem/services/pipeline-agent/src/main/java/org/rostilos/codecrow/pipelineagent/service/github/GitHubReportingService.java index ea622d77..3dfc7f42 100644 --- a/java-ecosystem/services/pipeline-agent/src/main/java/org/rostilos/codecrow/pipelineagent/github/service/GitHubReportingService.java +++ b/java-ecosystem/services/pipeline-agent/src/main/java/org/rostilos/codecrow/pipelineagent/service/github/GitHubReportingService.java @@ -1,4 +1,4 @@ -package org.rostilos.codecrow.pipelineagent.github.service; +package org.rostilos.codecrow.pipelineagent.service.github; import okhttp3.OkHttpClient; import org.rostilos.codecrow.core.model.codeanalysis.CodeAnalysis; @@ -54,17 +54,20 @@ public EVcsProvider getProvider() { } private VcsRepoInfo getVcsRepoInfo(Project project) { - if (project.getVcsBinding() != null) { - return project.getVcsBinding(); + // Use unified method that prefers VcsRepoBinding over legacy vcsBinding + VcsRepoInfo vcsInfo = project.getEffectiveVcsRepoInfo(); + if (vcsInfo != null) { + return vcsInfo; } + // Fallback to repository lookup (shouldn't be needed normally) VcsRepoBinding vcsRepoBinding = vcsRepoBindingRepository.findByProject_Id(project.getId()) .orElseThrow(() -> new IllegalStateException( "No VCS binding found for project " + project.getId() + ". Neither ProjectVcsConnectionBinding nor VcsRepoBinding is configured." )); - log.debug("Using VcsRepoBinding fallback for project {}: {}/{}", + log.debug("Using VcsRepoBinding repository fallback for project {}: {}/{}", project.getId(), vcsRepoBinding.getRepoWorkspace(), vcsRepoBinding.getRepoSlug()); return vcsRepoBinding; diff --git a/java-ecosystem/services/pipeline-agent/src/main/java/org/rostilos/codecrow/pipelineagent/gitlab/service/GitLabAiClientService.java b/java-ecosystem/services/pipeline-agent/src/main/java/org/rostilos/codecrow/pipelineagent/service/gitlab/GitLabAiClientService.java similarity index 88% rename from java-ecosystem/services/pipeline-agent/src/main/java/org/rostilos/codecrow/pipelineagent/gitlab/service/GitLabAiClientService.java rename to java-ecosystem/services/pipeline-agent/src/main/java/org/rostilos/codecrow/pipelineagent/service/gitlab/GitLabAiClientService.java index b6747af9..00e2d4d0 100644 --- a/java-ecosystem/services/pipeline-agent/src/main/java/org/rostilos/codecrow/pipelineagent/gitlab/service/GitLabAiClientService.java +++ b/java-ecosystem/services/pipeline-agent/src/main/java/org/rostilos/codecrow/pipelineagent/service/gitlab/GitLabAiClientService.java @@ -1,4 +1,4 @@ -package org.rostilos.codecrow.pipelineagent.gitlab.service; +package org.rostilos.codecrow.pipelineagent.service.gitlab; import com.fasterxml.jackson.databind.JsonNode; import okhttp3.OkHttpClient; @@ -6,12 +6,8 @@ import org.rostilos.codecrow.core.model.codeanalysis.AnalysisMode; import org.rostilos.codecrow.core.model.codeanalysis.CodeAnalysis; import org.rostilos.codecrow.core.model.project.Project; -import org.rostilos.codecrow.core.model.project.ProjectVcsConnectionBinding; -import org.rostilos.codecrow.core.model.vcs.EVcsConnectionType; import org.rostilos.codecrow.core.model.vcs.EVcsProvider; import org.rostilos.codecrow.core.model.vcs.VcsConnection; -import org.rostilos.codecrow.core.model.vcs.VcsRepoBinding; -import org.rostilos.codecrow.core.model.vcs.config.gitlab.GitLabConfig; import org.rostilos.codecrow.analysisengine.dto.request.ai.AiAnalysisRequest; import org.rostilos.codecrow.analysisengine.dto.request.ai.AiAnalysisRequestImpl; import org.rostilos.codecrow.analysisengine.dto.request.processor.AnalysisProcessRequest; @@ -25,6 +21,8 @@ import org.rostilos.codecrow.vcsclient.gitlab.actions.GetCommitRangeDiffAction; import org.rostilos.codecrow.vcsclient.gitlab.actions.GetMergeRequestAction; import org.rostilos.codecrow.vcsclient.gitlab.actions.GetMergeRequestDiffAction; +import org.rostilos.codecrow.vcsclient.utils.VcsConnectionCredentialsExtractor; +import org.rostilos.codecrow.vcsclient.utils.VcsConnectionCredentialsExtractor.VcsConnectionCredentials; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.stereotype.Service; @@ -52,6 +50,7 @@ public class GitLabAiClientService implements VcsAiClientService { private final TokenEncryptionService tokenEncryptionService; private final VcsClientProvider vcsClientProvider; + private final VcsConnectionCredentialsExtractor credentialsExtractor; public GitLabAiClientService( TokenEncryptionService tokenEncryptionService, @@ -59,6 +58,7 @@ public GitLabAiClientService( ) { this.tokenEncryptionService = tokenEncryptionService; this.vcsClientProvider = vcsClientProvider; + this.credentialsExtractor = new VcsConnectionCredentialsExtractor(tokenEncryptionService); } @Override @@ -69,15 +69,10 @@ public EVcsProvider getProvider() { private record VcsInfo(VcsConnection vcsConnection, String namespace, String repoSlug) {} private VcsInfo getVcsInfo(Project project) { - ProjectVcsConnectionBinding vcsBinding = project.getVcsBinding(); - if (vcsBinding != null && vcsBinding.getVcsConnection() != null) { - return new VcsInfo(vcsBinding.getVcsConnection(), vcsBinding.getWorkspace(), vcsBinding.getRepoSlug()); - } - - VcsRepoBinding repoBinding = project.getVcsRepoBinding(); - if (repoBinding != null && repoBinding.getVcsConnection() != null) { - log.debug("Using VcsRepoBinding for project {} as fallback", project.getId()); - return new VcsInfo(repoBinding.getVcsConnection(), repoBinding.getExternalNamespace(), repoBinding.getExternalRepoSlug()); + // Use unified method that prefers VcsRepoBinding over legacy vcsBinding + var vcsInfo = project.getEffectiveVcsRepoInfo(); + if (vcsInfo != null && vcsInfo.getVcsConnection() != null) { + return new VcsInfo(vcsInfo.getVcsConnection(), vcsInfo.getRepoWorkspace(), vcsInfo.getRepoSlug()); } throw new IllegalStateException("No VCS connection configured for project: " + project.getId()); @@ -292,15 +287,11 @@ private AiAnalysisRequest buildBranchAnalysisRequest( private void addVcsCredentials(AiAnalysisRequestImpl.Builder builder, VcsConnection connection) throws GeneralSecurityException { - if (connection.getConnectionType() == EVcsConnectionType.APPLICATION && connection.getAccessToken() != null) { - String accessToken = tokenEncryptionService.decrypt(connection.getAccessToken()); - builder.withAccessToken(accessToken); - } else if ((connection.getConnectionType() == EVcsConnectionType.PERSONAL_TOKEN || - connection.getConnectionType() == EVcsConnectionType.REPOSITORY_TOKEN) && - connection.getConfiguration() instanceof GitLabConfig config) { - builder.withAccessToken(config.accessToken()); + VcsConnectionCredentials credentials = credentialsExtractor.extractCredentials(connection); + if (VcsConnectionCredentialsExtractor.hasAccessToken(credentials)) { + builder.withAccessToken(credentials.accessToken()); } else { - log.warn("Unknown connection type for VCS credentials: {}", connection.getConnectionType()); + log.warn("No access token available for VCS connection type: {}", connection.getConnectionType()); } } } diff --git a/java-ecosystem/services/pipeline-agent/src/main/java/org/rostilos/codecrow/pipelineagent/gitlab/service/GitLabOperationsService.java b/java-ecosystem/services/pipeline-agent/src/main/java/org/rostilos/codecrow/pipelineagent/service/gitlab/GitLabOperationsService.java similarity index 97% rename from java-ecosystem/services/pipeline-agent/src/main/java/org/rostilos/codecrow/pipelineagent/gitlab/service/GitLabOperationsService.java rename to java-ecosystem/services/pipeline-agent/src/main/java/org/rostilos/codecrow/pipelineagent/service/gitlab/GitLabOperationsService.java index 785058f8..827f6d12 100644 --- a/java-ecosystem/services/pipeline-agent/src/main/java/org/rostilos/codecrow/pipelineagent/gitlab/service/GitLabOperationsService.java +++ b/java-ecosystem/services/pipeline-agent/src/main/java/org/rostilos/codecrow/pipelineagent/service/gitlab/GitLabOperationsService.java @@ -1,4 +1,4 @@ -package org.rostilos.codecrow.pipelineagent.gitlab.service; +package org.rostilos.codecrow.pipelineagent.service.gitlab; import okhttp3.OkHttpClient; import org.rostilos.codecrow.core.model.vcs.EVcsProvider; diff --git a/java-ecosystem/services/pipeline-agent/src/main/java/org/rostilos/codecrow/pipelineagent/gitlab/service/GitLabReportingService.java b/java-ecosystem/services/pipeline-agent/src/main/java/org/rostilos/codecrow/pipelineagent/service/gitlab/GitLabReportingService.java similarity index 97% rename from java-ecosystem/services/pipeline-agent/src/main/java/org/rostilos/codecrow/pipelineagent/gitlab/service/GitLabReportingService.java rename to java-ecosystem/services/pipeline-agent/src/main/java/org/rostilos/codecrow/pipelineagent/service/gitlab/GitLabReportingService.java index b1532fd4..186af8d1 100644 --- a/java-ecosystem/services/pipeline-agent/src/main/java/org/rostilos/codecrow/pipelineagent/gitlab/service/GitLabReportingService.java +++ b/java-ecosystem/services/pipeline-agent/src/main/java/org/rostilos/codecrow/pipelineagent/service/gitlab/GitLabReportingService.java @@ -1,4 +1,4 @@ -package org.rostilos.codecrow.pipelineagent.gitlab.service; +package org.rostilos.codecrow.pipelineagent.service.gitlab; import okhttp3.OkHttpClient; import org.rostilos.codecrow.core.model.codeanalysis.CodeAnalysis; @@ -55,17 +55,20 @@ public EVcsProvider getProvider() { } private VcsRepoInfo getVcsRepoInfo(Project project) { - if (project.getVcsBinding() != null) { - return project.getVcsBinding(); + // Use unified method that prefers VcsRepoBinding over legacy vcsBinding + VcsRepoInfo vcsInfo = project.getEffectiveVcsRepoInfo(); + if (vcsInfo != null) { + return vcsInfo; } + // Fallback to repository lookup (shouldn't be needed normally) VcsRepoBinding vcsRepoBinding = vcsRepoBindingRepository.findByProject_Id(project.getId()) .orElseThrow(() -> new IllegalStateException( "No VCS binding found for project " + project.getId() + ". Neither ProjectVcsConnectionBinding nor VcsRepoBinding is configured." )); - log.debug("Using VcsRepoBinding fallback for project {}: {}/{}", + log.debug("Using VcsRepoBinding repository fallback for project {}: {}/{}", project.getId(), vcsRepoBinding.getRepoWorkspace(), vcsRepoBinding.getRepoSlug()); return vcsRepoBinding; diff --git a/java-ecosystem/services/pipeline-agent/src/main/java/org/rostilos/codecrow/pipelineagent/webhookhandler/AbstractWebhookHandler.java b/java-ecosystem/services/pipeline-agent/src/main/java/org/rostilos/codecrow/pipelineagent/webhookhandler/AbstractWebhookHandler.java new file mode 100644 index 00000000..49f65f73 --- /dev/null +++ b/java-ecosystem/services/pipeline-agent/src/main/java/org/rostilos/codecrow/pipelineagent/webhookhandler/AbstractWebhookHandler.java @@ -0,0 +1,59 @@ +package org.rostilos.codecrow.pipelineagent.webhookhandler; + +import org.rostilos.codecrow.core.model.codeanalysis.AnalysisType; +import org.rostilos.codecrow.core.model.project.Project; +import org.rostilos.codecrow.core.model.project.config.ProjectConfig; +import org.rostilos.codecrow.core.util.BranchPatternMatcher; + +import java.util.List; + +public abstract class AbstractWebhookHandler { + protected static String validateProjectConnections(Project project) { + // Use unified hasVcsBinding() method that checks both bindings + if (!project.hasVcsBinding()) { + return "VCS connection is not configured for project: " + project.getId(); + } + + if (project.getAiBinding() == null) { + return "AI connection is not configured for project: " + project.getId(); + } + + return null; + } + + /** + * Check if a branch matches the configured analysis patterns. + * Note: isBranchAnalysisEnabled() check is done in the handle() method before this is called. + */ + protected static boolean shouldAnalyze(Project project, String branchName, AnalysisType analysisType) { + if(analysisType == AnalysisType.BRANCH_ANALYSIS) { + if (project.getConfiguration() == null) { + return true; + } + + ProjectConfig.BranchAnalysisConfig branchConfig = project.getConfiguration().branchAnalysis(); + if (branchConfig == null) { + return true; + } + + List branchPushPatterns = branchConfig.branchPushPatterns(); + return BranchPatternMatcher.shouldAnalyze(branchName, branchPushPatterns); + } + + if(analysisType == AnalysisType.PR_REVIEW) { + String targetBranch = branchName; + if (project.getConfiguration() == null) { + return true; + } + + ProjectConfig.BranchAnalysisConfig branchConfig = project.getConfiguration().branchAnalysis(); + if (branchConfig == null) { + return true; + } + + List prTargetBranches = branchConfig.prTargetBranches(); + return BranchPatternMatcher.shouldAnalyze(targetBranch, prTargetBranches); + } + return false; + } +} diff --git a/java-ecosystem/services/pipeline-agent/src/main/java/org/rostilos/codecrow/pipelineagent/generic/handler/CommentCommandWebhookHandler.java b/java-ecosystem/services/pipeline-agent/src/main/java/org/rostilos/codecrow/pipelineagent/webhookhandler/CommentCommandWebhookHandler.java similarity index 92% rename from java-ecosystem/services/pipeline-agent/src/main/java/org/rostilos/codecrow/pipelineagent/generic/handler/CommentCommandWebhookHandler.java rename to java-ecosystem/services/pipeline-agent/src/main/java/org/rostilos/codecrow/pipelineagent/webhookhandler/CommentCommandWebhookHandler.java index 6bd83b18..db4c29f5 100644 --- a/java-ecosystem/services/pipeline-agent/src/main/java/org/rostilos/codecrow/pipelineagent/generic/handler/CommentCommandWebhookHandler.java +++ b/java-ecosystem/services/pipeline-agent/src/main/java/org/rostilos/codecrow/pipelineagent/webhookhandler/CommentCommandWebhookHandler.java @@ -1,4 +1,4 @@ -package org.rostilos.codecrow.pipelineagent.generic.handler; +package org.rostilos.codecrow.pipelineagent.webhookhandler; import com.fasterxml.jackson.databind.JsonNode; import okhttp3.OkHttpClient; @@ -6,22 +6,19 @@ import org.rostilos.codecrow.core.model.codeanalysis.CodeAnalysis; import org.rostilos.codecrow.core.model.codeanalysis.PrSummarizeCache; import org.rostilos.codecrow.core.model.project.Project; -import org.rostilos.codecrow.core.model.project.ProjectVcsConnectionBinding; import org.rostilos.codecrow.core.model.project.config.ProjectConfig; import org.rostilos.codecrow.core.model.vcs.EVcsProvider; import org.rostilos.codecrow.core.model.vcs.VcsConnection; -import org.rostilos.codecrow.core.model.vcs.VcsRepoBinding; import org.rostilos.codecrow.core.persistence.repository.codeanalysis.PrSummarizeCacheRepository; import org.rostilos.codecrow.core.service.CodeAnalysisService; import org.rostilos.codecrow.analysisengine.dto.request.processor.PrProcessRequest; import org.rostilos.codecrow.analysisengine.processor.analysis.PullRequestAnalysisProcessor; -import org.rostilos.codecrow.pipelineagent.generic.service.CommandAuthorizationService; -import org.rostilos.codecrow.pipelineagent.generic.service.CommandAuthorizationService.AuthorizationResult; -import org.rostilos.codecrow.pipelineagent.generic.service.CommentCommandRateLimitService; -import org.rostilos.codecrow.pipelineagent.generic.service.PromptSanitizationService; -import org.rostilos.codecrow.pipelineagent.generic.webhook.WebhookPayload; -import org.rostilos.codecrow.pipelineagent.generic.webhook.WebhookPayload.CodecrowCommand; -import org.rostilos.codecrow.pipelineagent.generic.webhook.handler.WebhookHandler; +import org.rostilos.codecrow.pipelineagent.dto.webhook.WebhookPayload; +import org.rostilos.codecrow.pipelineagent.service.CommandAuthorizationService; +import org.rostilos.codecrow.pipelineagent.service.CommandAuthorizationService.AuthorizationResult; +import org.rostilos.codecrow.pipelineagent.service.CommentCommandRateLimitService; +import org.rostilos.codecrow.analysisengine.service.PromptSanitizationService; +import org.rostilos.codecrow.pipelineagent.dto.webhook.WebhookPayload.CodecrowCommand; import org.rostilos.codecrow.vcsclient.VcsClientProvider; import org.rostilos.codecrow.vcsclient.github.actions.GetPullRequestAction; import org.slf4j.Logger; @@ -461,26 +458,18 @@ private record PrDetails(String sourceBranch, String targetBranch, String headCo * This is needed when webhook events don't include branch information (e.g., GitHub issue_comment events). */ private PrDetails fetchPrDetails(Project project, int prNumber) throws IOException { - // Get VCS connection info from project - ProjectVcsConnectionBinding vcsBinding = project.getVcsBinding(); - VcsConnection vcsConnection = null; - String owner = null; - String repoSlug = null; - - if (vcsBinding != null && vcsBinding.getVcsConnection() != null) { - vcsConnection = vcsBinding.getVcsConnection(); - owner = vcsBinding.getWorkspace(); - repoSlug = vcsBinding.getRepoSlug(); - } else { - VcsRepoBinding repoBinding = project.getVcsRepoBinding(); - if (repoBinding != null && repoBinding.getVcsConnection() != null) { - vcsConnection = repoBinding.getVcsConnection(); - owner = repoBinding.getExternalNamespace(); - repoSlug = repoBinding.getExternalRepoSlug(); - } + // Get VCS connection info using unified accessor + var vcsInfo = project.getEffectiveVcsRepoInfo(); + if (vcsInfo == null || vcsInfo.getVcsConnection() == null) { + log.warn("Cannot fetch PR details: missing VCS connection info for project {}", project.getId()); + return null; } - if (vcsConnection == null || owner == null || repoSlug == null) { + VcsConnection vcsConnection = vcsInfo.getVcsConnection(); + String owner = vcsInfo.getRepoWorkspace(); + String repoSlug = vcsInfo.getRepoSlug(); + + if (owner == null || repoSlug == null) { log.warn("Cannot fetch PR details: missing VCS connection info for project {}", project.getId()); return null; } @@ -740,30 +729,17 @@ private WebhookPayload enrichFromBitbucket(WebhookPayload payload, VcsConnection } private VcsConnection getVcsConnection(Project project) { - ProjectVcsConnectionBinding vcsBinding = project.getVcsBinding(); - if (vcsBinding != null && vcsBinding.getVcsConnection() != null) { - return vcsBinding.getVcsConnection(); - } - - VcsRepoBinding repoBinding = project.getVcsRepoBinding(); - if (repoBinding != null && repoBinding.getVcsConnection() != null) { - return repoBinding.getVcsConnection(); - } - - return null; + // Use unified method + return project.getEffectiveVcsConnection(); } private record VcsInfo(String workspace, String repoSlug) {} private VcsInfo getVcsInfo(Project project) { - ProjectVcsConnectionBinding vcsBinding = project.getVcsBinding(); - if (vcsBinding != null) { - return new VcsInfo(vcsBinding.getWorkspace(), vcsBinding.getRepoSlug()); - } - - VcsRepoBinding repoBinding = project.getVcsRepoBinding(); - if (repoBinding != null) { - return new VcsInfo(repoBinding.getExternalNamespace(), repoBinding.getExternalRepoSlug()); + // Use unified accessor + var vcsInfo = project.getEffectiveVcsRepoInfo(); + if (vcsInfo != null) { + return new VcsInfo(vcsInfo.getRepoWorkspace(), vcsInfo.getRepoSlug()); } throw new IllegalStateException("No VCS binding found for project: " + project.getId()); diff --git a/java-ecosystem/services/pipeline-agent/src/main/java/org/rostilos/codecrow/pipelineagent/generic/webhook/handler/WebhookHandler.java b/java-ecosystem/services/pipeline-agent/src/main/java/org/rostilos/codecrow/pipelineagent/webhookhandler/WebhookHandler.java similarity index 94% rename from java-ecosystem/services/pipeline-agent/src/main/java/org/rostilos/codecrow/pipelineagent/generic/webhook/handler/WebhookHandler.java rename to java-ecosystem/services/pipeline-agent/src/main/java/org/rostilos/codecrow/pipelineagent/webhookhandler/WebhookHandler.java index e7549281..f9208807 100644 --- a/java-ecosystem/services/pipeline-agent/src/main/java/org/rostilos/codecrow/pipelineagent/generic/webhook/handler/WebhookHandler.java +++ b/java-ecosystem/services/pipeline-agent/src/main/java/org/rostilos/codecrow/pipelineagent/webhookhandler/WebhookHandler.java @@ -1,8 +1,8 @@ -package org.rostilos.codecrow.pipelineagent.generic.webhook.handler; +package org.rostilos.codecrow.pipelineagent.webhookhandler; import org.rostilos.codecrow.core.model.project.Project; import org.rostilos.codecrow.core.model.vcs.EVcsProvider; -import org.rostilos.codecrow.pipelineagent.generic.webhook.WebhookPayload; +import org.rostilos.codecrow.pipelineagent.dto.webhook.WebhookPayload; import org.springframework.http.ResponseEntity; import java.util.Map; diff --git a/java-ecosystem/services/pipeline-agent/src/main/java/org/rostilos/codecrow/pipelineagent/generic/webhook/handler/WebhookHandlerFactory.java b/java-ecosystem/services/pipeline-agent/src/main/java/org/rostilos/codecrow/pipelineagent/webhookhandler/WebhookHandlerFactory.java similarity index 98% rename from java-ecosystem/services/pipeline-agent/src/main/java/org/rostilos/codecrow/pipelineagent/generic/webhook/handler/WebhookHandlerFactory.java rename to java-ecosystem/services/pipeline-agent/src/main/java/org/rostilos/codecrow/pipelineagent/webhookhandler/WebhookHandlerFactory.java index 710d7dd2..a723d3f3 100644 --- a/java-ecosystem/services/pipeline-agent/src/main/java/org/rostilos/codecrow/pipelineagent/generic/webhook/handler/WebhookHandlerFactory.java +++ b/java-ecosystem/services/pipeline-agent/src/main/java/org/rostilos/codecrow/pipelineagent/webhookhandler/WebhookHandlerFactory.java @@ -1,4 +1,4 @@ -package org.rostilos.codecrow.pipelineagent.generic.webhook.handler; +package org.rostilos.codecrow.pipelineagent.webhookhandler; import org.rostilos.codecrow.core.model.vcs.EVcsProvider; import org.slf4j.Logger; diff --git a/java-ecosystem/services/pipeline-agent/src/main/java/org/rostilos/codecrow/pipelineagent/generic/webhook/WebhookProjectResolver.java b/java-ecosystem/services/pipeline-agent/src/main/java/org/rostilos/codecrow/pipelineagent/webhookhandler/WebhookProjectResolver.java similarity index 98% rename from java-ecosystem/services/pipeline-agent/src/main/java/org/rostilos/codecrow/pipelineagent/generic/webhook/WebhookProjectResolver.java rename to java-ecosystem/services/pipeline-agent/src/main/java/org/rostilos/codecrow/pipelineagent/webhookhandler/WebhookProjectResolver.java index 81151059..1bb98fba 100644 --- a/java-ecosystem/services/pipeline-agent/src/main/java/org/rostilos/codecrow/pipelineagent/generic/webhook/WebhookProjectResolver.java +++ b/java-ecosystem/services/pipeline-agent/src/main/java/org/rostilos/codecrow/pipelineagent/webhookhandler/WebhookProjectResolver.java @@ -1,4 +1,4 @@ -package org.rostilos.codecrow.pipelineagent.generic.webhook; +package org.rostilos.codecrow.pipelineagent.webhookhandler; import org.rostilos.codecrow.core.model.project.Project; import org.rostilos.codecrow.core.model.vcs.EVcsProvider; diff --git a/java-ecosystem/services/pipeline-agent/src/main/java/org/rostilos/codecrow/pipelineagent/bitbucket/handler/BitbucketCloudBranchWebhookHandler.java b/java-ecosystem/services/pipeline-agent/src/main/java/org/rostilos/codecrow/pipelineagent/webhookhandler/bitbucket/BitbucketCloudBranchWebhookHandler.java similarity index 74% rename from java-ecosystem/services/pipeline-agent/src/main/java/org/rostilos/codecrow/pipelineagent/bitbucket/handler/BitbucketCloudBranchWebhookHandler.java rename to java-ecosystem/services/pipeline-agent/src/main/java/org/rostilos/codecrow/pipelineagent/webhookhandler/bitbucket/BitbucketCloudBranchWebhookHandler.java index 13f49d5f..6271901a 100644 --- a/java-ecosystem/services/pipeline-agent/src/main/java/org/rostilos/codecrow/pipelineagent/bitbucket/handler/BitbucketCloudBranchWebhookHandler.java +++ b/java-ecosystem/services/pipeline-agent/src/main/java/org/rostilos/codecrow/pipelineagent/webhookhandler/bitbucket/BitbucketCloudBranchWebhookHandler.java @@ -1,19 +1,17 @@ -package org.rostilos.codecrow.pipelineagent.bitbucket.handler; +package org.rostilos.codecrow.pipelineagent.webhookhandler.bitbucket; import org.rostilos.codecrow.core.model.codeanalysis.AnalysisType; import org.rostilos.codecrow.core.model.project.Project; -import org.rostilos.codecrow.core.model.project.config.ProjectConfig; import org.rostilos.codecrow.core.model.vcs.EVcsProvider; -import org.rostilos.codecrow.core.util.BranchPatternMatcher; import org.rostilos.codecrow.analysisengine.dto.request.processor.BranchProcessRequest; import org.rostilos.codecrow.analysisengine.processor.analysis.BranchAnalysisProcessor; -import org.rostilos.codecrow.pipelineagent.generic.webhook.WebhookPayload; -import org.rostilos.codecrow.pipelineagent.generic.webhook.handler.WebhookHandler; +import org.rostilos.codecrow.pipelineagent.dto.webhook.WebhookPayload; +import org.rostilos.codecrow.pipelineagent.webhookhandler.AbstractWebhookHandler; +import org.rostilos.codecrow.pipelineagent.webhookhandler.WebhookHandler; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.stereotype.Component; -import java.util.List; import java.util.Map; import java.util.Set; import java.util.function.Consumer; @@ -23,7 +21,7 @@ * Bridges the generic webhook processing to existing Bitbucket-specific processors. */ @Component -public class BitbucketCloudBranchWebhookHandler implements WebhookHandler { +public class BitbucketCloudBranchWebhookHandler extends AbstractWebhookHandler implements WebhookHandler { private static final Logger log = LoggerFactory.getLogger(BitbucketCloudBranchWebhookHandler.class); @@ -79,7 +77,7 @@ public WebhookResult handle(WebhookPayload payload, Project project, Consumer branchPushPatterns = branchConfig.branchPushPatterns(); - return BranchPatternMatcher.shouldAnalyze(branchName, branchPushPatterns); - } - /** * Determine the branch name to analyze based on the event type. * For PR merges (pullrequest:fulfilled), use the target branch. diff --git a/java-ecosystem/services/pipeline-agent/src/main/java/org/rostilos/codecrow/pipelineagent/bitbucket/handler/BitbucketCloudPullRequestWebhookHandler.java b/java-ecosystem/services/pipeline-agent/src/main/java/org/rostilos/codecrow/pipelineagent/webhookhandler/bitbucket/BitbucketCloudPullRequestWebhookHandler.java similarity index 82% rename from java-ecosystem/services/pipeline-agent/src/main/java/org/rostilos/codecrow/pipelineagent/bitbucket/handler/BitbucketCloudPullRequestWebhookHandler.java rename to java-ecosystem/services/pipeline-agent/src/main/java/org/rostilos/codecrow/pipelineagent/webhookhandler/bitbucket/BitbucketCloudPullRequestWebhookHandler.java index 14723acf..e4d966ca 100644 --- a/java-ecosystem/services/pipeline-agent/src/main/java/org/rostilos/codecrow/pipelineagent/bitbucket/handler/BitbucketCloudPullRequestWebhookHandler.java +++ b/java-ecosystem/services/pipeline-agent/src/main/java/org/rostilos/codecrow/pipelineagent/webhookhandler/bitbucket/BitbucketCloudPullRequestWebhookHandler.java @@ -1,21 +1,19 @@ -package org.rostilos.codecrow.pipelineagent.bitbucket.handler; +package org.rostilos.codecrow.pipelineagent.webhookhandler.bitbucket; import org.rostilos.codecrow.core.model.codeanalysis.AnalysisType; import org.rostilos.codecrow.core.model.project.Project; -import org.rostilos.codecrow.core.model.project.config.ProjectConfig; import org.rostilos.codecrow.core.model.vcs.EVcsProvider; -import org.rostilos.codecrow.core.util.BranchPatternMatcher; import org.rostilos.codecrow.analysisengine.dto.request.processor.PrProcessRequest; import org.rostilos.codecrow.analysisengine.processor.analysis.PullRequestAnalysisProcessor; import org.rostilos.codecrow.analysisengine.service.vcs.VcsReportingService; import org.rostilos.codecrow.analysisengine.service.vcs.VcsServiceFactory; -import org.rostilos.codecrow.pipelineagent.generic.webhook.WebhookPayload; -import org.rostilos.codecrow.pipelineagent.generic.webhook.handler.WebhookHandler; +import org.rostilos.codecrow.pipelineagent.dto.webhook.WebhookPayload; +import org.rostilos.codecrow.pipelineagent.webhookhandler.AbstractWebhookHandler; +import org.rostilos.codecrow.pipelineagent.webhookhandler.WebhookHandler; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.stereotype.Component; -import java.util.List; import java.util.Map; import java.util.Set; import java.util.function.Consumer; @@ -25,7 +23,7 @@ * Bridges the generic webhook processing to existing Bitbucket-specific processors. */ @Component -public class BitbucketCloudPullRequestWebhookHandler implements WebhookHandler { +public class BitbucketCloudPullRequestWebhookHandler extends AbstractWebhookHandler implements WebhookHandler { private static final Logger log = LoggerFactory.getLogger(BitbucketCloudPullRequestWebhookHandler.class); @@ -88,7 +86,7 @@ public WebhookResult handle(WebhookPayload payload, Project project, Consumer prTargetBranches = branchConfig.prTargetBranches(); - return BranchPatternMatcher.shouldAnalyze(targetBranch, prTargetBranches); - } private WebhookResult handlePullRequestEvent(WebhookPayload payload, Project project, Consumer> eventConsumer) { String placeholderCommentId = null; diff --git a/java-ecosystem/services/pipeline-agent/src/main/java/org/rostilos/codecrow/pipelineagent/bitbucket/webhook/BitbucketCloudWebhookParser.java b/java-ecosystem/services/pipeline-agent/src/main/java/org/rostilos/codecrow/pipelineagent/webhookhandler/bitbucket/BitbucketCloudWebhookParser.java similarity index 96% rename from java-ecosystem/services/pipeline-agent/src/main/java/org/rostilos/codecrow/pipelineagent/bitbucket/webhook/BitbucketCloudWebhookParser.java rename to java-ecosystem/services/pipeline-agent/src/main/java/org/rostilos/codecrow/pipelineagent/webhookhandler/bitbucket/BitbucketCloudWebhookParser.java index 44e9f20f..feed0ed3 100644 --- a/java-ecosystem/services/pipeline-agent/src/main/java/org/rostilos/codecrow/pipelineagent/bitbucket/webhook/BitbucketCloudWebhookParser.java +++ b/java-ecosystem/services/pipeline-agent/src/main/java/org/rostilos/codecrow/pipelineagent/webhookhandler/bitbucket/BitbucketCloudWebhookParser.java @@ -1,9 +1,9 @@ -package org.rostilos.codecrow.pipelineagent.bitbucket.webhook; +package org.rostilos.codecrow.pipelineagent.webhookhandler.bitbucket; import com.fasterxml.jackson.databind.JsonNode; import org.rostilos.codecrow.core.model.vcs.EVcsProvider; -import org.rostilos.codecrow.pipelineagent.generic.webhook.WebhookPayload; -import org.rostilos.codecrow.pipelineagent.generic.webhook.WebhookPayload.CommentData; +import org.rostilos.codecrow.pipelineagent.dto.webhook.WebhookPayload; +import org.rostilos.codecrow.pipelineagent.dto.webhook.WebhookPayload.CommentData; import org.springframework.stereotype.Component; /** diff --git a/java-ecosystem/services/pipeline-agent/src/main/java/org/rostilos/codecrow/pipelineagent/github/handler/GitHubBranchWebhookHandler.java b/java-ecosystem/services/pipeline-agent/src/main/java/org/rostilos/codecrow/pipelineagent/webhookhandler/github/GitHubBranchWebhookHandler.java similarity index 69% rename from java-ecosystem/services/pipeline-agent/src/main/java/org/rostilos/codecrow/pipelineagent/github/handler/GitHubBranchWebhookHandler.java rename to java-ecosystem/services/pipeline-agent/src/main/java/org/rostilos/codecrow/pipelineagent/webhookhandler/github/GitHubBranchWebhookHandler.java index 93e863d0..2f63c1f3 100644 --- a/java-ecosystem/services/pipeline-agent/src/main/java/org/rostilos/codecrow/pipelineagent/github/handler/GitHubBranchWebhookHandler.java +++ b/java-ecosystem/services/pipeline-agent/src/main/java/org/rostilos/codecrow/pipelineagent/webhookhandler/github/GitHubBranchWebhookHandler.java @@ -1,19 +1,17 @@ -package org.rostilos.codecrow.pipelineagent.github.handler; +package org.rostilos.codecrow.pipelineagent.webhookhandler.github; import org.rostilos.codecrow.core.model.codeanalysis.AnalysisType; import org.rostilos.codecrow.core.model.project.Project; -import org.rostilos.codecrow.core.model.project.config.ProjectConfig; import org.rostilos.codecrow.core.model.vcs.EVcsProvider; -import org.rostilos.codecrow.core.util.BranchPatternMatcher; import org.rostilos.codecrow.analysisengine.dto.request.processor.BranchProcessRequest; import org.rostilos.codecrow.analysisengine.processor.analysis.BranchAnalysisProcessor; -import org.rostilos.codecrow.pipelineagent.generic.webhook.WebhookPayload; -import org.rostilos.codecrow.pipelineagent.generic.webhook.handler.WebhookHandler; +import org.rostilos.codecrow.pipelineagent.dto.webhook.WebhookPayload; +import org.rostilos.codecrow.pipelineagent.webhookhandler.AbstractWebhookHandler; +import org.rostilos.codecrow.pipelineagent.webhookhandler.WebhookHandler; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.stereotype.Component; -import java.util.List; import java.util.Map; import java.util.Set; import java.util.function.Consumer; @@ -22,7 +20,7 @@ * Webhook handler for GitHub push events (branch analysis). */ @Component -public class GitHubBranchWebhookHandler implements WebhookHandler { +public class GitHubBranchWebhookHandler extends AbstractWebhookHandler implements WebhookHandler { private static final Logger log = LoggerFactory.getLogger(GitHubBranchWebhookHandler.class); @@ -77,7 +75,7 @@ public WebhookResult handle(WebhookPayload payload, Project project, Consumer branchPushPatterns = branchConfig.branchPushPatterns(); - return BranchPatternMatcher.shouldAnalyze(branchName, branchPushPatterns); - } } diff --git a/java-ecosystem/services/pipeline-agent/src/main/java/org/rostilos/codecrow/pipelineagent/github/handler/GitHubPullRequestWebhookHandler.java b/java-ecosystem/services/pipeline-agent/src/main/java/org/rostilos/codecrow/pipelineagent/webhookhandler/github/GitHubPullRequestWebhookHandler.java similarity index 84% rename from java-ecosystem/services/pipeline-agent/src/main/java/org/rostilos/codecrow/pipelineagent/github/handler/GitHubPullRequestWebhookHandler.java rename to java-ecosystem/services/pipeline-agent/src/main/java/org/rostilos/codecrow/pipelineagent/webhookhandler/github/GitHubPullRequestWebhookHandler.java index 7bcf990c..8ddc2457 100644 --- a/java-ecosystem/services/pipeline-agent/src/main/java/org/rostilos/codecrow/pipelineagent/github/handler/GitHubPullRequestWebhookHandler.java +++ b/java-ecosystem/services/pipeline-agent/src/main/java/org/rostilos/codecrow/pipelineagent/webhookhandler/github/GitHubPullRequestWebhookHandler.java @@ -1,21 +1,19 @@ -package org.rostilos.codecrow.pipelineagent.github.handler; +package org.rostilos.codecrow.pipelineagent.webhookhandler.github; import org.rostilos.codecrow.core.model.codeanalysis.AnalysisType; import org.rostilos.codecrow.core.model.project.Project; -import org.rostilos.codecrow.core.model.project.config.ProjectConfig; import org.rostilos.codecrow.core.model.vcs.EVcsProvider; -import org.rostilos.codecrow.core.util.BranchPatternMatcher; import org.rostilos.codecrow.analysisengine.dto.request.processor.PrProcessRequest; import org.rostilos.codecrow.analysisengine.processor.analysis.PullRequestAnalysisProcessor; import org.rostilos.codecrow.analysisengine.service.vcs.VcsReportingService; import org.rostilos.codecrow.analysisengine.service.vcs.VcsServiceFactory; -import org.rostilos.codecrow.pipelineagent.generic.webhook.WebhookPayload; -import org.rostilos.codecrow.pipelineagent.generic.webhook.handler.WebhookHandler; +import org.rostilos.codecrow.pipelineagent.dto.webhook.WebhookPayload; +import org.rostilos.codecrow.pipelineagent.webhookhandler.AbstractWebhookHandler; +import org.rostilos.codecrow.pipelineagent.webhookhandler.WebhookHandler; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.stereotype.Component; -import java.util.List; import java.util.Map; import java.util.Set; import java.util.function.Consumer; @@ -24,7 +22,7 @@ * Webhook handler for GitHub Pull Request events. */ @Component -public class GitHubPullRequestWebhookHandler implements WebhookHandler { +public class GitHubPullRequestWebhookHandler extends AbstractWebhookHandler implements WebhookHandler { private static final Logger log = LoggerFactory.getLogger(GitHubPullRequestWebhookHandler.class); @@ -97,7 +95,7 @@ public WebhookResult handle(WebhookPayload payload, Project project, Consumer prTargetBranches = branchConfig.prTargetBranches(); - return BranchPatternMatcher.shouldAnalyze(targetBranch, prTargetBranches); - } private WebhookResult handlePullRequestEvent( WebhookPayload payload, diff --git a/java-ecosystem/services/pipeline-agent/src/main/java/org/rostilos/codecrow/pipelineagent/github/webhook/GitHubWebhookParser.java b/java-ecosystem/services/pipeline-agent/src/main/java/org/rostilos/codecrow/pipelineagent/webhookhandler/github/GitHubWebhookParser.java similarity index 96% rename from java-ecosystem/services/pipeline-agent/src/main/java/org/rostilos/codecrow/pipelineagent/github/webhook/GitHubWebhookParser.java rename to java-ecosystem/services/pipeline-agent/src/main/java/org/rostilos/codecrow/pipelineagent/webhookhandler/github/GitHubWebhookParser.java index dd89755a..dc490edd 100644 --- a/java-ecosystem/services/pipeline-agent/src/main/java/org/rostilos/codecrow/pipelineagent/github/webhook/GitHubWebhookParser.java +++ b/java-ecosystem/services/pipeline-agent/src/main/java/org/rostilos/codecrow/pipelineagent/webhookhandler/github/GitHubWebhookParser.java @@ -1,9 +1,9 @@ -package org.rostilos.codecrow.pipelineagent.github.webhook; +package org.rostilos.codecrow.pipelineagent.webhookhandler.github; import com.fasterxml.jackson.databind.JsonNode; import org.rostilos.codecrow.core.model.vcs.EVcsProvider; -import org.rostilos.codecrow.pipelineagent.generic.webhook.WebhookPayload; -import org.rostilos.codecrow.pipelineagent.generic.webhook.WebhookPayload.CommentData; +import org.rostilos.codecrow.pipelineagent.dto.webhook.WebhookPayload; +import org.rostilos.codecrow.pipelineagent.dto.webhook.WebhookPayload.CommentData; import org.springframework.stereotype.Component; /** diff --git a/java-ecosystem/services/pipeline-agent/src/main/java/org/rostilos/codecrow/pipelineagent/gitlab/handler/GitLabBranchWebhookHandler.java b/java-ecosystem/services/pipeline-agent/src/main/java/org/rostilos/codecrow/pipelineagent/webhookhandler/gitlab/GitLabBranchWebhookHandler.java similarity index 75% rename from java-ecosystem/services/pipeline-agent/src/main/java/org/rostilos/codecrow/pipelineagent/gitlab/handler/GitLabBranchWebhookHandler.java rename to java-ecosystem/services/pipeline-agent/src/main/java/org/rostilos/codecrow/pipelineagent/webhookhandler/gitlab/GitLabBranchWebhookHandler.java index c9224d6a..faa975a5 100644 --- a/java-ecosystem/services/pipeline-agent/src/main/java/org/rostilos/codecrow/pipelineagent/gitlab/handler/GitLabBranchWebhookHandler.java +++ b/java-ecosystem/services/pipeline-agent/src/main/java/org/rostilos/codecrow/pipelineagent/webhookhandler/gitlab/GitLabBranchWebhookHandler.java @@ -1,19 +1,17 @@ -package org.rostilos.codecrow.pipelineagent.gitlab.handler; +package org.rostilos.codecrow.pipelineagent.webhookhandler.gitlab; import org.rostilos.codecrow.core.model.codeanalysis.AnalysisType; import org.rostilos.codecrow.core.model.project.Project; -import org.rostilos.codecrow.core.model.project.config.ProjectConfig; import org.rostilos.codecrow.core.model.vcs.EVcsProvider; -import org.rostilos.codecrow.core.util.BranchPatternMatcher; import org.rostilos.codecrow.analysisengine.dto.request.processor.BranchProcessRequest; import org.rostilos.codecrow.analysisengine.processor.analysis.BranchAnalysisProcessor; -import org.rostilos.codecrow.pipelineagent.generic.webhook.WebhookPayload; -import org.rostilos.codecrow.pipelineagent.generic.webhook.handler.WebhookHandler; +import org.rostilos.codecrow.pipelineagent.dto.webhook.WebhookPayload; +import org.rostilos.codecrow.pipelineagent.webhookhandler.AbstractWebhookHandler; +import org.rostilos.codecrow.pipelineagent.webhookhandler.WebhookHandler; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.stereotype.Component; -import java.util.List; import java.util.Map; import java.util.Set; import java.util.function.Consumer; @@ -22,7 +20,7 @@ * Webhook handler for GitLab Push events (branch analysis). */ @Component -public class GitLabBranchWebhookHandler implements WebhookHandler { +public class GitLabBranchWebhookHandler extends AbstractWebhookHandler implements WebhookHandler { private static final Logger log = LoggerFactory.getLogger(GitLabBranchWebhookHandler.class); @@ -69,7 +67,7 @@ public WebhookResult handle(WebhookPayload payload, Project project, Consumer branchPushPatterns = branchConfig.branchPushPatterns(); - return BranchPatternMatcher.shouldAnalyze(branchName, branchPushPatterns); - } - private WebhookResult handlePushEvent( WebhookPayload payload, Project project, diff --git a/java-ecosystem/services/pipeline-agent/src/main/java/org/rostilos/codecrow/pipelineagent/gitlab/handler/GitLabMergeRequestWebhookHandler.java b/java-ecosystem/services/pipeline-agent/src/main/java/org/rostilos/codecrow/pipelineagent/webhookhandler/gitlab/GitLabMergeRequestWebhookHandler.java similarity index 84% rename from java-ecosystem/services/pipeline-agent/src/main/java/org/rostilos/codecrow/pipelineagent/gitlab/handler/GitLabMergeRequestWebhookHandler.java rename to java-ecosystem/services/pipeline-agent/src/main/java/org/rostilos/codecrow/pipelineagent/webhookhandler/gitlab/GitLabMergeRequestWebhookHandler.java index 08140334..99a78260 100644 --- a/java-ecosystem/services/pipeline-agent/src/main/java/org/rostilos/codecrow/pipelineagent/gitlab/handler/GitLabMergeRequestWebhookHandler.java +++ b/java-ecosystem/services/pipeline-agent/src/main/java/org/rostilos/codecrow/pipelineagent/webhookhandler/gitlab/GitLabMergeRequestWebhookHandler.java @@ -1,21 +1,19 @@ -package org.rostilos.codecrow.pipelineagent.gitlab.handler; +package org.rostilos.codecrow.pipelineagent.webhookhandler.gitlab; import org.rostilos.codecrow.core.model.codeanalysis.AnalysisType; import org.rostilos.codecrow.core.model.project.Project; -import org.rostilos.codecrow.core.model.project.config.ProjectConfig; import org.rostilos.codecrow.core.model.vcs.EVcsProvider; -import org.rostilos.codecrow.core.util.BranchPatternMatcher; import org.rostilos.codecrow.analysisengine.dto.request.processor.PrProcessRequest; import org.rostilos.codecrow.analysisengine.processor.analysis.PullRequestAnalysisProcessor; import org.rostilos.codecrow.analysisengine.service.vcs.VcsReportingService; import org.rostilos.codecrow.analysisengine.service.vcs.VcsServiceFactory; -import org.rostilos.codecrow.pipelineagent.generic.webhook.WebhookPayload; -import org.rostilos.codecrow.pipelineagent.generic.webhook.handler.WebhookHandler; +import org.rostilos.codecrow.pipelineagent.dto.webhook.WebhookPayload; +import org.rostilos.codecrow.pipelineagent.webhookhandler.AbstractWebhookHandler; +import org.rostilos.codecrow.pipelineagent.webhookhandler.WebhookHandler; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.stereotype.Component; -import java.util.List; import java.util.Map; import java.util.Set; import java.util.function.Consumer; @@ -24,7 +22,7 @@ * Webhook handler for GitLab Merge Request events. */ @Component -public class GitLabMergeRequestWebhookHandler implements WebhookHandler { +public class GitLabMergeRequestWebhookHandler extends AbstractWebhookHandler implements WebhookHandler { private static final Logger log = LoggerFactory.getLogger(GitLabMergeRequestWebhookHandler.class); @@ -98,7 +96,7 @@ public WebhookResult handle(WebhookPayload payload, Project project, Consumer prTargetBranches = branchConfig.prTargetBranches(); - return BranchPatternMatcher.shouldAnalyze(targetBranch, prTargetBranches); - } + private WebhookResult handleMergeRequestEvent( WebhookPayload payload, diff --git a/java-ecosystem/services/pipeline-agent/src/main/java/org/rostilos/codecrow/pipelineagent/gitlab/webhook/GitLabWebhookParser.java b/java-ecosystem/services/pipeline-agent/src/main/java/org/rostilos/codecrow/pipelineagent/webhookhandler/gitlab/GitLabWebhookParser.java similarity index 97% rename from java-ecosystem/services/pipeline-agent/src/main/java/org/rostilos/codecrow/pipelineagent/gitlab/webhook/GitLabWebhookParser.java rename to java-ecosystem/services/pipeline-agent/src/main/java/org/rostilos/codecrow/pipelineagent/webhookhandler/gitlab/GitLabWebhookParser.java index fd746ff4..77ef16db 100644 --- a/java-ecosystem/services/pipeline-agent/src/main/java/org/rostilos/codecrow/pipelineagent/gitlab/webhook/GitLabWebhookParser.java +++ b/java-ecosystem/services/pipeline-agent/src/main/java/org/rostilos/codecrow/pipelineagent/webhookhandler/gitlab/GitLabWebhookParser.java @@ -1,9 +1,9 @@ -package org.rostilos.codecrow.pipelineagent.gitlab.webhook; +package org.rostilos.codecrow.pipelineagent.webhookhandler.gitlab; import com.fasterxml.jackson.databind.JsonNode; import org.rostilos.codecrow.core.model.vcs.EVcsProvider; -import org.rostilos.codecrow.pipelineagent.generic.webhook.WebhookPayload; -import org.rostilos.codecrow.pipelineagent.generic.webhook.WebhookPayload.CommentData; +import org.rostilos.codecrow.pipelineagent.dto.webhook.WebhookPayload; +import org.rostilos.codecrow.pipelineagent.dto.webhook.WebhookPayload.CommentData; import org.springframework.stereotype.Component; /** diff --git a/java-ecosystem/services/web-server/src/main/java/org/rostilos/codecrow/webserver/project/service/AllowedCommandUserService.java b/java-ecosystem/services/web-server/src/main/java/org/rostilos/codecrow/webserver/project/service/AllowedCommandUserService.java index f13faf23..26c96915 100644 --- a/java-ecosystem/services/web-server/src/main/java/org/rostilos/codecrow/webserver/project/service/AllowedCommandUserService.java +++ b/java-ecosystem/services/web-server/src/main/java/org/rostilos/codecrow/webserver/project/service/AllowedCommandUserService.java @@ -2,10 +2,8 @@ import org.rostilos.codecrow.core.model.project.AllowedCommandUser; import org.rostilos.codecrow.core.model.project.Project; -import org.rostilos.codecrow.core.model.project.ProjectVcsConnectionBinding; import org.rostilos.codecrow.core.model.vcs.EVcsProvider; import org.rostilos.codecrow.core.model.vcs.VcsConnection; -import org.rostilos.codecrow.core.model.vcs.VcsRepoBinding; import org.rostilos.codecrow.core.persistence.repository.project.AllowedCommandUserRepository; import org.rostilos.codecrow.vcsclient.VcsClient; import org.rostilos.codecrow.vcsclient.VcsClientProvider; @@ -242,37 +240,19 @@ private List fetchCollaborators(VcsConnection connection, Proje } /** - * Get the workspace slug from project bindings. + * Get the workspace slug from project bindings using unified accessor. */ private String getWorkspaceSlug(Project project) { - ProjectVcsConnectionBinding vcsBinding = project.getVcsBinding(); - if (vcsBinding != null && vcsBinding.getWorkspace() != null) { - return vcsBinding.getWorkspace(); - } - - VcsRepoBinding repoBinding = project.getVcsRepoBinding(); - if (repoBinding != null && repoBinding.getExternalNamespace() != null) { - return repoBinding.getExternalNamespace(); - } - - return null; + var vcsInfo = project.getEffectiveVcsRepoInfo(); + return vcsInfo != null ? vcsInfo.getRepoWorkspace() : null; } /** - * Get the repository slug from project bindings. + * Get the repository slug from project bindings using unified accessor. */ private String getRepoSlug(Project project) { - ProjectVcsConnectionBinding vcsBinding = project.getVcsBinding(); - if (vcsBinding != null && vcsBinding.getRepoSlug() != null) { - return vcsBinding.getRepoSlug(); - } - - VcsRepoBinding repoBinding = project.getVcsRepoBinding(); - if (repoBinding != null && repoBinding.getExternalRepoSlug() != null) { - return repoBinding.getExternalRepoSlug(); - } - - return null; + var vcsInfo = project.getEffectiveVcsRepoInfo(); + return vcsInfo != null ? vcsInfo.getRepoSlug() : null; } private boolean hasWritePermission(String permission) { @@ -280,20 +260,10 @@ private boolean hasWritePermission(String permission) { } /** - * Get VCS connection from project (via VcsBinding or VcsRepoBinding). + * Get VCS connection from project using unified accessor. */ private VcsConnection getVcsConnection(Project project) { - ProjectVcsConnectionBinding vcsBinding = project.getVcsBinding(); - if (vcsBinding != null && vcsBinding.getVcsConnection() != null) { - return vcsBinding.getVcsConnection(); - } - - VcsRepoBinding repoBinding = project.getVcsRepoBinding(); - if (repoBinding != null && repoBinding.getVcsConnection() != null) { - return repoBinding.getVcsConnection(); - } - - return null; + return project.getEffectiveVcsConnection(); } /** diff --git a/java-ecosystem/services/web-server/src/main/java/org/rostilos/codecrow/webserver/project/service/ProjectService.java b/java-ecosystem/services/web-server/src/main/java/org/rostilos/codecrow/webserver/project/service/ProjectService.java index b0c7f31f..87043958 100644 --- a/java-ecosystem/services/web-server/src/main/java/org/rostilos/codecrow/webserver/project/service/ProjectService.java +++ b/java-ecosystem/services/web-server/src/main/java/org/rostilos/codecrow/webserver/project/service/ProjectService.java @@ -10,7 +10,6 @@ import org.rostilos.codecrow.core.model.branch.Branch; import org.rostilos.codecrow.core.model.project.Project; import org.rostilos.codecrow.core.model.project.ProjectAiConnectionBinding; -import org.rostilos.codecrow.core.model.project.ProjectVcsConnectionBinding; import org.rostilos.codecrow.core.model.vcs.EVcsConnectionType; import org.rostilos.codecrow.core.model.vcs.EVcsProvider; import org.rostilos.codecrow.core.model.vcs.VcsConnection; @@ -157,14 +156,27 @@ public Project createProject(Long workspaceId, CreateProjectRequest request) thr VcsConnection vcsConnection = vcsConnectionRepository.findByWorkspace_IdAndId(workspaceId, request.getVcsConnectionId()) .orElseThrow(() -> new NoSuchElementException("VCS connection not found!")); - ProjectVcsConnectionBinding vcsBinding = new ProjectVcsConnectionBinding(); - vcsBinding.setProject(newProject); - vcsBinding.setVcsProvider(request.getVcsProvider()); - vcsBinding.setVcsConnection(vcsConnection); - vcsBinding.setRepoSlug(request.getRepositorySlug()); - vcsBinding.setRepositoryUUID(request.getRepositoryUUID()); - vcsBinding.setWorkspace(((BitbucketCloudConfig) vcsConnection.getConfiguration()).workspaceId()); - newProject.setVcsBinding(vcsBinding); + // Use VcsRepoBinding (provider-agnostic) instead of legacy ProjectVcsConnectionBinding + VcsRepoBinding vcsRepoBinding = new VcsRepoBinding(); + vcsRepoBinding.setProject(newProject); + vcsRepoBinding.setWorkspace(ws); + vcsRepoBinding.setProvider(request.getVcsProvider()); + vcsRepoBinding.setVcsConnection(vcsConnection); + vcsRepoBinding.setExternalRepoSlug(request.getRepositorySlug()); + // Store UUID as string for provider-agnostic storage + if (request.getRepositoryUUID() != null) { + vcsRepoBinding.setExternalRepoId(request.getRepositoryUUID().toString()); + } + // For Bitbucket, extract workspace from config; for other providers, use a default + String externalNamespace = null; + if (vcsConnection.getConfiguration() instanceof BitbucketCloudConfig bbConfig) { + externalNamespace = bbConfig.workspaceId(); + } else if (vcsConnection.getExternalWorkspaceSlug() != null) { + externalNamespace = vcsConnection.getExternalWorkspaceSlug(); + } + vcsRepoBinding.setExternalNamespace(externalNamespace); + vcsRepoBinding.setDefaultBranch(defaultBranch); + newProject.setVcsRepoBinding(vcsRepoBinding); } if (request.getAiConnectionId() != null) { diff --git a/python-ecosystem/mcp-client/model/models.py b/python-ecosystem/mcp-client/model/models.py index 553b21d5..48b9c3c3 100644 --- a/python-ecosystem/mcp-client/model/models.py +++ b/python-ecosystem/mcp-client/model/models.py @@ -70,7 +70,7 @@ class ReviewRequestDto(BaseModel): maxAllowedTokens: Optional[int] = Field(default=None, description="Optional per-request token limit enforced by the client before calling the AI. If provided and the estimated token count exceeds this value, the request will be rejected.") previousCodeAnalysisIssues: Optional[List[IssueDTO]] = Field(default_factory=list, description="List of issues from the previous CodeAnalysis version, if available.") - vcsProvider: Optional[str] = Field(default=None, description="VCS provider type for MCP server selection (github, bitbucket_cloud)") + vcsProvider: Optional[str] = Field(default=None, description="VCS provider type for MCP server selection (github, bitbucket_cloud, gitlab)") # Incremental analysis fields analysisMode: Optional[str] = Field(default="FULL", description="Analysis mode: FULL or INCREMENTAL") deltaDiff: Optional[str] = Field(default=None, description="Delta diff between previous and current commit (only for INCREMENTAL mode)")