Skip to content

Commit b1f12fd

Browse files
TeksuSiKclaude
andauthored
feat: add Drops Public API client (#30)
Add DropsClient covering the Drops reward-claims endpoints: - GET /drops/claims (filters: campaign_id, limit, cursor, user_id, claim_id, external_status) -> DropClaimsResponse (claims + nested cursor) - PATCH /drops/claims -> update external_status for up to 100 claims (204) Models DropClaim, DropClaimsResponse, DropClaimUpdate and GetDropClaimsRequest. Registered in KickClient with a configurable path in KickConfiguration. Organizations has no public REST API (management is via email/dashboard per the docs), so no client is added for it. Refs #21 Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
1 parent 9357631 commit b1f12fd

7 files changed

Lines changed: 296 additions & 0 deletions

File tree

src/main/java/pl/teksusik/kick4j/KickClient.java

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
import pl.teksusik.kick4j.categories.CategoriesClient;
1818
import pl.teksusik.kick4j.channels.ChannelsClient;
1919
import pl.teksusik.kick4j.chat.ChatClient;
20+
import pl.teksusik.kick4j.drops.DropsClient;
2021
import pl.teksusik.kick4j.events.EventsClient;
2122
import pl.teksusik.kick4j.events.handler.BuiltInKickWebhookHandler;
2223
import pl.teksusik.kick4j.events.handler.KickEventDispatcher;
@@ -44,6 +45,7 @@ public class KickClient {
4445
private final KicksClient kicksClient;
4546
private final PublicKeyClient publicKeyClient;
4647
private final EventsClient eventsClient;
48+
private final DropsClient dropsClient;
4749
private final KickSignatureVerifier signatureVerifier;
4850
private final KickEventDispatcher eventDispatcher;
4951

@@ -84,6 +86,7 @@ public boolean handleUnknownProperty(DeserializationContext ctxt, JsonParser p,
8486
this.kicksClient = new KicksClient(httpClient, objectMapper, configuration, this.authorizationClient);
8587
this.publicKeyClient = new PublicKeyClient(httpClient, objectMapper, configuration, this.authorizationClient);
8688
this.eventsClient = new EventsClient(httpClient, objectMapper, configuration, this.authorizationClient);
89+
this.dropsClient = new DropsClient(httpClient, objectMapper, configuration, this.authorizationClient);
8790

8891
this.signatureVerifier = new KickSignatureVerifier(this.publicKeyClient);
8992
this.eventDispatcher = new KickEventDispatcher(objectMapper);
@@ -152,6 +155,10 @@ public EventsClient events() {
152155
return eventsClient;
153156
}
154157

158+
public DropsClient drops() {
159+
return dropsClient;
160+
}
161+
155162
public KickSignatureVerifier signatureVerifier() {
156163
return signatureVerifier;
157164
}

src/main/java/pl/teksusik/kick4j/KickConfiguration.java

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@ public final class KickConfiguration {
3535
private final String channelsRewardsRedemptionsAccept;
3636
private final String channelsRewardsRedemptionsReject;
3737
private final String kicksLeaderboard;
38+
private final String dropsClaims;
3839

3940
private KickConfiguration(Builder builder) {
4041
this.tokenStore = builder.tokenStore;
@@ -67,6 +68,7 @@ private KickConfiguration(Builder builder) {
6768
this.channelsRewardsRedemptionsAccept = builder.channelsRewardsRedemptionsAccept;
6869
this.channelsRewardsRedemptionsReject = builder.channelsRewardsRedemptionsReject;
6970
this.kicksLeaderboard = builder.kicksLeaderboard;
71+
this.dropsClaims = builder.dropsClaims;
7072
}
7173

7274
public static Builder builder() {
@@ -193,6 +195,10 @@ public String getKicksLeaderboard() {
193195
return kicksLeaderboard;
194196
}
195197

198+
public String getDropsClaims() {
199+
return dropsClaims;
200+
}
201+
196202
public static final class Builder {
197203
private RefreshTokenStore tokenStore;
198204
private String clientId;
@@ -224,6 +230,7 @@ public static final class Builder {
224230
private String channelsRewardsRedemptionsAccept = "/channels/rewards/redemptions/accept";
225231
private String channelsRewardsRedemptionsReject = "/channels/rewards/redemptions/reject";
226232
private String kicksLeaderboard = "/kicks/leaderboard";
233+
private String dropsClaims = "/drops/claims";
227234

228235
public Builder tokenStore(RefreshTokenStore tokenStore) {
229236
this.tokenStore = tokenStore;
@@ -375,6 +382,11 @@ public Builder kicksLeaderboard(String kicksLeaderboard) {
375382
return this;
376383
}
377384

385+
public Builder dropsClaims(String dropsClaims) {
386+
this.dropsClaims = dropsClaims;
387+
return this;
388+
}
389+
378390
public KickConfiguration build() {
379391
if (tokenStore == null) {
380392
throw new IllegalStateException("TokenStore is required");
Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
package pl.teksusik.kick4j.drops;
2+
3+
import com.fasterxml.jackson.annotation.JsonCreator;
4+
import com.fasterxml.jackson.annotation.JsonProperty;
5+
6+
import java.time.Instant;
7+
8+
public class DropClaim {
9+
private final String claimId;
10+
private final Integer userId;
11+
private final String campaignId;
12+
private final String rewardId;
13+
private final String externalId;
14+
private final String externalStatus;
15+
private final Instant createdAt;
16+
private final Instant updatedAt;
17+
18+
@JsonCreator
19+
public DropClaim(@JsonProperty("claim_id") String claimId,
20+
@JsonProperty("user_id") Integer userId,
21+
@JsonProperty("campaign_id") String campaignId,
22+
@JsonProperty("reward_id") String rewardId,
23+
@JsonProperty("external_id") String externalId,
24+
@JsonProperty("external_status") String externalStatus,
25+
@JsonProperty("created_at") Instant createdAt,
26+
@JsonProperty("updated_at") Instant updatedAt) {
27+
this.claimId = claimId;
28+
this.userId = userId;
29+
this.campaignId = campaignId;
30+
this.rewardId = rewardId;
31+
this.externalId = externalId;
32+
this.externalStatus = externalStatus;
33+
this.createdAt = createdAt;
34+
this.updatedAt = updatedAt;
35+
}
36+
37+
public String getClaimId() {
38+
return claimId;
39+
}
40+
41+
public Integer getUserId() {
42+
return userId;
43+
}
44+
45+
public String getCampaignId() {
46+
return campaignId;
47+
}
48+
49+
public String getRewardId() {
50+
return rewardId;
51+
}
52+
53+
public String getExternalId() {
54+
return externalId;
55+
}
56+
57+
public String getExternalStatus() {
58+
return externalStatus;
59+
}
60+
61+
public Instant getCreatedAt() {
62+
return createdAt;
63+
}
64+
65+
public Instant getUpdatedAt() {
66+
return updatedAt;
67+
}
68+
}
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
package pl.teksusik.kick4j.drops;
2+
3+
import com.fasterxml.jackson.annotation.JsonProperty;
4+
5+
/**
6+
* A single claim status update for {@code PATCH /drops/claims}.
7+
*/
8+
public class DropClaimUpdate {
9+
@JsonProperty("claim_id")
10+
private final String claimId;
11+
@JsonProperty("external_status")
12+
private final String externalStatus;
13+
14+
public DropClaimUpdate(String claimId, String externalStatus) {
15+
this.claimId = claimId;
16+
this.externalStatus = externalStatus;
17+
}
18+
19+
public String getClaimId() {
20+
return claimId;
21+
}
22+
23+
public String getExternalStatus() {
24+
return externalStatus;
25+
}
26+
}
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
package pl.teksusik.kick4j.drops;
2+
3+
import com.fasterxml.jackson.annotation.JsonCreator;
4+
import com.fasterxml.jackson.annotation.JsonProperty;
5+
6+
import java.util.List;
7+
8+
/**
9+
* A page of Drops reward claims. Unlike the generic paginated endpoints, the cursor
10+
* is nested inside the {@code data} object; pass {@link #getCursor()} back as the
11+
* {@code cursor} filter to fetch the next page.
12+
*/
13+
public class DropClaimsResponse {
14+
private final List<DropClaim> claims;
15+
private final String cursor;
16+
17+
@JsonCreator
18+
public DropClaimsResponse(@JsonProperty("claims") List<DropClaim> claims,
19+
@JsonProperty("cursor") String cursor) {
20+
this.claims = claims;
21+
this.cursor = cursor;
22+
}
23+
24+
public List<DropClaim> getClaims() {
25+
return claims;
26+
}
27+
28+
public String getCursor() {
29+
return cursor;
30+
}
31+
}
Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
package pl.teksusik.kick4j.drops;
2+
3+
import com.fasterxml.jackson.core.type.TypeReference;
4+
import com.fasterxml.jackson.databind.ObjectMapper;
5+
import pl.teksusik.kick4j.KickConfiguration;
6+
import pl.teksusik.kick4j.api.ApiClient;
7+
import pl.teksusik.kick4j.authorization.AuthorizationClient;
8+
9+
import java.net.http.HttpClient;
10+
import java.util.List;
11+
import java.util.Map;
12+
13+
/**
14+
* Drops Public API. Only OAuth apps associated with an organization can access these
15+
* endpoints (they require an app access token).
16+
*/
17+
public class DropsClient extends ApiClient {
18+
public DropsClient(HttpClient httpClient, ObjectMapper mapper, KickConfiguration configuration, AuthorizationClient authorization) {
19+
super(httpClient, mapper, configuration, authorization);
20+
}
21+
22+
/**
23+
* Retrieves Drops reward claims (first page, API default limit).
24+
*/
25+
public DropClaimsResponse getClaims() {
26+
return this.getClaims(GetDropClaimsRequest.builder().build());
27+
}
28+
29+
/**
30+
* Retrieves Drops reward claims using the provided filters. Page through results by
31+
* passing {@link DropClaimsResponse#getCursor()} back as the request cursor.
32+
*/
33+
public DropClaimsResponse getClaims(GetDropClaimsRequest request) {
34+
return this.get(this.configuration.getDropsClaims())
35+
.queryParams(request)
36+
.send(new TypeReference<>() {});
37+
}
38+
39+
/**
40+
* Updates the {@code external_status} of one or more claims (up to 100 per request).
41+
* Returns on 204 No Content.
42+
*/
43+
public void updateClaims(List<DropClaimUpdate> claims) {
44+
this.patch(this.configuration.getDropsClaims())
45+
.body(Map.of("claims", claims))
46+
.send(new TypeReference<>() {});
47+
}
48+
}
Lines changed: 104 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,104 @@
1+
package pl.teksusik.kick4j.drops;
2+
3+
import com.fasterxml.jackson.annotation.JsonInclude;
4+
import com.fasterxml.jackson.annotation.JsonProperty;
5+
6+
/**
7+
* Filters for {@code GET /drops/claims}. All fields are optional.
8+
*/
9+
@JsonInclude(JsonInclude.Include.NON_NULL)
10+
public class GetDropClaimsRequest {
11+
@JsonProperty("campaign_id")
12+
private final String campaignId;
13+
@JsonProperty("limit")
14+
private final Integer limit;
15+
@JsonProperty("cursor")
16+
private final String cursor;
17+
@JsonProperty("user_id")
18+
private final Integer userId;
19+
@JsonProperty("claim_id")
20+
private final String claimId;
21+
@JsonProperty("external_status")
22+
private final String externalStatus;
23+
24+
public GetDropClaimsRequest(String campaignId, Integer limit, String cursor, Integer userId,
25+
String claimId, String externalStatus) {
26+
this.campaignId = campaignId;
27+
this.limit = limit;
28+
this.cursor = cursor;
29+
this.userId = userId;
30+
this.claimId = claimId;
31+
this.externalStatus = externalStatus;
32+
}
33+
34+
public String getCampaignId() {
35+
return campaignId;
36+
}
37+
38+
public Integer getLimit() {
39+
return limit;
40+
}
41+
42+
public String getCursor() {
43+
return cursor;
44+
}
45+
46+
public Integer getUserId() {
47+
return userId;
48+
}
49+
50+
public String getClaimId() {
51+
return claimId;
52+
}
53+
54+
public String getExternalStatus() {
55+
return externalStatus;
56+
}
57+
58+
public static Builder builder() {
59+
return new Builder();
60+
}
61+
62+
public static class Builder {
63+
private String campaignId;
64+
private Integer limit;
65+
private String cursor;
66+
private Integer userId;
67+
private String claimId;
68+
private String externalStatus;
69+
70+
public Builder campaignId(String campaignId) {
71+
this.campaignId = campaignId;
72+
return this;
73+
}
74+
75+
public Builder limit(Integer limit) {
76+
this.limit = limit;
77+
return this;
78+
}
79+
80+
public Builder cursor(String cursor) {
81+
this.cursor = cursor;
82+
return this;
83+
}
84+
85+
public Builder userId(Integer userId) {
86+
this.userId = userId;
87+
return this;
88+
}
89+
90+
public Builder claimId(String claimId) {
91+
this.claimId = claimId;
92+
return this;
93+
}
94+
95+
public Builder externalStatus(String externalStatus) {
96+
this.externalStatus = externalStatus;
97+
return this;
98+
}
99+
100+
public GetDropClaimsRequest build() {
101+
return new GetDropClaimsRequest(campaignId, limit, cursor, userId, claimId, externalStatus);
102+
}
103+
}
104+
}

0 commit comments

Comments
 (0)