Skip to content

Commit 3011dc4

Browse files
authored
Merge pull request #171 from GetStream/audit_logs
[FEEDS-140]chore: add query audit logs support
2 parents afe5e56 + 5aa7e08 commit 3011dc4

File tree

13 files changed

+838
-5
lines changed

13 files changed

+838
-5
lines changed

.github/workflows/ci.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ jobs:
2727
uses: actions/cache@v3
2828
with:
2929
path: ~/.gradle/caches
30-
key: ${{ runner.os }}-gradle-${{ hashFiles('**/*.gradle') }}
30+
key: ${{ runner.os }}-gradle-${{ hashFiles('**/*') }}
3131
restore-keys: |
3232
${{ runner.os }}-gradle-
3333
Lines changed: 97 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,97 @@
1+
package io.getstream.client;
2+
3+
import io.getstream.core.Stream;
4+
import io.getstream.core.http.Token;
5+
import io.getstream.core.exceptions.StreamException;
6+
import io.getstream.core.models.AuditLog;
7+
import io.getstream.core.options.RequestOption;
8+
import io.getstream.core.options.CustomQueryParameter;
9+
import io.getstream.core.utils.Auth.TokenAction;
10+
import java8.util.concurrent.CompletableFuture;
11+
12+
import java.util.ArrayList;
13+
import java.util.List;
14+
15+
import static io.getstream.core.utils.Auth.buildAuditLogsToken;
16+
17+
/**
18+
* Client for querying Stream audit logs.
19+
* Audit logs record changes to various entities within your Stream app.
20+
*/
21+
public final class AuditLogsClient {
22+
private final String secret;
23+
private final Stream stream;
24+
25+
public AuditLogsClient(String secret, Stream stream) {
26+
this.secret = secret;
27+
this.stream = stream;
28+
}
29+
30+
/**
31+
* Query audit logs with the specified filters and default pagination.
32+
*
33+
* @param filters Filters to apply to the query (either entityType+entityID OR userID is required)
34+
* @return CompletableFuture with the query response
35+
* @throws StreamException if the filters are invalid or if there's an API error
36+
*/
37+
public CompletableFuture<QueryAuditLogsResponse> queryAuditLogs(QueryAuditLogsFilters filters) throws StreamException {
38+
return queryAuditLogs(filters, new QueryAuditLogsPager());
39+
}
40+
41+
/**
42+
* Query audit logs with the specified filters and pagination.
43+
*
44+
* @param filters Filters to apply to the query (either entityType+entityID OR userID is required)
45+
* @param pager Pagination settings for the query
46+
* @return CompletableFuture with the query response
47+
* @throws StreamException if the filters are invalid or if there's an API error
48+
*/
49+
public CompletableFuture<QueryAuditLogsResponse> queryAuditLogs(QueryAuditLogsFilters filters, QueryAuditLogsPager pager) throws StreamException {
50+
// Validate filters before making the API call
51+
if (filters == null) {
52+
throw new StreamException("Filters cannot be null for audit logs queries");
53+
}
54+
55+
final Token token = buildAuditLogsToken(secret, TokenAction.READ);
56+
57+
RequestOption[] options = buildRequestOptions(filters, pager);
58+
return stream.queryAuditLogs(token, options);
59+
}
60+
61+
/**
62+
* Builds request options from filters and pagination settings.
63+
*
64+
* @param filters Filters to apply to the query
65+
* @param pager Pagination settings
66+
* @return Array of RequestOption for the API call
67+
*/
68+
private RequestOption[] buildRequestOptions(QueryAuditLogsFilters filters, QueryAuditLogsPager pager) {
69+
List<RequestOption> options = new ArrayList<>();
70+
71+
if (filters.getEntityType() != null && !filters.getEntityType().isEmpty() &&
72+
filters.getEntityID() != null && !filters.getEntityID().isEmpty()) {
73+
options.add(new CustomQueryParameter("entity_type", filters.getEntityType()));
74+
options.add(new CustomQueryParameter("entity_id", filters.getEntityID()));
75+
}
76+
77+
if (filters.getUserID() != null && !filters.getUserID().isEmpty()) {
78+
options.add(new CustomQueryParameter("user_id", filters.getUserID()));
79+
}
80+
81+
if (pager != null) {
82+
if (pager.getNext() != null && !pager.getNext().isEmpty()) {
83+
options.add(new CustomQueryParameter("next", pager.getNext()));
84+
}
85+
86+
if (pager.getPrev() != null && !pager.getPrev().isEmpty()) {
87+
options.add(new CustomQueryParameter("prev", pager.getPrev()));
88+
}
89+
90+
if (pager.getLimit() > 0) {
91+
options.add(new CustomQueryParameter("limit", Integer.toString(pager.getLimit())));
92+
}
93+
}
94+
95+
return options.toArray(new RequestOption[0]);
96+
}
97+
}

