Skip to content

feat: Microsoft Teams integration (calls/room, calls/user, meetings)#316

Merged
mliu33 merged 9 commits into
hmchangw:mainfrom
chenjr0719:feat/teams-integration
Jun 18, 2026
Merged

feat: Microsoft Teams integration (calls/room, calls/user, meetings)#316
mliu33 merged 9 commits into
hmchangw:mainfrom
chenjr0719:feat/teams-integration

Conversation

@chenjr0719

@chenjr0719 chenjr0719 commented Jun 15, 2026

Copy link
Copy Markdown
Collaborator

Summary

Implements the Microsoft Teams integration from #314 as three NATS RPC handlers in room-service. Two are pure Teams deep-link builders (no Graph); one creates a Graph onlineMeeting and is idempotent per room. Built against mocks — the maintainer runs the live Azure test (no creds held here).

Closes #314

What's included

  • pkg/model — request/reply contracts (TeamsRoomCallRequest, TeamsUserCallRequest, TeamsCallReply, TeamsMeetingRequest, TeamsMeetingReply), MessageTypeTeamsMeetStarted = "teams_meet_started", and TeamsMeetStartedSysData{meetingId, joinUrl}.
  • pkg/msgraph — minimal Graph client: client-credentials (app-only) token with caching + CreateOnlineMeeting, behind a Client interface so the meetings RPC is unit-testable without Azure.
  • room-service — three handlers registered in Handler.Register:
    • teams.call (calls/room): ListRoomMembersaccount@TEAMS_EMAIL_DOMAIN excluding self → https://teams.microsoft.com/l/call/0/0?users=...{joinUrl}. Enforces ROOM_MEMBERS_CALL_LIMIT (20).
    • teams.call.user (calls/user): accountName@domain → same deep-link shape → {joinUrl}.
    • teams.meeting (meetings): validate membership/room → fast-path read the teams_meetings record (idempotent return if present) → else ListRoomMembers (enforce ROOM_MEMBERS_LIMIT 500) → Graph createOrGet (externalId = siteID:roomID) → insert the teams_meetings record (unique on roomId+siteId; on duplicate-key, read back the concurrent winner and return without publishing) → on first creation, publish teams_meet_started via the canonical message path → {id, joinUrl}.
  • Idempotency (concurrency-safe) — Graph createOrGet keyed on a stable per-room externalId (siteID:roomID) guarantees one meeting per room from the source of truth; a first-class teams_meetings Mongo record with a UNIQUE index on (roomId, siteId) guards local state so exactly one teams_meet_started system message is published per room (TeamsMeetingStore in room-service/store.go, MongoStore.GetTeamsMeeting/InsertTeamsMeeting + the unique index in room-service/store_mongo.go). Any remaining Cassandra reads stay keyspace-bound (the gocql session is bound to CASSANDRA_KEYSPACE; never hardcodes chat/tchat).
  • ConfigTEAMS_TENANT_ID/CLIENT_ID/CLIENT_SECRET, TEAMS_EMAIL_DOMAIN, ROOM_MEMBERS_LIMIT (500), ROOM_MEMBERS_CALL_LIMIT (20). (The previous MESSAGE_BUCKET_HOURS/MESSAGE_READ_MAX_BUCKETS knobs are removed — room-service no longer scans message buckets for meetings.)
  • Docsdocs/client-api.md gains the 3 calls (subjects, bodies, responses, error tables) + the RPC index rows.

NATS subjects (chosen)

Endpoint Subject
calls/room chat.user.{account}.request.room.{roomID}.{siteID}.teams.call
calls/user chat.user.{account}.request.teams.{siteID}.call.user
meetings chat.user.{account}.request.room.{roomID}.{siteID}.teams.meeting

Rejected alternative: the issue sketched chat.user.{account}.request.teams.{siteID}.{room|user|meeting}. The room-scoped endpoints instead carry room.{roomID}.{siteID} so natsrouter extracts {account} + {roomID} exactly like every other room RPC (member.list, mute.toggle, …) and requireMembershipAndGetRoom works unchanged. calls/user has no room, so it stays user-scoped with the target in the body.

Design rationale (idempotency)

Two concurrent teams.meeting calls for the same room previously could each create a Graph meeting and each publish a teams_meet_started system message: the old idempotency read scanned message buckets for the last marker, and the marker is written asynchronously via the canonical message path, so racing callers saw no marker yet (documented as a known race in the prior code). The fix pushes idempotency to two cooperating layers:

  • Graph createOrGet = idempotency at the source of truth. POST /users/{organizerId}/onlineMeetings/createOrGet with an externalId (pkg/msgraph/msgraph.go CreateOnlineMeeting) returns one meeting per (organizer, externalId). The externalId is the stable per-room key siteID:roomID (room-service/handler_teams.go teamsMeetingExternalID). This holds across process restarts and multiple service instances with no local lock — even a true race returns the same Graph meeting to both callers.

  • Mongo unique-key record (NOT Cassandra) = local single-publish guard. room-service's primary store is Mongo, and it already uses unique-index + mongo.IsDuplicateKeyError-as-success as its retry-safe-write idempotency convention — see the room_members (rid, member.type, member.id) and subscriptions (roomId, u.account) unique indexes in room-service/store_mongo.go EnsureIndexes (the room-worker bulk-insert path treats the dup-key error as success for the same reason). This change adds a teams_meetings collection with a UNIQUE index on (roomId, siteId) in the same style. The handler inserts the record after createOrGet; on a concurrent insert the loser hits the unique constraint, reads back the winner's record, and returns without publishing — so exactly one teams_meet_started is published per room. The record also makes the meeting a queryable first-class document instead of scanning message buckets.

    Cassandra in room-service is a read-only message-history reader (room-service/store_cassandra.go only SELECTs); a Cassandra LWT/Paxos IF NOT EXISTS would introduce a new, heavier consensus primitive the service doesn't otherwise use — the wrong tool when the store the service owns already has the exact idempotency convention needed.

Self-audit (pre-review)

