feat(02-03): ClickHouse search engine, search controller, and 13 integration tests

- ClickHouseSearchEngine with dynamic WHERE clause building and LIKE escape
- SearchController with GET (basic filters) and POST (advanced JSON body)
- SearchBeanConfig wiring SearchEngine, SearchService, DetailService beans
- 13 integration tests covering all filter types, combinations, pagination, empty results

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
hsiegeln
2026-03-11 16:23:20 +01:00
parent dcae89f404
commit 82a190c8e2
4 changed files with 604 additions and 0 deletions

View File

@@ -0,0 +1,64 @@
package com.cameleer3.server.app.controller;
import com.cameleer3.server.core.search.ExecutionSummary;
import com.cameleer3.server.core.search.SearchRequest;
import com.cameleer3.server.core.search.SearchResult;
import com.cameleer3.server.core.search.SearchService;
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.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.time.Instant;
/**
* Search endpoints for querying route executions.
* <p>
* GET supports basic filters via query parameters. POST accepts a full
* {@link SearchRequest} JSON body for advanced search with all filter types.
*/
@RestController
@RequestMapping("/api/v1/search")
@Tag(name = "Search", description = "Transaction search endpoints")
public class SearchController {
private final SearchService searchService;
public SearchController(SearchService searchService) {
this.searchService = searchService;
}
@GetMapping("/executions")
@Operation(summary = "Search executions with basic filters")
public ResponseEntity<SearchResult<ExecutionSummary>> searchGet(
@RequestParam(required = false) String status,
@RequestParam(required = false) Instant timeFrom,
@RequestParam(required = false) Instant timeTo,
@RequestParam(required = false) String correlationId,
@RequestParam(required = false) String text,
@RequestParam(defaultValue = "0") int offset,
@RequestParam(defaultValue = "50") int limit) {
SearchRequest request = new SearchRequest(
status, timeFrom, timeTo,
null, null,
correlationId,
text, null, null, null,
offset, limit
);
return ResponseEntity.ok(searchService.search(request));
}
@PostMapping("/executions")
@Operation(summary = "Advanced search with all filters")
public ResponseEntity<SearchResult<ExecutionSummary>> searchPost(
@RequestBody SearchRequest request) {
return ResponseEntity.ok(searchService.search(request));
}
}