AdminSearchController.java
package com.ecommerce.catalog.interfaces.rest;
import com.ecommerce.catalog.application.model.SearchModels.SearchOutboxView;
import com.ecommerce.catalog.application.model.SearchModels.SearchRebuildRecoveryView;
import com.ecommerce.catalog.application.model.SearchModels.SearchRebuildView;
import com.ecommerce.catalog.application.model.SearchModels.SearchReconciliationIssueView;
import com.ecommerce.catalog.application.model.SearchModels.SearchReconciliationResult;
import com.ecommerce.catalog.application.model.SearchModels.SearchRecoveryView;
import com.ecommerce.catalog.application.service.CatalogSearchOutboxService;
import com.ecommerce.catalog.application.service.CatalogSearchRebuildService;
import com.ecommerce.catalog.application.service.CatalogSearchReconciliationService;
import com.ecommerce.platform.common.api.ApiResponse;
import jakarta.validation.Valid;
import jakarta.validation.constraints.Max;
import jakarta.validation.constraints.Min;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.Pattern;
import jakarta.validation.constraints.Positive;
import jakarta.validation.constraints.Size;
import org.springframework.security.core.annotation.AuthenticationPrincipal;
import org.springframework.security.oauth2.jwt.Jwt;
import org.springframework.validation.annotation.Validated;
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.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import java.util.List;
@Validated
@RestController
@RequestMapping("/api/v1/catalog/admin/search")
public class AdminSearchController {
private final CatalogSearchOutboxService outboxService;
private final CatalogSearchRebuildService rebuildService;
private final CatalogSearchReconciliationService reconciliationService;
public AdminSearchController(
CatalogSearchOutboxService outboxService,
CatalogSearchRebuildService rebuildService,
CatalogSearchReconciliationService reconciliationService) {
this.outboxService = outboxService;
this.rebuildService = rebuildService;
this.reconciliationService = reconciliationService;
}
@GetMapping("/outbox")
public ApiResponse<List<SearchOutboxView>> outbox(
@RequestParam(defaultValue = "NEEDS_ATTENTION")
@Pattern(regexp = "PENDING|PROJECTING|PUBLISHED|NEEDS_ATTENTION") String status,
@RequestParam(defaultValue = "50") @Min(1) @Max(100) int limit) {
return ApiResponse.success(outboxService.list(status, limit));
}
@PostMapping("/outbox/{outboxId}/recover")
public ApiResponse<SearchRecoveryView> recoverOutbox(
@AuthenticationPrincipal Jwt jwt,
@PathVariable @Size(min = 36, max = 36) String outboxId,
@Valid @RequestBody RecoveryRequest request) {
return ApiResponse.success(outboxService.recover(
outboxId,
userId(jwt),
request.commandId(),
request.reason()));
}
@PostMapping("/rebuilds")
public ApiResponse<SearchRebuildView> rebuild(
@AuthenticationPrincipal Jwt jwt,
@Valid @RequestBody RebuildRequest request) {
return ApiResponse.success(rebuildService.submit(
userId(jwt),
request.commandId(),
request.reason()));
}
@GetMapping("/rebuilds/{rebuildId}")
public ApiResponse<SearchRebuildView> rebuild(
@PathVariable @Positive long rebuildId) {
return ApiResponse.success(rebuildService.get(rebuildId));
}
@GetMapping("/rebuilds")
public ApiResponse<List<SearchRebuildView>> rebuilds(
@RequestParam(defaultValue = "20") @Min(1) @Max(100) int limit) {
return ApiResponse.success(rebuildService.list(limit));
}
@PostMapping("/rebuilds/{rebuildId}/recover")
public ApiResponse<SearchRebuildRecoveryView> recoverRebuild(
@AuthenticationPrincipal Jwt jwt,
@PathVariable @Positive long rebuildId,
@Valid @RequestBody RecoveryRequest request) {
return ApiResponse.success(rebuildService.recover(
rebuildId,
userId(jwt),
request.commandId(),
request.reason()));
}
@PostMapping("/reconciliation")
public ApiResponse<SearchReconciliationResult> reconcile(
@Valid @RequestBody ReconciliationRequest request) {
return ApiResponse.success(reconciliationService.reconcile(request.repair()));
}
@GetMapping("/reconciliation/issues")
public ApiResponse<List<SearchReconciliationIssueView>> reconciliationIssues(
@RequestParam(defaultValue = "OPEN")
@Pattern(regexp = "OPEN|RESOLVED") String status,
@RequestParam(defaultValue = "50") @Min(1) @Max(100) int limit) {
return ApiResponse.success(reconciliationService.listIssues(status, limit));
}
private long userId(Jwt jwt) {
try {
long value = Long.parseLong(jwt.getSubject());
if (value <= 0) {
throw new IllegalArgumentException("JWT subject must be positive");
}
return value;
} catch (RuntimeException exception) {
throw new IllegalArgumentException("JWT subject is invalid", exception);
}
}
public record RebuildRequest(
@NotBlank @Size(max = 64) String commandId,
@NotBlank @Size(min = 8, max = 500) String reason) {
}
public record RecoveryRequest(
@NotBlank @Size(max = 64) String commandId,
@NotBlank @Size(min = 8, max = 500) String reason) {
}
public record ReconciliationRequest(boolean repair) {
}
}