Message-path trace: 1 client-req ✅ — Teams RPC structs (pkg/model/teams.go) + docs/client-api.md · 2 gatekeeper N/A — room-service NATS RPC, not the message-gatekeeper pipeline · 3 model ✅ — pkg/model/teams.go + MessageTypeTeamsMeetStarted (pkg/model/event.go) + TeamsMeetStartedSysData (pkg/model/message.go) · 4 workers ✅ no-change — message-worker persists any non-empty Type · 5 storage ✅ — first-class teams_meetings collection, UNIQUE index (roomId, siteId) (room-service/store_mongo.go EnsureIndexes) · 6 read ✅ — MongoStore.GetTeamsMeeting fast-path read (room-service/store_mongo.go) · 7 client-events ✅ — teams_meet_started via existing system-message fan-out

Checklist — item · evidence (file) · source-of-truth (ref / prior review)

Decisions & rationale (with source)

  • Email = account@TEAMS_EMAIL_DOMAIN, no user-service lookup — at the NATS layer only account is available; the OIDC email claim exists only at auth-service token-exchange (auth-service/handler.go:178), and dev already derives account + "@dev.local" (auth-service/handler.go:220). Open gate: prod account (= claims.PreferredUsername, auth-service/handler.go:152) must equal the email local-part under one domain.
  • Concurrency-safe idempotency without a new lock primitive — see the ## Design rationale (idempotency) section below. Graph createOrGet is the cross-instance correctness guarantee; the teams_meetings unique index is the local single-publish guard, reusing room-service's existing unique-index + IsDuplicateKeyError convention. Source: ref F2.
  • Subjects room-scoped for room/meeting (chat.user.{account}.request.room.{roomID}.{siteID}.teams.{call|meeting}requireMembershipAndGetRoom), flat for the 1:1 user call — matches every existing room RPC. Source: ref F1 / F11.

Source-of-truth discipline: every ✅/citation above verified against the changed files in this branch; ref ids (A/C/D/F-series) map to the project's mined review history.

Verification

go build ./..., go vet ./..., and go test ./room-service/... ./pkg/msgraph/... ./pkg/model/... all pass locally, including the new idempotency/concurrency tests (a -race run of the concurrent meetings test is clean). Live Azure/Graph test is for the maintainer.


Summary by CodeRabbit

Release Notes

  • New Features

    • Added Microsoft Teams integration with three new room-service operations to start Teams room calls, start 1:1 Teams user calls, and create Teams online meetings.
    • Deep links are generated with proper recipients and enforced call member limits.
    • Online meeting creation is idempotent per room and publishes a single system message on first creation (with meeting ID and join URL).
  • Documentation

    • Documented all Teams operations, payloads, routing behavior, and error codes.
  • Tests

    • Added unit coverage for Teams deep-link generation, idempotency, and Graph-meeting creation behavior.

🤖 Generated with Claude Code

@coderabbitai

coderabbitai Bot commented Jun 15, 2026

Copy link
Copy Markdown

Review Change Stack

Warning

Review limit reached

@chenjr0719, we couldn't start this review because you've reached your PR review rate limit.

More reviews will be available in 51 minutes and 42 seconds. Learn how PR review limits work.

Your organization has used up its prepaid credits, and credit purchases are no longer available. Enable the review add-on in the billing tab to keep reviews running — you're only billed for reviews past your plan's rate limits ($0.25/file).

⌛ How to resolve this issue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based credits.

🚦 How do rate limits work?

CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan refill rate.

For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, the refill rate gradually slows as usage increases. The highest same-day bursts are limited more strictly.

Please see our Fair Usage Limits Policy for further information.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: cc40cd83-ee7b-411f-b93e-1e2a793d779f

📥 Commits

Reviewing files that changed from the base of the PR and between 0558e61 and cb1c6bf.

📒 Files selected for processing (1)
  • room-service/store_mongo.go
📝 Walkthrough

Walkthrough

Adds three NATS RPC handlers to room-service for Microsoft Teams integration: a group room call deep-link, a 1:1 user call deep-link, and an idempotent Graph onlineMeeting creator. Introduces a new pkg/msgraph OAuth2 client, a MongoDB-backed meeting idempotency store, shared model types, NATS subject builders, service startup wiring, full test coverage, and API documentation.

Changes

Microsoft Teams Integration: Calls & Meetings RPCs

Layer / File(s) Summary
Shared model contracts and NATS subject builders
pkg/model/event.go, pkg/model/message.go, pkg/model/teams.go, pkg/subject/subject.go, pkg/subject/subject_test.go
Adds MessageTypeTeamsMeetStarted constant, TeamsMeetStartedSysData payload struct, five Teams RPC request/reply structs, TeamsMeetingRecord persistence model with BSON tags, and six NATS subject builder/pattern functions that return (string, error) for invalid account tokens. Tests validate subject generation, error handling, and pattern outputs.
Microsoft Graph OAuth2 client
pkg/msgraph/msgraph.go, pkg/msgraph/msgraph_test.go
Introduces a Client interface backed by graphClient that caches OAuth2 client-credentials tokens with expiry skew, selects organizer-scoped or /me createOrGet endpoints, POSTs onlineMeeting payloads with externalId idempotency, parses Graph error envelopes, and requires joinWebUrl in the response. Tests cover token caching, token/Graph errors, idempotency, and missing joinWebUrl.
MongoDB idempotency store for Teams meetings
room-service/store.go, room-service/store_mongo.go, room-service/mock_store_test.go
Adds TeamsMeetingStore interface with GetTeamsMeeting/InsertTeamsMeeting, extends MongoStore with a teamsMeetings collection, creates a unique compound index on (roomId, siteId) in EnsureIndexes, and surfaces raw insert errors for duplicate-key race detection. Includes the GoMock MockTeamsMeetingStore.
Teams RPC handler methods, helpers, and sentinel errors
room-service/handler_teams.go, room-service/helper.go
Adds errTeams* sentinel errors, email/deep-link helper functions, and three NATS handler methods: teamsRoomCall (multi-user deep-link excluding self with call limit), teamsUserCall (1:1 deep-link), and teamsMeeting (fast-path idempotency read, Graph createOrGet with stable siteID:roomID externalId, duplicate-key race recovery, winner-only publish of teams_meet_started). Adds publishTeamsMeetStarted and member counting/email derivation utilities.
Handler struct extension and service startup wiring
room-service/handler.go, room-service/main.go, room-service/deploy/docker-compose.yml
Adds five Teams fields to Handler; registers three NATS subscriptions in Handler.Register; extends config with Teams credentials and member limits; conditionally constructs msgraph.Client when credentials are present; wires all dependencies into the handler; adds Teams credential and limit env vars to docker-compose.
Handler tests and test doubles
room-service/handler_teams_test.go
Adds fakeGraphClient, stubTeamsMeetingStore, raceTeamsMeetingStore test doubles, and 20 test functions covering all three handlers across success, validation, membership, limit, concurrency, idempotency, and failure paths.
Client API and msgraph package documentation
docs/client-api.md, docs/msgraph-client.md
Extends the room-service RPC subject index with three Teams subjects; documents request/response schemas, error codes, and teams_meet_started idempotency semantics for all three RPCs; adds docs/msgraph-client.md covering OAuth token flow, createOrGet idempotency, production permissions, and stub-based test approach.

Sequence Diagram(s)

sequenceDiagram
  participant Client
  participant roomService as room-service Handler
  participant TeamsMeetingStore as MongoDB TeamsMeetingStore
  participant graphClient as pkg/msgraph Client
  participant MsgStream as Canonical Message Stream

  Client->>roomService: NATS RPC — TeamsRoomCall / TeamsUserCall
  roomService->>roomService: validate account/roomID, check membership, build deep-link URL
  roomService-->>Client: TeamsCallReply { joinUrl }

  Client->>roomService: NATS RPC — TeamsMeeting
  roomService->>roomService: validate config, membership, member count
  roomService->>TeamsMeetingStore: GetTeamsMeeting(roomID, siteID)
  alt record found (fast-path)
    TeamsMeetingStore-->>roomService: TeamsMeetingRecord
    roomService-->>Client: TeamsMeetingReply { id, joinUrl }
  else no record
    TeamsMeetingStore-->>roomService: (nil, false, nil)
    roomService->>graphClient: CreateOnlineMeeting(externalId=siteID:roomID)
    graphClient-->>roomService: OnlineMeeting { id, joinWebUrl }
    roomService->>TeamsMeetingStore: InsertTeamsMeeting(record)
    alt duplicate-key race (concurrent writer won)
      TeamsMeetingStore-->>roomService: duplicate-key error
      roomService->>TeamsMeetingStore: GetTeamsMeeting(roomID, siteID)
      TeamsMeetingStore-->>roomService: winner record
    else this writer is the winner
      roomService->>MsgStream: publish teams_meet_started (dedup ID)
    end
    roomService-->>Client: TeamsMeetingReply { id, joinUrl }
  end
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Suggested labels

ready

Suggested reviewers

  • mliu33
  • vjauhari-work

Poem

🐰 Hop, hop, through the Teams deep link I go,
A meeting spawned where the Graph winds blow,
externalId keeps it idempotent and neat,
Only one teams_meet_started per room-service beat!
The duplicate-key race? The winner takes all —
Just one publish to stream, one join URL for all! 🎉

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 49.15% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'feat: Microsoft Teams integration (calls/room, calls/user, meetings)' clearly summarizes the main change and is specific to the Teams integration feature with all three endpoints.
Linked Issues check ✅ Passed The PR implements all objectives from #314: three NATS RPC handlers for Teams calls/room, calls/user, and meetings with idempotency, email derivation, member limits, system messages, and comprehensive test coverage.
Out of Scope Changes check ✅ Passed All changes are directly aligned with #314 objectives: Teams RPC handlers, msgraph client, model contracts, subject builders, storage layer, tests, and documentation—no unrelated modifications detected.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

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 and usage tips.

@chenjr0719 chenjr0719 marked this pull request as draft June 15, 2026 08:38

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 6

🧹 Nitpick comments (1)
pkg/subject/subject.go (1)

754-756: ⚡ Quick win

Validate account in the new Teams subject builders for wildcard safety.

TeamsRoomCall, TeamsMeeting, and TeamsUserCall accept account but skip the shared token guard used elsewhere in this file. Add isValidAccountToken(account) checks for consistent NATS-subject safety.

Suggested patch
func TeamsRoomCall(account, roomID, siteID string) string {
+	if !isValidAccountToken(account) {
+		panic("invalid account token: contains NATS wildcard characters")
+	}
	return fmt.Sprintf("chat.user.%s.request.room.%s.%s.teams.call", account, roomID, siteID)
}
...
func TeamsMeeting(account, roomID, siteID string) string {
+	if !isValidAccountToken(account) {
+		panic("invalid account token: contains NATS wildcard characters")
+	}
	return fmt.Sprintf("chat.user.%s.request.room.%s.%s.teams.meeting", account, roomID, siteID)
}
...
func TeamsUserCall(account, siteID string) string {
+	if !isValidAccountToken(account) {
+		panic("invalid account token: contains NATS wildcard characters")
+	}
	return fmt.Sprintf("chat.user.%s.request.teams.%s.call.user", account, siteID)
}

Based on learnings: for pkg/subject, functions accepting account tokens should consistently reject wildcard tokens via a shared validator.

Also applies to: 764-766, 774-776

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@pkg/subject/subject.go` around lines 754 - 756, The functions TeamsRoomCall,
TeamsMeeting, and TeamsUserCall accept an account parameter but lack validation
checks for wildcard safety, which is inconsistent with other subject builders in
the file that use isValidAccountToken(account). Add isValidAccountToken(account)
checks at the beginning of each of these three functions to validate the account
parameter and return an empty string if validation fails, ensuring consistent
NATS-subject safety across all account-accepting subject builders.

Source: Learnings

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@docs/client-api.md`:
- Around line 1891-1894: The documentation for the triggered events success path
currently describes the `teams_meet_started` system message publication without
clarifying that this only occurs on initial meeting creation. Modify the text in
the "Triggered events — success path" section to explicitly state that the
`teams_meet_started` system message is emitted only when a new meeting is first
created, and clarify that idempotent repeat calls which return cached meeting
data do not trigger a new system message emission. This distinction is important
for understanding the behavior difference between first-time and cached
responses.

