diff --git a/docs/channels/channel_management/deleting.md b/docs/channels/channel_management/deleting.md index 4c06c1fc5..059a98fe5 100644 --- a/docs/channels/channel_management/deleting.md +++ b/docs/channels/channel_management/deleting.md @@ -16,6 +16,17 @@ Channel.delete( > If you recreate this channel, it will show up empty. Recovering old messages is not supported. Use the disable method if you want a reversible change. +### Keeping the messages + +Call `setSkipTruncate(true)` to keep the messages of a soft deleted channel, so recreating the channel with the same id restores its history. It cannot be combined with a hard delete, and only distinct channels are eligible. + +```java +Channel.delete("messaging", channelId).setSkipTruncate(true).request(); + +// same option on the batch endpoint +Channel.deleteMany(Arrays.asList(cid1, cid2)).setSkipTruncate(true).request(); +``` + ## Deleting Many Channels You can delete up to 100 channels and optionally all of their messages using this method. This can be a large amount of data to delete, so this endpoint processes asynchronously, meaning responses contain a `task ID` which can be polled using the [getTask endpoint](/chat/docs/java#tasks-gettask) to check status of the deletions. Channels will be soft-deleted immediately so that channels no longer return from queries, but permanently deleting the channel and deleting messages takes longer to process. diff --git a/src/main/java/io/getstream/chat/java/models/Channel.java b/src/main/java/io/getstream/chat/java/models/Channel.java index cf2873226..f6ab4d29f 100644 --- a/src/main/java/io/getstream/chat/java/models/Channel.java +++ b/src/main/java/io/getstream/chat/java/models/Channel.java @@ -792,9 +792,22 @@ public static class ChannelDeleteRequest extends StreamRequest generateCall(Client client) { - return client.create(ChannelService.class).delete(this.channelType, this.channelId); + return client + .create(ChannelService.class) + .delete(this.channelType, this.channelId, this.skipTruncate); } } @@ -811,11 +824,23 @@ public static class ChannelDeleteManyRequest extends StreamRequest generateCall(Client svcFactory) throws StreamException { diff --git a/src/main/java/io/getstream/chat/java/services/ChannelService.java b/src/main/java/io/getstream/chat/java/services/ChannelService.java index f71803836..488558e99 100644 --- a/src/main/java/io/getstream/chat/java/services/ChannelService.java +++ b/src/main/java/io/getstream/chat/java/services/ChannelService.java @@ -39,6 +39,12 @@ Call getChannel( Call delete( @NotNull @Path("type") String channelType, @NotNull @Path("id") String channelId); + @DELETE("channels/{type}/{id}") + Call delete( + @NotNull @Path("type") String channelType, + @NotNull @Path("id") String channelId, + @Nullable @Query("skip_truncate") Boolean skipTruncate); + @POST("channels/delete") Call deleteMany( @NotNull @Body Channel.ChannelDeleteManyRequest channelDeleteManyRequest); diff --git a/src/test/java/io/getstream/chat/java/ChannelDeleteSkipTruncateTest.java b/src/test/java/io/getstream/chat/java/ChannelDeleteSkipTruncateTest.java new file mode 100644 index 000000000..f8512c03a --- /dev/null +++ b/src/test/java/io/getstream/chat/java/ChannelDeleteSkipTruncateTest.java @@ -0,0 +1,76 @@ +package io.getstream.chat.java; + +import com.fasterxml.jackson.annotation.JsonAutoDetect; +import com.fasterxml.jackson.annotation.PropertyAccessor; +import com.fasterxml.jackson.databind.ObjectMapper; +import io.getstream.chat.java.models.Channel; +import io.getstream.chat.java.services.ChannelService; +import java.util.Arrays; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import retrofit2.Retrofit; +import retrofit2.converter.jackson.JacksonConverterFactory; + +public class ChannelDeleteSkipTruncateTest { + + // Mirrors the visibility configuration of DefaultClient's mapper. + private static final ObjectMapper MAPPER = + new ObjectMapper() + .setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.NONE) + .setVisibility(PropertyAccessor.FIELD, JsonAutoDetect.Visibility.ANY); + + private static ChannelService service() { + return new Retrofit.Builder() + .baseUrl("https://chat.example.com/") + .addConverterFactory(JacksonConverterFactory.create(MAPPER)) + .build() + .create(ChannelService.class); + } + + @DisplayName("Delete sends skip_truncate as a query param when set") + @Test + void whenSkipTruncateSet_thenQueryParamIsSent() { + String url = service().delete("messaging", "chan", true).request().url().toString(); + + Assertions.assertTrue(url.contains("skip_truncate=true"), url); + } + + @DisplayName("Delete omits skip_truncate when unset") + @Test + void whenSkipTruncateUnset_thenQueryParamIsOmitted() { + String url = service().delete("messaging", "chan", null).request().url().toString(); + + Assertions.assertFalse(url.contains("skip_truncate"), url); + } + + @DisplayName("The two argument delete overload is still callable") + @Test + void whenCallingTwoArgumentDelete_thenNoQueryParamIsSent() { + String url = service().delete("messaging", "chan").request().url().toString(); + + Assertions.assertFalse(url.contains("skip_truncate"), url); + } + + @DisplayName("Delete request carries the flag to the service call") + @Test + void whenSettingSkipTruncateOnRequest_thenFlagIsKept() { + Assertions.assertEquals( + true, Channel.delete("messaging", "chan").setSkipTruncate(true).getSkipTruncate()); + Assertions.assertNull(Channel.delete("messaging", "chan").getSkipTruncate()); + } + + @DisplayName("Delete many serializes skip_truncate only when set") + @Test + void whenSettingSkipTruncateOnDeleteMany_thenBodyCarriesIt() throws Exception { + Assertions.assertFalse( + MAPPER + .writeValueAsString(Channel.deleteMany(Arrays.asList("messaging:chan"))) + .contains("skip_truncate")); + Assertions.assertTrue( + MAPPER + .writeValueAsString( + Channel.deleteMany(Arrays.asList("messaging:chan")).setSkipTruncate(true)) + .contains("\"skip_truncate\":true")); + } +}