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 2e4ffd344..2d99aee3d 100644 --- a/src/main/java/io/getstream/chat/java/models/Channel.java +++ b/src/main/java/io/getstream/chat/java/models/Channel.java @@ -419,6 +419,10 @@ public static class ConfigOverridesRequestObject { @Nullable @JsonProperty("commands") private List Commands; + + @Nullable + @JsonProperty("user_message_reminders") + private Boolean userMessageReminders; } @Builder diff --git a/src/main/java/io/getstream/chat/java/models/Reminder.java b/src/main/java/io/getstream/chat/java/models/Reminder.java new file mode 100644 index 000000000..759fc0b6d --- /dev/null +++ b/src/main/java/io/getstream/chat/java/models/Reminder.java @@ -0,0 +1,267 @@ +package io.getstream.chat.java.models; + +import com.fasterxml.jackson.annotation.JsonAnyGetter; +import com.fasterxml.jackson.annotation.JsonAnySetter; +import com.fasterxml.jackson.annotation.JsonIgnore; +import com.fasterxml.jackson.annotation.JsonProperty; +import io.getstream.chat.java.models.Reminder.ReminderCreateRequestData.ReminderCreateRequest; +import io.getstream.chat.java.models.Reminder.ReminderQueryRequestData.ReminderQueryRequest; +import io.getstream.chat.java.models.Reminder.ReminderUpdateRequestData.ReminderUpdateRequest; +import io.getstream.chat.java.models.framework.StreamRequest; +import io.getstream.chat.java.models.framework.StreamResponseObject; +import io.getstream.chat.java.services.ReminderService; +import io.getstream.chat.java.services.framework.Client; +import java.util.Date; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import lombok.Builder; +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.NoArgsConstructor; +import lombok.RequiredArgsConstructor; +import lombok.Singular; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; +import retrofit2.Call; + +@Data +@NoArgsConstructor +public class Reminder { + @NotNull + @JsonProperty("id") + private String id; + + @NotNull + @JsonProperty("message_id") + private String messageId; + + @Nullable + @JsonProperty("message") + private Message message; + + @NotNull + @JsonProperty("user_id") + private String userId; + + @Nullable + @JsonProperty("user") + private User user; + + @NotNull + @JsonProperty("channel_cid") + private String channelCid; + + @Nullable + @JsonProperty("remind_at") + private Date remindAt; + + @Nullable + @JsonProperty("created_at") + private Date createdAt; + + @Nullable + @JsonProperty("updated_at") + private Date updatedAt; + + @NotNull @JsonIgnore private Map additionalFields = new HashMap<>(); + + @JsonAnyGetter + public Map getAdditionalFields() { + return additionalFields; + } + + @JsonAnySetter + public void setAdditionalField(String name, Object value) { + additionalFields.put(name, value); + } + + @Builder( + builderClassName = "ReminderCreateRequest", + builderMethodName = "", + buildMethodName = "internalBuild") + public static class ReminderCreateRequestData { + @NotNull + @JsonProperty("user_id") + private String userId; + + @Nullable + @JsonProperty("remind_at") + private Date remindAt; + + public static class ReminderCreateRequest extends StreamRequest { + @NotNull private String messageId; + + private ReminderCreateRequest(@NotNull String messageId) { + this.messageId = messageId; + } + + @Override + protected Call generateCall(Client client) { + return client.create(ReminderService.class).create(messageId, this.internalBuild()); + } + } + } + + @Builder( + builderClassName = "ReminderUpdateRequest", + builderMethodName = "", + buildMethodName = "internalBuild") + public static class ReminderUpdateRequestData { + @NotNull + @JsonProperty("user_id") + private String userId; + + @Nullable + @JsonProperty("remind_at") + private Date remindAt; + + public static class ReminderUpdateRequest extends StreamRequest { + @NotNull private String messageId; + + private ReminderUpdateRequest(@NotNull String messageId) { + this.messageId = messageId; + } + + @Override + protected Call generateCall(Client client) { + return client.create(ReminderService.class).update(messageId, this.internalBuild()); + } + } + } + + @RequiredArgsConstructor + public static class ReminderDeleteRequest extends StreamRequest { + @NotNull private String messageId; + @NotNull private String userId; + + @Override + protected Call generateCall(Client client) { + return client.create(ReminderService.class).delete(messageId, userId); + } + } + + @Builder( + builderClassName = "ReminderQueryRequest", + builderMethodName = "", + buildMethodName = "internalBuild") + public static class ReminderQueryRequestData { + @NotNull + @JsonProperty("user_id") + private String userId; + + @Singular + @Nullable + @JsonProperty("filter_conditions") + private Map filterConditions; + + @Singular + @Nullable + @JsonProperty("sort") + private List> sorts; + + @Nullable + @JsonProperty("limit") + private Integer limit; + + @Nullable + @JsonProperty("offset") + private Integer offset; + + public static class ReminderQueryRequest extends StreamRequest { + @Override + protected Call generateCall(Client client) { + return client.create(ReminderService.class).query(this.internalBuild()); + } + } + } + + @Data + @NoArgsConstructor + @EqualsAndHashCode(callSuper = true) + public static class ReminderCreateResponse extends StreamResponseObject { + @NotNull + @JsonProperty("reminder") + private Reminder reminder; + } + + @Data + @NoArgsConstructor + @EqualsAndHashCode(callSuper = true) + public static class ReminderUpdateResponse extends StreamResponseObject { + @NotNull + @JsonProperty("reminder") + private Reminder reminder; + } + + @Data + @NoArgsConstructor + @EqualsAndHashCode(callSuper = true) + public static class ReminderDeleteResponse extends StreamResponseObject { + @NotNull + @JsonProperty("reminder") + private Reminder reminder; + } + + @Data + @NoArgsConstructor + @EqualsAndHashCode(callSuper = true) + public static class ReminderQueryResponse extends StreamResponseObject { + @NotNull + @JsonProperty("reminders") + private List reminders; + + @Nullable + @JsonProperty("prev") + private String prev; + + @Nullable + @JsonProperty("next") + private String next; + } + + /** + * Creates a reminder for a message. + * + * @param messageId The ID of the message to create a reminder for + * @return A request builder for creating a reminder + */ + @NotNull + public static ReminderCreateRequest createReminder(@NotNull String messageId) { + return new ReminderCreateRequest(messageId); + } + + /** + * Updates a reminder for a message. + * + * @param messageId The ID of the message with the reminder + * @return A request builder for updating a reminder + */ + @NotNull + public static ReminderUpdateRequest updateReminder(@NotNull String messageId) { + return new ReminderUpdateRequest(messageId); + } + + /** + * Deletes a reminder for a message. + * + * @param messageId The ID of the message with the reminder + * @param userId The ID of the user who owns the reminder + * @return A request for deleting a reminder + */ + @NotNull + public static ReminderDeleteRequest deleteReminder( + @NotNull String messageId, @NotNull String userId) { + return new ReminderDeleteRequest(messageId, userId); + } + + /** + * Queries reminders based on filter conditions. + * + * @return A request builder for querying reminders + */ + @NotNull + public static ReminderQueryRequest queryReminders() { + return new ReminderQueryRequest(); + } +} diff --git a/src/main/java/io/getstream/chat/java/services/ReminderService.java b/src/main/java/io/getstream/chat/java/services/ReminderService.java new file mode 100644 index 000000000..5fb7793f2 --- /dev/null +++ b/src/main/java/io/getstream/chat/java/services/ReminderService.java @@ -0,0 +1,37 @@ +package io.getstream.chat.java.services; + +import io.getstream.chat.java.models.Reminder.ReminderCreateRequestData; +import io.getstream.chat.java.models.Reminder.ReminderCreateResponse; +import io.getstream.chat.java.models.Reminder.ReminderDeleteResponse; +import io.getstream.chat.java.models.Reminder.ReminderQueryRequestData; +import io.getstream.chat.java.models.Reminder.ReminderQueryResponse; +import io.getstream.chat.java.models.Reminder.ReminderUpdateRequestData; +import io.getstream.chat.java.models.Reminder.ReminderUpdateResponse; +import org.jetbrains.annotations.NotNull; +import retrofit2.Call; +import retrofit2.http.Body; +import retrofit2.http.DELETE; +import retrofit2.http.PATCH; +import retrofit2.http.POST; +import retrofit2.http.Path; +import retrofit2.http.Query; + +public interface ReminderService { + @POST("messages/{id}/reminders") + Call create( + @NotNull @Path("id") String messageId, + @NotNull @Body ReminderCreateRequestData reminderCreateRequestData); + + @PATCH("messages/{id}/reminders") + Call update( + @NotNull @Path("id") String messageId, + @NotNull @Body ReminderUpdateRequestData reminderUpdateRequestData); + + @DELETE("messages/{id}/reminders") + Call delete( + @NotNull @Path("id") String messageId, @NotNull @Query("user_id") String userId); + + @POST("reminders/query") + Call query( + @NotNull @Body ReminderQueryRequestData reminderQueryRequestData); +} diff --git a/src/test/java/io/getstream/chat/java/MessageTest.java b/src/test/java/io/getstream/chat/java/MessageTest.java index a793531e3..bbf5726cb 100644 --- a/src/test/java/io/getstream/chat/java/MessageTest.java +++ b/src/test/java/io/getstream/chat/java/MessageTest.java @@ -545,7 +545,7 @@ void whenDeletingMessageWithDeletedBy_thenIsDeletedWithSpecifiedUser() { .getMessage(); Assertions.assertNull(message.getDeletedAt()); - String deletedByUserId = "test-deleted-by-user"; + String deletedByUserId = testUsersRequestObjects.get(1).getId(); Message deletedMessage = Assertions.assertDoesNotThrow( () -> Message.delete(message.getId()).deletedBy(deletedByUserId).request()) diff --git a/src/test/java/io/getstream/chat/java/ReminderTest.java b/src/test/java/io/getstream/chat/java/ReminderTest.java new file mode 100644 index 000000000..050fe12ad --- /dev/null +++ b/src/test/java/io/getstream/chat/java/ReminderTest.java @@ -0,0 +1,210 @@ +package io.getstream.chat.java; + +import io.getstream.chat.java.models.Channel; +import io.getstream.chat.java.models.Message; +import io.getstream.chat.java.models.Reminder; +import java.util.Collections; +import java.util.Date; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; + +public class ReminderTest extends BasicTest { + + @BeforeAll + static void setupReminders() { + Map configOverrides = new HashMap<>(); + configOverrides.put("user_message_reminders", true); + + Assertions.assertDoesNotThrow( + () -> + Channel.partialUpdate(testChannel.getType(), testChannel.getId()) + .setValue("config_overrides", configOverrides) + .request()); + } + + @AfterAll + static void teardownReminders() { + Assertions.assertDoesNotThrow( + () -> + Channel.partialUpdate(testChannel.getType(), testChannel.getId()) + .setValue("config_overrides", Collections.emptyMap()) + .request()); + } + + @DisplayName("Can create a reminder for a message") + @Test + void whenCreatingAReminder_thenNoException() { + // Create a new message for this test + Message message = Assertions.assertDoesNotThrow(() -> sendTestMessage()); + + // Create a reminder for the test message + Reminder reminder = + Assertions.assertDoesNotThrow( + () -> + Reminder.createReminder(message.getId()) + .userId(testUserRequestObject.getId()) + .remindAt(new Date(System.currentTimeMillis() + 86400000)) // 1 day from now + .request()) + .getReminder(); + + // Verify the reminder was created correctly + Assertions.assertNotNull(reminder); + Assertions.assertEquals(message.getId(), reminder.getMessageId()); + Assertions.assertEquals(testUserRequestObject.getId(), reminder.getUserId()); + Assertions.assertNotNull(reminder.getRemindAt()); + + // Verify the new fields + Assertions.assertNotNull(reminder.getChannelCid(), "Channel CID should not be null"); + // The channel CID should be in the format "type:id" + Assertions.assertTrue( + reminder.getChannelCid().contains(testChannel.getType() + ":" + testChannel.getId()), + "Channel CID should contain the channel type and ID"); + } + + @DisplayName("Can update a reminder for a message") + @Test + void whenUpdatingAReminder_thenNoException() { + // Create a new message for this test + Message message = Assertions.assertDoesNotThrow(() -> sendTestMessage()); + + // Create a reminder first + Reminder reminder = + Assertions.assertDoesNotThrow( + () -> + Reminder.createReminder(message.getId()) + .userId(testUserRequestObject.getId()) + .remindAt(new Date(System.currentTimeMillis() + 86400000)) // 1 day from now + .request()) + .getReminder(); + + // Update the reminder with a new remind_at time + Date newRemindAt = new Date(System.currentTimeMillis() + 172800000); // 2 days from now + Reminder updatedReminder = + Assertions.assertDoesNotThrow( + () -> + Reminder.updateReminder(message.getId()) + .userId(testUserRequestObject.getId()) + .remindAt(newRemindAt) + .request()) + .getReminder(); + + // Verify the reminder was updated correctly + Assertions.assertNotNull(updatedReminder); + Assertions.assertEquals(reminder.getId(), updatedReminder.getId()); + Assertions.assertEquals(message.getId(), updatedReminder.getMessageId()); + Assertions.assertEquals(testUserRequestObject.getId(), updatedReminder.getUserId()); + + // The updated remind_at time should be different from the original + Assertions.assertNotEquals( + reminder.getRemindAt().getTime(), updatedReminder.getRemindAt().getTime()); + + // Verify the channel_cid is preserved + Assertions.assertEquals(reminder.getChannelCid(), updatedReminder.getChannelCid()); + } + + @DisplayName("Can delete a reminder for a message") + @Test + void whenDeletingAReminder_thenNoException() { + // Create a new message for this test + Message message = Assertions.assertDoesNotThrow(() -> sendTestMessage()); + + // Create a reminder first + Reminder reminder = + Assertions.assertDoesNotThrow( + () -> + Reminder.createReminder(message.getId()) + .userId(testUserRequestObject.getId()) + .remindAt(new Date(System.currentTimeMillis() + 86400000)) // 1 day from now + .request()) + .getReminder(); + + // Delete the reminder + Assertions.assertDoesNotThrow( + () -> Reminder.deleteReminder(message.getId(), testUserRequestObject.getId()).request()); + + // Since the API might not return the deleted reminder, we just verify that the delete operation + // completed without throwing an exception + } + + @DisplayName("Can query reminders with filter conditions") + @Test + void whenQueryingRemindersWithFilterConditions_thenNoException() { + // Create a new message for this test + Message message = Assertions.assertDoesNotThrow(() -> sendTestMessage()); + + // Create a reminder first + Reminder reminder = + Assertions.assertDoesNotThrow( + () -> + Reminder.createReminder(message.getId()) + .userId(testUserRequestObject.getId()) + .remindAt(new Date(System.currentTimeMillis() + 86400000)) // 1 day from now + .request()) + .getReminder(); + + // Create sort parameters + Map sortParams = new HashMap<>(); + sortParams.put("field", "remind_at"); + sortParams.put("direction", 1); + + // Query reminders with filter conditions + List reminders = + Assertions.assertDoesNotThrow( + () -> + Reminder.queryReminders() + .userId(testUserRequestObject.getId()) + .filterCondition("message_id", message.getId()) + .sort(sortParams) + .limit(10) + .request()) + .getReminders(); + + // Verify the query returned results + Assertions.assertNotNull(reminders); + // Note: The API might not return any reminders if they were deleted or not indexed yet + // So we don't assert that the list is not empty + + // Check for pagination fields + Assertions.assertDoesNotThrow( + () -> { + String prev = + Reminder.queryReminders().userId(testUserRequestObject.getId()).request().getPrev(); + + String next = + Reminder.queryReminders().userId(testUserRequestObject.getId()).request().getNext(); + + // We don't assert specific values as they might be null depending on the data + }); + } + + @DisplayName("Can create a reminder without a remind_at date") + @Test + void whenCreatingAReminderWithoutRemindAt_thenNoException() { + // Create a new message for this test + Message message = Assertions.assertDoesNotThrow(() -> sendTestMessage()); + + // Create a reminder without a remind_at date + Reminder reminder = + Assertions.assertDoesNotThrow( + () -> + Reminder.createReminder(message.getId()) + .userId(testUserRequestObject.getId()) + .request()) + .getReminder(); + + // Verify the reminder was created correctly + Assertions.assertNotNull(reminder); + Assertions.assertEquals(message.getId(), reminder.getMessageId()); + Assertions.assertEquals(testUserRequestObject.getId(), reminder.getUserId()); + // The API might not set a default remind_at time, so we don't assert that it's not null + + // Verify the channel_cid field + Assertions.assertNotNull(reminder.getChannelCid(), "Channel CID should not be null"); + } +}