In `@pkg/msgraph/msgraph.go`:
- Around line 23-27: Rename the single-method `Client` interface to follow the
"-er" suffix convention used for single-method interfaces (e.g.,
`OnlineMeetingCreator` or similar name ending in "-er" that describes the
action). Update the interface definition and its method `CreateOnlineMeeting` to
use the new name. Update the constructor (likely the `New` function around line
92-106) to return the concrete struct type instead of the interface, and update
all usages of the `Client` interface type to reference the struct type instead,
aligning with the "accept interfaces, return structs" convention.
- Around line 227-229: The error message in the createOnlineMeeting function
(around the status code validation) is exposing the raw Graph response body by
including string(respBody) in the fmt.Errorf call. Remove the string(respBody)
portion from the error return statement and only include the status code in the
message. This prevents sensitive information from the upstream Graph API
response from being propagated into error logs and ensures internal error
details are not exposed to callers.
- Around line 8-19: The code uses direct net/http client calls for OAuth2 token
endpoint requests and Graph API calls, which violates the repository standard
requiring Resty for all outbound HTTP calls. Replace the direct net/http client
usage in both locations (the OAuth2 token endpoint call and the Graph API call)
with pkg/restyutil.New() to enable automatic OTel instrumentation and structured
logging. If testing requires a custom HTTP client, use the WithHTTPClient option
provided by restyutil instead of passing the raw http.Client directly.

In `@room-service/handler_teams.go`:
- Around line 116-124: The room-level idempotency check in the handler_teams.go
file has a race condition between reading the marker with
GetLastTeamsMeetStarted and performing the CreateOnlineMeeting operation.
Concurrent calls or fast retries can all see no existing marker and proceed to
create duplicate meetings before the marker is durably written. To fix this,
ensure the marker write happens atomically with or before the
CreateOnlineMeeting call. Consider either creating the meeting first and then
durably writing the marker, or implementing a mutual exclusion mechanism (such
as a distributed lock or database constraint) to guarantee only one concurrent
caller can proceed through the CreateOnlineMeeting step. This ensures that by
the time CreateOnlineMeeting is invoked, the marker is already durably visible
to prevent subsequent concurrent calls from making duplicate create requests.

In `@room-service/main.go`:
- Around line 49-62: Add startup validation checks to fail fast on invalid Teams
configuration before the service starts accepting requests. Validate that if any
of the Teams credentials (TeamsTenantID, TeamsClientID, TeamsClientSecret) are
set, all three must be provided together to avoid partial configuration; also
validate that TeamsEmailDomain is not empty and that RoomMembersLimit and
RoomMembersCallLimit are positive integers. If any validation fails, log a
descriptive error message and exit with a non-zero status code, following the
pattern established for bucket settings validation. This validation should
happen during startup initialization to prevent runtime failures in Teams RPCs.

---

Nitpick comments:
In `@pkg/subject/subject.go`:
- Around line 754-756: The functions TeamsRoomCall, TeamsMeeting, and
TeamsUserCall accept an account parameter but lack validation checks for
wildcard safety, which is inconsistent with other subject builders in the file
that use isValidAccountToken(account). Add isValidAccountToken(account) checks
at the beginning of each of these three functions to validate the account
parameter and return an empty string if validation fails, ensuring consistent
NATS-subject safety across all account-accepting subject builders.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 7ff146dd-3fd5-4e0f-95a8-4a224cda3ee7

📥 Commits

Reviewing files that changed from the base of the PR and between 7e96fdb and 892f22f.

📒 Files selected for processing (16)
  • docs/client-api.md
  • pkg/model/event.go
  • pkg/model/message.go
  • pkg/model/teams.go
  • pkg/msgraph/msgraph.go
  • pkg/msgraph/msgraph_test.go
  • pkg/subject/subject.go
  • room-service/deploy/docker-compose.yml
  • room-service/handler.go
  • room-service/handler_teams.go
  • room-service/handler_teams_test.go
  • room-service/helper.go
  • room-service/main.go
  • room-service/mock_store_test.go
  • room-service/store.go
  • room-service/store_cassandra.go

Comment thread docs/client-api.md
Comment thread pkg/msgraph/msgraph.go
Comment on lines +8 to +19
import (
"bytes"
"context"
"encoding/json"
"fmt"
"io"
"net/http"
"net/url"
"strings"
"sync"
"time"
)

@coderabbitai coderabbitai Bot Jun 15, 2026

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

find . -type f -name "msgraph.go" | head -20

Repository: hmchangw/chat

Length of output: 82


🏁 Script executed:

wc -l pkg/msgraph/msgraph.go

Repository: hmchangw/chat

Length of output: 84


🏁 Script executed:

cat -n pkg/msgraph/msgraph.go | sed -n '1,50p'

Repository: hmchangw/chat

Length of output: 2054


🏁 Script executed:

cat -n pkg/msgraph/msgraph.go | sed -n '85,150p'

Repository: hmchangw/chat

Length of output: 2467


🏁 Script executed:

cat -n pkg/msgraph/msgraph.go | sed -n '200,239p'

Repository: hmchangw/chat

Length of output: 1689


🏁 Script executed:

rg "github.com/go-resty/resty" --type go | head -20

Repository: hmchangw/chat

Length of output: 357


🏁 Script executed:

rg "resty\." --type go | head -30

Repository: hmchangw/chat

Length of output: 1241


🏁 Script executed:

grep -r "http.Client" --include="*.go" | head -20

Repository: hmchangw/chat

Length of output: 672


🏁 Script executed:

cat -n pkg/msgraph/msgraph.go | sed -n '50,85p'

Repository: hmchangw/chat

Length of output: 1324


🏁 Script executed:

cat -n pkg/restyutil/restyutil.go | head -50

Repository: hmchangw/chat

Length of output: 1714


🏁 Script executed:

rg "msgraph\." --type go | head -20

Repository: hmchangw/chat

Length of output: 1130


🏁 Script executed:

grep -A 5 "import" pkg/msgraph/msgraph.go | head -15

Repository: hmchangw/chat

Length of output: 116


Replace direct net/http calls with Resty for outbound Graph and token requests.

This implementation uses net/http directly for both the OAuth2 token endpoint (lines 131–137) and Graph API calls (lines 210–217), which diverges from the repository's outbound HTTP standard.

