Skip to content

feat(history-service): rooms.get — batch room last-message read (#393)#413

Draft
chenjr0719 wants to merge 3 commits into
mainfrom
feat/393-rooms-get
Draft

feat(history-service): rooms.get — batch room last-message read (#393)#413
chenjr0719 wants to merge 3 commits into
mainfrom
feat/393-rooms-get

Conversation

@chenjr0719

@chenjr0719 chenjr0719 commented Jun 29, 2026

Copy link
Copy Markdown
Collaborator

Summary

Implements rooms.get (chat#393) — the room last-message endpoint (the customer's
/api/v1/rooms.get, used by mobile to render the last-message snippet in the room
list). A new history-service, per-site, account-scoped batch RPC that resolves each
requested room's latest message at read time (A2). Maintainer-approved approach (A2,
option E2) per the #393 design-lock.

What's included

  • Subject chat.user.{account}.request.history.{siteID}.rooms.get — account+site from
    the subject, {roomIds[]} in the body. One batch per site; the caller groups a user's
    rooms by site and fans out (same shape as ThreadSubscriptionList).
  • RoomsGet handler (history-service) → roomLastMessage: per room, the existing
    access check (checkAccessAndRoomTimes) then the latest message via the existing
    messages_by_room … DESC reads (GetMessagesBefore / GetMessagesBetweenDesc,
    limit 1) — access-window + history-floor aware (mirrors LoadHistory). No new repo method.
  • A2 read-only: no denormalized write, no event, no edit/delete hooks, no
    walk-back
    — a soft-deleted latest message is returned as-is with deleted=true.
  • Best-effort batch: bounded-concurrency per-room resolve; a room that's
    not-accessible / empty / errored is omitted, never failing the batch. Batch ≤ 100;
    content preview-trimmed to 256 runes.
  • docs/client-api.md §3.2 "Get Rooms Last Message" (doc-ratchet).

#393 completion — web-sidebar integration

  • Shared wire types moved to pkg/model (LastMessage, RoomsGetRequest,
    RoomsGetResponse) so user-service can consume them without importing
    history-service internals; history-service's own models package now aliases them
    (type LastMessage = model.LastMessage, etc. — no behavior change).
  • SubscriptionRoom.LastMessage *model.LastMessage (pkg/model/subscription.go) —
    new optional field on the room-derived view returned by subscription.list.
  • historyclient.Client.RoomsGet — new per-site RPC call mirroring the existing
    GetThreadList pattern (marshal request, NATS request/reply, errcode.Parse,
    unmarshal). Added to the HistoryClient interface; mocks regenerated.
  • UserService.enrichWithLastMessage (user-service/service/subscriptions.go) —
    new enrichment pass in ListSubscriptions, run after enrichWithRoomInfo. Groups
    every subscription with a resolved Room by site (local and cross-site — unlike
    room-info enrichment, last-message isn't part of the local $lookup baseline), fans
    out one rooms.get call per site (bounded by the existing maxSiteFanout
    semaphore), chunking each site's roomIds at 100 to respect history-service's own
    batch cap. A degraded or absent site just leaves those rooms' lastMessage nil —
    it never fails the list.
  • docs/client-api.md — added the lastMessage field to the SubscriptionRoom
    table + example, and reconciled the rooms.get RPC description to note
    subscription.list now calls it server-side (the RPC also remains directly callable).

Out of scope (follow-up)

  • None — the user-service integration originally called out as follow-up is now
    included above.

Verification

  • go build ./pkg/... ./history-service/... ./user-service/... — clean.
  • go test ./pkg/... ./history-service/... ./user-service/... — green, including two
    new TestListSubscriptions_LocalBaselineRoom_NoKey assertions and a new
    TestListSubscriptions_LastMessage_SiteDegrades test (site RPC failure degrades
    gracefully, list still succeeds).
  • NATS wire-layer e2e against the dev stack — to follow before marking ready.

🤖 Generated with Claude Code

New per-site, account-scoped RPC chat.user.{account}.request.history.{siteID}.rooms.get:
given {roomIds[]}, returns each accessible room's latest message ({roomId -> lastMessage}),
resolved at read time. Mirrors the ThreadSubscriptionList per-site shape; the caller
groups rooms by site and issues one batch per site.

Per room: existing access check, then the latest message via the existing
messages_by_room DESC read (access-window + history-floor aware). A2 read-only — no
denormalized write, no event, no walk-back: a soft-deleted latest message is returned
as-is with deleted=true. Best-effort batch (bounded concurrency; a non-accessible/empty/
errored room is omitted, never failing the batch); content preview-trimmed to 256 runes.

docs/client-api.md updated (doc-ratchet). Closes #393.
@coderabbitai

coderabbitai Bot commented Jun 29, 2026

Copy link
Copy Markdown

Important

Review skipped

Draft detected.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: f2b7fb32-ab3d-458c-a6a4-948e3ca2539d

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/393-rooms-get

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

The per-room loop broke on c.Err() then returned a nil-error partial
response, which golangci-lint's nilerr flagged. On a cancelled/timed-out
context the caller is gone, so propagate the error instead of returning a
partial OK that won't be read.
@chenjr0719 chenjr0719 marked this pull request as ready for review June 30, 2026 06:14
@chenjr0719 chenjr0719 marked this pull request as draft July 1, 2026 02:43
@chenjr0719 chenjr0719 removed the ready label Jul 1, 2026
…rooms.get (#393)

Add read-time (A2) last-message enrichment to subscription.list: a new
enrichWithLastMessage pass groups every resolved room by site and fans out one
rooms.get RPC per site (bounded by maxSiteFanout, chunked at 100), embedding the
result as SubscriptionRoom.LastMessage. No denormalized writes, no edit/delete
hooks, no event — a degraded site simply leaves LastMessage nil.

Move the rooms.get wire types (LastMessage/RoomsGetRequest/RoomsGetResponse) to
pkg/model so user-service consumes them without importing history-service
internals; history-service aliases them (no behavior change). Add
historyclient.RoomsGet mirroring GetThreadList; regenerate mocks. Doc-ratchet
client-api.md.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant