diff --git a/src/main/java/io/getstream/client/ReactionsClient.java b/src/main/java/io/getstream/client/ReactionsClient.java index d2754881..c2658163 100644 --- a/src/main/java/io/getstream/client/ReactionsClient.java +++ b/src/main/java/io/getstream/client/ReactionsClient.java @@ -18,6 +18,7 @@ import io.getstream.core.utils.Auth.TokenAction; import io.getstream.core.utils.DefaultOptions; import java.util.List; +import java.util.Map; import java8.util.concurrent.CompletableFuture; public final class ReactionsClient { @@ -167,6 +168,12 @@ public CompletableFuture add(String userID, Reaction reaction, FeedID. return reactions.add(token, userID, reaction, targetFeeds); } + public CompletableFuture add(String userID, Reaction reaction, FeedID[] targetFeeds, Map targetFeedsExtraData) + throws StreamException { + final Token token = buildReactionsToken(secret, TokenAction.WRITE); + return reactions.add(token, userID, reaction, targetFeeds, targetFeedsExtraData); + } + public CompletableFuture addChild( String userID, String kind, String parentID, Iterable targetFeeds) throws StreamException { @@ -180,6 +187,12 @@ public CompletableFuture addChild( return add(userID, child, targetFeeds); } + public CompletableFuture addChild( + String userID, String kind, String parentID, FeedID[] targetFeeds, Map targetFeedsExtraData) throws StreamException { + Reaction child = Reaction.builder().kind(kind).parent(parentID).build(); + return add(userID, child, targetFeeds, targetFeedsExtraData); + } + public CompletableFuture addChild( String userID, String parentID, Reaction reaction, Iterable targetFeeds) throws StreamException { @@ -194,6 +207,13 @@ public CompletableFuture addChild( return add(userID, child, targetFeeds); } + public CompletableFuture addChild( + String userID, String parentID, Reaction reaction, FeedID[] targetFeeds, Map targetFeedsExtraData) + throws StreamException { + Reaction child = Reaction.builder().fromReaction(reaction).parent(parentID).build(); + return add(userID, child, targetFeeds, targetFeedsExtraData); + } + public CompletableFuture update(String id, Iterable targetFeeds) throws StreamException { return update(id, Iterables.toArray(targetFeeds, FeedID.class)); diff --git a/src/main/java/io/getstream/cloud/CloudReactionsClient.java b/src/main/java/io/getstream/cloud/CloudReactionsClient.java index 887ff9e4..ab04d5a4 100644 --- a/src/main/java/io/getstream/cloud/CloudReactionsClient.java +++ b/src/main/java/io/getstream/cloud/CloudReactionsClient.java @@ -14,6 +14,7 @@ import io.getstream.core.options.Limit; import io.getstream.core.utils.DefaultOptions; import java.util.List; +import java.util.Map; import java8.util.concurrent.CompletableFuture; public final class CloudReactionsClient { @@ -150,6 +151,11 @@ public CompletableFuture add(String userID, Reaction reaction, FeedID. return reactions.add(token, userID, reaction, targetFeeds); } + public CompletableFuture add(String userID, Reaction reaction, FeedID[] targetFeeds, Map targetFeedsExtraData) + throws StreamException { + return reactions.add(token, userID, reaction, targetFeeds, targetFeedsExtraData); + } + public CompletableFuture addChild( String userID, String kind, String parentID, Iterable targetFeeds) throws StreamException { @@ -163,6 +169,12 @@ public CompletableFuture addChild( return add(userID, child, targetFeeds); } + public CompletableFuture addChild( + String userID, String kind, String parentID, FeedID[] targetFeeds, Map targetFeedsExtraData) throws StreamException { + Reaction child = Reaction.builder().kind(kind).parent(parentID).build(); + return add(userID, child, targetFeeds, targetFeedsExtraData); + } + public CompletableFuture addChild( String userID, String parentID, Reaction reaction, Iterable targetFeeds) throws StreamException { @@ -177,6 +189,13 @@ public CompletableFuture addChild( return add(userID, child, targetFeeds); } + public CompletableFuture addChild( + String userID, String parentID, Reaction reaction, FeedID[] targetFeeds, Map targetFeedsExtraData) + throws StreamException { + Reaction child = Reaction.builder().fromReaction(reaction).parent(parentID).build(); + return add(userID, child, targetFeeds, targetFeedsExtraData); + } + public CompletableFuture update(String id, Iterable targetFeeds) throws StreamException { return update(id, Iterables.toArray(targetFeeds, FeedID.class)); diff --git a/src/main/java/io/getstream/core/StreamReactions.java b/src/main/java/io/getstream/core/StreamReactions.java index 87bab7f5..1bbba031 100644 --- a/src/main/java/io/getstream/core/StreamReactions.java +++ b/src/main/java/io/getstream/core/StreamReactions.java @@ -204,6 +204,11 @@ public CompletableFuture> paginatedFilter(Token token, Strin public CompletableFuture add( Token token, String userID, Reaction reaction, FeedID... targetFeeds) throws StreamException { + return add(token, userID, reaction, targetFeeds, null); + } + + public CompletableFuture add( + Token token, String userID, Reaction reaction, FeedID[] targetFeeds, Map targetFeedsExtraData) throws StreamException { checkNotNull(reaction, "Reaction can't be null"); checkArgument( reaction.getActivityID() != null || reaction.getParent() != null, @@ -227,6 +232,9 @@ public CompletableFuture add( ImmutableMap.Builder payloadBuilder = ImmutableMap.builder(); payloadBuilder.put("kind", reaction.getKind()); payloadBuilder.put("target_feeds", targetFeedIDs); + if (targetFeedsExtraData != null) { + payloadBuilder.put("target_feeds_extra_data", targetFeedsExtraData); + } if (reaction.getActivityID() != null) { payloadBuilder.put("activity_id", reaction.getActivityID()); } diff --git a/src/test/java/io/getstream/client/TargetFeedsExtraDataTest.java b/src/test/java/io/getstream/client/TargetFeedsExtraDataTest.java new file mode 100644 index 00000000..15139d3e --- /dev/null +++ b/src/test/java/io/getstream/client/TargetFeedsExtraDataTest.java @@ -0,0 +1,118 @@ +package io.getstream.client; + +import io.getstream.core.LookupKind; +import io.getstream.core.models.Activity; +import io.getstream.core.models.FeedID; +import io.getstream.core.models.Reaction; +import org.junit.Test; + +import java.util.Date; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.UUID; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertTrue; + +public class TargetFeedsExtraDataTest { + private static final String apiKey = + System.getenv("STREAM_KEY") != null + ? System.getenv("STREAM_KEY") + : System.getProperty("STREAM_KEY"); + private static final String secret = + System.getenv("STREAM_SECRET") != null + ? System.getenv("STREAM_SECRET") + : System.getProperty("STREAM_SECRET"); + + @Test + public void testTargetFeedsExtraData() throws Exception { + // Create client + Client client = Client.builder(apiKey, secret).build(); + + // Use unique user id to avoid conflicts in notification feed + String uniqueId = UUID.randomUUID().toString().replace("-", ""); + String userId = "test-user-" + uniqueId; + + // 1. Create a test activity + String activityId = UUID.randomUUID().toString(); + Activity activity = Activity.builder() + .actor(userId) + .verb("post") + .object("test-object") + .foreignID("test-foreignId-" + activityId) + .time(new Date()) + .build(); + + Activity postedActivity = client.flatFeed("user", userId).addActivity(activity).join(); + + // 2. Create a comment reaction on the activity + Map commentData = new HashMap<>(); + commentData.put("text", "This is a test comment"); + + Reaction comment = Reaction.builder() + .kind("comment") + .activityID(postedActivity.getID()) + .extraField("data", commentData) + .build(); + + Reaction postedComment = client.reactions().add(userId, comment, new FeedID[0]).join(); + + // 3. Create a like reaction on the comment with targetFeedsExtraData + Map targetFeedsExtraData = new HashMap<>(); + String extraDataValue = "SR:" + postedComment.getId(); + targetFeedsExtraData.put("parent_reaction", extraDataValue); + + FeedID[] targetFeeds = new FeedID[] { + new FeedID("notification", userId) + }; + + // The critical part of the test: Can we successfully create a reaction with targetFeedsExtraData + Reaction like = client.reactions().addChild( + "actor-" + uniqueId, // Different user performs the like action + "like", + postedComment.getId(), + targetFeeds, + targetFeedsExtraData + ).join(); + + // 4. Verify that the reaction was created successfully + assertNotNull("Like reaction should not be null", like); + assertEquals("Like reaction should have kind='like'", "like", like.getKind()); + assertEquals("Like reaction should have parent ID", postedComment.getId(), like.getParent()); + + // Check if the reaction has extra data + Map reactionExtra = like.getExtra(); + assertNotNull("Reaction should have extra data", reactionExtra); + + // Check for targetFeedsExtraData directly + assertTrue("Reaction should contain target_feeds_extra_data", + reactionExtra.containsKey("target_feeds_extra_data")); + + // Verify the content of targetFeedsExtraData + Object extraDataObj = reactionExtra.get("target_feeds_extra_data"); + assertTrue("target_feeds_extra_data should be a Map", extraDataObj instanceof Map); + + Map extraDataMap = (Map) extraDataObj; + assertTrue("target_feeds_extra_data should contain parent_reaction", + extraDataMap.containsKey("parent_reaction")); + assertEquals("parent_reaction value should match what we sent", + extraDataValue, extraDataMap.get("parent_reaction")); + + // 5. Get the reactions to verify + List reactions = client.reactions().filter( + LookupKind.REACTION, + postedComment.getId(), + "like" + ).join(); + + assertEquals("Should have one like reaction", 1, reactions.size()); + assertEquals("Reaction should match the one we created", like.getId(), reactions.get(0).getId()); + + // Clean up + client.reactions().delete(like.getId()).join(); + client.reactions().delete(postedComment.getId()).join(); + client.flatFeed("user", userId).removeActivityByID(postedActivity.getID()).join(); + } +} \ No newline at end of file