Per coding guidelines: "Use Resty for all outbound HTTP calls — never net/http client directly." Migrate to pkg/restyutil.New() to enable automatic OTel instrumentation and structured logging for both calls. The WithHTTPClient option can be preserved for testing without needing the raw http.Client.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@pkg/msgraph/msgraph.go` around lines 8 - 19, The code uses direct net/http
client calls for OAuth2 token endpoint requests and Graph API calls, which
violates the repository standard requiring Resty for all outbound HTTP calls.
Replace the direct net/http client usage in both locations (the OAuth2 token
endpoint call and the Graph API call) with pkg/restyutil.New() to enable
automatic OTel instrumentation and structured logging. If testing requires a
custom HTTP client, use the WithHTTPClient option provided by restyutil instead
of passing the raw http.Client directly.

Source: Coding guidelines

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Leaving this as net/http for now — out of the agreed quick-win scope for this round. Happy to migrate to pkg/restyutil in a follow-up if you'd prefer the OTel/logging consistency; flagging it as a deliberate deferral rather than an oversight.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@chenjr0719, understood — noted as a deliberate deferral rather than an oversight. The concern remains valid for consistency with the codebase's OTel/logging standard, so it's worth tracking. Would you like me to open a GitHub follow-up issue to capture the pkg/msgraphpkg/restyutil migration so it doesn't get lost between rounds?

(˳˘ ɜ˘)˳ ♡


🧠 Learnings used
Learnt from: Joey0538
Repo: hmchangw/chat PR: 59
File: pkg/roomkeysender/roomkeysender.go:28-30
Timestamp: 2026-04-08T08:25:32.108Z
Learning: In this repository’s Go code, follow the CLAUDE.md rule: don’t add defensive error handling, fallbacks, or nil/validation guards for conditions that are guaranteed not to occur. For internal functions/methods (e.g., when all callers are known to pass non-nil pointer parameters, such as pkg/roomkeysender/roomkeysender.go’s Send), the absence of nil-pointer guards is acceptable—do not flag it as a review issue. Reserve pointer validation and other safety checks for system boundaries (e.g., external inputs, API/HTTP requests, deserialization, or other untrusted entry points).

Learnt from: Joey0538
Repo: hmchangw/chat PR: 59
File: pkg/model/model_test.go:306-317
Timestamp: 2026-04-08T08:25:48.132Z
Learning: In Go, `time.Time` is `comparable` and therefore may be used as a type argument to generic constraints requiring `comparable` (e.g., `func roundTrip[T comparable](...)`). This is because `time.Time`’s underlying fields are all comparable (`uint64`, `int64`, and a pointer `*Location`, where pointers are comparable by address). As a result, any struct that contains or embeds a `time.Time` field (e.g., `model.Message`, or structs embedding it like `model.NotificationEvent`) should also satisfy `comparable` and should not be flagged as a compilation-error when used with `T comparable`.

Learnt from: general-lex
Repo: hmchangw/chat PR: 141
File: message-worker/main.go:95-99
Timestamp: 2026-05-04T03:30:38.678Z
Learning: In hmchangw/chat, `pkg/natsutil/connect.go`’s `natsutil.Connect` returns an OpenTelemetry-wrapped NATS connection (`*otelnats.Conn` from `github.com/Marz32onE/instrumentation-go/otel-nats/otelnats`), not a plain `*nats.Conn`. Because `otelnats.Conn.Publish` is defined as `Publish(ctx context.Context, subj string, data []byte) error` (it uses `ctx` to propagate traces via NATS headers), code review should NOT flag `nc.Publish(ctx, subj, data)` as an incorrect method call shape. This `Publish(ctx, ...)` pattern is the standard/correct usage across the codebase (e.g., message-worker, broadcast-worker, notification-worker, history-service).

Learnt from: Allan-Code-hub
Repo: hmchangw/chat PR: 263
File: upload-service/main.go:111-115
Timestamp: 2026-06-03T03:26:01.642Z
Learning: In this repo’s Go code, `mongoutil.Disconnect(ctx context.Context, client *mongo.Client)` returns no value and internally logs disconnect errors (e.g., via `slog.Error("mongo disconnect failed", ...)`). When reviewing, never suggest `return mongoutil.Disconnect(...)`—it will not compile. Instead, call it as a statement (`mongoutil.Disconnect(ctx, client)`) and then return `nil` (or the appropriate value for the surrounding function) if needed. This call pattern should be consistent across services’ shutdown paths (e.g., `*/main.go`).

Comment thread pkg/msgraph/msgraph.go
Comment thread pkg/msgraph/msgraph.go
Comment thread room-service/handler_teams.go Outdated
Comment thread room-service/main.go Outdated
Comment on lines +49 to +62
// Microsoft Teams integration. Teams* credentials are required only for the
// meetings RPC (Graph onlineMeeting create); the deep-link RPCs use only
// EmailDomain. When TenantID/ClientID/ClientSecret are unset the meetings RPC
// returns errTeamsNotConfigured; the deep-link RPCs still work.
TeamsTenantID string `env:"TEAMS_TENANT_ID" envDefault:""`
TeamsClientID string `env:"TEAMS_CLIENT_ID" envDefault:""`
TeamsClientSecret string `env:"TEAMS_CLIENT_SECRET" envDefault:""`
TeamsEmailDomain string `env:"TEAMS_EMAIL_DOMAIN" envDefault:"dev.local"`
RoomMembersLimit int `env:"ROOM_MEMBERS_LIMIT" envDefault:"500"`
RoomMembersCallLimit int `env:"ROOM_MEMBERS_CALL_LIMIT" envDefault:"20"`
// MessageBucketHours must match message-worker's MESSAGE_BUCKET_HOURS so the
// teams_meet_started idempotency read walks the same partitions writes land in.
MessageBucketHours int `env:"MESSAGE_BUCKET_HOURS" envDefault:"72"`
MessageReadMaxBuckets int `env:"MESSAGE_READ_MAX_BUCKETS" envDefault:"30"`

@coderabbitai coderabbitai Bot Jun 15, 2026

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Fail fast on invalid/partial Teams startup config instead of deferring to runtime errors.

The new startup checks validate bucket settings, but invalid Teams runtime config can still slip through (non-positive member limits, empty email domain, or partially set Graph credentials). That leads to hard-to-diagnose runtime behavior where Teams RPCs fail or are silently disabled.

Suggested patch
 import (
 	"context"
 	"fmt"
 	"log/slog"
 	"net/url"
 	"os"
+	"strings"
 	"time"
@@
 	if cfg.MessageReadMaxBuckets <= 0 {
 		slog.Error("invalid MESSAGE_READ_MAX_BUCKETS: must be > 0", "value", cfg.MessageReadMaxBuckets)
 		os.Exit(1)
 	}
+	if cfg.RoomMembersLimit <= 0 {
+		slog.Error("invalid ROOM_MEMBERS_LIMIT: must be > 0", "value", cfg.RoomMembersLimit)
+		os.Exit(1)
+	}
+	if cfg.RoomMembersCallLimit <= 0 {
+		slog.Error("invalid ROOM_MEMBERS_CALL_LIMIT: must be > 0", "value", cfg.RoomMembersCallLimit)
+		os.Exit(1)
+	}
+	if strings.TrimSpace(cfg.TeamsEmailDomain) == "" {
+		slog.Error("invalid TEAMS_EMAIL_DOMAIN: must be non-empty")
+		os.Exit(1)
+	}
@@
 	var graphClient msgraph.Client
-	if cfg.TeamsTenantID != "" && cfg.TeamsClientID != "" && cfg.TeamsClientSecret != "" {
+	hasAnyTeamsCred := cfg.TeamsTenantID != "" || cfg.TeamsClientID != "" || cfg.TeamsClientSecret != ""
+	hasAllTeamsCred := cfg.TeamsTenantID != "" && cfg.TeamsClientID != "" && cfg.TeamsClientSecret != ""
+	if hasAnyTeamsCred && !hasAllTeamsCred {
+		slog.Error("invalid Teams Graph config: TEAMS_TENANT_ID, TEAMS_CLIENT_ID, and TEAMS_CLIENT_SECRET must be set together")
+		os.Exit(1)
+	}
+	if hasAllTeamsCred {
 		graphClient = msgraph.New(msgraph.Config{
 			TenantID:     cfg.TeamsTenantID,
 			ClientID:     cfg.TeamsClientID,
 			ClientSecret: cfg.TeamsClientSecret,
 		})
 	}

As per coding guidelines: **/*main*.go requires services to fail fast on invalid startup config (log error + non-zero exit).

Also applies to: 156-163, 174-180

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@room-service/main.go` around lines 49 - 62, Add startup validation checks to
fail fast on invalid Teams configuration before the service starts accepting
requests. Validate that if any of the Teams credentials (TeamsTenantID,
TeamsClientID, TeamsClientSecret) are set, all three must be provided together
to avoid partial configuration; also validate that TeamsEmailDomain is not empty
and that RoomMembersLimit and RoomMembersCallLimit are positive integers. If any
validation fails, log a descriptive error message and exit with a non-zero
status code, following the pattern established for bucket settings validation.
This validation should happen during startup initialization to prevent runtime
failures in Teams RPCs.

