Skip to content

feat: Added a v2 API endpoint for team member management#2502

Merged
jordan-simonovski merged 6 commits into
mainfrom
jordansimonovski/teams-api-v2
Jun 23, 2026
Merged

feat: Added a v2 API endpoint for team member management#2502
jordan-simonovski merged 6 commits into
mainfrom
jordansimonovski/teams-api-v2

Conversation

@jordan-simonovski

Copy link
Copy Markdown
Contributor

Summary

Added a new v2 API endpoint for team member management to support the ClickStack terraform provider.

contributes to HDX-4491

@changeset-bot

changeset-bot Bot commented Jun 23, 2026

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: 5405119

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 3 packages
Name Type
@hyperdx/api Minor
@hyperdx/app Minor
@hyperdx/otel-collector Minor

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

@vercel

vercel Bot commented Jun 23, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
hyperdx-oss Ready Ready Preview, Comment Jun 23, 2026 11:14pm
hyperdx-storybook Ready Ready Preview, Comment Jun 23, 2026 11:14pm

Request Review

@github-actions

github-actions Bot commented Jun 23, 2026

Copy link
Copy Markdown
Contributor

🔴 Tier 4 — Critical

Touches auth, data models, config, tasks, OTel pipeline, ClickHouse, or CI/CD.

Why this tier:

  • Critical-path files (3):
    • packages/api/src/routers/api/team.ts
    • packages/api/src/routers/external-api/v2/index.ts
    • packages/api/src/routers/external-api/v2/team.ts

Review process: Deep review from a domain expert. Synchronous walkthrough may be required.
SLA: Schedule synchronous review within 2 business days.

Stats
  • Production files changed: 5
  • Production lines changed: 448 (+ 140 in test files, excluded from tier calculation)
  • Branch: jordansimonovski/teams-api-v2
  • Author: jordan-simonovski

To override this classification, remove the review/tier-4 label and apply a different review/tier-* label. Manual overrides are preserved on subsequent pushes.

@github-actions github-actions Bot added the review/tier-4 Critical — deep review + domain expert sign-off label Jun 23, 2026
@greptile-apps

greptile-apps Bot commented Jun 23, 2026

Copy link
Copy Markdown
Contributor

Greptile Summary

This PR adds a new /api/v2/team external API surface for team management (get team, list members, invite, list invitations, delete invitation, remove member), intended to power the ClickStack Terraform provider. It also extracts a getTeamInviteUrl helper used in both the internal v1 and new v2 routers.

  • All concerns from the previous review round are resolved: the DELETE /member/:id handler now returns 404 when the user is not found, a self-deletion guard returns 400, the GET /invitations response includes the join URL (with token in the projection), and a try/catch on the MongoDB E11000 error makes concurrent invite requests idempotent.
  • The new test file covers the GET, invitation lifecycle, and concurrent-invite race, but the destructive DELETE /member/:id path has no test coverage.
  • The OpenAPI spec entries for the new endpoints omit requestBody and response body schemas, which limits auto-generation for the Terraform provider this PR targets.

Confidence Score: 5/5

Safe to merge; all previous review concerns are addressed and the new endpoints are correctly auth-gated and error-handled.

The implementation correctly handles the key edge cases (idempotent invites, self-deletion guard, 404 on missing member). The two remaining gaps — no test for member deletion and thin OpenAPI schemas — are quality improvements that don't affect runtime correctness.

packages/api/src/routers/external-api/tests/team.test.ts is missing coverage for DELETE /member/:id; packages/api/openapi.json is missing the requestBody schema for POST /invitation.

Important Files Changed

Filename Overview
packages/api/src/routers/external-api/v2/team.ts New external v2 router implementing team management endpoints; prior review comments (silent 200 on deletion, self-deletion guard, concurrent E11000 race, missing invite URL) are all addressed
packages/api/src/routers/external-api/tests/team.test.ts Good coverage for GET/POST/DELETE invitation flows and concurrent-invite idempotency; DELETE /member/:id is not tested at all
packages/api/src/controllers/team.ts Extracted getTeamInviteUrl helper to avoid URL-building duplication; safe refactor
packages/api/openapi.json New v2 team endpoints added but POST /invitation is missing a requestBody schema; all response body shapes are also undocumented
packages/api/src/routers/external-api/v2/index.ts Team router mounted with correct rate-limiter and access-key auth middleware
packages/api/src/routers/api/team.ts Two URL-building call sites cleanly replaced with getTeamInviteUrl; no logic change

Sequence Diagram

