Skip to content

Commit e9ff324

Browse files
committed
feat(user-service): cross-site thread-unread badge RPC
Adds a client-facing RPC that returns a per-user thread-unread badge aggregated across every federation site the user participates in, and removes the superseded (callerless) ThreadUnreadSummary leaf. New client RPC: chat.user.{account}.request.user.{siteID}.thread.unread -> ThreadUnreadResponse{ unread, unreadDirectMessage, unreadMention, lastMessageAt?, unavailableSites? }. How it works (mirrors the existing subscription-unread pattern, one unread() helper): user-service reads all of the user's thread-subs from the home-site replica (ThreadSubscriptionRepo.ListByAccount), groups them by the room's home siteId, and per site (uncapped fan-out, chunked by maxBatchSize) calls the new room-service ThreadRoomInfoBatch RPC (chat.server.request.room.{siteID}.thread.info.batch) for each thread room's lastMsgAt + parent roomType. Results fold via unread(): OR the booleans, max lastMsgAt over found rows, unreadDirectMessage = unread && roomType==dm, unreadMention = hasMention. Per-site RPC failures degrade into unavailableSites. Grouping by siteId counts each thread exactly once. - pkg/subject: UserThreadUnread/Pattern, ThreadRoomInfoBatch/Subscribe. - pkg/model: ThreadUnreadRequest/Response, ThreadRoomInfoBatchRequest/ Response, ThreadRoomInfo, ThreadSubRef. - room-service: GetThreadRoomInfos store method (two projected finds, no $lookup) + threadRoomInfoBatch handler. - user-service: roomclient.GetThreadRoomInfoBatch, mongorepo.ThreadSubscriptionRepo.ListByAccount, RoomClient/ ThreadSubscriptionRepository interfaces, GetThreadUnread aggregator. - docs/client-api.md: documents the new thread.unread RPC. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01BocBvJn2N9YYMmciA5EWJX
1 parent 4bba9cd commit e9ff324

25 files changed

Lines changed: 2459 additions & 364 deletions

docs/client-api.md

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3776,6 +3776,7 @@ Additional legacy fields may be present, mirroring the `GET /api/v3/users` respo
37763776
| `chat.user.{account}.request.user.{siteID}.subscription.setAppSubscription` | [`subscription.setAppSubscription`](#subscriptionsetappsubscription) |
37773777
| `chat.user.{account}.request.user.{siteID}.apps.list` | [`apps.list`](#appslist) |
37783778
| `chat.user.{account}.request.user.{siteID}.thread.list` | [List User Threads](#list-user-threads) |
3779+
| `chat.user.{account}.request.user.{siteID}.thread.unread` | [Get Thread Unread](#get-thread-unread) |
37793780

37803781
#### status.getByName
37813782

@@ -4532,6 +4533,58 @@ See [Error envelope](#6-error-envelope-reference). A malformed `cursor` returns
45324533

45334534
---
45344535

4536+
#### Get Thread Unread
4537+
4538+
**Subject:** `chat.user.{account}.request.user.{siteID}.thread.unread`
4539+
**Reply subject:** auto-generated `_INBOX.>` (NATS request/reply)
4540+
4541+
- `{siteID}` is the **caller's own home site** — the site that holds the user's federated subscriptions and runs the aggregator.
4542+
4543+
Returns the aggregated unread status of the user's thread subscriptions across every site the user participates in. `user-service` determines those sites from the user's local thread-subscription replica, queries each owning site's `room-service` for the threads' latest activity, and merges the results into one response. Sites that fail to respond are listed in `unavailableSites` rather than failing the request.
4544+
4545+
##### Request body
4546+
4547+
Empty object.
4548+
4549+
```json
4550+
{}
4551+
```
4552+
4553+
##### Success response
4554+
4555+
| Field | Type | Notes |
4556+
|---|---|---|
4557+
| `unread` | boolean | Any thread has activity newer than the user's last-seen. |
4558+
| `unreadDirectMessage` | boolean | `unread` restricted to DM-room threads. |
4559+
| `unreadMention` | boolean | The user is @-mentioned in any unread-tracked thread. |
4560+
| `lastMessageAt` | number? | Optional. UnixMilli of the newest thread activity across sites; omitted when none. |
4561+
| `unavailableSites` | string[]? | Optional. Sites whose per-site lookup failed; omitted when all responded. |
4562+
4563+
```json
4564+
{
4565+
"unread": true,
4566+
"unreadDirectMessage": false,
4567+
"unreadMention": true,
4568+
"lastMessageAt": 1717000000000
4569+
}
4570+
```
4571+
4572+
##### Error response
4573+
4574+
| Condition | `code` | Notes |
4575+
|-----------|--------|-------|
4576+
| Internal failure | `internal` | Local thread-subscription read failed. Per-site RPC failures degrade into `unavailableSites` rather than erroring. |
4577+
4578+
##### Triggered events — success path
4579+
4580+
`None — reply only.`
4581+
4582+
##### Triggered events — error path
4583+
4584+
`None — error returned only via the reply subject.`
4585+
4586+
---
4587+
45354588
## 4. Message Send
45364589

45374590
### Send Message

0 commit comments

Comments
 (0)