Source: Coding guidelines

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good catch — out of scope for this quick-win round, but I agree the partial-Teams-creds case should fail fast. Flagging as a follow-up; the current behavior is: all-three-creds set → meetings enabled, none set → meetings report not-configured, partial → currently constructs no client (meetings disabled) which is the silent case you note.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@chenjr0719, understood — the current silent-disable behavior for partial credentials is intentional and self-consistent within this PR. Would you like me to open a GitHub follow-up issue to track the three startup guards:

  1. Partial Teams credentials (any-but-not-all) → fail fast with a descriptive error
  2. TEAMS_EMAIL_DOMAIN empty → fail fast
  3. ROOM_MEMBERS_LIMIT / ROOM_MEMBERS_CALL_LIMIT non-positive → fail fast

I can create it now and assign it to you if that would be helpful.


🧠 Learnings used
Learnt from: Joey0538
Repo: hmchangw/chat PR: 59
File: pkg/roomkeysender/roomkeysender.go:28-30
Timestamp: 2026-04-08T08:25:32.108Z
Learning: In this repository’s Go code, follow the CLAUDE.md rule: don’t add defensive error handling, fallbacks, or nil/validation guards for conditions that are guaranteed not to occur. For internal functions/methods (e.g., when all callers are known to pass non-nil pointer parameters, such as pkg/roomkeysender/roomkeysender.go’s Send), the absence of nil-pointer guards is acceptable—do not flag it as a review issue. Reserve pointer validation and other safety checks for system boundaries (e.g., external inputs, API/HTTP requests, deserialization, or other untrusted entry points).

Learnt from: Joey0538
Repo: hmchangw/chat PR: 59
File: pkg/model/model_test.go:306-317
Timestamp: 2026-04-08T08:25:48.132Z
Learning: In Go, `time.Time` is `comparable` and therefore may be used as a type argument to generic constraints requiring `comparable` (e.g., `func roundTrip[T comparable](...)`). This is because `time.Time`’s underlying fields are all comparable (`uint64`, `int64`, and a pointer `*Location`, where pointers are comparable by address). As a result, any struct that contains or embeds a `time.Time` field (e.g., `model.Message`, or structs embedding it like `model.NotificationEvent`) should also satisfy `comparable` and should not be flagged as a compilation-error when used with `T comparable`.

Learnt from: general-lex
Repo: hmchangw/chat PR: 141
File: message-worker/main.go:95-99
Timestamp: 2026-05-04T03:30:38.678Z
Learning: In hmchangw/chat, `pkg/natsutil/connect.go`’s `natsutil.Connect` returns an OpenTelemetry-wrapped NATS connection (`*otelnats.Conn` from `github.com/Marz32onE/instrumentation-go/otel-nats/otelnats`), not a plain `*nats.Conn`. Because `otelnats.Conn.Publish` is defined as `Publish(ctx context.Context, subj string, data []byte) error` (it uses `ctx` to propagate traces via NATS headers), code review should NOT flag `nc.Publish(ctx, subj, data)` as an incorrect method call shape. This `Publish(ctx, ...)` pattern is the standard/correct usage across the codebase (e.g., message-worker, broadcast-worker, notification-worker, history-service).

