Skip to content

Commit 0b0c8cf

Browse files
authored
Add QueryReactions LLC operation (#6040)
* Add QueryReactions. * Update CHANGELOG.md.
1 parent 18dd0e8 commit 0b0c8cf

File tree

14 files changed

+272
-2
lines changed

14 files changed

+272
-2
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
### ⬆️ Improved
1717

1818
### ✅ Added
19+
- Add `ChatClient.queryReactions(String, FilterObject?, Int?, String?, QuerySorter<Reaction>?)` operation for querying reactions with filtering, sorting, and pagination support. [#6040](https://github.com/GetStream/stream-chat-android/pull/6040)
1920
- Add `ChatClient.markUnread(String, String, Date)` for marking a channel as unread from a given timestamp. [#6027](https://github.com/GetStream/stream-chat-android/pull/6027)
2021
- Add `ChatClient.markThreadUnread(String, String, String)` for marking a thread as unread. [#6027](https://github.com/GetStream/stream-chat-android/pull/6027)
2122
- Add `ChannelClient.markUnread(Date)` for marking a channel as unread from a given timestamp. [#6027](https://github.com/GetStream/stream-chat-android/pull/6027)

stream-chat-android-client/api/stream-chat-android-client.api

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -169,6 +169,8 @@ public final class io/getstream/chat/android/client/ChatClient {
169169
public static synthetic fun queryPollVotes$default (Lio/getstream/chat/android/client/ChatClient;Ljava/lang/String;Lio/getstream/chat/android/models/FilterObject;Ljava/lang/Integer;Ljava/lang/String;Lio/getstream/chat/android/models/querysort/QuerySorter;ILjava/lang/Object;)Lio/getstream/result/call/Call;
170170
public final fun queryPolls (Lio/getstream/chat/android/models/FilterObject;Ljava/lang/Integer;Ljava/lang/String;Lio/getstream/chat/android/models/querysort/QuerySorter;)Lio/getstream/result/call/Call;
171171
public static synthetic fun queryPolls$default (Lio/getstream/chat/android/client/ChatClient;Lio/getstream/chat/android/models/FilterObject;Ljava/lang/Integer;Ljava/lang/String;Lio/getstream/chat/android/models/querysort/QuerySorter;ILjava/lang/Object;)Lio/getstream/result/call/Call;
172+
public final fun queryReactions (Ljava/lang/String;Lio/getstream/chat/android/models/FilterObject;Ljava/lang/Integer;Ljava/lang/String;Lio/getstream/chat/android/models/querysort/QuerySorter;)Lio/getstream/result/call/Call;
173+
public static synthetic fun queryReactions$default (Lio/getstream/chat/android/client/ChatClient;Ljava/lang/String;Lio/getstream/chat/android/models/FilterObject;Ljava/lang/Integer;Ljava/lang/String;Lio/getstream/chat/android/models/querysort/QuerySorter;ILjava/lang/Object;)Lio/getstream/result/call/Call;
172174
public final fun queryReminders (Lio/getstream/chat/android/models/FilterObject;ILjava/lang/String;Lio/getstream/chat/android/models/querysort/QuerySorter;)Lio/getstream/result/call/Call;
173175
public static synthetic fun queryReminders$default (Lio/getstream/chat/android/client/ChatClient;Lio/getstream/chat/android/models/FilterObject;ILjava/lang/String;Lio/getstream/chat/android/models/querysort/QuerySorter;ILjava/lang/Object;)Lio/getstream/result/call/Call;
174176
public final fun queryThreads (Lio/getstream/chat/android/client/api/models/QueryThreadsRequest;)Lio/getstream/result/call/Call;

stream-chat-android-client/src/main/java/io/getstream/chat/android/client/ChatClient.kt

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -198,6 +198,7 @@ import io.getstream.chat.android.models.PushPreferenceLevel
198198
import io.getstream.chat.android.models.QueryDraftsResult
199199
import io.getstream.chat.android.models.QueryPollVotesResult
200200
import io.getstream.chat.android.models.QueryPollsResult
201+
import io.getstream.chat.android.models.QueryReactionsResult
201202
import io.getstream.chat.android.models.QueryRemindersResult
202203
import io.getstream.chat.android.models.QueryThreadsResult
203204
import io.getstream.chat.android.models.Reaction
@@ -1144,6 +1145,26 @@ internal constructor(
11441145
return api.getReactions(messageId, offset, limit)
11451146
}
11461147

1148+
/**
1149+
* Queries reactions for a given message.
1150+
*
1151+
* @param messageId The ID of the message to which the reactions belong.
1152+
* @param filter The filter to apply to the reactions.
1153+
* @param limit The maximum number of reactions to retrieve.
1154+
* @param next The pagination token for fetching the next set of results.
1155+
* @param sort The sorting criteria for the reactions.
1156+
*/
1157+
@CheckResult
1158+
public fun queryReactions(
1159+
messageId: String,
1160+
filter: FilterObject? = null,
1161+
limit: Int? = null,
1162+
next: String? = null,
1163+
sort: QuerySorter<Reaction>? = null,
1164+
): Call<QueryReactionsResult> {
1165+
return api.queryReactions(messageId, filter, limit, next, sort)
1166+
}
1167+
11471168
/**
11481169
* Deletes the reaction associated with the message with the given message id.
11491170
* [cid] parameter is being used in side effect functions executed by plugins.

stream-chat-android-client/src/main/java/io/getstream/chat/android/client/api/ChatApi.kt

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,7 @@ import io.getstream.chat.android.models.PushPreferenceLevel
5353
import io.getstream.chat.android.models.QueryDraftsResult
5454
import io.getstream.chat.android.models.QueryPollVotesResult
5555
import io.getstream.chat.android.models.QueryPollsResult
56+
import io.getstream.chat.android.models.QueryReactionsResult
5657
import io.getstream.chat.android.models.QueryRemindersResult
5758
import io.getstream.chat.android.models.QueryThreadsResult
5859
import io.getstream.chat.android.models.Reaction
@@ -177,6 +178,15 @@ internal interface ChatApi {
177178
limit: Int,
178179
): Call<List<Reaction>>
179180

181+
@CheckResult
182+
fun queryReactions(
183+
messageId: String,
184+
filter: FilterObject?,
185+
limit: Int?,
186+
next: String?,
187+
sort: QuerySorter<Reaction>?,
188+
): Call<QueryReactionsResult>
189+
180190
@CheckResult
181191
fun sendReaction(reaction: Reaction, enforceUnique: Boolean, skipPush: Boolean): Call<Reaction>
182192

stream-chat-android-client/src/main/java/io/getstream/chat/android/client/api2/MoshiChatApi.kt

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -75,6 +75,7 @@ import io.getstream.chat.android.client.api2.model.requests.QueryDraftMessagesRe
7575
import io.getstream.chat.android.client.api2.model.requests.QueryDraftsRequest
7676
import io.getstream.chat.android.client.api2.model.requests.QueryPollVotesRequest
7777
import io.getstream.chat.android.client.api2.model.requests.QueryPollsRequest
78+
import io.getstream.chat.android.client.api2.model.requests.QueryReactionsRequest
7879
import io.getstream.chat.android.client.api2.model.requests.QueryRemindersRequest
7980
import io.getstream.chat.android.client.api2.model.requests.ReactionRequest
8081
import io.getstream.chat.android.client.api2.model.requests.RejectInviteRequest
@@ -141,6 +142,7 @@ import io.getstream.chat.android.models.PushPreferenceLevel
141142
import io.getstream.chat.android.models.QueryDraftsResult
142143
import io.getstream.chat.android.models.QueryPollVotesResult
143144
import io.getstream.chat.android.models.QueryPollsResult
145+
import io.getstream.chat.android.models.QueryReactionsResult
144146
import io.getstream.chat.android.models.QueryRemindersResult
145147
import io.getstream.chat.android.models.QueryThreadsResult
146148
import io.getstream.chat.android.models.Reaction
@@ -405,6 +407,27 @@ constructor(
405407
}
406408
}
407409

410+
override fun queryReactions(
411+
messageId: String,
412+
filter: FilterObject?,
413+
limit: Int?,
414+
next: String?,
415+
sort: QuerySorter<Reaction>?,
416+
): Call<QueryReactionsResult> {
417+
val body = QueryReactionsRequest(
418+
filter = filter?.toMap(),
419+
limit = limit,
420+
next = next,
421+
sort = sort?.toDto(),
422+
)
423+
return messageApi.queryReactions(messageId, body).mapDomain {
424+
QueryReactionsResult(
425+
reactions = it.reactions.map { reaction -> reaction.toDomain() },
426+
next = it.next,
427+
)
428+
}
429+
}
430+
408431
override fun sendReaction(reaction: Reaction, enforceUnique: Boolean, skipPush: Boolean): Call<Reaction> {
409432
return messageApi.sendReaction(
410433
messageId = reaction.messageId,

stream-chat-android-client/src/main/java/io/getstream/chat/android/client/api2/endpoint/MessageApi.kt

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ import io.getstream.chat.android.client.api.AuthenticatedApi
2020
import io.getstream.chat.android.client.api2.model.requests.PartialUpdateMessageRequest
2121
import io.getstream.chat.android.client.api2.model.requests.QueryDraftMessagesRequest
2222
import io.getstream.chat.android.client.api2.model.requests.QueryDraftsRequest
23+
import io.getstream.chat.android.client.api2.model.requests.QueryReactionsRequest
2324
import io.getstream.chat.android.client.api2.model.requests.ReactionRequest
2425
import io.getstream.chat.android.client.api2.model.requests.SendActionRequest
2526
import io.getstream.chat.android.client.api2.model.requests.SendMessageRequest
@@ -29,6 +30,7 @@ import io.getstream.chat.android.client.api2.model.response.DraftMessageResponse
2930
import io.getstream.chat.android.client.api2.model.response.MessageResponse
3031
import io.getstream.chat.android.client.api2.model.response.MessagesResponse
3132
import io.getstream.chat.android.client.api2.model.response.QueryDraftMessagesResponse
33+
import io.getstream.chat.android.client.api2.model.response.QueryReactionsResponse
3234
import io.getstream.chat.android.client.api2.model.response.ReactionResponse
3335
import io.getstream.chat.android.client.api2.model.response.ReactionsResponse
3436
import io.getstream.chat.android.client.api2.model.response.TranslateMessageRequest
@@ -133,6 +135,12 @@ internal interface MessageApi {
133135
@Query("limit") limit: Int,
134136
): RetrofitCall<ReactionsResponse>
135137

138+
@POST("/messages/{id}/reactions")
139+
fun queryReactions(
140+
@Path("id") messageId: String,
141+
@Body body: QueryReactionsRequest,
142+
): RetrofitCall<QueryReactionsResponse>
143+
136144
@POST("/messages/{messageId}/translate")
137145
fun translate(
138146
@Path("messageId") messageId: String,
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
/*
2+
* Copyright (c) 2014-2025 Stream.io Inc. All rights reserved.
3+
*
4+
* Licensed under the Stream License;
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://github.com/GetStream/stream-chat-android/blob/main/LICENSE
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package io.getstream.chat.android.client.api2.model.requests
18+
19+
import com.squareup.moshi.JsonClass
20+
21+
/**
22+
* Request for querying reactions on a message.
23+
*
24+
* @property filter The filter criteria.
25+
* @property limit The maximum number of reactions to return.
26+
* @property next The pagination token for fetching the next set of results.
27+
* @property sort The sorting criteria to apply.
28+
*/
29+
@JsonClass(generateAdapter = true)
30+
internal data class QueryReactionsRequest(
31+
val filter: Map<*, *>? = null,
32+
val limit: Int? = null,
33+
val next: String? = null,
34+
val sort: List<Map<String, Any>>? = null,
35+
)
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
/*
2+
* Copyright (c) 2014-2025 Stream.io Inc. All rights reserved.
3+
*
4+
* Licensed under the Stream License;
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://github.com/GetStream/stream-chat-android/blob/main/LICENSE
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package io.getstream.chat.android.client.api2.model.response
18+
19+
import com.squareup.moshi.JsonClass
20+
import io.getstream.chat.android.client.api2.model.dto.DownstreamReactionDto
21+
22+
/**
23+
* Response for querying reactions on a message.
24+
*
25+
* @property reactions The list of reactions returned by the query.
26+
* @property next The identifier for the next page of reactions.
27+
*/
28+
@JsonClass(generateAdapter = true)
29+
internal data class QueryReactionsResponse(
30+
val reactions: List<DownstreamReactionDto>,
31+
val next: String?,
32+
)

stream-chat-android-client/src/test/java/io/getstream/chat/android/client/ChatClientMessageApiTests.kt

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,8 +24,10 @@ import io.getstream.chat.android.client.plugin.Plugin
2424
import io.getstream.chat.android.client.utils.RetroError
2525
import io.getstream.chat.android.client.utils.verifyNetworkError
2626
import io.getstream.chat.android.client.utils.verifySuccess
27+
import io.getstream.chat.android.models.Filters
2728
import io.getstream.chat.android.models.Message
2829
import io.getstream.chat.android.models.PendingMessage
30+
import io.getstream.chat.android.models.QueryReactionsResult
2931
import io.getstream.chat.android.models.Reaction
3032
import io.getstream.chat.android.models.User
3133
import io.getstream.chat.android.models.querysort.QuerySortByField
@@ -615,6 +617,43 @@ internal class ChatClientMessageApiTests : BaseChatClientTest() {
615617
verifyNetworkError(result, errorCode)
616618
}
617619

620+
@Test
621+
fun queryReactionsSuccess() = runTest {
622+
// given
623+
val messageId = randomString()
624+
val filter = Filters.neutral()
625+
val limit = positiveRandomInt()
626+
val next = randomString()
627+
val sort = QuerySortByField<Reaction>()
628+
val reactions = listOf(randomReaction(messageId = messageId))
629+
val queryResult = QueryReactionsResult(reactions = reactions, next = randomString())
630+
val sut = Fixture()
631+
.givenQueryReactionsResult(queryResult.asCall())
632+
.get()
633+
// when
634+
val result = sut.queryReactions(messageId, filter, limit, next, sort).await()
635+
// then
636+
verifySuccess(result, queryResult)
637+
}
638+
639+
@Test
640+
fun queryReactionsError() = runTest {
641+
// given
642+
val messageId = randomString()
643+
val filter = Filters.neutral()
644+
val limit = positiveRandomInt()
645+
val next = randomString()
646+
val sort = QuerySortByField<Reaction>()
647+
val errorCode = positiveRandomInt()
648+
val sut = Fixture()
649+
.givenQueryReactionsResult(RetroError<QueryReactionsResult>(errorCode).toRetrofitCall())
650+
.get()
651+
// when
652+
val result = sut.queryReactions(messageId, filter, limit, next, sort).await()
653+
// then
654+
verifyNetworkError(result, errorCode)
655+
}
656+
618657
@Test
619658
fun updateMessageSuccess() = runTest {
620659
// given
@@ -892,6 +931,10 @@ internal class ChatClientMessageApiTests : BaseChatClientTest() {
892931
whenever(api.getReactions(any(), any(), any())).thenReturn(result)
893932
}
894933

934+
fun givenQueryReactionsResult(result: Call<QueryReactionsResult>) = apply {
935+
whenever(api.queryReactions(any(), any(), any(), any(), any())).thenReturn(result)
936+
}
937+
895938
fun givenUpdateMessageResult(result: Call<Message>) = apply {
896939
whenever(api.updateMessage(any())).thenReturn(result)
897940
}

stream-chat-android-client/src/test/java/io/getstream/chat/android/client/api2/MoshiChatApiTest.kt

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,7 @@ import io.getstream.chat.android.client.api2.model.requests.PollVoteRequest
6666
import io.getstream.chat.android.client.api2.model.requests.QueryBannedUsersRequest
6767
import io.getstream.chat.android.client.api2.model.requests.QueryPollVotesRequest
6868
import io.getstream.chat.android.client.api2.model.requests.QueryPollsRequest
69+
import io.getstream.chat.android.client.api2.model.requests.QueryReactionsRequest
6970
import io.getstream.chat.android.client.api2.model.requests.QueryRemindersRequest
7071
import io.getstream.chat.android.client.api2.model.requests.RejectInviteRequest
7172
import io.getstream.chat.android.client.api2.model.requests.ReminderRequest
@@ -102,6 +103,7 @@ import io.getstream.chat.android.client.api2.model.response.QueryDraftMessagesRe
102103
import io.getstream.chat.android.client.api2.model.response.QueryMembersResponse
103104
import io.getstream.chat.android.client.api2.model.response.QueryPollVotesResponse
104105
import io.getstream.chat.android.client.api2.model.response.QueryPollsResponse
106+
import io.getstream.chat.android.client.api2.model.response.QueryReactionsResponse
105107
import io.getstream.chat.android.client.api2.model.response.QueryRemindersResponse
106108
import io.getstream.chat.android.client.api2.model.response.QueryThreadsResponse
107109
import io.getstream.chat.android.client.api2.model.response.ReactionResponse
@@ -137,6 +139,7 @@ import io.getstream.chat.android.models.NoOpMessageTransformer
137139
import io.getstream.chat.android.models.NoOpUserTransformer
138140
import io.getstream.chat.android.models.Poll
139141
import io.getstream.chat.android.models.PushPreferenceLevel
142+
import io.getstream.chat.android.models.Reaction
140143
import io.getstream.chat.android.models.UnreadCounts
141144
import io.getstream.chat.android.models.UploadedFile
142145
import io.getstream.chat.android.models.Vote
@@ -413,6 +416,33 @@ internal class MoshiChatApiTest {
413416
verify(api, times(1)).getReactions(messageId, offset, limit)
414417
}
415418

419+
@ParameterizedTest
420+
@MethodSource("io.getstream.chat.android.client.api2.MoshiChatApiTestArguments#queryReactionsInput")
421+
fun testQueryReactions(call: RetrofitCall<QueryReactionsResponse>, expected: KClass<*>) = runTest {
422+
// given
423+
val api = mock<MessageApi>()
424+
whenever(api.queryReactions(any(), any())).doReturn(call)
425+
val sut = Fixture()
426+
.withMessageApi(api)
427+
.get()
428+
// when
429+
val messageId = randomString()
430+
val filter = Filters.neutral()
431+
val limit = randomInt()
432+
val next = randomString()
433+
val sort = QuerySortByField<Reaction>()
434+
val result = sut.queryReactions(messageId, filter, limit, next, sort).await()
435+
// then
436+
val expectedRequest = QueryReactionsRequest(
437+
filter = filter.toMap(),
438+
limit = limit,
439+
next = next,
440+
sort = sort.toDto(),
441+
)
442+
result `should be instance of` expected
443+
verify(api, times(1)).queryReactions(messageId, expectedRequest)
444+
}
445+
416446
@ParameterizedTest
417447
@MethodSource("io.getstream.chat.android.client.api2.MoshiChatApiTestArguments#sendReactionInput")
418448
fun testSendReaction(call: RetrofitCall<ReactionResponse>, expected: KClass<*>) = runTest {

0 commit comments

Comments
 (0)