diff --git a/src/main/java/io/getstream/chat/java/models/Moderation.java b/src/main/java/io/getstream/chat/java/models/Moderation.java new file mode 100644 index 000000000..0fe6859fa --- /dev/null +++ b/src/main/java/io/getstream/chat/java/models/Moderation.java @@ -0,0 +1,213 @@ +package io.getstream.chat.java.models; + +import com.fasterxml.jackson.annotation.*; +import io.getstream.chat.java.models.Moderation.UpsertConfigRequestData.UpsertConfigRequest; +import io.getstream.chat.java.models.framework.StreamRequest; +import io.getstream.chat.java.models.framework.StreamResponseObject; +import io.getstream.chat.java.services.ModerationService; +import io.getstream.chat.java.services.framework.Client; +import java.util.Date; +import java.util.List; +import lombok.*; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; +import retrofit2.Call; + +@Data +@NoArgsConstructor +public class Moderation { + + @Builder( + builderClassName = "ConfigGetRequest", + builderMethodName = "", + buildMethodName = "internalBuild") + public static class ConfigGetRequestData { + public static class ConfigGetRequest extends StreamRequest { + @NotNull private String key; + + private ConfigGetRequest(@NotNull String key) { + this.key = key; + } + + @Override + protected Call generateCall(Client client) { + return client.create(ModerationService.class).getConfig(this.key); + } + } + } + + @Data + @NoArgsConstructor + @EqualsAndHashCode(callSuper = true) + public static class ConfigGetResponse extends StreamResponseObject { + @Nullable + @JsonProperty("config") + private Config config; + } + + @Data + @NoArgsConstructor + public static class Config { + @Nullable + @JsonProperty("key") + private String key; + + @Nullable + @JsonProperty("async") + private Boolean async; + + @Nullable + @JsonProperty("block_list_config") + private BlockListConfig blockListConfig; + + @Nullable + @JsonProperty("created_at") + private Date createdAt; + + @Nullable + @JsonProperty("updated_at") + private Date updatedAt; + } + + @Data + @NoArgsConstructor + public static class BlockListConfig { + @Nullable + @JsonProperty("async") + private Boolean async; + + @NotNull + @JsonProperty("enabled") + private Boolean enabled; + + @NotNull + @JsonProperty("rules") + private List rules; + } + + public enum Action { + @JsonProperty("flag") + FLAG, + @JsonProperty("shadow") + SHADOW, + @JsonProperty("remove") + REMOVE, + @JsonProperty("bounce") + BOUNCE, + @JsonProperty("bounce_flag") + BOUNCE_FLAG, + @JsonProperty("bounce_remove") + BOUNCE_REMOVE, + @JsonEnumDefaultValue + UNKNOWN + } + + @Data + @NoArgsConstructor + @Builder + @AllArgsConstructor + public static class BlockListRule { + @NotNull + @JsonProperty("name") + private String name; + + @NotNull + @JsonProperty("action") + private Action action; + } + + @Builder + public static class BlockListConfigRequestObject { + @Nullable + @JsonProperty("async") + private Boolean async; + + @NotNull + @JsonProperty("rules") + private List rules; + } + + @Builder( + builderClassName = "UpsertConfigRequest", + builderMethodName = "", + buildMethodName = "internalBuild") + public static class UpsertConfigRequestData { + @Nullable + @JsonProperty("key") + private String key; + + @Nullable + @JsonProperty("async") + private Boolean async; + + @Nullable + @JsonProperty("block_list_config") + private BlockListConfigRequestObject blockListConfig; + + public static class UpsertConfigRequest extends StreamRequest { + @NotNull private String key; + + private UpsertConfigRequest(@NotNull String key) { + this.key = key; + } + + @Override + protected Call generateCall(Client client) { + return client + .create(ModerationService.class) + .upsertConfig(this.key(this.key).internalBuild()); + } + } + } + + @Data + @NoArgsConstructor + @EqualsAndHashCode(callSuper = true) + public static class UpsertConfigResponse extends StreamResponseObject { + @Nullable + @JsonProperty("config") + private Config config; + } + + @RequiredArgsConstructor + public static class DeleteConfigRequest extends StreamRequest { + @NotNull private String key; + + @Override + protected Call generateCall(Client client) { + return client.create(ModerationService.class).deleteConfig(this.key); + } + } + + @RequiredArgsConstructor + public static class ConfigGetRequest extends StreamRequest { + @NotNull private String key; + + @Override + protected Call generateCall(Client client) { + return client.create(ModerationService.class).getConfig(this.key); + } + } + + /** + * Creates a get or create request + * + * @param type the channel type + * @param id the channel id + * @return the created request + */ + @NotNull + public static UpsertConfigRequest upsertConfig(@NotNull String key) { + return new UpsertConfigRequest(key); + } + + @NotNull + public static DeleteConfigRequest deleteConfig(@NotNull String key) { + return new DeleteConfigRequest(key); + } + + @NotNull + public static ConfigGetRequest getConfig(@NotNull String key) { + return new ConfigGetRequest(key); + } +} diff --git a/src/main/java/io/getstream/chat/java/services/ModerationService.java b/src/main/java/io/getstream/chat/java/services/ModerationService.java new file mode 100644 index 000000000..09664520b --- /dev/null +++ b/src/main/java/io/getstream/chat/java/services/ModerationService.java @@ -0,0 +1,19 @@ +package io.getstream.chat.java.services; + +import io.getstream.chat.java.models.Moderation.*; +import io.getstream.chat.java.models.framework.StreamResponseObject; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; +import retrofit2.Call; +import retrofit2.http.*; + +public interface ModerationService { + @GET("api/v2/moderation/config/{key}") + Call getConfig(@NotNull @Path("key") String key); + + @DELETE("api/v2/moderation/config/{key}") + Call deleteConfig(@NotNull @Path("key") String key); + + @POST("api/v2/moderation/config") + Call upsertConfig(@Nullable @Body UpsertConfigRequestData upsertConfig); +} diff --git a/src/test/java/io/getstream/chat/java/MessageHistoryTest.java b/src/test/java/io/getstream/chat/java/MessageHistoryTest.java index b2aed2dfa..3ef561a6c 100644 --- a/src/test/java/io/getstream/chat/java/MessageHistoryTest.java +++ b/src/test/java/io/getstream/chat/java/MessageHistoryTest.java @@ -79,7 +79,8 @@ void whenMessageUpdated_thenGetHistory() { initialCustomFieldValue, firstUpdate.getAdditionalFields().get(customField)); var secondUpdate = history.get(0); Assertions.assertEquals(updatedText1, secondUpdate.getText()); - Assertions.assertEquals(secondUser.getId(), secondUpdate.getMessageUpdatedById()); + Assertions.assertEquals( + testUserRequestObject.getId(), secondUpdate.getMessageUpdatedById()); Assertions.assertEquals( updatedCustomFieldValue, secondUpdate.getAdditionalFields().get(customField)); @@ -106,7 +107,8 @@ void whenMessageUpdated_thenGetHistory() { secondUpdate = sortedHistory.get(1); Assertions.assertEquals(updatedText1, secondUpdate.getText()); - Assertions.assertEquals(secondUser.getId(), secondUpdate.getMessageUpdatedById()); + Assertions.assertEquals( + testUserRequestObject.getId(), secondUpdate.getMessageUpdatedById()); }); } } diff --git a/src/test/java/io/getstream/chat/java/MessageTest.java b/src/test/java/io/getstream/chat/java/MessageTest.java index 4f9969c66..97d8fd9b1 100644 --- a/src/test/java/io/getstream/chat/java/MessageTest.java +++ b/src/test/java/io/getstream/chat/java/MessageTest.java @@ -3,10 +3,11 @@ import io.getstream.chat.java.models.App; import io.getstream.chat.java.models.App.FileUploadConfigRequestObject; import io.getstream.chat.java.models.Blocklist; -import io.getstream.chat.java.models.ChannelType; import io.getstream.chat.java.models.Language; import io.getstream.chat.java.models.Message; import io.getstream.chat.java.models.Message.*; +import io.getstream.chat.java.models.Moderation; +import io.getstream.chat.java.models.Moderation.*; import io.getstream.chat.java.models.Sort; import io.getstream.chat.java.models.framework.DefaultFileHandler; import io.getstream.chat.java.services.framework.DefaultClient; @@ -699,11 +700,14 @@ void whenForcingModerationOnAMessage_thenIsForced() { Assertions.assertDoesNotThrow(() -> Thread.sleep(5000)); Assertions.assertDoesNotThrow( - () -> - ChannelType.update(testChannel.getType()) - .blocklist(blocklistName) - .blocklistBehavior(ChannelType.BlocklistBehavior.BLOCK) - .request()); + () -> { + String key = String.format("chat:%s:%s", testChannel.getType(), testChannel.getId()); + BlockListRule rule = + BlockListRule.builder().name(blocklistName).action(Moderation.Action.REMOVE).build(); + Moderation.upsertConfig(key) + .blockListConfig(BlockListConfigRequestObject.builder().rules(List.of(rule)).build()) + .request(); + }); Assertions.assertDoesNotThrow(() -> Thread.sleep(5000)); @@ -718,7 +722,7 @@ void whenForcingModerationOnAMessage_thenIsForced() { .request()) .getMessage(); - Assertions.assertTrue(msg1.getText().equals("Message was blocked by moderation policies")); + Assertions.assertEquals("Message was blocked by moderation policies", msg1.getText()); MessageRequestObject messageRequest2 = MessageRequestObject.builder().text(text).userId(testUserRequestObject.getId()).build(); @@ -731,13 +735,14 @@ void whenForcingModerationOnAMessage_thenIsForced() { .request()) .getMessage(); - Assertions.assertTrue(msg2.getText().equals(text)); + Assertions.assertEquals(text, msg2.getText()); Assertions.assertDoesNotThrow(() -> Blocklist.delete(blocklistName).request()); } @DisplayName("Can unblock a message") @Test + @Disabled("Need to implement unblock with moderation v2") void whenUnblockingAMessage_thenIsUnblocked() { final String swearText = "This is a hate message"; final String blocklistName = RandomStringUtils.randomAlphabetic(5); @@ -747,11 +752,14 @@ void whenUnblockingAMessage_thenIsUnblocked() { Assertions.assertDoesNotThrow(() -> Thread.sleep(5000)); Assertions.assertDoesNotThrow( - () -> - ChannelType.update(testChannel.getType()) - .blocklist(blocklistName) - .blocklistBehavior(ChannelType.BlocklistBehavior.BLOCK) - .request()); + () -> { + String key = String.format("chat:%s:%s", testChannel.getType(), testChannel.getId()); + BlockListRule rule = + BlockListRule.builder().name(blocklistName).action(Moderation.Action.REMOVE).build(); + Moderation.upsertConfig(key) + .blockListConfig(BlockListConfigRequestObject.builder().rules(List.of(rule)).build()) + .request(); + }); Assertions.assertDoesNotThrow(() -> Thread.sleep(5000)); diff --git a/src/test/java/io/getstream/chat/java/ModerationTest.java b/src/test/java/io/getstream/chat/java/ModerationTest.java new file mode 100644 index 000000000..e347db531 --- /dev/null +++ b/src/test/java/io/getstream/chat/java/ModerationTest.java @@ -0,0 +1,36 @@ +package io.getstream.chat.java; + +import io.getstream.chat.java.exceptions.StreamException; +import io.getstream.chat.java.models.Moderation; +import io.getstream.chat.java.models.Moderation.*; +import java.util.List; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; + +public class ModerationTest extends BasicTest { + @DisplayName("Can upsert, get and delete moderation config") + @Test + void whenUpsertingGetttingDeletingModerationConfig_thenNoException() { + BlockListRule rule = + BlockListRule.builder().name("test").action(Moderation.Action.REMOVE).build(); + + String key = "chat:messaging:1234"; + Assertions.assertDoesNotThrow( + () -> + Moderation.upsertConfig(key) + .blockListConfig( + BlockListConfigRequestObject.builder().rules(List.of(rule)).build()) + .request()); + + ConfigGetResponse response = + Assertions.assertDoesNotThrow(() -> Moderation.getConfig(key).request()); + + Assertions.assertEquals( + response.getConfig().getBlockListConfig().getRules().get(0).getName(), "test"); + + Assertions.assertDoesNotThrow(() -> Moderation.deleteConfig(key).request()); + + Assertions.assertThrows(StreamException.class, () -> Moderation.getConfig(key).request()); + } +}