Skip to content

Expose peer reaction READS on unified v2 (list / summary / by-identity, keyed by globalTransitId) #1610

Description

@2002Bishwajeet

Summary

Reaction writes already work over peer on v2. Reaction reads do not — there is no peer variant of list / summary / list-by-identity on the unified v2 surface, so a client can only ever see its own reactions on another identity's post. The PeerReactionSenderService that does the work already exists.

Found while building the native KMP feed (homebase-id/chat-kmp#802).

What breaks without it

Tap "who reacted" on a post by an identity you follow and you see only yourself, while the post's own reaction count (which comes from the file header and is correct) says otherwise.

Root cause on the client: DriveFileGroupReactionProvider builds every reaction URL from creds.domainour identity — so on a followed post it reads our local feed-drive copy, which only ever holds reactions we sent:

  • homebase-api/.../drives/files/reactions/DriveFileGroupReactionProvider.kt:198, :222, :249 — all apiUrl(creds.domain, endpoint)
  • homebase-core/.../feed/services/PostReactionService.kt:195-219listReactors / reactorsFor
  • The feed currently ships an honest degradation: authoritative counts from the header, plus a "partial list" footnote (FeedTimelineViewModel.kt:270)

The web client handles this by branching on locality — dotyoucore-js/.../core/ReactionData/ReactionService.ts:26, 57, 92 (const isLocal = odinId === dotYouClient.getHostIdentity()) and calling '/transit/reactions' + '/list' (line 116) otherwise.

Scope: reads only — writes are done, please don't rebuild them

Op Over peer today? Path
add ✅ works client posts to its own host with transitOptions.recipientsGroupReactionService.AddReactionAsync (GroupReactionService.cs:65-77) enqueues OutboxItemType.AddRemoteReactionIPeerGroupReactionHttpClientPeerPerimeterGroupReactionController [HttpPost("add")]
delete ✅ works same path, GroupReactionService.cs:105-115
list reactors ❌ missing own host only
summary / counts ❌ missing own host only
list-by-identity ❌ missing own host only

Proposed routes

GET /api/v2/peer/{peerIdentity}/drives/{driveId}/files/by-gtid/{gtid}/reactions?cursor=&maxRecords=
GET /api/v2/peer/{peerIdentity}/drives/{driveId}/files/by-gtid/{gtid}/reactions/summary
GET /api/v2/peer/{peerIdentity}/drives/{driveId}/files/by-gtid/{gtid}/reactions/by-identity?identity=

Why keyed by globalTransitId: a followed identity's post lands on the feed drive as a reference — it has no fileId on the author's drive and no uniqueId at all (measured: uniqueId is NULL on 100% of feed-drive rows). The gtid is the only handle the client holds. Conveniently the peer service is already gtid-keyed: GetRemoteReactionsRequest.File and AddRemoteReactionRequest.File are both GlobalTransitIdFileIdentifier. The PeerByGtid route template already exists (UnifiedApiRouteConstants.cs:31) and V2DrivePeerQueryByGtidController already binds {gtid} this way.

Policy: OwnerOrApp.

What already exists

Piece Location
Does the work src/services/Odin.Services/Peer/Outgoing/Drive/Reactions/PeerReactionSenderService.csGetReactionsAsync (:52), GetReactionCountsAsync (:77), GetReactionsByIdentityAndFileAsync (:101)
Actions to copy src/apps/Odin.Hosting/Controllers/Base/Transit/PeerReactionContentSenderControllerBase.cs:35, :72, :83
Perimeter half (live) PeerPerimeterReactionContentController.cslist (:33), summary (:56), listbyidentity (:63) → PeerIncomingReactionService

One thing worth double-checking

The client writes group reactions, but this peer path is the direct reaction service. They appear to share storage — GroupReactionService takes ReactionContentService as a ctor dependency (GroupReactionService.cs:25) and delegates every read and write to it (:64, :105, :131, :145, :155), and PeerIncomingReactionService is constructed from the same ReactionContentService (PeerIncomingReactionService.cs:17) and calls reactionContentService.GetReactionsAsync (:69). So the peer read should return exactly the rows the group route wrote — but please confirm this holds for fileSystemType handling before assuming it's a clean lift.

Response shape request

The peer path currently returns GetReactionsPerimeterResponse { List<PerimeterReaction>, int? Cursor } where PerimeterReaction { OdinId, ReactionContent, Created, GlobalTransitIdFileIdentifier }.

The local v2 group route returns GetReactionsResponse { List<Reaction>, int? Cursor }, which the client decodes as GroupReactionItem { reactionContent, odinId, fileId, created }.

Please emit the local shape from the new v2 peer routes so the client can reuse its existing decoder; fileId can be omitted or zeroed.

Why v1 doesn't work for this client

POST /api/apps/v1/transit/reactions/{list,summary,listbyidentity}AppPeerReactionContentSenderController (src/apps/Odin.Hosting/Controllers/ClientToken/App/Transit/AppTransitReactionContentSenderController.cs:11-16) carries [AuthorizeValidAppToken] (line 13) → 401 for the v2 bearer token.

Note on a doc that may get cited

dotyoucore-js/docs/api/v2/reactions.md lists [GET] /api/apps/v2/peer/files/reactions and an unified-reactions family. These are not implementeddocs/api/v2/index.md:1 labels the folder "First draft of a potential new api v2 structure", it proposes the /api/apps/v2/… prefix rather than the shipped /api/v2/…, and grepping apps/v2 / unified-reactions across src and Tests returns zero hits. Wish-list, not spec.

Verification

Confirmed against HEAD 84d50011e: src/apps/Odin.Hosting/UnifiedV2/Drive/Reactions/ contains only V2DriveGroupReactionController.cs and V2DriveReactionController.cs, both local-drive only.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions