Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ jobs:
uses: actions/cache@v3
with:
path: ~/.gradle/caches
key: ${{ runner.os }}-gradle-${{ hashFiles('**/*.gradle') }}
key: ${{ runner.os }}-gradle-${{ hashFiles('**/*') }}
restore-keys: |
${{ runner.os }}-gradle-

Expand Down
97 changes: 97 additions & 0 deletions src/main/java/io/getstream/client/AuditLogsClient.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
package io.getstream.client;

import io.getstream.core.Stream;
import io.getstream.core.http.Token;
import io.getstream.core.exceptions.StreamException;
import io.getstream.core.models.AuditLog;
import io.getstream.core.options.RequestOption;
import io.getstream.core.options.CustomQueryParameter;
import io.getstream.core.utils.Auth.TokenAction;
import java8.util.concurrent.CompletableFuture;

import java.util.ArrayList;
import java.util.List;

import static io.getstream.core.utils.Auth.buildAuditLogsToken;

/**
* Client for querying Stream audit logs.
* Audit logs record changes to various entities within your Stream app.
*/
public final class AuditLogsClient {
private final String secret;
private final Stream stream;

public AuditLogsClient(String secret, Stream stream) {
this.secret = secret;
this.stream = stream;
}

/**
* Query audit logs with the specified filters and default pagination.
*
* @param filters Filters to apply to the query (either entityType+entityID OR userID is required)
* @return CompletableFuture with the query response
* @throws StreamException if the filters are invalid or if there's an API error
*/
public CompletableFuture<QueryAuditLogsResponse> queryAuditLogs(QueryAuditLogsFilters filters) throws StreamException {
return queryAuditLogs(filters, new QueryAuditLogsPager());
}

/**
* Query audit logs with the specified filters and pagination.
*
* @param filters Filters to apply to the query (either entityType+entityID OR userID is required)
* @param pager Pagination settings for the query
* @return CompletableFuture with the query response
* @throws StreamException if the filters are invalid or if there's an API error
*/
public CompletableFuture<QueryAuditLogsResponse> queryAuditLogs(QueryAuditLogsFilters filters, QueryAuditLogsPager pager) throws StreamException {
// Validate filters before making the API call
if (filters == null) {
throw new StreamException("Filters cannot be null for audit logs queries");
}

final Token token = buildAuditLogsToken(secret, TokenAction.READ);

RequestOption[] options = buildRequestOptions(filters, pager);
return stream.queryAuditLogs(token, options);
}

/**
* Builds request options from filters and pagination settings.
*
* @param filters Filters to apply to the query
* @param pager Pagination settings
* @return Array of RequestOption for the API call
*/
private RequestOption[] buildRequestOptions(QueryAuditLogsFilters filters, QueryAuditLogsPager pager) {
List<RequestOption> options = new ArrayList<>();

if (filters.getEntityType() != null && !filters.getEntityType().isEmpty() &&
filters.getEntityID() != null && !filters.getEntityID().isEmpty()) {
options.add(new CustomQueryParameter("entity_type", filters.getEntityType()));
options.add(new CustomQueryParameter("entity_id", filters.getEntityID()));
}

if (filters.getUserID() != null && !filters.getUserID().isEmpty()) {
options.add(new CustomQueryParameter("user_id", filters.getUserID()));
}

if (pager != null) {
if (pager.getNext() != null && !pager.getNext().isEmpty()) {
options.add(new CustomQueryParameter("next", pager.getNext()));
}

if (pager.getPrev() != null && !pager.getPrev().isEmpty()) {
options.add(new CustomQueryParameter("prev", pager.getPrev()));
}

if (pager.getLimit() > 0) {
options.add(new CustomQueryParameter("limit", Integer.toString(pager.getLimit())));
}
}

return options.toArray(new RequestOption[0]);
}
}
4 changes: 4 additions & 0 deletions src/main/java/io/getstream/client/Client.java
Original file line number Diff line number Diff line change
Expand Up @@ -256,6 +256,10 @@ public ModerationClient moderation() {
return new ModerationClient(secret, stream.moderation());
}

public AuditLogsClient auditLogs() {
return new AuditLogsClient(secret, stream);
}

public FileStorageClient files() {
return new FileStorageClient(secret, stream.files());
}
Expand Down
202 changes: 202 additions & 0 deletions src/main/java/io/getstream/client/QueryAuditLogsFilters.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,202 @@
package io.getstream.client;

import io.getstream.core.exceptions.StreamException;

