diff --git a/src/BatchOperations.cs b/src/BatchOperations.cs index a4c84ff..61b096c 100644 --- a/src/BatchOperations.cs +++ b/src/BatchOperations.cs @@ -56,6 +56,28 @@ public async Task FollowManyAsync(IEnumerable follows, int throw StreamException.FromResponse(response); } + public async Task UnfollowManyAsync(IEnumerable unfollows) + { + var request = _client.BuildAppRequest("unfollow_many/", HttpMethod.Post); + + // Create a new anonymous object array with the properties expected by the API + var unfollowRequests = unfollows.Select(f => new + { + source = f.Source, + target = f.Target, + keep_history = f.KeepHistory + }); + + request.SetJsonBody(StreamJsonConverter.SerializeObject(unfollowRequests)); + + var response = await _client.MakeRequestAsync(request); + + if (response.StatusCode == HttpStatusCode.Created || response.StatusCode == HttpStatusCode.OK) + return StreamJsonConverter.DeserializeObject(response.Content); + + throw StreamException.FromResponse(response); + } + public async Task> GetActivitiesByIdAsync(IEnumerable ids) => await GetActivitiesAsync(ids, null); diff --git a/src/IBatchOperations.cs b/src/IBatchOperations.cs index 8e4454d..74b333a 100644 --- a/src/IBatchOperations.cs +++ b/src/IBatchOperations.cs @@ -21,6 +21,10 @@ public interface IBatchOperations /// Follow muiltiple feeds. /// https://getstream.io/activity-feeds/docs/dotnet-csharp/add_many_activities/?language=csharp Task FollowManyAsync(IEnumerable follows, int activityCopyLimit = 100); + + /// Unfollow multiple feeds in a single request using UnfollowRelation objects. + /// https://getstream.io/activity-feeds/docs/dotnet-csharp/add_many_activities/?language=csharp + Task UnfollowManyAsync(IEnumerable unfollows); /// Get multiple activities by activity ids. /// https://getstream.io/activity-feeds/docs/dotnet-csharp/add_many_activities/?language=csharp diff --git a/src/Models/UnfollowRelation.cs b/src/Models/UnfollowRelation.cs new file mode 100644 index 0000000..7933d7d --- /dev/null +++ b/src/Models/UnfollowRelation.cs @@ -0,0 +1,49 @@ +namespace Stream.Models +{ + /// + /// Represents a relationship to unfollow in batch operations + /// + public class UnfollowRelation + { + /// + /// Source feed id + /// + public string Source { get; set; } + + /// + /// Target feed id + /// + public string Target { get; set; } + + /// + /// Whether to keep activities from the unfollowed feed + /// + public bool KeepHistory { get; set; } + + /// + /// Creates a new instance of the UnfollowRelation class + /// + /// Source feed id + /// Target feed id + /// Whether to keep activities from the unfollowed feed + public UnfollowRelation(string source, string target, bool keepHistory = false) + { + Source = source; + Target = target; + KeepHistory = keepHistory; + } + + /// + /// Creates a new instance of the UnfollowRelation class + /// + /// Source feed + /// Target feed + /// Whether to keep activities from the unfollowed feed + public UnfollowRelation(IStreamFeed source, IStreamFeed target, bool keepHistory = false) + { + Source = source.FeedId; + Target = target.FeedId; + KeepHistory = keepHistory; + } + } +} \ No newline at end of file diff --git a/tests/BatchTests.cs b/tests/BatchTests.cs index 0aff891..44895eb 100644 --- a/tests/BatchTests.cs +++ b/tests/BatchTests.cs @@ -10,6 +10,28 @@ namespace StreamNetTests [TestFixture] public class BatchTests : TestBase { + [Test] + public void TestUnfollowManyArgumentValidation() + { + // Should work with empty array + Assert.DoesNotThrowAsync(async () => + { + await Client.Batch.UnfollowManyAsync(new UnfollowRelation[] { }); + }); + + // Should work with valid unfollow relation objects + Assert.DoesNotThrowAsync(async () => + { + await Client.Batch.UnfollowManyAsync(new[] { new UnfollowRelation("user:1", "user:2", false) }); + }); + + // Should work with keepHistory true + Assert.DoesNotThrowAsync(async () => + { + await Client.Batch.UnfollowManyAsync(new[] { new UnfollowRelation("user:1", "user:2", true) }); + }); + } + [Test] public void TestGetEnrichedActivitiesArgumentValidation() { @@ -305,5 +327,93 @@ public async Task TestBatchActivityForeignIdTime() Assert.AreEqual(1, result.Results.Count); } + + [Test] + public async Task TestBatchUnfollowManyKeepHistoryFalse() + { + // First set up follows and add activities + await Client.Batch.FollowManyAsync(new[] + { + new Follow(UserFeed, FlatFeed), + new Follow(UserFeed2, FlatFeed), + }); + + var newActivity = new Activity("1", "test", "1"); + var response = await this.FlatFeed.AddActivityAsync(newActivity); + + // Verify follows are working + var activities1 = (await this.UserFeed.GetActivitiesAsync(0, 1)).Results; + var activities2 = (await this.UserFeed2.GetActivitiesAsync(0, 1)).Results; + + Assert.IsNotNull(activities1); + Assert.AreEqual(1, activities1.Count()); + Assert.AreEqual(response.Id, activities1.First().Id); + + Assert.IsNotNull(activities2); + Assert.AreEqual(1, activities2.Count()); + Assert.AreEqual(response.Id, activities2.First().Id); + + // Use UnfollowMany with keepHistory=false + await Client.Batch.UnfollowManyAsync(new[] + { + new UnfollowRelation(UserFeed, FlatFeed, false), + new UnfollowRelation(UserFeed2, FlatFeed, false), + }); + + // Verify activities are removed + activities1 = (await this.UserFeed.GetActivitiesAsync(0, 1)).Results; + activities2 = (await this.UserFeed2.GetActivitiesAsync(0, 1)).Results; + + Assert.IsNotNull(activities1); + Assert.AreEqual(0, activities1.Count()); + + Assert.IsNotNull(activities2); + Assert.AreEqual(0, activities2.Count()); + } + + [Test] + public async Task TestBatchUnfollowManyKeepHistoryTrue() + { + // First set up follows and add activities + await Client.Batch.FollowManyAsync(new[] + { + new Follow(UserFeed, FlatFeed), + new Follow(UserFeed2, FlatFeed), + }); + + var newActivity = new Activity("1", "test", "1"); + var response = await this.FlatFeed.AddActivityAsync(newActivity); + + // Verify follows are working + var activities1 = (await this.UserFeed.GetActivitiesAsync(0, 1)).Results; + var activities2 = (await this.UserFeed2.GetActivitiesAsync(0, 1)).Results; + + Assert.IsNotNull(activities1); + Assert.AreEqual(1, activities1.Count()); + Assert.AreEqual(response.Id, activities1.First().Id); + + Assert.IsNotNull(activities2); + Assert.AreEqual(1, activities2.Count()); + Assert.AreEqual(response.Id, activities2.First().Id); + + // Use UnfollowMany with keepHistory=true + await Client.Batch.UnfollowManyAsync(new[] + { + new UnfollowRelation(UserFeed, FlatFeed, true), + new UnfollowRelation(UserFeed2, FlatFeed, true), + }); + + // Verify activities are retained + activities1 = (await this.UserFeed.GetActivitiesAsync(0, 1)).Results; + activities2 = (await this.UserFeed2.GetActivitiesAsync(0, 1)).Results; + + Assert.IsNotNull(activities1); + Assert.AreEqual(1, activities1.Count()); + Assert.AreEqual(response.Id, activities1.First().Id); + + Assert.IsNotNull(activities2); + Assert.AreEqual(1, activities2.Count()); + Assert.AreEqual(response.Id, activities2.First().Id); + } } } diff --git a/tests/ModerationTests.cs b/tests/ModerationTests.cs index 6ac4b22..479c440 100644 --- a/tests/ModerationTests.cs +++ b/tests/ModerationTests.cs @@ -98,6 +98,13 @@ public async Task TestFlagActivity() newActivity.SetData("stringint", "42"); newActivity.SetData("stringdouble", "42.2"); newActivity.SetData("stringcomplex", "{ \"test1\": 1, \"test2\": \"testing\" }"); + + // Set moderation data with origin_feed + var moderationData = new Dictionary + { + { "origin_feed", this.UserFeed.FeedId } + }; + newActivity.SetData("moderation", moderationData); var response = await this.UserFeed.AddActivityAsync(newActivity); Assert.IsNotNull(response);