CopilotAnalysisController.java

package com.taxonomy.portfolio.controller;

import com.taxonomy.portfolio.dto.CopilotDtos.CopilotOperationView;
import com.taxonomy.portfolio.dto.CopilotDtos.CopilotRunRequest;
import com.taxonomy.portfolio.service.CopilotAutomationService;
import com.taxonomy.workspace.service.WorkspaceContext;
import com.taxonomy.workspace.service.WorkspaceResolver;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import java.net.URI;

/** Persistent, resumable full-analysis surface for saved project requirements. */
@RestController
@RequestMapping("/api/projects/{projectId}")
@Tag(name = "Requirement Copilot")
public class CopilotAnalysisController {

    private final CopilotAutomationService automationService;
    private final WorkspaceResolver workspaceResolver;

    public CopilotAnalysisController(
            CopilotAutomationService automationService,
            WorkspaceResolver workspaceResolver) {
        this.automationService = automationService;
        this.workspaceResolver = workspaceResolver;
    }

    @PostMapping("/requirements/{requirementId}/copilot")
    @Operation(summary = "Start or reuse a full, persisted Copilot analysis")
    public ResponseEntity<CopilotOperationView> analyzeRequirement(
            @PathVariable Long projectId,
            @PathVariable Long requirementId,
            @RequestBody(required = false) CopilotRunRequest request) {
        RequestScope scope = scope();
        CopilotOperationView operation = automationService.enqueueManual(
                projectId, requirementId, request, scope.username(), scope.context());
        return ResponseEntity.accepted()
                .location(operationLocation(projectId, operation.operationId()))
                .body(operation);
    }

    @GetMapping("/requirements/{requirementId}/copilot/latest")
    @Operation(summary = "Resume the newest Copilot or Autopilot operation for a requirement")
    public ResponseEntity<CopilotOperationView> latest(
            @PathVariable Long projectId,
            @PathVariable Long requirementId) {
        RequestScope scope = scope();
        return automationService.latestOperation(
                        projectId, requirementId, scope.username(), scope.context())
                .map(ResponseEntity::ok)
                .orElseGet(() -> ResponseEntity.noContent().build());
    }

    @GetMapping("/copilot-operations/{operationId}")
    @Operation(summary = "Read and resume a persisted Copilot operation")
    public CopilotOperationView getOperation(
            @PathVariable Long projectId,
            @PathVariable String operationId) {
        RequestScope scope = scope();
        return automationService.getOperation(
                projectId, operationId, scope.username(), scope.context());
    }

    @PostMapping("/copilot-operations/{operationId}/cancel")
    @Operation(summary = "Cancel all active passes of a Copilot operation")
    public CopilotOperationView cancel(
            @PathVariable Long projectId,
            @PathVariable String operationId) {
        RequestScope scope = scope();
        return automationService.cancelOperation(
                projectId, operationId, scope.username(), scope.context());
    }

    private URI operationLocation(Long projectId, String operationId) {
        return URI.create("/api/projects/" + projectId
                + "/copilot-operations/" + operationId);
    }

    private RequestScope scope() {
        return new RequestScope(
                workspaceResolver.resolveCurrentUsername(),
                workspaceResolver.resolveCurrentContext());
    }

    private record RequestScope(String username, WorkspaceContext context) {
    }
}