feat(events): cursor-paginated GET /agents/events

Returns {data, nextCursor, hasMore} instead of a bare list. Adds
?cursor= param; existing filters (appId, agentId, from, to, limit)
unchanged. Ordering is (timestamp DESC, instance_id ASC).
This commit is contained in:
hsiegeln
2026-04-17 12:22:48 +02:00
parent 0194549f25
commit 20b8d4ccaf
2 changed files with 23 additions and 9 deletions

View File

@@ -1,7 +1,9 @@
package com.cameleer.server.app.controller;
import com.cameleer.server.app.dto.AgentEventPageResponse;
import com.cameleer.server.app.dto.AgentEventResponse;
import com.cameleer.server.app.web.EnvPath;
import com.cameleer.server.core.agent.AgentEventPage;
import com.cameleer.server.core.agent.AgentEventService;
import com.cameleer.server.core.runtime.Environment;
import io.swagger.v3.oas.annotations.Operation;
@@ -14,7 +16,6 @@ import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import java.time.Instant;
import java.util.List;
@RestController
@RequestMapping("/api/v1/environments/{envSlug}/agents/events")
@@ -29,24 +30,25 @@ public class AgentEventsController {
@GetMapping
@Operation(summary = "Query agent events in this environment",
description = "Returns agent lifecycle events, optionally filtered by app and/or agent ID")
@ApiResponse(responseCode = "200", description = "Events returned")
public ResponseEntity<List<AgentEventResponse>> getEvents(
description = "Cursor-paginated. Returns newest first. Pass nextCursor back as ?cursor= for the next page.")
@ApiResponse(responseCode = "200", description = "Event page returned")
public ResponseEntity<AgentEventPageResponse> getEvents(
@EnvPath Environment env,
@RequestParam(required = false) String appId,
@RequestParam(required = false) String agentId,
@RequestParam(required = false) String from,
@RequestParam(required = false) String to,
@RequestParam(required = false) String cursor,
@RequestParam(defaultValue = "50") int limit) {
Instant fromInstant = from != null ? Instant.parse(from) : null;
Instant toInstant = to != null ? Instant.parse(to) : null;
var events = agentEventService.queryEvents(appId, agentId, env.slug(), fromInstant, toInstant, limit)
.stream()
.map(AgentEventResponse::from)
.toList();
AgentEventPage page = agentEventService.queryEventPage(
appId, agentId, env.slug(), fromInstant, toInstant, cursor, limit);
return ResponseEntity.ok(events);
var data = page.data().stream().map(AgentEventResponse::from).toList();
return ResponseEntity.ok(new AgentEventPageResponse(data, page.nextCursor(), page.hasMore()));
}
}

View File

@@ -0,0 +1,12 @@
package com.cameleer.server.app.dto;
import io.swagger.v3.oas.annotations.media.Schema;
import java.util.List;
@Schema(description = "Cursor-paginated agent event list")
public record AgentEventPageResponse(
List<AgentEventResponse> data,
String nextCursor,
boolean hasMore
) {}