src/main/java/io/getstream/client/Client.java

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -256,6 +256,10 @@ public ModerationClient moderation() {
256256
return new ModerationClient(secret, stream.moderation());
257257
}
258258

259+
public AuditLogsClient auditLogs() {
260+
return new AuditLogsClient(secret, stream);
261+
}
262+
259263
public FileStorageClient files() {
260264
return new FileStorageClient(secret, stream.files());
261265
}
Lines changed: 202 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,202 @@
1+
package io.getstream.client;
2+
3+
import io.getstream.core.exceptions.StreamException;
4+
5+
/**
6+
* Filters for querying audit logs.
7+
* Either entityType+entityID pair OR userID is required by the API.
8+
*
9+
* Common entity types in Stream include:
10+
* - "activity" - for feed activities
11+
* - "reaction" - for activity reactions
12+
* - "user" - for Stream users
13+
* - "feed" - for feed configurations
14+
*/
15+
public class QueryAuditLogsFilters {
16+
private String entityType;
17+
private String entityID;
18+
private String userID;
19+
20+
/**
21+
* Private constructor, use builder instead.
22+
*/
23+
private QueryAuditLogsFilters() {
24+
}
25+
26+
/**
27+
* Creates a new builder for QueryAuditLogsFilters.
28+
*
29+
* @return a new QueryAuditLogsFilters.Builder
30+
*/
31+
public static Builder builder() {
32+
return new Builder();
33+
}
34+
35+
/**
36+
* Creates a new filter for user ID queries.
37+
*
38+
* @param userID The ID of the user
39+
* @return a new QueryAuditLogsFilters with the user ID set
40+
*/
41+
public static QueryAuditLogsFilters forUser(String userID) {
42+
return builder().withUserID(userID).build();
43+
}
44+
45+
/**
46+
* Creates a new filter for entity type and ID queries.
47+
*
48+
* @param entityType The type of entity (e.g., "activity", "reaction", "user", "feed")
49+
* @param entityID The ID of the entity
50+
* @return a new QueryAuditLogsFilters with the entity type and ID set
51+
*/
52+
public static QueryAuditLogsFilters forEntity(String entityType, String entityID) {
53+
return builder().withEntityType(entityType).withEntityID(entityID).build();
54+
}
55+
56+
/**
57+
* Convenience method to create a filter for activity entities.
58+
*
59+
* @param activityID The ID of the activity
60+
* @return a new QueryAuditLogsFilters for the activity
61+
*/
62+
public static QueryAuditLogsFilters forActivity(String activityID) {
63+
return forEntity("activity", activityID);
64+
}
65+
66+
/**
67+
* Convenience method to create a filter for reaction entities.
68+
*
69+
* @param reactionID The ID of the reaction
70+
* @return a new QueryAuditLogsFilters for the reaction
71+
*/
72+
public static QueryAuditLogsFilters forReaction(String reactionID) {
73+
return forEntity("reaction", reactionID);
74+
}
75+
76+
public String getEntityType() {
77+
return entityType;
78+
}
79+
80+
public String getEntityID() {
81+
return entityID;
82+
}
83+
84+
public String getUserID() {
85+
return userID;
86+
}
87+
88+
/**
89+
* Set the entity type for existing filter instance.
90+
*
91+
* @param entityType The type of entity (e.g., "activity", "reaction")
92+
* @return this instance for method chaining
93+
*/
94+
public QueryAuditLogsFilters setEntityType(String entityType) {
95+
this.entityType = entityType;
96+
return this;
97+
}
98+
99+
/**
100+
* Set the entity ID for existing filter instance.
101+
*
102+
* @param entityID The ID of the entity
103+
* @return this instance for method chaining
104+
*/
105+
public QueryAuditLogsFilters setEntityID(String entityID) {
106+
this.entityID = entityID;
107+
return this;
108+
}
109+
110+
/**
111+
* Set the user ID for existing filter instance.
112+
*
113+
* @param userID The ID of the user
114+
* @return this instance for method chaining
115+
*/
116+
public QueryAuditLogsFilters setUserID(String userID) {
117+
this.userID = userID;
118+
return this;
119+
}
120+
121+
/**
122+
* Validates that the filters contain the required fields.
123+
* Either (entityType AND entityID) OR userID must be set.
124+
*
125+
* @throws StreamException if the required fields are not set
126+
*/
127+
public void validate() throws StreamException {
128+
boolean hasEntityFields = entityType != null && !entityType.isEmpty() &&
129+
entityID != null && !entityID.isEmpty();
130+
boolean hasUserID = userID != null && !userID.isEmpty();
131+
132+
if (!hasEntityFields && !hasUserID) {
133+
throw new StreamException("Either entityType+entityID or userID is required for audit logs queries");
134+
}
135+
}
136+
137+
/**
138+
* Checks if the filter is valid according to API requirements.
139+
*
140+
* @return true if either (entityType AND entityID) OR userID is set
141+
*/
142+
public boolean isValid() {
143+
boolean hasEntityFields = entityType != null && !entityType.isEmpty() &&
144+
entityID != null && !entityID.isEmpty();
145+
boolean hasUserID = userID != null && !userID.isEmpty();
146+
147+
return hasEntityFields || hasUserID;
148+
}
149+
150+
/**
151+
* Builder class for QueryAuditLogsFilters.
152+
*/
153+
public static class Builder {
154+
private final QueryAuditLogsFilters filters;
155+
156+
private Builder() {
157+
filters = new QueryAuditLogsFilters();
158+
}
159+
160+
/**
161+
* Set the entity type.
162+
*
163+
* @param entityType The type of entity (e.g., "activity", "reaction")
164+
* @return this builder for method chaining
165+
*/
166+
public Builder withEntityType(String entityType) {
167+
filters.entityType = entityType;
168+
return this;
169+
}
170+
171+
/**
172+
* Set the entity ID.
173+
*
174+
* @param entityID The ID of the entity
175+
* @return this builder for method chaining
176+
*/
177+
public Builder withEntityID(String entityID) {
178+
filters.entityID = entityID;
179+
return this;
180+
}
181+
182+
/**
183+
* Set the user ID.
184+
*
185+
* @param userID The ID of the user
186+
* @return this builder for method chaining
187+
*/
188+
public Builder withUserID(String userID) {
189+
filters.userID = userID;
190+
return this;
191+
}
192+
193+
/**
194+
* Builds the QueryAuditLogsFilters instance.
195+
*
196+
* @return a new QueryAuditLogsFilters instance
197+
*/
198+
public QueryAuditLogsFilters build() {
199+
return filters;
200+
}
201+
}
202+
}
Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
package io.getstream.client;
2+
3+
public class QueryAuditLogsPager {
4+
private String next;
5+
private String prev;
6+
private int limit;
7+
8+
public QueryAuditLogsPager() {
9+
}
10+
11+
public QueryAuditLogsPager(int limit) {
12+
this.limit = limit;
13+
}
14+
15+
public QueryAuditLogsPager(String next, String prev, int limit) {
16+
this.next = next;
17+
this.prev = prev;
18+
this.limit = limit;
19+
}
20+
21+
public String getNext() {
22+
return next;
23+
}
24+
25+
public void setNext(String next) {
26+
this.next = next;
27+
}
28+
29+
public String getPrev() {
30+
return prev;
31+
}
32+
33+
public void setPrev(String prev) {
34+
this.prev = prev;
35+
}
36+
37+
public int getLimit() {
38+
return limit;
39+
}
40+
41+
public void setLimit(int limit) {
42+
this.limit = limit;
43+
}
44+
}

0 commit comments

Comments
 (0)