%%{init: {'theme': 'neutral'}}%%
sequenceDiagram
    participant TF as Terraform Provider
    participant MW as Auth Middleware (validateUserAccessKey)
    participant R as v2/team Router
    participant C as Controllers
    participant DB as MongoDB

    TF->>MW: "Bearer {accessKey}"
    MW->>DB: findUserByAccessKey
    DB-->>MW: user doc
    MW->>R: req.user attached

    alt GET /team
        R->>C: getTeam(teamId)
        C->>DB: Team.findById
        DB-->>C: team doc
        R-->>TF: "{ data: { id, name } }"
    end

    alt POST /invitation
        R->>C: findUserByEmail(email)
        C->>DB: User.findOne
        alt user exists
            R-->>TF: 400 User already exists
        else
            R->>DB: TeamInvite.findOne
            alt invite not found
                R->>DB: new TeamInvite.save()
                alt E11000 race
                    R->>DB: TeamInvite.findOne retry
                end
            end
            R-->>TF: "{ data: { invitationId, status, url } }"
        end
    end

    alt DELETE /member/:id
        R->>R: self-deletion guard
        alt self
            R-->>TF: 400
        else
            R->>C: deleteTeamMember
            C->>DB: Alert.updateMany + User.findOneAndDelete
            alt not found
                R-->>TF: 404
            else
                R-->>TF: "{ data: { success: true } }"
            end
        end
    end
Loading
%%{init: {'theme': 'base', 'themeVariables': {"darkMode": true, "background": "#0d1117", "primaryColor": "#21262d", "primaryTextColor": "#e6edf3", "primaryBorderColor": "#8b949e", "lineColor": "#8b949e", "textColor": "#e6edf3", "edgeLabelBackground": "#161b22", "actorBkg": "#21262d", "actorBorder": "#8b949e", "actorTextColor": "#e6edf3", "actorLineColor": "#8b949e", "signalColor": "#8b949e", "signalTextColor": "#e6edf3", "noteBkgColor": "#373320", "noteBorderColor": "#d4a72c", "noteTextColor": "#f0e6c0", "labelBoxBkgColor": "#21262d", "labelBoxBorderColor": "#8b949e", "labelTextColor": "#e6edf3", "loopTextColor": "#e6edf3", "activationBkgColor": "#30363d", "activationBorderColor": "#8b949e"}}}%%
sequenceDiagram
    participant TF as Terraform Provider
    participant MW as Auth Middleware (validateUserAccessKey)
    participant R as v2/team Router
    participant C as Controllers
    participant DB as MongoDB

    TF->>MW: "Bearer {accessKey}"
    MW->>DB: findUserByAccessKey
    DB-->>MW: user doc
    MW->>R: req.user attached

    alt GET /team
        R->>C: getTeam(teamId)
        C->>DB: Team.findById
        DB-->>C: team doc
        R-->>TF: "{ data: { id, name } }"
    end

    alt POST /invitation
        R->>C: findUserByEmail(email)
        C->>DB: User.findOne
        alt user exists
            R-->>TF: 400 User already exists
        else
            R->>DB: TeamInvite.findOne
            alt invite not found
                R->>DB: new TeamInvite.save()
                alt E11000 race
                    R->>DB: TeamInvite.findOne retry
                end
            end
            R-->>TF: "{ data: { invitationId, status, url } }"
        end
    end

    alt DELETE /member/:id
        R->>R: self-deletion guard
        alt self
            R-->>TF: 400
        else
            R->>C: deleteTeamMember
            C->>DB: Alert.updateMany + User.findOneAndDelete
            alt not found
                R-->>TF: 404
            else
                R-->>TF: "{ data: { success: true } }"
            end
        end
    end
Loading

Reviews (4): Last reviewed commit: "fix: race condition with multiple invita..." | Re-trigger Greptile

Comment thread packages/api/src/routers/external-api/v2/team.ts
Comment thread packages/api/src/routers/external-api/v2/team.ts
Comment thread packages/api/src/routers/external-api/v2/team.ts
@github-actions

github-actions Bot commented Jun 23, 2026

Copy link
Copy Markdown
Contributor

E2E Test Results

All tests passed • 216 passed • 3 skipped • 1568s

Status Count
✅ Passed 216
❌ Failed 0
⚠️ Flaky 6
⏭️ Skipped 3

Tests ran across 4 shards in parallel.

View full report →

@knudtty knudtty left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Just a few things

Comment thread packages/api/src/routers/external-api/__tests__/team.test.ts Outdated
Comment thread packages/api/src/routers/external-api/v2/team.ts Outdated
Comment thread packages/api/src/routers/external-api/v2/team.ts Outdated
Comment thread packages/api/src/routers/external-api/v2/team.ts
@jordan-simonovski

Copy link
Copy Markdown
Contributor Author

@knudtty I've fixed the relative pathing issues you brought up. I was going to add a lint rule for this, but that would have picked up about 70 files as part of this change. I'll scope it to a single PR.

@jordan-simonovski jordan-simonovski merged commit 9f23b7e into main Jun 23, 2026
22 checks passed
@jordan-simonovski jordan-simonovski deleted the jordansimonovski/teams-api-v2 branch June 23, 2026 23:22
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

review/tier-4 Critical — deep review + domain expert sign-off

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants