ArchitectureRepositoryController.java

package com.taxonomy.workspace.controller;

import com.taxonomy.workspace.model.RepositoryMembership;
import com.taxonomy.workspace.model.RepositoryRole;
import com.taxonomy.workspace.model.RepositoryVisibility;
import com.taxonomy.workspace.model.SystemRepository;
import com.taxonomy.workspace.model.UserWorkspace;
import com.taxonomy.workspace.service.ArchitectureRepositoryProvisioningService;
import com.taxonomy.workspace.service.RepositoryMembershipService;
import com.taxonomy.workspace.service.RepositoryWorkspaceService;
import com.taxonomy.workspace.service.SystemRepositoryService;
import com.taxonomy.workspace.service.WorkspaceResolver;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.security.access.AccessDeniedException;
import org.springframework.web.bind.annotation.DeleteMapping;
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.PutMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;

/** REST catalog for central architecture repositories and their working copies/forks. */
@ConditionalOnProperty(
        prefix = "taxonomy.features.multi-repository-api",
        name = "enabled",
        havingValue = "true",
        matchIfMissing = false)
@RestController
@RequestMapping("/api/repositories")
@Tag(name = "Architecture Repositories")
public class ArchitectureRepositoryController {

    private final SystemRepositoryService repositoryService;
    private final ArchitectureRepositoryProvisioningService provisioningService;
    private final RepositoryWorkspaceService workspaceService;
    private final RepositoryMembershipService membershipService;
    private final WorkspaceResolver workspaceResolver;

    public ArchitectureRepositoryController(
            SystemRepositoryService repositoryService,
            ArchitectureRepositoryProvisioningService provisioningService,
            RepositoryWorkspaceService workspaceService,
            RepositoryMembershipService membershipService,
            WorkspaceResolver workspaceResolver) {
        this.repositoryService = repositoryService;
        this.provisioningService = provisioningService;
        this.workspaceService = workspaceService;
        this.membershipService = membershipService;
        this.workspaceResolver = workspaceResolver;
    }

    @GetMapping
    @Operation(summary = "List visible central architecture repositories")
    public ResponseEntity<List<Map<String, Object>>> listRepositories() {
        String user = workspaceResolver.resolveCurrentUsername();
        return ResponseEntity.ok(repositoryService.listActiveRepositories().stream()
                .filter(repository -> canView(repository, user))
                .map(this::toMap)
                .toList());
    }

    @GetMapping("/{repositoryId}")
    @Operation(summary = "Get one visible central architecture repository")
    public ResponseEntity<Map<String, Object>> getRepository(@PathVariable String repositoryId) {
        String user = workspaceResolver.resolveCurrentUsername();
        SystemRepository repository;
        try {
            repository = repositoryService.getRepository(repositoryId);
        } catch (IllegalArgumentException exception) {
            return ResponseEntity.notFound().build();
        }
        if (!canView(repository, user)) {
            return ResponseEntity.notFound().build();
        }
        return ResponseEntity.ok(toMap(repository));
    }

    @PostMapping
    @Operation(summary = "Create a new central architecture repository")
    public ResponseEntity<?> createRepository(@RequestBody CreateRepositoryRequest request) {
        String user = workspaceResolver.resolveCurrentUsername();
        try {
            SystemRepository repository = provisioningService.createRepository(
                    request.displayName(),
                    request.slug(),
                    request.description(),
                    request.visibility(),
                    user,
                    request.defaultBranch());
            return ResponseEntity.ok(toMap(repository));
        } catch (IllegalArgumentException exception) {
            return ResponseEntity.badRequest().body(Map.of("error", exception.getMessage()));
        } catch (RuntimeException exception) {
            return ResponseEntity.internalServerError().body(Map.of(
                    "error", "Repository provisioning failed",
                    "message", safeMessage(exception)));
        }
    }

