feat(dm): build the inbox from GET /api/dm/conversations (#85) - #90
Merged
Merged
Conversation
The inbox paged `GET /api/dm` — a flat message list — and grouped it
client-side, so a row was really "the newest message that happened to be
cached" rather than a conversation. `GET /api/dm/conversations` returns one
row per conversation (grouped by `pairKey`, newest activity first), which is
what the inbox should render.
- Add `ConversationDto` + `ConversationsResponse` as the conversation row's
own wire model. The endpoint is unmodelled in the OpenAPI spec and absent
from the help centre, so the DTO is tolerant: optional everywhere, the
participant accepted as `otherUser`/`user`/`author`, the newest message
either nested (`lastMessage`/`message`) or flattened
(`preview`/`lastMessageAt`/...), the list under `items` or `conversations`.
- `ConversationEntity` now stores the server's `pairKey` and a per-conversation
`unreadCount` instead of a client-derived `hasUnread` flag; the `Conversation`
domain type derives `hasUnread` from it, so the inbox unread badge and unread
dot keep working unchanged. DB version 2 (destructive migration is enabled).
- `refreshInbox` keeps its cursor-paging contract and offline-first Room-backed
shape; only the data source changed. Pages merge, so paging appends.
- Remove `getInbox`/`InboxResponse` and the client-side grouping helper: nothing
needed the flat list any more (the thread view uses the by-username endpoint).
- Unwrap `POST /api/dm`'s documented `{ "message": ... }` envelope, found while
reading the DM help centre for the conversation shape; without it a sent
message kept its optimistic local id, so read/trash on it would 404.
Tests: conversation DTOs parse (canonical, flattened and a defensive case with
explicit nulls, missing fields and unknown keys); the inbox emits one row per
conversation newest-first; cursor paging forwards the cursor and appends;
cached per-conversation unread counts sum to `GET /api/dm/unread-count`;
opening a thread zeroes that conversation's unread count.
Closes #85
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Closes #85. Part of epic #84.
What changed
The DM inbox now renders
GET /api/dm/conversations— one row per conversation, grouped bypairKey, newest activity first — instead of paging the flatGET /api/dmmessage list andgrouping client-side. Cursor paging and the offline-first Room-backed pattern are unchanged; this
is a data-source swap, not a rewrite. Send / read / trash / restore / recipients / image upload
and
/updatespolling are untouched.ConversationDto+ConversationEntity+Conversationdomain type — the row is modelledin its own right rather than reusing the message entity.
ConversationEntitycarriespairKeyand a realunreadCount: Int(replacing theclient-derived
hasUnread); DBversion = 2, destructive migration was already enabled.DirectMessagesApi.getInboxremoved,getConversationsadded.hasUnreadis nowunreadCount > 0, and the cached per-conversationcounts are asserted to agree with
GET /api/dm/unread-count.Drive-by fix
POST /api/dmreturns{ "message": { … } }, whichSendMessageResponsenever unwrapped — a sentmessage kept its optimistic
local-…id, so a later read/trash on it would 404. Fixed, with aregression test.
Verification
./gradlew :app:assembleDebug testDebugUnitTest→ BUILD SUCCESSFUL. A--rerun-taskspass reports695 unit tests, 0 failures.
:feature:directmessages:compileDebugAndroidTestKotlinalsocompiles. Instrumented tests not run (no emulator available to the agent).
New/extended tests cover: DTO parsing (canonical nested row, flattened
preview/lastMessageAtrow under the
conversationskey, a defensive row with explicit nulls + missing fields + unknownkeys, an empty row, an empty body); one row per conversation, newest-first; cursor paging appends
page 2; unread counts summing to the unread-count endpoint; rows without a participant username
skipped; opening a thread zeroing unread; the send envelope.
Reviewer note — the one soft spot
/api/dm/conversationsis declared as a bareobjectin the OpenAPI spec and is not documented at/help/api/direct-messagesat all, so the row shape is inferred, not confirmed. The DTO acceptsthe participant under
otherUser/user/authorand the newest message either nested(
lastMessage/message) or flattened (preview/lastMessageBody/lastMessageAt). Oneauthenticated
curlagainst the live endpoint would settle it and let the DTO be trimmed down.Also note
GET /api/dmis now unimplemented — if a Sent/Deleted folder view is wanted later itneeds re-adding.