WorkflowHistoryIndexHttpAdapter.java
package org.hammer.audio.workflow.editor.http;
import java.time.Instant;
import java.util.List;
import java.util.Objects;
import org.hammer.audio.workflow.history.IndexedWorkflowHistorySearch;
import org.hammer.audio.workflow.history.WorkflowHistoryTextQuery;
import org.hammer.audio.workflow.history.WorkflowHistoryTextResult;
import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
/** HTTP boundary for rebuildable indexed checkpoint history search. */
@RestController
@RequestMapping("/workflow/history/index")
@ConditionalOnBean(IndexedWorkflowHistorySearch.class)
public final class WorkflowHistoryIndexHttpAdapter {
private static final String DEFAULT_LIMIT = "20";
private final IndexedWorkflowHistorySearch historySearch;
public WorkflowHistoryIndexHttpAdapter(IndexedWorkflowHistorySearch historySearch) {
this.historySearch = Objects.requireNonNull(historySearch, "historySearch");
}
/**
* Searches messages, paths and deterministic workflow DSL content with optional metadata filters.
*/
@GetMapping
public List<SearchHitResponse> search(
@RequestParam(name = "q", defaultValue = "") String query,
@RequestParam(name = "author", required = false) String authorEmail,
@RequestParam(name = "path", required = false) String pathText,
@RequestParam(name = "from", required = false) Instant from,
@RequestParam(name = "to", required = false) Instant to,
@RequestParam(name = "limit", defaultValue = DEFAULT_LIMIT) int limit) {
return historySearch
.search(new WorkflowHistoryTextQuery(query, authorEmail, pathText, from, to, limit))
.stream()
.map(SearchHitResponse::from)
.toList();
}
/** Rebuilds missing derived projections from authoritative branch history. */
@PostMapping("/rebuild")
public RebuildResponse rebuild(
@RequestParam(name = "branch", defaultValue = "main") String branch,
@RequestParam(name = "limit", defaultValue = "-1") int limit) {
return new RebuildResponse(historySearch.rebuild(branch, limit));
}
/**
* Exact indexed commit hit returned without persistence implementation types.
*
* @param commitId authoritative Git commit identity
* @param message indexed commit summary
* @param authorName author display name, or {@code null}
* @param authorEmail author email, or {@code null}
* @param timestamp author timestamp, or {@code null}
* @param changedPaths first-parent changed paths represented by the projection
*/
public record SearchHitResponse(
String commitId,
String message,
String authorName,
String authorEmail,
Instant timestamp,
List<String> changedPaths) {
public SearchHitResponse {
commitId = Objects.requireNonNull(commitId, "commitId");
message = message == null ? "" : message;
changedPaths = List.copyOf(Objects.requireNonNull(changedPaths, "changedPaths"));
}
static SearchHitResponse from(WorkflowHistoryTextResult result) {
return new SearchHitResponse(
result.commitId().value(),
result.shortMessage(),
result.authorName(),
result.authorEmail(),
result.timestamp(),
result.changedPaths());
}
}
/**
* Outcome of an explicit rebuild request.
*
* @param indexedCommits number of previously missing commit projections created
*/
public record RebuildResponse(int indexedCommits) {
public RebuildResponse {
if (indexedCommits < 0) {
throw new IllegalArgumentException("indexedCommits must be >= 0");
}
}
}
}