    @PostMapping("/{repositoryId}/workspaces")
    @Operation(summary = "Create a personal working copy from a central repository")
    public ResponseEntity<?> createWorkspace(
            @PathVariable String repositoryId,
            @RequestBody CreateWorkspaceRequest request) {
        String user = workspaceResolver.resolveCurrentUsername();
        try {
            SystemRepository repository = repositoryService.getRepository(repositoryId);
            if (!canView(repository, user)) {
                return ResponseEntity.notFound().build();
            }
            if (!membershipService.canContribute(repository, user)) {
                return forbidden("Repository CONTRIBUTOR role required");
            }
            UserWorkspace workspace = workspaceService.createWorkingCopy(
                    user,
                    repositoryId,
                    request.sourceBranch(),
                    request.displayName(),
                    request.description());
            LinkedHashMap<String, Object> result = new LinkedHashMap<>();
            result.put("workspaceId", workspace.getWorkspaceId());
            result.put("displayName", workspace.getDisplayName());
            result.put("sourceRepositoryId", workspace.getSourceRepositoryId());
            result.put("sourceBranch", workspace.getSourceBranch());
            result.put("currentBranch", workspace.getCurrentBranch());
            result.put("baseCommit", workspace.getBaseCommit());
            result.put("currentCommit", workspace.getCurrentCommit());
            result.put("provisioningStatus", workspace.getProvisioningStatus().name());
            return ResponseEntity.ok(result);
        } catch (IllegalArgumentException | IllegalStateException exception) {
            return ResponseEntity.badRequest().body(Map.of("error", safeMessage(exception)));
        }
    }

    @PostMapping("/{repositoryId}/forks")
    @Operation(summary = "Create a durable central fork")
    public ResponseEntity<?> createFork(
            @PathVariable String repositoryId,
            @RequestBody CreateForkRequest request) {
        String user = workspaceResolver.resolveCurrentUsername();
        try {
            SystemRepository source = repositoryService.getRepository(repositoryId);
            if (!canView(source, user)) {
                return ResponseEntity.notFound().build();
            }
            if (!membershipService.canContribute(source, user)) {
                return forbidden("Repository CONTRIBUTOR role required");
            }
            SystemRepository fork = provisioningService.createFork(
                    repositoryId,
                    request.sourceBranch(),
                    request.displayName(),
                    request.slug(),
                    request.description(),
                    request.visibility(),
                    user);
            return ResponseEntity.ok(toMap(fork));
        } catch (IllegalArgumentException exception) {
            return ResponseEntity.badRequest().body(Map.of("error", safeMessage(exception)));
        } catch (RuntimeException exception) {
            return ResponseEntity.internalServerError().body(Map.of(
                    "error", "Fork provisioning failed",
                    "message", safeMessage(exception)));
        }
    }

    @GetMapping("/{repositoryId}/members")
    @Operation(summary = "List repository memberships")
    public ResponseEntity<?> listMemberships(@PathVariable String repositoryId) {
        String user = workspaceResolver.resolveCurrentUsername();
        SystemRepository repository = visibleRepository(repositoryId, user);
        if (repository == null) {
            return ResponseEntity.notFound().build();
        }
        if (!membershipService.isOwner(repository, user)) {
            return forbidden("Repository OWNER role required");
        }
        return ResponseEntity.ok(membershipService.listMemberships(repository, user).stream()
                .map(this::toMembershipMap)
                .toList());
    }

    @PutMapping("/{repositoryId}/members/{username}")
    @Operation(summary = "Assign or change a repository membership")
    public ResponseEntity<?> updateMembership(
            @PathVariable String repositoryId,
            @PathVariable String username,
            @RequestBody UpdateMembershipRequest request) {
        String user = workspaceResolver.resolveCurrentUsername();
        SystemRepository repository = visibleRepository(repositoryId, user);
        if (repository == null) {
            return ResponseEntity.notFound().build();
        }
        if (!membershipService.isOwner(repository, user)) {
            return forbidden("Repository OWNER role required");
        }
        try {
            RepositoryMembership membership = membershipService.assignRole(
                    repository, username, request.role(), user);
            return ResponseEntity.ok(toMembershipMap(membership));
        } catch (IllegalArgumentException | IllegalStateException exception) {
            return ResponseEntity.badRequest().body(Map.of("error", safeMessage(exception)));
        } catch (AccessDeniedException exception) {
            return forbidden(safeMessage(exception));
        }
    }