Learnt from: general-lex
Repo: hmchangw/chat PR: 205
File: history-service/internal/service/pin.go:66-83
Timestamp: 2026-05-21T05:30:30.938Z
Learning: When reviewing cap enforcement in hmchangw/chat within history-service and room-service (e.g., functions like enforcePinLimit in history-service/internal/service/pin.go using MAX_* limits), the code may intentionally use a non-atomic read-then-write “soft cap” (allowing a small TOCTOU overage). Do not flag it as a TOCTOU/race-condition bug if the cap is explicitly designed to be operationally bounded rather than a hard invariant—i.e., it self-corrects because subsequent requests observe the inflated count and are rejected, and the codebase has not chosen atomic enforcement (e.g., LWT/counter table or findAndModify) as a requirement. Only flag as a TOCTOU issue when the surrounding codebase/design explicitly adopts atomic cap enforcement for these limits.

Learnt from: general-lex
Repo: hmchangw/chat PR: 247
File: room-service/handler.go:1506-1559
Timestamp: 2026-06-01T09:01:36.432Z
Learning: In hmchangw/chat’s room-service cross-site handlers, it’s intentional to (1) perform the local MongoDB write first (DB is the source of truth), then (2) publish the cross-site OUTBOX event, and (3) return an error to the client if the publish step fails—because clients are expected to debounce/retry. During review, do not flag individual handlers that follow this uniform pattern as incorrect or missing a “durable outbox” implementation; the durable-outbox work (separate Mongo outbox collection, new store method, idempotency keys, background drainer, DLQ/dead-letter handling) is tracked as a separate service-wide initiative. Only raise issues if a handler deviates from this established sequence/behavior (e.g., writes after publishing, or hides publish failures inconsistent with this design).

Learnt from: Allan-Code-hub
Repo: hmchangw/chat PR: 263
File: upload-service/main.go:111-115
Timestamp: 2026-06-03T03:26:01.642Z
Learning: In this repo’s Go code, `mongoutil.Disconnect(ctx context.Context, client *mongo.Client)` returns no value and internally logs disconnect errors (e.g., via `slog.Error("mongo disconnect failed", ...)`). When reviewing, never suggest `return mongoutil.Disconnect(...)`—it will not compile. Instead, call it as a statement (`mongoutil.Disconnect(ctx, client)`) and then return `nil` (or the appropriate value for the surrounding function) if needed. This call pattern should be consistent across services’ shutdown paths (e.g., `*/main.go`).

Learnt from: hmchangw
Repo: hmchangw/chat PR: 256
File: room-service/handler.go:709-713
Timestamp: 2026-06-12T00:02:37.654Z
Learning: In hmchangw/chat, NATS events and subscription guard timestamps use the shared `Timestamp int64` convention: set from `time.Now().UTC().UnixMilli()` (millisecond resolution). High-water-mark guards (e.g., `lastSeenAt` read-receipts, `rolesUpdatedAt`, `muteUpdatedAt`) intentionally use strict `$lt`/“only advance when newer” semantics; an equal-millisecond timestamp resulting in a guard no-op (second mutation in the same millisecond) is intentional, tested behavior that should self-heal on the next mutation. During review, do NOT flag millisecond-precision timestamp collisions/equal-millisecond no-ops as bugs in guard logic. If the code changes the shared `Timestamp` ordering strategy (e.g., nanosecond key or per-subscription atomic sequence), that is a coordinated future initiative requiring updates to the shared convention in `pkg/model` and every guard site (inbox-worker and room-service).

@chenjr0719

Copy link
Copy Markdown
Collaborator Author

Also hardened the new Teams subject builders in 57a2b50: the concrete TeamsRoomCall / TeamsMeeting / TeamsUserCall builders now reject account tokens containing NATS wildcards (*/>) via isValidAccountToken, matching the existing room-scoped concrete builders (RoomAppTabs, MsgHistory, OrgMembers).

@chenjr0719

Copy link
Copy Markdown
Collaborator Author

Per the maintainer decision: converted the 3 Teams subject builders (TeamsRoomCall, TeamsMeeting, TeamsUserCall) from panic to (string, error) return on an invalid (NATS-wildcard) account token, aligning with the shared-pkg no-panic policy (F12). Confirmed via grep there are no callers of the concrete builders (only the *Pattern variants are wired), so no cascade. Added subject tests for the happy path and the wildcard/empty error paths. Also appended the pre-review self-audit block to the PR description above. Pushed as a29143b.

@chenjr0719 chenjr0719 force-pushed the feat/teams-integration branch from a29143b to 7479d57 Compare June 16, 2026 01:59
@chenjr0719 chenjr0719 marked this pull request as ready for review June 16, 2026 02:27
@chenjr0719 chenjr0719 force-pushed the feat/teams-integration branch from b948d86 to f19a7e9 Compare June 16, 2026 09:05

@mliu33 mliu33 left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Outstanding work! Just some minor comments, thanks!

Comment thread docs/client-api.md
##### Triggered events — success path

On first creation, a `teams_meet_started` system message is published on the canonical message path (`chat.msg.canonical.{siteID}.created`), persisted by `message-worker`, and fanned out to room members like other system messages. Its `sysMsgData` carries `{ "meetingId": "...", "joinUrl": "..." }`.
On idempotent repeat calls that return cached meeting details, no additional system message is published.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Excellent!

Comment thread room-service/handler_teams.go Outdated
RoomID: roomID,
UserAccount: byAccount,
Type: model.MessageTypeTeamsMeetStarted,
Content: "started a Teams meeting",

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Shouldn't this be "xxx started a Teams meeting" where xxx is user display name (check add member system message for example)

Comment thread room-service/handler_teams.go Outdated
// the same meeting for repeated/concurrent calls with the same key, so even
// a true race never produces two distinct Graph meetings (the correctness
// guarantee across restarts and multiple service instances).
// 2. A teams_meetings Mongo record with a UNIQUE index on (roomId, siteId) —

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we simplify the comments here? It's too long

Comment thread room-service/handler_teams.go
chenjr0719 and others added 8 commits June 18, 2026 01:24
Add three NATS RPC handlers in room-service:
- teams.call (calls/room): Teams deep link for a room call, self excluded.
- teams.call.user (calls/user): Teams deep link for a 1:1 call.
- teams.meeting (meetings): create a Graph onlineMeeting, idempotent per room.

Email is derived as account@TEAMS_EMAIL_DOMAIN (no user-service lookup); the
requester comes from the NATS subject. The meetings RPC reads back the last
teams_meet_started system message (keyspace-aware) for idempotency and writes
the marker via the canonical message path. Adds pkg/msgraph (client-credentials
token + CreateOnlineMeeting behind a mockable interface) and pkg/model contracts.

