From 67aba2a8eb7b9458850894be4bd8398c1ad35dd0 Mon Sep 17 00:00:00 2001 From: navin10sharma <3096611+navin10sharma@users.noreply.github.com> Date: Fri, 21 Aug 2026 11:21:46 +0530 Subject: [PATCH 1/2] feat: add Performance Groups server resource --- .../java/io/seatlayer/PerformanceGroups.java | 136 ++++++++++++++++++ src/main/java/io/seatlayer/SeatLayer.java | 7 + src/test/java/io/seatlayer/ClientTest.java | 51 +++++++ 3 files changed, 194 insertions(+) create mode 100644 src/main/java/io/seatlayer/PerformanceGroups.java diff --git a/src/main/java/io/seatlayer/PerformanceGroups.java b/src/main/java/io/seatlayer/PerformanceGroups.java new file mode 100644 index 0000000..5b2beb9 --- /dev/null +++ b/src/main/java/io/seatlayer/PerformanceGroups.java @@ -0,0 +1,136 @@ +package io.seatlayer; + +import static io.seatlayer.SeatLayerHttpClient.body; +import static io.seatlayer.SeatLayerHttpClient.encode; + +import java.util.List; +import java.util.Map; + +/** + * Fixed multi-performance runs, trusted hold inspection, and host-authorized booking. + * + *

This is a secret-key resource. Pass only the one-time token returned from + * {@link #createBuyerAccessSession(String, String, boolean)} to PerformanceGroupPicker in a browser. + */ +public final class PerformanceGroups { + + private final SeatLayerHttpClient http; + + PerformanceGroups(SeatLayerHttpClient http) { + this.http = http; + } + + private static String path(String performanceGroupKey, String suffix) { + return "/v1/performance-groups/" + encode(performanceGroupKey) + suffix; + } + + public Map list() { + return http.get("/v1/performance-groups"); + } + + public Map list( + String workspaceId, String externalRef, String state, Integer limit, String cursor) { + return http.get( + "/v1/performance-groups", + body( + "workspaceId", workspaceId, + "externalRef", externalRef, + "state", state, + "limit", limit, + "cursor", cursor)); + } + + /** Create a draft run with exact idempotency replay. */ + public Map create(String name, List eventKeys) { + return create(name, eventKeys, null, null); + } + + public Map create( + String name, List eventKeys, String externalRef, String idempotencyKey) { + return http.postWithHeaderReplay( + "/v1/performance-groups", + body("name", name, "eventKeys", eventKeys, "externalRef", externalRef), + idempotencyKey); + } + + public Map retrieve(String performanceGroupKey) { + return http.get(path(performanceGroupKey, "")); + } + + /** Delete a draft only; activated runs remain available for audit. */ + public void delete(String performanceGroupKey) { + http.delete(path(performanceGroupKey, "")); + } + + /** Start activation. Poll retrieveLifecycle when the returned operation is not terminal. */ + public Map activate(String performanceGroupKey, int expectedRevision) { + return http.post(path(performanceGroupKey, "/activate"), body("expectedRevision", expectedRevision)); + } + + /** Stop new group sales. Poll retrieveLifecycle until the close becomes terminal. */ + public Map close(String performanceGroupKey, int expectedRevision) { + return http.post(path(performanceGroupKey, "/close"), body("expectedRevision", expectedRevision)); + } + + public Map retrieveLifecycle(String performanceGroupKey, String operationId) { + return http.get(path(performanceGroupKey, "/lifecycle/" + encode(operationId))); + } + + /** + * Reveal one origin-bound browser bearer. This call is deliberately single-attempt. + */ + public Map createBuyerAccessSession( + String performanceGroupKey, String allowedOrigin, boolean includePublic) { + return createBuyerAccessSession( + performanceGroupKey, allowedOrigin, includePublic, null, null, null, null, null); + } + + public Map createBuyerAccessSession( + String performanceGroupKey, + String allowedOrigin, + boolean includePublic, + Map> channelIdsByEvent, + Integer expiresInSeconds, + Integer maxQuantity, + String buyerRef, + String partnerRef) { + return http.post( + path(performanceGroupKey, "/buyer-access-sessions"), + body( + "allowedOrigin", allowedOrigin, + "includePublic", includePublic, + "channelIdsByEvent", channelIdsByEvent, + "expiresInSeconds", expiresInSeconds, + "maxQuantity", maxQuantity, + "buyerRef", buyerRef, + "partnerRef", partnerRef)); + } + + public Map listBuyerAccessSessions(String performanceGroupKey) { + return listBuyerAccessSessions(performanceGroupKey, null); + } + + public Map listBuyerAccessSessions(String performanceGroupKey, Integer limit) { + return http.get(path(performanceGroupKey, "/buyer-access-sessions"), body("limit", limit)); + } + + public Map revokeBuyerAccessSession(String performanceGroupKey, String sessionId) { + return http.delete(path(performanceGroupKey, "/buyer-access-sessions/" + encode(sessionId))); + } + + public Map retrieveHold(String performanceGroupKey, String operationId) { + return http.get(path(performanceGroupKey, "/holds/" + encode(operationId))); + } + + /** Reuse both stable IDs and poll retrieveBooking while the returned booking is pending. */ + public Map bookHold( + String performanceGroupKey, String operationId, String bookActionId, String bookingRef) { + return http.post( + path(performanceGroupKey, "/holds/" + encode(operationId) + "/book"), + body("bookActionId", bookActionId, "bookingRef", bookingRef)); + } + + public Map retrieveBooking(String performanceGroupKey, String actionId) { + return http.get(path(performanceGroupKey, "/bookings/" + encode(actionId))); + } +} diff --git a/src/main/java/io/seatlayer/SeatLayer.java b/src/main/java/io/seatlayer/SeatLayer.java index 29ba74e..7d733b0 100644 --- a/src/main/java/io/seatlayer/SeatLayer.java +++ b/src/main/java/io/seatlayer/SeatLayer.java @@ -21,6 +21,7 @@ public final class SeatLayer { private final Channels channels; private final Events events; private final Inventory inventory; + private final PerformanceGroups performanceGroups; private final Sessions sessions; private final Templates templates; private final Webhooks webhooks; @@ -37,6 +38,7 @@ private SeatLayer(Builder builder) { this.channels = new Channels(http); this.events = new Events(http); this.inventory = new Inventory(http); + this.performanceGroups = new PerformanceGroups(http); this.sessions = new Sessions(http); this.templates = new Templates(http); this.webhooks = new Webhooks(http); @@ -64,6 +66,11 @@ public Inventory inventory() { return inventory; } + /** Fixed multi-performance lifecycle and host-side booking coordination. */ + public PerformanceGroups performanceGroups() { + return performanceGroups; + } + public Sessions sessions() { return sessions; } diff --git a/src/test/java/io/seatlayer/ClientTest.java b/src/test/java/io/seatlayer/ClientTest.java index da26cb7..90090fc 100644 --- a/src/test/java/io/seatlayer/ClientTest.java +++ b/src/test/java/io/seatlayer/ClientTest.java @@ -180,6 +180,57 @@ void dropsNullQueryParameters() { sdk.charts().list(ChartListOptions.builder().workspaceId("ws_1").build()); assertEquals("https://api.seatlayer.io/v1/charts?workspaceId=ws_1", call(0).url()); } + + @Test + @DisplayName("maps the full Performance Groups lifecycle") + void performanceGroupLifecycle() { + SeatLayer sdk = client(List.of( + Stub.of(200, "{\"performanceGroups\":[]}"), + Stub.of(201, "{\"performanceGroup\":{}}"), + Stub.of(200, "{\"performanceGroup\":{}}"), + Stub.of(204, ""), + Stub.of(202, "{\"lifecycleOperation\":{\"terminal\":false}}"), + Stub.of(200, "{\"lifecycleOperation\":{\"terminal\":true}}"), + Stub.of(200, "{\"lifecycleOperation\":{}}"), + Stub.of(201, "{\"token\":\"bsg_secret\"}"), + Stub.of(200, "{\"sessions\":[]}"), + Stub.of(200, "{\"ok\":true}"), + Stub.of(200, "{\"hold\":{}}"), + Stub.of(202, "{\"booking\":{\"state\":\"book_pending\"}}"), + Stub.of(200, "{\"booking\":{\"state\":\"booked\"}}"))); + String groupKey = "pg_a/b"; + + sdk.performanceGroups().list("ws_1", null, "draft", null, null); + sdk.performanceGroups().create("Weekend run", List.of("ev_1", "ev_2"), null, "weekend-run-1"); + sdk.performanceGroups().retrieve(groupKey); + sdk.performanceGroups().delete(groupKey); + sdk.performanceGroups().activate(groupKey, 1); + sdk.performanceGroups().close(groupKey, 2); + sdk.performanceGroups().retrieveLifecycle(groupKey, "pga_1"); + sdk.performanceGroups().createBuyerAccessSession(groupKey, "https://tickets.example.test", true); + sdk.performanceGroups().listBuyerAccessSessions(groupKey, 25); + sdk.performanceGroups().revokeBuyerAccessSession(groupKey, "pgbs_1"); + sdk.performanceGroups().retrieveHold(groupKey, "pgh_1"); + sdk.performanceGroups().bookHold(groupKey, "pgh_1", "book_1", "order_1"); + sdk.performanceGroups().retrieveBooking(groupKey, "book_1"); + + String base = "https://api.seatlayer.io/v1/performance-groups/pg_a%2Fb"; + assertEquals("https://api.seatlayer.io/v1/performance-groups?workspaceId=ws_1&state=draft", call(0).url()); + assertEquals("weekend-run-1", call(1).headers().get("Idempotency-Key")); + assertEquals(base, call(2).url()); + assertEquals("DELETE", call(3).method()); + assertEquals(base + "/activate", call(4).url()); + assertEquals(base + "/close", call(5).url()); + assertEquals(base + "/lifecycle/pga_1", call(6).url()); + assertEquals(base + "/buyer-access-sessions", call(7).url()); + assertNull(call(7).headers().get("Idempotency-Key")); + assertEquals(base + "/buyer-access-sessions?limit=25", call(8).url()); + assertEquals(base + "/buyer-access-sessions/pgbs_1", call(9).url()); + assertEquals(base + "/holds/pgh_1", call(10).url()); + assertEquals(base + "/holds/pgh_1/book", call(11).url()); + assertNull(call(11).headers().get("Idempotency-Key")); + assertEquals(base + "/bookings/book_1", call(12).url()); + } } @Nested From a8390d040dd39515822193be391d81b86a158581 Mon Sep 17 00:00:00 2001 From: navin10sharma <3096611+navin10sharma@users.noreply.github.com> Date: Fri, 21 Aug 2026 11:27:52 +0530 Subject: [PATCH 2/2] release: prepare 0.5.0 --- CHANGELOG.md | 8 ++++++++ README.md | 4 ++-- pom.xml | 2 +- 3 files changed, 11 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index bd5aeb6..00c9468 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,14 @@ ## Unreleased +## 0.5.0 — 2026-08-21 + +- Added `performanceGroups()`, the trusted server resource for fixed two-to-eight + performance runs. It creates and activates groups, mints one-time browser + access, retrieves authoritative group holds, and confirms bookings with + stable action and order references. Browser-only group routes remain outside + this secret-key SDK. + - **Security/reliability:** Mutations now default to a single attempt. Automatic header-replay retries are limited to chart create/copy, template instantiation, event create, and workspace create, preventing transient failures from duplicating holds or best-available results and from diff --git a/README.md b/README.md index a431026..687a3f4 100644 --- a/README.md +++ b/README.md @@ -16,12 +16,12 @@ Official Java server SDK for the [SeatLayer](https://seatlayer.io) reserved-seat io.seatlayer seatlayer-java - 0.4.0 + 0.5.0 ``` ```groovy -implementation 'io.seatlayer:seatlayer-java:0.4.0' +implementation 'io.seatlayer:seatlayer-java:0.5.0' ``` Requires Java 17 or newer. **Zero runtime dependencies** — the SDK uses diff --git a/pom.xml b/pom.xml index d7f6387..bac198d 100644 --- a/pom.xml +++ b/pom.xml @@ -6,7 +6,7 @@ io.seatlayer seatlayer-java - 0.4.0 + 0.5.0 jar SeatLayer Java SDK