    @DeleteMapping("/{repositoryId}/members/{username}")
    @Operation(summary = "Remove a repository membership")
    public ResponseEntity<?> removeMembership(
            @PathVariable String repositoryId,
            @PathVariable String username) {
        String user = workspaceResolver.resolveCurrentUsername();
        SystemRepository repository = visibleRepository(repositoryId, user);
        if (repository == null) {
            return ResponseEntity.notFound().build();
        }
        if (!membershipService.isOwner(repository, user)) {
            return forbidden("Repository OWNER role required");
        }
        try {
            membershipService.removeMembership(repository, username, user);
            return ResponseEntity.noContent().build();
        } catch (IllegalArgumentException | IllegalStateException exception) {
            return ResponseEntity.badRequest().body(Map.of("error", safeMessage(exception)));
        } catch (AccessDeniedException exception) {
            return forbidden(safeMessage(exception));
        }
    }

    private SystemRepository visibleRepository(String repositoryId, String username) {
        try {
            SystemRepository repository = repositoryService.getRepository(repositoryId);
            return canView(repository, username) ? repository : null;
        } catch (IllegalArgumentException exception) {
            return null;
        }
    }

    private boolean canView(SystemRepository repository, String username) {
        return membershipService.canRead(repository, username);
    }

    private Map<String, Object> toMap(SystemRepository repository) {
        LinkedHashMap<String, Object> result = new LinkedHashMap<>();
        result.put("repositoryId", repository.getRepositoryId());
        result.put("slug", repository.getSlug());
        result.put("displayName", repository.getDisplayName());
        result.put("description", repository.getDescription());
        result.put("defaultBranch", repository.getDefaultBranch());
        result.put("visibility", repository.getVisibility());
        result.put("lifecycleState", repository.getLifecycleState());
        result.put("ownerType", repository.getOwnerType());
        result.put("ownerId", repository.getOwnerId());
        result.put("primary", repository.isPrimaryRepo());
        result.put("topologyMode", repository.getTopologyMode());
        result.put("upstreamRepositoryId", repository.getUpstreamRepositoryId());
        result.put("upstreamBranch", repository.getUpstreamBranch());
        result.put("forkPointCommit", repository.getForkPointCommit());
        result.put("createdAt", repository.getCreatedAt());
        result.put("updatedAt", repository.getUpdatedAt());
        return result;
    }

    private Map<String, Object> toMembershipMap(RepositoryMembership membership) {
        LinkedHashMap<String, Object> result = new LinkedHashMap<>();
        result.put("username", membership.getUsername());
        result.put("role", membership.getRole());
        result.put("createdAt", membership.getCreatedAt());
        result.put("createdBy", membership.getCreatedBy());
        result.put("updatedAt", membership.getUpdatedAt());
        return result;
    }

    private static ResponseEntity<Map<String, String>> forbidden(String message) {
        return ResponseEntity.status(HttpStatus.FORBIDDEN)
                .body(Map.of("error", message));
    }

    private static String safeMessage(Throwable throwable) {
        return throwable.getMessage() == null ? throwable.getClass().getSimpleName() : throwable.getMessage();
    }

    public record CreateRepositoryRequest(
            String displayName,
            String slug,
            String description,
            RepositoryVisibility visibility,
            String defaultBranch) {
    }

    public record CreateWorkspaceRequest(
            String displayName,
            String description,
            String sourceBranch) {
    }

    public record CreateForkRequest(
            String displayName,
            String slug,
            String description,
            RepositoryVisibility visibility,
            String sourceBranch) {
    }

    public record UpdateMembershipRequest(RepositoryRole role) {
    }
}