/**
* Filters for querying audit logs.
* Either entityType+entityID pair OR userID is required by the API.
*
* Common entity types in Stream include:
* - "activity" - for feed activities
* - "reaction" - for activity reactions
* - "user" - for Stream users
* - "feed" - for feed configurations
*/
public class QueryAuditLogsFilters {
private String entityType;
private String entityID;
private String userID;

/**
* Private constructor, use builder instead.
*/
private QueryAuditLogsFilters() {
}

/**
* Creates a new builder for QueryAuditLogsFilters.
*
* @return a new QueryAuditLogsFilters.Builder
*/
public static Builder builder() {
return new Builder();
}

/**
* Creates a new filter for user ID queries.
*
* @param userID The ID of the user
* @return a new QueryAuditLogsFilters with the user ID set
*/
public static QueryAuditLogsFilters forUser(String userID) {
return builder().withUserID(userID).build();
}

/**
* Creates a new filter for entity type and ID queries.
*
* @param entityType The type of entity (e.g., "activity", "reaction", "user", "feed")
* @param entityID The ID of the entity
* @return a new QueryAuditLogsFilters with the entity type and ID set
*/
public static QueryAuditLogsFilters forEntity(String entityType, String entityID) {
return builder().withEntityType(entityType).withEntityID(entityID).build();
}

/**
* Convenience method to create a filter for activity entities.
*
* @param activityID The ID of the activity
* @return a new QueryAuditLogsFilters for the activity
*/
public static QueryAuditLogsFilters forActivity(String activityID) {
return forEntity("activity", activityID);
}

/**
* Convenience method to create a filter for reaction entities.
*
* @param reactionID The ID of the reaction
* @return a new QueryAuditLogsFilters for the reaction
*/
public static QueryAuditLogsFilters forReaction(String reactionID) {
return forEntity("reaction", reactionID);
}

public String getEntityType() {
return entityType;
}

public String getEntityID() {
return entityID;
}

public String getUserID() {
return userID;
}

/**
* Set the entity type for existing filter instance.
*
* @param entityType The type of entity (e.g., "activity", "reaction")
* @return this instance for method chaining
*/
public QueryAuditLogsFilters setEntityType(String entityType) {
this.entityType = entityType;
return this;
}

/**
* Set the entity ID for existing filter instance.
*
* @param entityID The ID of the entity
* @return this instance for method chaining
*/
public QueryAuditLogsFilters setEntityID(String entityID) {
this.entityID = entityID;
return this;
}

/**
* Set the user ID for existing filter instance.
*
* @param userID The ID of the user
* @return this instance for method chaining
*/
public QueryAuditLogsFilters setUserID(String userID) {
this.userID = userID;
return this;
}

/**
* Validates that the filters contain the required fields.
* Either (entityType AND entityID) OR userID must be set.
*
* @throws StreamException if the required fields are not set
*/
public void validate() throws StreamException {
boolean hasEntityFields = entityType != null && !entityType.isEmpty() &&
entityID != null && !entityID.isEmpty();
boolean hasUserID = userID != null && !userID.isEmpty();

if (!hasEntityFields && !hasUserID) {
throw new StreamException("Either entityType+entityID or userID is required for audit logs queries");
}
}

/**
* Checks if the filter is valid according to API requirements.
*
* @return true if either (entityType AND entityID) OR userID is set
*/
public boolean isValid() {
boolean hasEntityFields = entityType != null && !entityType.isEmpty() &&
entityID != null && !entityID.isEmpty();
boolean hasUserID = userID != null && !userID.isEmpty();

return hasEntityFields || hasUserID;
}

/**
* Builder class for QueryAuditLogsFilters.
*/
public static class Builder {
private final QueryAuditLogsFilters filters;

private Builder() {
filters = new QueryAuditLogsFilters();
}

/**
* Set the entity type.
*
* @param entityType The type of entity (e.g., "activity", "reaction")
* @return this builder for method chaining
*/
public Builder withEntityType(String entityType) {
filters.entityType = entityType;
return this;
}

/**
* Set the entity ID.
*
* @param entityID The ID of the entity
* @return this builder for method chaining
*/
public Builder withEntityID(String entityID) {
filters.entityID = entityID;
return this;
}

/**
* Set the user ID.
*
* @param userID The ID of the user
* @return this builder for method chaining
*/
public Builder withUserID(String userID) {
filters.userID = userID;
return this;
}

/**
* Builds the QueryAuditLogsFilters instance.
*
* @return a new QueryAuditLogsFilters instance
*/
public QueryAuditLogsFilters build() {
return filters;
}
}
}
44 changes: 44 additions & 0 deletions src/main/java/io/getstream/client/QueryAuditLogsPager.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
package io.getstream.client;

public class QueryAuditLogsPager {
private String next;
private String prev;
private int limit;

public QueryAuditLogsPager() {
}

public QueryAuditLogsPager(int limit) {
this.limit = limit;
}

public QueryAuditLogsPager(String next, String prev, int limit) {
this.next = next;
this.prev = prev;
this.limit = limit;
}

public String getNext() {
return next;
}

public void setNext(String next) {
this.next = next;
}

public String getPrev() {
return prev;
}

public void setPrev(String prev) {
this.prev = prev;
}

public int getLimit() {
return limit;
}

public void setLimit(int limit) {
this.limit = limit;
}
}
Loading
Loading