Closes hmchangw#314

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
… doc race

- pkg/msgraph: parse the Graph error envelope and surface only status +
  error.code; never wrap the raw response body into the error/cause.
- pkg/subject: validate the account token (reject NATS wildcards) in the Teams
  concrete subject builders, matching the existing room-scoped builders.
- docs/client-api.md: clarify teams_meet_started is emitted only on first
  creation, not on idempotent cache hits.
- room-service: document the known read-then-create idempotency race at the
  meetings handler (eventually-idempotent via the marker; no lock primitive in
  this layer — left for maintainer scope decision).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Per the shared-pkg no-panic policy (F12 / hmchangw#224 / hmchangw#215): TeamsRoomCall,
TeamsMeeting, and TeamsUserCall now return (string, error) and return an error
on an invalid (NATS-wildcard) account token instead of panicking. The wildcard
check itself is unchanged. These concrete builders have no callers yet (only the
*Pattern variants are wired), so there is no cascade. Sibling builders
(RoomAppTabs, etc.) are left as-is — new code adopts the preferred form.

Adds subject tests for the happy path and the error path.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Two concurrent teams.meeting calls for the same room could each create a
Graph onlineMeeting and each publish a teams_meet_started system message:
the prior idempotency read scanned message buckets for the last marker,
which is written asynchronously, so racing callers saw no marker yet.

Push idempotency to two cooperating layers:

- pkg/msgraph: switch onlineMeeting creation to Graph's createOrGet
  endpoint with a stable per-room externalId (siteID:roomID). Graph
  guarantees one meeting per (organizer, externalId), so concurrent
  calls return the same meeting — correctness across restarts/instances
  with no local lock. externalId is now required.

- room-service: replace the Cassandra message-bucket marker scan with a
  first-class teams_meetings Mongo record, UNIQUE on (roomId, siteId).
  This mirrors the room_members / subscriptions retry-safe-write
  convention (unique index + IsDuplicateKeyError == success). The
  meetings handler now: fast-path reads the record, else createOrGet,
  then inserts the record; on a duplicate-key race the loser reads back
  the winner's record and returns it WITHOUT publishing a second system
  message. The teams_meet_started message is published only by the
  insert winner — exactly once per room.

Removes the now-unused cassMeetMarkerReader and its MESSAGE_BUCKET_HOURS
/ MESSAGE_READ_MAX_BUCKETS config (room-service no longer scans buckets
for meetings).

Tests: msgraph httptest asserts the createOrGet path + externalId in the
body and that a repeat returns the same meeting; room-service covers
fresh create, fast-path read-hit, duplicate-key read-back, and a
race-detector concurrent test asserting one Graph meeting + one system
message across 8 goroutines.

Co-Authored-By: Claude <noreply@anthropic.com>
…esting)

Documents pkg/msgraph: env config (TEAMS_*), the client-credentials token flow,
the createOrGet+externalId idempotency, the production application-access-policy
gate, and the credential-free httptest pattern. Linked from docs/client-api.md
(doc-ratchet).
The httptest token-server mocks encode tokenResponse{AccessToken:"tok"...};
gosec G117 flags the marshaled access_token field as a secret pattern. These are
dummy values in test doubles of Microsoft's OAuth endpoint, not real secrets —
annotated with // #nosec G117 + justification per repo SAST policy.
…me + trim comments

Address review feedback on the Teams meeting handler:
- sys message Content now formats `"<display>" started a Teams meeting`
  (matches the room_restricted / members_added sys-message convention),
  falling back to the account when the user lookup fails.
- condense the long idempotency doc-block and inline step comments.
@chenjr0719 chenjr0719 force-pushed the feat/teams-integration branch from f19a7e9 to 0558e61 Compare June 18, 2026 01:29

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@room-service/store_mongo.go`:
- Around line 205-209: The error returned from s.teamsMeetings.InsertOne at line
209 is returned bare instead of being wrapped with context, which violates the
project's error-wrapping guidelines. Modify the return statement to wrap the
error using fmt.Errorf with a descriptive message and the error value using the
%w verb (e.g., fmt.Errorf("insert teams meeting record: %w", err)). This
wrapping maintains compatibility with the upstream mongo.IsDuplicateKeyError
check since errors.As can unwrap fmt.Errorf-wrapped errors while providing
necessary operation context.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: ed0c4b8e-d4c9-4c31-b53d-88455ff02e2b

📥 Commits

Reviewing files that changed from the base of the PR and between fd3294a and 0558e61.

📒 Files selected for processing (18)
  • docs/client-api.md
  • docs/msgraph-client.md
  • pkg/model/event.go
  • pkg/model/message.go
  • pkg/model/teams.go
  • pkg/msgraph/msgraph.go
  • pkg/msgraph/msgraph_test.go
  • pkg/subject/subject.go
  • pkg/subject/subject_test.go
  • room-service/deploy/docker-compose.yml
  • room-service/handler.go
  • room-service/handler_teams.go
  • room-service/handler_teams_test.go
  • room-service/helper.go
  • room-service/main.go
  • room-service/mock_store_test.go
  • room-service/store.go
  • room-service/store_mongo.go
✅ Files skipped from review due to trivial changes (2)
  • room-service/deploy/docker-compose.yml
  • docs/msgraph-client.md
🚧 Files skipped from review as they are similar to previous changes (13)
  • pkg/model/message.go
  • pkg/subject/subject_test.go
  • room-service/mock_store_test.go
  • room-service/store.go
  • room-service/main.go
  • room-service/helper.go
  • pkg/model/teams.go
  • pkg/msgraph/msgraph_test.go
  • pkg/msgraph/msgraph.go
  • docs/client-api.md
  • room-service/handler.go
  • room-service/handler_teams_test.go
  • room-service/handler_teams.go

Comment thread room-service/store_mongo.go Outdated
Per the repo error-wrapping rule. mongo.IsDuplicateKeyError unwraps via
errors.As, so the handler's idempotency-race dup-key check still works.

@mliu33 mliu33 left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Super, thanks!

@mliu33 mliu33 merged commit ced841e into hmchangw:main Jun 18, 2026
7 checks passed
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.

[Feature] Connect chat to Microsoft Teams — calls/room, calls/user, meetings (NATS RPC in room-service)

2 participants