Skip to content

project owner team#835

Merged
BilalG1 merged 27 commits intodevfrom
project-owner-team
Aug 19, 2025
Merged

project owner team#835
BilalG1 merged 27 commits intodevfrom
project-owner-team

Conversation

@BilalG1
Copy link
Copy Markdown
Collaborator

@BilalG1 BilalG1 commented Aug 7, 2025

Screenshot 2025-08-12 at 10 44 41 AM

Important

Introduces team-based project ownership, refactoring existing user-based model, and updates UI, backend, and tests to support this feature.

  • Behavior:
    • Introduced team-based project ownership, replacing user-based ownership.
    • Updated project creation, transfer, and deletion flows to use team ownership.
    • Added team selection UI during project creation in the dashboard.
    • Projects now display owning team's name and include "owner team" field in API responses.
  • Refactor:
    • Enhanced backend and schema for team-based project management.
    • Removed legacy user metadata updates related to project ownership.
    • Modified project listing and management to rely on team associations.
    • Streamlined failed emails digest and contact channel queries to resolve contacts via team membership.
  • Tests:
    • Updated tests to validate team ownership and project-user association handling.
    • Adjusted test snapshots and assertions for non-null selected team data.
    • Improved test flows for authentication and project deletion with team context.
  • Chores:
    • Minor improvements to logging and code clarity.

This description was created by Ellipsis for 0f6f12b. You can customize this summary. It will automatically update as commits are pushed.


Summary by CodeRabbit

  • New Features

    • Team-based project ownership: teams can own projects; UI to pick a team when creating projects; dashboard groups projects by team; TeamSwitcher component added.
  • Improvements

    • API and responses now include owner_team_id and populated selected_team/selected_team_id; provisioning and transfer flows assign teams for ownership; seeds create internal/emulator owner teams.
  • Tests

    • E2E and backend tests updated to reflect team ownership and enriched team fields.

@vercel
Copy link
Copy Markdown

vercel Bot commented Aug 7, 2025

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

Project Deployment Preview Comments Updated (UTC)
stack-backend Ready Ready Preview Comment Aug 19, 2025 6:08pm
stack-dashboard Ready Ready Preview Comment Aug 19, 2025 6:08pm
stack-demo Ready Ready Preview Comment Aug 19, 2025 6:08pm
stack-docs Ready Ready Preview Comment Aug 19, 2025 6:08pm

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Aug 7, 2025

Note

Other AI code review bot(s) detected

CodeRabbit has detected other AI code review bot(s) in this pull request and will avoid duplicating their findings in the review comments. This may lead to a less comprehensive review.

Walkthrough

Adds a nullable Project.ownerTeamId, a migration to convert managedProjectIds into Team and TeamMember records and assign ownerTeamId, and broad refactors so project ownership, provision/transfer flows, seeds, UI, libs, and tests use owner_team_id (teams) instead of per-user serverMetadata.

Changes

Cohort / File(s) Summary
DB migration & schema
apps/backend/prisma/migrations/.../migration.sql, apps/backend/prisma/schema.prisma
Adds nullable ownerTeamId to Project and a DO-block migration that creates personal and shared Teams/TeamMembers from serverMetadata.managedProjectIds and sets Project.ownerTeamId.
Seed & permissions
apps/backend/prisma/seed.ts
Adds Internal and Emulator teams (fixed UUIDs), assigns them as projects' owner_team_id, updates permissions, and links admin/emulator users to those teams; toggles project config flags.
Core projects lib & helpers
apps/backend/src/lib/projects.tsx, apps/backend/src/route-handlers/smart-request.tsx
listManagedProjectIds → async/tenancy-based; createOrUpdateProjectWithLegacyConfig removes ownerIds, accepts owner_team_id (create path), surfaces owner_team_id in reads, and removes serverMetadata owner updates.
Provision routes
apps/backend/src/app/api/latest/integrations/.../projects/provision/route.tsx
Replaced ownerIds: [] with owner_team_id: null in provisioning payloads to convey ownership via owner_team_id.
Transfer handlers
.../projects/transfer/confirm/verification-code-handler.tsx (custom & neon)
Switched from tenancy-scoped ProjectUser/serverMetadata mutations to global project lookup; validated internal tenancy; created ownership Team for transferee and set project.ownerTeamId; retained provisioning cleanup.
Internal projects & digest queries
apps/backend/src/app/api/latest/internal/projects/*.tsx, .../failed-emails-digest/crud.tsx
Project CRUD uses owner_team_id, ensures team membership on create, removed managedProjectIds cleanup on delete; failed-emails query now resolves contact channels via Team→TeamMember→ContactChannel.
Dashboard UI
apps/dashboard/.../new-project/page-client.tsx, apps/dashboard/.../projects/page-client.tsx
New-project form now requires/selects Team (TeamSwitcher); projects page groups admin-owned projects by ownerTeamId and renders per-team sections.
Template components & app libs
packages/template/src/components/team-switcher.tsx, .../selected-team-switcher.tsx, packages/template/src/index.ts, .../admin-app-impl.ts, .../client-app-impl.ts, .../projects/index.ts
Adds TeamSwitcher, integrates it, adds ownerTeamId/teamId mappings and updates client/admin create signatures and types to pass teamIdowner_team_id.
Shared CRUD schema
packages/stack-shared/src/interface/crud/projects.ts
Adds `owner_team_id: string
E2E helpers & tests
apps/e2e/..., apps/e2e/tests/backend/backend-helpers.ts, apps/e2e/tests/js/js-helpers.ts
Project.create now supplies owner_team_id (defaults to current user’s selected_team_id async); many tests/snapshots updated to expect owner_team_id and non-null selected_team/selected_team_id; helpers adjusted to derive/require teamId.
Other callsites & imports
assorted files under apps/backend/..., apps/dashboard/..., packages/template/...
Added awaits, swapped tenancy/global Prisma usage, added membership checks (ensureTeamMembershipExists), and updated imports/usages to new signatures across multiple handlers and call sites.

Sequence Diagram(s)

sequenceDiagram
  participant U as User
  participant API as Transfer Verify Handler
  participant Teams as Teams CRUD
  participant DB as Global Prisma

  U->>API: POST /transfer/confirm { code, project_id }
  API->>DB: find project by project_id (global)
  DB-->>API: project (ownerTeamId maybe null)
  alt project not found or already transferred
    API-->>U: 400 error
  else
    API->>Teams: adminCreate(team for transferee)
    Teams-->>API: Team { id }
    API->>DB: update project.ownerTeamId = Team.id
    DB-->>API: OK
    API-->>U: 200 { project_id }
  end
Loading
sequenceDiagram
  participant U as User (client)
  participant API as Create Project Endpoint
  participant Ten as Tenancy / Membership
  participant DB as Prisma

  U->>API: POST /projects { display_name, teamId }
  API->>Ten: ensureTeamMembershipExists(tenancyId, teamId, userId)
  Ten-->>API: OK
  API->>DB: create Project { ownerTeamId: teamId, ... }
  DB-->>API: Project { owner_team_id }
  API-->>U: 201 Project { owner_team_id: teamId }
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related PRs

  • Config override CRUD #803: Touches the same project creation pathway and usages of createOrUpdateProjectWithLegacyConfig; likely overlaps on signature and call-site changes.
  • Project logo upload #817: Modifies backend project management and create/update handling in the same module (apps/backend/src/lib/projects.tsx); likely intersects on ownership/read mapping.

Poem

"I hopped through migrations with a twitchy nose,
Made teams from users where ownership grows.
Seeds sowed in rows, tests learned the new tune,
Projects found homes beneath a shared moon.
Carrots, UUIDs, and a tidy burrow — hooray!" 🥕🐇

Tip

🔌 Remote MCP (Model Context Protocol) integration is now available!

Pro plan users can now connect to remote MCP servers from the Integrations page. Connect with popular remote MCPs such as Notion and Linear to add more context to your reviews and chats.

✨ Finishing Touches
  • 📝 Generate Docstrings
🧪 Generate unit tests
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch project-owner-team

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
🪧 Tips

Chat

There are 3 ways to chat with CodeRabbit:

  • Review comments: Directly reply to a review comment made by CodeRabbit. Example:
    • I pushed a fix in commit <commit_id>, please review it.
    • Open a follow-up GitHub issue for this discussion.
  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbitai in a new review comment at the desired location with your query.
  • PR comments: Tag @coderabbitai in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.

Support

Need help? Create a ticket on our support page for assistance with any issues or questions.

CodeRabbit Commands (Invoked using PR/Issue comments)

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

Other keywords and placeholders

  • Add @coderabbitai ignore anywhere in the PR description to prevent this PR from being reviewed.
  • Add @coderabbitai summary to generate the high-level summary at a specific location in the PR description.
  • Add @coderabbitai anywhere in the PR title to generate the title automatically.

CodeRabbit Configuration File (.coderabbit.yaml)

  • You can programmatically configure CodeRabbit by adding a .coderabbit.yaml file to the root of your repository.
  • Please see the configuration documentation for more information.
  • If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: # yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json

Status, Documentation and Community

  • Visit our Status Page to check the current availability of CodeRabbit.
  • Visit our Documentation for detailed information on how to use CodeRabbit.
  • Join our Discord Community to get help, request features, and share feedback.
  • Follow us on X/Twitter for updates and announcements.

Copy link
Copy Markdown
Contributor

@greptile-apps greptile-apps Bot left a comment

Choose a reason for hiding this comment

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

Greptile Summary

This PR implements a major architectural shift from individual user-based project ownership to team-based project ownership in the Stack Auth system. The changes introduce an ownerTeamId field throughout the system, replacing the previous approach where project ownership was tracked through user metadata (managedProjectIds arrays).

Key changes include:

Database Schema: Added an optional ownerTeamId UUID field to the Project model, with a comprehensive migration that creates personal teams for existing users who manage projects and assigns their projects to these teams.

API Layer: Updated all project CRUD operations to work with team-based ownership. The listManagedProjectIds function now queries team memberships instead of user metadata. Project creation endpoints across different integrations (Neon, custom) now use ownerTeamId: null for externally provisioned projects.

Frontend Components: Project cards now display the owner team's name, and the team switcher component gained styling customization capabilities to support different contexts.

Type Definitions: Added ownerTeamId: string | null to the AdminProject type and updated CRUD schemas to include the owner_team_id field for read operations.

Transfer Handlers: Refactored project transfer confirmation handlers to create personal teams for users and assign transferred projects to those teams, rather than updating user metadata.

Testing: Comprehensively updated E2E tests to expect owner_team_id fields in responses and verify proper team-based ownership behavior.

This change enables better collaboration by allowing multiple team members to manage projects collectively, provides clearer permission boundaries, and creates a foundation for more sophisticated team-based features. The migration preserves existing functionality by creating personal teams for current project owners, ensuring backward compatibility during the transition.

Confidence score: 3/5

  • This PR introduces significant architectural changes that affect core ownership and permission systems across the entire codebase
  • Score reflects the complexity of the changes and potential for subtle permission or access control issues during the transition period
  • Pay close attention to the database migration file and the listManagedProjectIds function implementation

18 files reviewed, 6 comments

Edit Code Review Bot Settings | Greptile

Comment thread apps/dashboard/src/components/project-card.tsx Outdated
Comment thread apps/backend/prisma/schema.prisma
Comment thread apps/backend/prisma/seed.ts Outdated
Comment thread apps/e2e/tests/backend/endpoints/api/v1/projects.test.ts Outdated
@recurseml
Copy link
Copy Markdown

recurseml Bot commented Aug 7, 2025

✨ No issues found! Your code is sparkling clean! ✨

🗒️ View all ignored comments in this repo
  • The constraint 'TokenStoreType extends string' is too restrictive. It should likely be 'TokenStoreType extends string | object' to match the condition check in line 113 where TokenStoreType is checked against {}
  • Return type mismatch - the interface declares useUsers() returning ServerUser[] but the Team interface that this extends declares useUsers() returning TeamUser[]
  • There is a syntax error in the super constructor call due to the ellipsis operator used incorrectly. Objects aren't being merged correctly. This syntax usage can lead to runtime errors when trying to pass the merged object to 'super()'. Verify that the intended alterations to the object occur before or outside of the super() call if needed.
  • Throwing an error when no active span is found is too aggressive. The log function should gracefully fallback to console.log or another logging mechanism when there's no active span, since not all execution contexts will have an active span. This makes the code less resilient and could break functionality in non-traced environments.

📚 Relevant Docs

  • Function sets backendContext with a new configuration but doesn't pass 'defaultProjectKeys'. Since defaultProjectKeys is required in the type definition and cannot be updated (throws error if tried to set), this will cause a type error.
  • The schema is using array syntax for pick() which is incorrect for Yup schemas. The pick() method in Yup expects individual arguments, not an array. Should be changed to: emailConfigSchema.pick('type', 'host', 'port', 'username', 'sender_name', 'sender_email')

📚 Relevant Docs

  • Creating a refresh token with current timestamp as expiration means it expires immediately. Should set a future date for token expiration.
  • The 'tools' object is initialized as an empty object, even though 'tools' is presumably expected to contain tool definitions. This could cause the server capabilities to lack necessary tool configurations, thus potentially impacting functionalities that depend on certain tool setups.

📚 Relevant Docs

  • 'STACK_SECRET_SERVER_KEY' is potentially being included in every request header without checking its existence again here. Although it's checked during initialization, this could lead to security issues as it's exposed in all communications where the header is logged or captured.

📚 Relevant Docs

  • When adding 'use client' directive at the beginning, it doesn't check if file.text already contains the 'use client' directive. This could lead to duplicate 'use client' directives if the file already has one.

📚 Relevant Docs

Need help? Join our Discord for support!
https://discord.gg/qEjHQk64Z9

@patched-codes
Copy link
Copy Markdown

patched-codes Bot commented Aug 7, 2025

Documentation Changes Required

  1. docs/templates/components/selected-team-switcher.mdx

    • Add a new prop to the PropTable:
      • Name: triggerClassName
      • Type: string
      • Description: "Additional CSS class name to apply to the select trigger element."
      • Optional: Yes
  2. AdminProject Type

    • No documentation changes are required for the AdminProject type, as it is not currently documented in any user-facing documentation files.

Please ensure these changes are reflected in the relevant documentation file.

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 4

🔭 Outside diff range comments (1)
apps/backend/prisma/seed.ts (1)

39-61: Potential race condition: Team referenced before creation

The internal project is created with ownerTeamId: internalTeamId at line 40, but the corresponding team is only created later at lines 95-103. If there's a foreign key constraint between projects and teams, this could cause the project creation to fail.

Consider reordering the operations to create the team before assigning it as the owner.

🧹 Nitpick comments (4)
apps/backend/prisma/schema.prisma (1)

22-22: Consider adding referential integrity for ownerTeamId.

The addition of ownerTeamId String? @db.Uuid correctly implements team-based ownership. However, consider adding a foreign key relationship to the Team model to ensure referential integrity, even though the composite primary key structure may make this complex.

If referential integrity is desired, you could consider adding a relation like:

ownerTeam Team? @relation(fields: [ownerTeamId], references: [teamId])

However, this might require additional constraints to handle the tenancy relationship properly.

apps/dashboard/src/components/project-card.tsx (1)

9-10: Consider safer fallback for ownerTeamId.

The current implementation uses project.ownerTeamId ?? "" as a fallback, but this may result in calling useTeam("") which could be problematic. Consider using a more explicit check:

-  const user = useUser({ "or": "redirect", projectIdMustMatch: "internal" });
-  const team = user.useTeam(project.ownerTeamId ?? "");
+  const user = useUser({ "or": "redirect", projectIdMustMatch: "internal" });
+  const team = project.ownerTeamId ? user.useTeam(project.ownerTeamId) : null;
apps/backend/src/app/api/latest/integrations/custom/projects/transfer/confirm/verification-code-handler.tsx (1)

59-79: Consider extracting common team creation and ownership logic.

The team creation and project ownership assignment logic is identical between this handler and the neon integration handler, indicating potential code duplication.

Consider creating a shared utility function for project transfer team creation:

// In a shared utilities file
async function createProjectOwnershipTeam(user: User, tenancy: Tenancy) {
  return await teamsCrudHandlers.adminCreate({
    data: {
      display_name: user.display_name ?
        `${user.display_name}'s Team` :
        user.primary_email ?
          `${user.primary_email}'s Team` :
          "Personal Team",
      creator_user_id: 'me',
    },
    tenancy,
    user,
  });
}

async function assignProjectToTeam(projectId: string, teamId: string) {
  return await globalPrismaClient.project.update({
    where: { id: projectId },
    data: { ownerTeamId: teamId },
  });
}
apps/backend/src/lib/projects.tsx (1)

14-38: Consider optimizing with a single join query

The current implementation makes two separate database queries - first to fetch teams, then to fetch projects. This could be optimized into a single query with a join for better performance.

Consider refactoring to use a single query:

const projectIds = await globalPrismaClient.project.findMany({
  where: {
    ownerTeam: {
      tenancyId: internalTenancy.id,
      teamMembers: {
        some: {
          projectUserId: projectUser.id,
        }
      }
    }
  },
  select: {
    id: true,
  },
});
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 98d6ca3 and 522b532.

📒 Files selected for processing (19)
  • apps/backend/prisma/migrations/20250806171211_add_team_based_project_ownership/migration.sql (1 hunks)
  • apps/backend/prisma/schema.prisma (1 hunks)
  • apps/backend/prisma/seed.ts (11 hunks)
  • apps/backend/src/app/api/latest/integrations/custom/projects/provision/route.tsx (1 hunks)
  • apps/backend/src/app/api/latest/integrations/custom/projects/transfer/confirm/verification-code-handler.tsx (2 hunks)
  • apps/backend/src/app/api/latest/integrations/neon/projects/provision/route.tsx (1 hunks)
  • apps/backend/src/app/api/latest/integrations/neon/projects/transfer/confirm/verification-code-handler.tsx (2 hunks)
  • apps/backend/src/app/api/latest/internal/failed-emails-digest/crud.tsx (1 hunks)
  • apps/backend/src/app/api/latest/internal/projects/crud.tsx (2 hunks)
  • apps/backend/src/app/api/latest/internal/projects/current/crud.tsx (0 hunks)
  • apps/backend/src/lib/projects.tsx (4 hunks)
  • apps/backend/src/route-handlers/smart-request.tsx (1 hunks)
  • apps/dashboard/src/app/(main)/(protected)/(outside-dashboard)/new-project/page-client.tsx (3 hunks)
  • apps/dashboard/src/components/project-card.tsx (1 hunks)
  • apps/e2e/tests/backend/endpoints/api/v1/projects.test.ts (19 hunks)
  • packages/stack-shared/src/interface/crud/projects.ts (1 hunks)
  • packages/template/src/components/selected-team-switcher.tsx (4 hunks)
  • packages/template/src/lib/stack-app/apps/implementations/admin-app-impl.ts (1 hunks)
  • packages/template/src/lib/stack-app/projects/index.ts (1 hunks)
💤 Files with no reviewable changes (1)
  • apps/backend/src/app/api/latest/internal/projects/current/crud.tsx
🧰 Additional context used
📓 Path-based instructions (2)
apps/backend/src/app/api/latest/**/*

📄 CodeRabbit Inference Engine (CLAUDE.md)

apps/backend/src/app/api/latest/**/*: Main API routes are located in /apps/backend/src/app/api/latest
The project uses a custom route handler system in the backend for consistent API responses

Files:

  • apps/backend/src/app/api/latest/integrations/custom/projects/provision/route.tsx
  • apps/backend/src/app/api/latest/integrations/neon/projects/provision/route.tsx
  • apps/backend/src/app/api/latest/internal/projects/crud.tsx
  • apps/backend/src/app/api/latest/integrations/custom/projects/transfer/confirm/verification-code-handler.tsx
  • apps/backend/src/app/api/latest/integrations/neon/projects/transfer/confirm/verification-code-handler.tsx
  • apps/backend/src/app/api/latest/internal/failed-emails-digest/crud.tsx
apps/backend/prisma/schema.prisma

📄 CodeRabbit Inference Engine (CLAUDE.md)

Database models use Prisma

Files:

  • apps/backend/prisma/schema.prisma
🧠 Learnings (2)
📚 Learning: 2025-08-04T22:25:51.260Z
Learnt from: CR
PR: stack-auth/stack-auth#0
File: CLAUDE.md:0-0
Timestamp: 2025-08-04T22:25:51.260Z
Learning: Applies to apps/backend/src/app/api/latest/**/* : The project uses a custom route handler system in the backend for consistent API responses

Applied to files:

  • apps/backend/src/app/api/latest/integrations/custom/projects/provision/route.tsx
  • apps/backend/src/app/api/latest/integrations/neon/projects/provision/route.tsx
📚 Learning: 2025-08-04T22:25:51.260Z
Learnt from: CR
PR: stack-auth/stack-auth#0
File: CLAUDE.md:0-0
Timestamp: 2025-08-04T22:25:51.260Z
Learning: Applies to apps/backend/prisma/schema.prisma : Database models use Prisma

Applied to files:

  • apps/backend/prisma/schema.prisma
  • apps/backend/prisma/migrations/20250806171211_add_team_based_project_ownership/migration.sql
  • apps/backend/src/app/api/latest/integrations/neon/projects/transfer/confirm/verification-code-handler.tsx
  • apps/backend/prisma/seed.ts
🧬 Code Graph Analysis (5)
apps/backend/src/route-handlers/smart-request.tsx (1)
apps/backend/src/lib/projects.tsx (1)
  • listManagedProjectIds (14-38)
apps/dashboard/src/components/project-card.tsx (3)
packages/template/src/lib/stack-app/projects/index.ts (1)
  • AdminProject (15-35)
apps/dashboard/src/hooks/use-from-now.tsx (1)
  • useFromNow (4-22)
packages/stack-shared/src/utils/urls.tsx (1)
  • urlString (172-174)
packages/template/src/components/selected-team-switcher.tsx (2)
packages/stack-ui/src/components/ui/select.tsx (1)
  • SelectTrigger (160-160)
packages/stack-ui/src/index.ts (1)
  • cn (53-53)
apps/backend/src/app/api/latest/integrations/neon/projects/transfer/confirm/verification-code-handler.tsx (2)
apps/backend/src/app/api/latest/teams/crud.tsx (1)
  • teamsCrudHandlers (29-245)
apps/backend/src/prisma-client.tsx (1)
  • globalPrismaClient (31-31)
apps/backend/src/lib/projects.tsx (3)
packages/stack-shared/src/interface/crud/users.ts (1)
  • UsersCrud (103-103)
apps/backend/src/lib/tenancies.tsx (2)
  • getSoleTenancyFromProjectBranch (59-66)
  • DEFAULT_BRANCH_ID (17-17)
apps/backend/src/prisma-client.tsx (2)
  • getPrismaClientForTenancy (50-52)
  • globalPrismaClient (31-31)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (9)
  • GitHub Check: lint_and_build (latest)
  • GitHub Check: docker
  • GitHub Check: build (22.x)
  • GitHub Check: docker
  • GitHub Check: all-good
  • GitHub Check: restart-dev-and-test
  • GitHub Check: build (22.x)
  • GitHub Check: setup-tests
  • GitHub Check: Security Check
🔇 Additional comments (13)
apps/backend/src/app/api/latest/integrations/custom/projects/provision/route.tsx (1)

32-32: LGTM! Team-based ownership correctly implemented.

The change from user-based ownership to team-based ownership is correctly implemented. Setting ownerTeamId: null during provisioning is appropriate as no specific team is assigned at this stage, consistent with other integration provisioning routes.

packages/template/src/lib/stack-app/projects/index.ts (1)

21-21: LGTM! Type definition correctly updated for team-based ownership.

The addition of ownerTeamId: string | null to the AdminProject type is appropriate and aligns with the database schema changes. The field is correctly marked as readonly and nullable to match the expected usage pattern.

apps/backend/src/route-handlers/smart-request.tsx (1)

219-219: Critical fix: Correctly awaiting async listManagedProjectIds.

This await addition is essential. The listManagedProjectIds function was changed from synchronous to asynchronous as part of the team-based ownership migration. Without this await, the function would return a Promise object instead of an array, causing allProjects.includes(options.projectId) to always return false and break admin authentication.

packages/template/src/lib/stack-app/apps/implementations/admin-app-impl.ts (1)

116-116: LGTM! Clean field mapping for team ownership.

The addition of ownerTeamId field mapping from data.owner_team_id is consistent with the team-based ownership model and follows the established camelCase convention for the AdminProject interface.

apps/backend/src/app/api/latest/integrations/neon/projects/provision/route.tsx (1)

36-36: LGTM! Correct transition to team-based ownership.

The change from ownerIds: [] to ownerTeamId: null properly aligns with the new team-based ownership model. Using null is appropriate for the Neon integration context where no specific team is pre-assigned during provisioning.

packages/template/src/components/selected-team-switcher.tsx (1)

5-5: LGTM! Good styling flexibility enhancement.

The addition of the optional triggerClassName prop and its proper integration using the cn utility provides excellent styling flexibility while maintaining backward compatibility. This enhancement supports the component's usage across different UI contexts.

Also applies to: 37-37, 122-122

apps/dashboard/src/components/project-card.tsx (1)

18-18: LGTM! Clean team display integration.

The addition of team display name in the CardDescription properly shows project ownership information and uses safe optional chaining.

apps/backend/src/app/api/latest/internal/projects/crud.tsx (2)

27-27: LGTM! Team-based ownership implementation is correct.

The change from owner packs to using the user's selected_team_id aligns with the team-based ownership migration. The null check with throwErr ensures data integrity.


48-48: Correct async handling.

The addition of await is necessary since listManagedProjectIds is now asynchronous to support team-based project queries.

apps/backend/prisma/migrations/20250806171211_add_team_based_project_ownership/migration.sql (1)

51-51: Potential SQL injection vulnerability in team name generation.

The string concatenation in the display name could be vulnerable to SQL injection if user display names contain malicious SQL.

Use proper parameterization for the team display name:

-            COALESCE(user_record."displayName", 'User') || '''s Team',
+            COALESCE(user_record."displayName", 'User') || E'\'s Team',

Or better yet, use format() function:

-            COALESCE(user_record."displayName", 'User') || '''s Team',
+            format('%s''s Team', COALESCE(user_record."displayName", 'User')),

Likely an incorrect or invalid review comment.

apps/backend/src/app/api/latest/integrations/neon/projects/transfer/confirm/verification-code-handler.tsx (2)

58-69: Team creation logic is well-structured.

The team creation with proper fallbacks for display name generation is well-implemented. The use of teamsCrudHandlers.adminCreate ensures consistent team creation logic.


71-78: Correct team ownership assignment.

The project ownership update properly assigns the newly created team as the owner using the global Prisma client.

apps/backend/src/app/api/latest/internal/failed-emails-digest/crud.tsx (1)

31-36: Consider optional ownerTeamId in your JOINs

The Prisma schema (apps/backend/prisma/schema.prisma) declares ownerTeamId as String?, so it can be NULL. Using INNER JOIN on Team (and subsequently on TeamMember/ContactChannel) will drop any projects without an ownerTeamId.

Please confirm whether excluding those projects is intended. If you need to include projects lacking an owner team, consider switching to LEFT JOIN for the Team table (and adjust downstream joins or add WHERE filters accordingly).

• File: apps/backend/src/app/api/latest/internal/failed-emails-digest/crud.tsx
• Lines: 31–36

Original snippet:

INNER JOIN "Team" team
  ON team."teamId" = p."ownerTeamId"
  AND team."tenancyId" = t.id
INNER JOIN "TeamMember" tm
  ON tm."teamId" = team."teamId"
  AND tm."projectUserId" = pu."projectUserId"
  AND tm."tenancyId" = t.id
INNER JOIN "ContactChannel" cc
  ON tm."projectUserId" = cc."projectUserId"

Comment thread apps/e2e/tests/backend/endpoints/api/v1/projects.test.ts Outdated
Comment thread packages/stack-shared/src/interface/crud/projects.ts Outdated
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 0

🧹 Nitpick comments (1)
apps/e2e/tests/backend/endpoints/api/v1/auth/password/sign-up.test.ts (1)

228-230: Address TODO items for team creation configuration tests.

These TODO items indicate missing test coverage for different team creation configurations. Consider implementing these tests or creating issues to track them.

Would you like me to help implement these missing test cases or create issues to track this work?

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 1932f50 and b55453a.

📒 Files selected for processing (20)
  • apps/backend/prisma/migrations/20250806171211_add_team_based_project_ownership/migration.sql (1 hunks)
  • apps/backend/src/app/api/latest/internal/failed-emails-digest/crud.tsx (1 hunks)
  • apps/e2e/tests/backend/endpoints/api/v1/auth/anonymous/sign-up.test.ts (1 hunks)
  • apps/e2e/tests/backend/endpoints/api/v1/auth/oauth/token.test.ts (1 hunks)
  • apps/e2e/tests/backend/endpoints/api/v1/auth/password/sign-in.test.ts (1 hunks)
  • apps/e2e/tests/backend/endpoints/api/v1/auth/password/sign-up.test.ts (1 hunks)
  • apps/e2e/tests/backend/endpoints/api/v1/contact-channels/contact-channels.test.ts (3 hunks)
  • apps/e2e/tests/backend/endpoints/api/v1/integrations/custom/projects/provision.test.ts (1 hunks)
  • apps/e2e/tests/backend/endpoints/api/v1/integrations/neon/oauth-providers.test.ts (1 hunks)
  • apps/e2e/tests/backend/endpoints/api/v1/integrations/neon/projects/current.test.ts (3 hunks)
  • apps/e2e/tests/backend/endpoints/api/v1/integrations/neon/projects/provision.test.ts (1 hunks)
  • apps/e2e/tests/backend/endpoints/api/v1/internal/failed-emails-digest.test.ts (2 hunks)
  • apps/e2e/tests/backend/endpoints/api/v1/internal/projects.test.ts (11 hunks)
  • apps/e2e/tests/backend/endpoints/api/v1/project-permissions.test.ts (1 hunks)
  • apps/e2e/tests/backend/endpoints/api/v1/team-invitations.test.ts (2 hunks)
  • apps/e2e/tests/backend/endpoints/api/v1/team-memberships.test.ts (8 hunks)
  • apps/e2e/tests/backend/endpoints/api/v1/team-permissions.test.ts (1 hunks)
  • apps/e2e/tests/backend/endpoints/api/v1/teams.test.ts (6 hunks)
  • apps/e2e/tests/backend/endpoints/api/v1/users.test.ts (19 hunks)
  • packages/stack-shared/src/interface/crud/projects.ts (1 hunks)
✅ Files skipped from review due to trivial changes (9)
  • apps/e2e/tests/backend/endpoints/api/v1/integrations/neon/oauth-providers.test.ts
  • apps/e2e/tests/backend/endpoints/api/v1/project-permissions.test.ts
  • apps/e2e/tests/backend/endpoints/api/v1/team-permissions.test.ts
  • apps/e2e/tests/backend/endpoints/api/v1/integrations/custom/projects/provision.test.ts
  • apps/e2e/tests/backend/endpoints/api/v1/integrations/neon/projects/current.test.ts
  • apps/e2e/tests/backend/endpoints/api/v1/internal/projects.test.ts
  • apps/e2e/tests/backend/endpoints/api/v1/contact-channels/contact-channels.test.ts
  • apps/e2e/tests/backend/endpoints/api/v1/teams.test.ts
  • apps/e2e/tests/backend/endpoints/api/v1/team-memberships.test.ts
🚧 Files skipped from review as they are similar to previous changes (3)
  • packages/stack-shared/src/interface/crud/projects.ts
  • apps/backend/src/app/api/latest/internal/failed-emails-digest/crud.tsx
  • apps/backend/prisma/migrations/20250806171211_add_team_based_project_ownership/migration.sql
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (8)
  • GitHub Check: restart-dev-and-test
  • GitHub Check: setup-tests
  • GitHub Check: build (22.x)
  • GitHub Check: build (22.x)
  • GitHub Check: docker
  • GitHub Check: all-good
  • GitHub Check: lint_and_build (latest)
  • GitHub Check: Security Check
🔇 Additional comments (13)
apps/e2e/tests/backend/endpoints/api/v1/integrations/neon/projects/provision.test.ts (1)

85-85: LGTM! Test snapshot correctly updated for team ownership migration.

The addition of "owner_team_id": null to the test snapshot properly reflects the new project schema. The null value is appropriate for projects provisioned via the Neon integration that don't have explicit team ownership assigned.

apps/e2e/tests/backend/endpoints/api/v1/auth/anonymous/sign-up.test.ts (1)

27-34: LGTM! Team-based ownership correctly implemented for anonymous users.

The test properly validates that anonymous users now receive a selected_team object with appropriate metadata and a corresponding selected_team_id. This aligns with the broader migration to team-based project ownership.

apps/e2e/tests/backend/endpoints/api/v1/auth/password/sign-in.test.ts (1)

39-46: LGTM! Consistent team assignment for password authentication.

The test correctly validates the new team-based user model. The selected_team object structure and selected_team_id field are properly populated, maintaining consistency with other authentication methods.

apps/e2e/tests/backend/endpoints/api/v1/auth/password/sign-up.test.ts (1)

50-57: LGTM! Password sign-up properly implements team assignment.

The test correctly validates that new users signing up with passwords receive appropriate team assignments following the established pattern.

apps/e2e/tests/backend/endpoints/api/v1/auth/oauth/token.test.ts (1)

57-64: LGTM! OAuth authentication properly integrated with team model.

The test correctly validates that OAuth users receive proper team assignments, maintaining consistency across all authentication methods in the system.

apps/e2e/tests/backend/endpoints/api/v1/internal/failed-emails-digest.test.ts (1)

332-363: LGTM! Properly implements team-based ownership for multiple owners scenario.

The refactored approach correctly:

  • Captures user IDs explicitly for team management
  • Uses the team-memberships API to establish ownership relationships
  • Validates team membership creation with proper assertions

This is a cleaner implementation than the previous user metadata patching approach and aligns with the new team-based ownership model.

apps/e2e/tests/backend/endpoints/api/v1/team-invitations.test.ts (2)

99-107: LGTM! Test correctly reflects personal team creation.

The addition of the personal team object in the expected response aligns with the new team-based ownership model. The team structure includes all necessary metadata fields and follows the expected naming pattern.


149-150: LGTM! Assertion updated for new team structure.

The updated assertion correctly expects 2 teams instead of 1, accounting for both the explicitly created team and the personal team that's now automatically generated for users.

apps/e2e/tests/backend/endpoints/api/v1/users.test.ts (5)

147-154: LGTM! User response updated for team-based model.

The selected_team object correctly includes all necessary metadata fields for client access, replacing the previous null value to reflect the new personal team assignment.


186-194: LGTM! Consistent team structure in user responses.

The selected_team structure is consistently applied across different test scenarios, maintaining the same metadata pattern.


270-278: LGTM! Server and client responses aligned.

The selected_team object maintains consistency between client and server access patterns while including appropriate fields for each access level.


803-812: LGTM! Server response includes additional metadata.

The server access response correctly includes created_at_millis and server_metadata fields in the selected_team object, which is appropriate for server-level access.


743-774: LGTM! Selected team functionality properly tested.

The test case comprehensively validates the selected team update functionality, including switching between teams and setting to null. This ensures the new team selection feature works correctly.

Comment thread apps/backend/src/lib/projects.tsx
@BilalG1 BilalG1 assigned N2D4 and unassigned BilalG1 Aug 7, 2025
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

♻️ Duplicate comments (2)
apps/backend/prisma/migrations/20250806171211_add_team_based_project_ownership/migration.sql (2)

82-89: Still missing existence check before updating projects.
The prior review comment suggesting a guard clause for nonexistent project IDs has not been addressed.


76-76: Insert stores 'TRUE' instead of boolean TRUE in TeamMember.isSelected.

This will silently coerce or fail depending on DB settings and violates the column’s type.

-   'TRUE',
+   TRUE,
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between b55453a and 4e6e45f.

📒 Files selected for processing (1)
  • apps/backend/prisma/migrations/20250806171211_add_team_based_project_ownership/migration.sql (1 hunks)
🧰 Additional context used
🧠 Learnings (1)
📚 Learning: 2025-08-04T22:25:51.260Z
Learnt from: CR
PR: stack-auth/stack-auth#0
File: CLAUDE.md:0-0
Timestamp: 2025-08-04T22:25:51.260Z
Learning: Applies to apps/backend/prisma/schema.prisma : Database models use Prisma

Applied to files:

  • apps/backend/prisma/migrations/20250806171211_add_team_based_project_ownership/migration.sql
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (10)
  • GitHub Check: restart-dev-and-test
  • GitHub Check: build (22.x)
  • GitHub Check: docker
  • GitHub Check: setup-tests
  • GitHub Check: build (22.x)
  • GitHub Check: all-good
  • GitHub Check: lint_and_build (latest)
  • GitHub Check: check_prisma_migrations (22.x)
  • GitHub Check: docker
  • GitHub Check: Security Check

@claude
Copy link
Copy Markdown
Contributor

claude Bot commented Aug 8, 2025

Claude finished @BilalG1's task —— View job


Comprehensive Code Review: Team-Based Project Ownership

This is a significant architectural refactoring that introduces team-based project ownership, replacing the previous user-based ownership model. The changes are well-architected overall, but there are several important issues that need to be addressed.

🚨 Critical Issues

1. Database Migration Safety Concerns

File: apps/backend/prisma/migrations/20250806171211_add_team_based_project_ownership/migration.sql

  • Line 30: Using string 'TRUE' instead of boolean TRUE for isPrimary comparison will cause type issues
  • Line 76: Inserting string 'TRUE' instead of boolean TRUE for isSelected field
  • Lines 82-89: No existence validation before updating projects - could silently fail for invalid project IDs
  • Line 3: Missing foreign key constraint and index on ownerTeamId field

Recommendations:

-- Fix boolean comparisons
AND cc."isPrimary" = TRUE  -- not 'TRUE'

-- Add foreign key constraint and index
ALTER TABLE "Project"
  ADD COLUMN "ownerTeamId" UUID,
  ADD CONSTRAINT "Project_ownerTeamId_fkey"
    FOREIGN KEY ("ownerTeamId") REFERENCES "Team"("teamId") ON DELETE SET NULL;
CREATE INDEX IF NOT EXISTS "idx_Project_ownerTeamId" ON "Project" ("ownerTeamId");

-- Add project existence check
IF NOT EXISTS (SELECT 1 FROM "Project" WHERE "id" = project_id_text) THEN
    RAISE WARNING 'Project % does not exist, skipping', project_id_text;
    CONTINUE;
END IF;

2. UI Form Integration Issue

File: apps/dashboard/src/app/(main)/(protected)/(outside-dashboard)/new-project/page-client.tsx:99-102

The SelectedTeamSwitcher component is displayed but not integrated with the form submission. The selected team won't be captured or sent to the API.

Fix Required:

// Add to form schema
ownerTeamId: yup.string().nullable(),

// Replace current team selection with proper form field
<FormField
  control={form.control}
  name="ownerTeamId"
  render={({ field }) => (
    <div className="flex flex-col gap-2">
      <Label>Team</Label>
      <SelectedTeamSwitcher 
        triggerClassName="max-w-full"
        onChange={field.onChange}
      />
      <FormMessage />
    </div>
  )}
/>

⚠️ Notable Issues

3. Unsafe Team Lookup

File: apps/dashboard/src/components/project-card.tsx:10

Using project.ownerTeamId ?? "" could cause useTeam("") calls with empty strings.

Fix: const team = project.ownerTeamId ? user.useTeam(project.ownerTeamId) : null;

4. Potential SQL Join Issues

File: apps/backend/src/app/api/latest/internal/failed-emails-digest/crud.tsx:31-36

Using INNER JOIN on Team/TeamMember may exclude projects without owner teams (since ownerTeamId is nullable). Consider if LEFT JOIN is more appropriate.

5. Performance Consideration

File: apps/backend/src/lib/projects.tsx:14-38

The listManagedProjectIds function makes two separate queries instead of a single optimized join. Consider combining for better performance:

const projectIds = await globalPrismaClient.project.findMany({
  where: {
    ownerTeam: {
      tenancyId: internalTenancy.id,
      teamMembers: { some: { projectUserId: projectUser.id } }
    }
  },
  select: { id: true }
});

✅ Positive Aspects

1. Excellent Test Coverage

The PR includes comprehensive test updates across all affected endpoints. The snapshots properly reflect the new team-based model and validate expected behaviors.

2. Clean API Design

The addition of owner_team_id to the project schema is well-structured and maintains backward compatibility by being nullable.

3. Consistent Implementation

The team ownership model is consistently applied across all integration endpoints (Neon, custom) and API handlers.

4. Safe Migration Strategy

The migration creates personal teams for existing users, preserving their project ownership while transitioning to the new model.

5. Proper Error Handling

Good use of throwErr for required fields and proper error messages in API handlers.

🔒 Security Assessment

  • No security vulnerabilities identified - the team-based ownership model appears secure
  • Authorization checks are properly updated to use team membership
  • No sensitive information exposed in API responses

🧪 Test Analysis

  • Excellent coverage - Tests updated across authentication, projects, teams, and user endpoints
  • Proper assertions - Tests validate both structure and content of team data
  • Edge cases covered - Tests handle various scenarios including team switching and membership

📊 Overall Assessment

This is a well-architected change that successfully migrates from user-based to team-based project ownership. The implementation is largely sound, but the critical database migration issues must be fixed before deployment to prevent data integrity problems.

Priority Actions:

  1. High Priority: Fix database migration boolean type issues and add constraints
  2. High Priority: Fix UI form integration for team selection
  3. Medium Priority: Address performance and safety concerns in the codebase
  4. Low Priority: Consider UX improvements for team display

The architectural direction is excellent and will provide a strong foundation for collaborative project management features.


@claude
Copy link
Copy Markdown
Contributor

claude Bot commented Aug 9, 2025

Claude finished @BilalG1's task —— View job


Pull Request Review: Team-Based Project Ownership

Review Checklist:

  • Set up development environment and understand repository structure
  • Analyze database migration script for safety and correctness
  • Review API changes and business logic updates
  • Examine UI components and form integration
  • Assess test coverage and quality
  • Check for security vulnerabilities
  • Evaluate performance implications
  • Provide comprehensive feedback and recommendations

Comprehensive Code Review: Team-Based Project Ownership

This is a significant architectural refactoring that introduces team-based project ownership, replacing the previous user-based ownership model. The changes are well-architected overall, but there are several important issues that need to be addressed.

🚨 Critical Issues

1. Database Migration Safety Concerns

File: apps/backend/prisma/migrations/20250806171211_add_team_based_project_ownership/migration.sql

  • Line 30: Using string 'TRUE' instead of boolean TRUE for isPrimary comparison will cause type issues
  • Line 76: Inserting string 'TRUE' instead of boolean TRUE for isSelected field
  • Lines 82-89: No existence validation before updating projects - could silently fail for invalid project IDs
  • Line 3: Missing foreign key constraint and index on ownerTeamId field

Recommendations:

-- Fix boolean comparisons
AND cc."isPrimary" = TRUE  -- not 'TRUE'

-- Add foreign key constraint and index
ALTER TABLE "Project"
  ADD COLUMN "ownerTeamId" UUID,
  ADD CONSTRAINT "Project_ownerTeamId_fkey"
    FOREIGN KEY ("ownerTeamId") REFERENCES "Team"("teamId") ON DELETE SET NULL;
CREATE INDEX IF NOT EXISTS "idx_Project_ownerTeamId" ON "Project" ("ownerTeamId");

-- Add project existence check
IF NOT EXISTS (SELECT 1 FROM "Project" WHERE "id" = project_id_text) THEN
    RAISE WARNING 'Project % does not exist, skipping', project_id_text;
    CONTINUE;
END IF;

2. UI Form Integration Issue

File: apps/dashboard/src/app/(main)/(protected)/(outside-dashboard)/new-project/page-client.tsx:99-102

The SelectedTeamSwitcher component is displayed but not integrated with the form submission. The selected team won't be captured or sent to the API.

Fix Required:

// Add to form schema
ownerTeamId: yup.string().nullable(),

// Replace current team selection with proper form field
<FormField
  control={form.control}
  name="ownerTeamId"
  render={({ field }) => (
    <div className="flex flex-col gap-2">
      <Label>Team</Label>
      <SelectedTeamSwitcher 
        triggerClassName="max-w-full"
        onChange={field.onChange}
      />
      <FormMessage />
    </div>
  )}
/>

⚠️ Notable Issues

3. Unsafe Team Lookup

File: apps/dashboard/src/components/project-card.tsx:10

Using project.ownerTeamId ?? "" could cause useTeam("") calls with empty strings.

Fix: const team = project.ownerTeamId ? user.useTeam(project.ownerTeamId) : null;

4. Potential SQL Join Issues

File: apps/backend/src/app/api/latest/internal/failed-emails-digest/crud.tsx:31-36

Using INNER JOIN on Team/TeamMember may exclude projects without owner teams (since ownerTeamId is nullable). Consider if LEFT JOIN is more appropriate.

5. Performance Consideration

File: apps/backend/src/lib/projects.tsx:14-38

The listManagedProjectIds function makes two separate queries instead of a single optimized join. Consider combining for better performance:

const projectIds = await globalPrismaClient.project.findMany({
  where: {
    ownerTeam: {
      tenancyId: internalTenancy.id,
      teamMembers: { some: { projectUserId: projectUser.id } }
    }
  },
  select: { id: true }
});

✅ Positive Aspects

1. Excellent Test Coverage

The PR includes comprehensive test updates across all affected endpoints. The snapshots properly reflect the new team-based model and validate expected behaviors.

2. Clean API Design

The addition of owner_team_id to the project schema is well-structured and maintains backward compatibility by being nullable.

3. Consistent Implementation

The team ownership model is consistently applied across all integration endpoints (Neon, custom) and API handlers.

4. Safe Migration Strategy

The migration creates personal teams for existing users, preserving their project ownership while transitioning to the new model.

5. Proper Error Handling

Good use of throwErr for required fields and proper error messages in API handlers.

🔒 Security Assessment

  • No security vulnerabilities identified - the team-based ownership model appears secure
  • Authorization checks are properly updated to use team membership
  • No sensitive information exposed in API responses

🧪 Test Analysis

  • Excellent coverage - Tests updated across authentication, projects, teams, and user endpoints
  • Proper assertions - Tests validate both structure and content of team data
  • Edge cases covered - Tests handle various scenarios including team switching and membership

📊 Overall Assessment

This is a well-architected change that successfully migrates from user-based to team-based project ownership. The implementation is largely sound, but the critical database migration issues must be fixed before deployment to prevent data integrity problems.

Priority Actions:

  1. High Priority: Fix database migration boolean type issues and add constraints
  2. High Priority: Fix UI form integration for team selection
  3. Medium Priority: Address performance and safety concerns in the codebase
  4. Low Priority: Consider UX improvements for team display

The architectural direction is excellent and will provide a strong foundation for collaborative project management features.


@claude
Copy link
Copy Markdown
Contributor

claude Bot commented Aug 11, 2025

Claude finished @BilalG1's task —— View job


Pull Request Review: Team-Based Project Ownership

Review Checklist:

  • Set up development environment and understand repository structure
  • Analyze database migration script for safety and correctness
  • Review API changes and business logic updates
  • Examine UI components and form integration
  • Assess test coverage and quality
  • Check for security vulnerabilities
  • Evaluate performance implications
  • Provide comprehensive feedback and recommendations

Comprehensive Code Review: Team-Based Project Ownership

This is a significant architectural refactoring that introduces team-based project ownership, replacing the previous user-based ownership model. The changes are well-architected overall, but there are several important issues that need to be addressed.

🚨 Critical Issues

1. Database Migration Safety Concerns

File: apps/backend/prisma/migrations/20250806171211_add_team_based_project_ownership/migration.sql

  • Line 30: Using string 'TRUE' instead of boolean TRUE for isPrimary comparison will cause type issues
  • Line 76: Inserting string 'TRUE' instead of boolean TRUE for isSelected field
  • Lines 82-89: No existence validation before updating projects - could silently fail for invalid project IDs
  • Line 3: Missing foreign key constraint and index on ownerTeamId field

Recommendations:

-- Fix boolean comparisons
AND cc."isPrimary" = TRUE  -- not 'TRUE'

-- Add foreign key constraint and index
ALTER TABLE "Project"
  ADD COLUMN "ownerTeamId" UUID,
  ADD CONSTRAINT "Project_ownerTeamId_fkey"
    FOREIGN KEY ("ownerTeamId") REFERENCES "Team"("teamId") ON DELETE SET NULL;
CREATE INDEX IF NOT EXISTS "idx_Project_ownerTeamId" ON "Project" ("ownerTeamId");

-- Add project existence check
IF NOT EXISTS (SELECT 1 FROM "Project" WHERE "id" = project_id_text) THEN
    RAISE WARNING 'Project % does not exist, skipping', project_id_text;
    CONTINUE;
END IF;

2. UI Form Integration Issue

File: apps/dashboard/src/app/(main)/(protected)/(outside-dashboard)/new-project/page-client.tsx:99-102

The SelectedTeamSwitcher component is displayed but not integrated with the form submission. The selected team won't be captured or sent to the API.

Fix Required:

// Add to form schema
ownerTeamId: yup.string().nullable(),

// Replace current team selection with proper form field
<FormField
  control={form.control}
  name="ownerTeamId"
  render={({ field }) => (
    <div className="flex flex-col gap-2">
      <Label>Team</Label>
      <SelectedTeamSwitcher 
        triggerClassName="max-w-full"
        onChange={field.onChange}
      />
      <FormMessage />
    </div>
  )}
/>

⚠️ Notable Issues

3. Unsafe Team Lookup

File: apps/dashboard/src/components/project-card.tsx:10

Using project.ownerTeamId ?? "" could cause useTeam("") calls with empty strings.

Fix: const team = project.ownerTeamId ? user.useTeam(project.ownerTeamId) : null;

4. Potential SQL Join Issues

File: apps/backend/src/app/api/latest/internal/failed-emails-digest/crud.tsx:31-36

Using INNER JOIN on Team/TeamMember may exclude projects without owner teams (since ownerTeamId is nullable). Consider if LEFT JOIN is more appropriate.

5. Performance Consideration

File: apps/backend/src/lib/projects.tsx:14-38

The listManagedProjectIds function makes two separate queries instead of a single optimized join. Consider combining for better performance:

const projectIds = await globalPrismaClient.project.findMany({
  where: {
    ownerTeam: {
      tenancyId: internalTenancy.id,
      teamMembers: { some: { projectUserId: projectUser.id } }
    }
  },
  select: { id: true }
});

✅ Positive Aspects

1. Excellent Test Coverage

The PR includes comprehensive test updates across all affected endpoints. The snapshots properly reflect the new team-based model and validate expected behaviors.

2. Clean API Design

The addition of owner_team_id to the project schema is well-structured and maintains backward compatibility by being nullable.

3. Consistent Implementation

The team ownership model is consistently applied across all integration endpoints (Neon, custom) and API handlers.

4. Safe Migration Strategy

The migration creates personal teams for existing users, preserving their project ownership while transitioning to the new model.

5. Proper Error Handling

Good use of throwErr for required fields and proper error messages in API handlers.

🔒 Security Assessment

  • No security vulnerabilities identified - the team-based ownership model appears secure
  • Authorization checks are properly updated to use team membership
  • No sensitive information exposed in API responses

🧪 Test Analysis

  • Excellent coverage - Tests updated across authentication, projects, teams, and user endpoints
  • Proper assertions - Tests validate both structure and content of team data
  • Edge cases covered - Tests handle various scenarios including team switching and membership

📊 Overall Assessment

This is a well-architected change that successfully migrates from user-based to team-based project ownership. The implementation is largely sound, but the critical database migration issues must be fixed before deployment to prevent data integrity problems.

Priority Actions:

  1. High Priority: Fix database migration boolean type issues and add constraints
  2. High Priority: Fix UI form integration for team selection
  3. Medium Priority: Address performance and safety concerns in the codebase
  4. Low Priority: Consider UX improvements for team display

The architectural direction is excellent and will provide a strong foundation for collaborative project management features.


@N2D4 N2D4 assigned BilalG1 and unassigned N2D4 Aug 19, 2025
@claude
Copy link
Copy Markdown
Contributor

claude Bot commented Aug 19, 2025

Claude finished @BilalG1's task —— View job


I'll analyze this and get back to you.

Comment thread examples/demo/src/app/gmail/page.tsx
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 0

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (2)
apps/e2e/tests/backend/endpoints/api/v1/integrations/neon/projects/current.test.ts (1)

1-177: Add missing ownerTeamId to consumer Project type

The API now returns owner_team_id, but the client-facing Project type in packages/template/src/lib/stack-app/projects/index.ts doesn’t include it. Update the type (and any JSON-to-TS mapping) to prevent drift:

Impacted file:

  • packages/template/src/lib/stack-app/projects/index.ts

Proposed diff:

 export type Project = {
   readonly id: string,
   readonly displayName: string,
+  /** Maps from API’s `owner_team_id` */
+  readonly ownerTeamId: string,
   readonly config: ProjectConfig,
 };

Also verify your fetch/deserialize logic maps the JSON owner_team_idownerTeamId.

apps/e2e/tests/backend/endpoints/api/v1/users.test.ts (1)

744-776: Fix inconsistent initial expectation for selected_team_id and drop the “set to null” step

Elsewhere in this file, selected_team and selected_team_id are now non-null after sign-in. This test still expects null initially and later sets selected_team_id to null, which contradicts the new semantics and will likely fail.

Apply the following minimal update: assert a non-null initial selected_team_id and remove the final “set to null” block.

 it("should be able to update selected team", async ({ expect }) => {
-    await Project.createAndSwitch({ config: { magic_link_enabled: true } });
+    await Project.createAndSwitch({ config: { magic_link_enabled: true } });
     await Auth.Otp.signIn();
     const { teamId: team1Id } = await Team.createWithCurrentAsCreator({});
     const { teamId: team2Id } = await Team.createWithCurrentAsCreator({});
     const response1 = await niceBackendFetch("/api/v1/users/me", {
       accessType: "client",
     });
-    expect(response1.body.selected_team_id).toEqual(null);
+    // With team-based semantics, the user should already have a selected team (e.g., personal team).
+    expect(typeof response1.body.selected_team_id).toBe("string");
     const response2 = await niceBackendFetch("/api/v1/users/me", {
       accessType: "client",
       method: "PATCH",
       body: {
         selected_team_id: team1Id,
       },
     });
     expect(response2.body.selected_team_id).toEqual(team1Id);
     const response3 = await niceBackendFetch("/api/v1/users/me", {
       accessType: "client",
       method: "PATCH",
       body: {
         selected_team_id: team2Id,
       },
     });
     expect(response3.body.selected_team_id).toEqual(team2Id);
-    const response4 = await niceBackendFetch("/api/v1/users/me", {
-      accessType: "client",
-      method: "PATCH",
-      body: {
-        selected_team_id: null,
-      },
-    });
-    expect(response4.body.selected_team_id).toEqual(null);

If clearing the selection to null is still a supported scenario, consider moving that into a separate, explicitly named test and aligning expectations with the intended behavior (e.g., error or fallback to personal team).

♻️ Duplicate comments (4)
packages/template/src/components/selected-team-switcher.tsx (1)

71-90: Handle nullable team correctly and drop unsafe casts in onChange

Current code force-casts team as Team in multiple places. When AllowNull is true, this can cause runtime errors (e.g., navigating with null or calling setSelectedTeam(null)). Narrow the value and eliminate the casts.

Apply this diff:

       onChange={async (team) => {
-        if (props.onChange) {
-          props.onChange(team as Team);
-        }
+        props.onChange?.(team);
         // Skip actual navigation/updates in mock mode
         if (props.mockUser) return;
-        if (!props.noUpdateSelectedTeam) {
-          await user?.setSelectedTeam(team as Team);
-        }
-        if (props.urlMap) {
-          navigate(props.urlMap(team as Team));
-        }
+        // Only proceed when a non-null team is selected
+        if (team == null) return;
+        if (!props.noUpdateSelectedTeam) {
+          await user?.setSelectedTeam(team);
+        }
+        if (props.urlMap) {
+          navigate(props.urlMap(team));
+        }
       }}
apps/backend/src/lib/projects.tsx (1)

76-90: Verify all create call sites now provide owner_team_id

The create signature now requires owner_team_id. Some routes (e.g., Neon provisioning) may still miss it.

Run:

#!/bin/bash
set -euo pipefail

echo "== Call sites for createOrUpdateProjectWithLegacyConfig =="
rg -n -C2 'createOrUpdateProjectWithLegacyConfig\('

echo
echo "== Instances passing type: \"create\" without owner_team_id =="
rg -n -C3 'createOrUpdateProjectWithLegacyConfig\(\s*\{\s*type:\s*"create".*?\}\s*\)' | sed -n '1,220p'

If the provisioning route is still missing owner_team_id, add it (null or a UUID) to unblock the create path.

apps/e2e/tests/backend/endpoints/api/v1/projects.test.ts (2)

1406-1409: User token incorrectly used as admin token

This is using a user access token obtained from Auth.Otp.signIn() as an admin token, which is incorrect. User tokens should not authenticate admin endpoints.

-  await Auth.Otp.signIn();
-  const adminAccessToken = backendContext.value.userAuth?.accessToken;
-  const { projectId } = await Project.create();
+  await Auth.Otp.signIn();
+  const { projectId, adminAccessToken } = await Project.createAndGetAdminToken();

1451-1453: User token incorrectly used as admin token (second instance)

Similar issue - using user auth token instead of proper admin token.

-  await Auth.Otp.signIn();
-  const user1Auth = backendContext.value.userAuth;
-  const { projectId } = await Project.create();
+  await Auth.Otp.signIn();
+  const user1Auth = backendContext.value.userAuth;
+  const { projectId, adminAccessToken: user1AdminToken } = await Project.createAndGetAdminToken();
🧹 Nitpick comments (11)
packages/template/src/components/selected-team-switcher.tsx (3)

6-6: Avoid double Suspense boundaries; TeamSwitcher already wraps itself with Suspense

Per the referenced snippet, TeamSwitcher renders its own Suspense boundary with a fallback. Wrapping SelectedTeamSwitcher in an additional Suspense duplicates the loading UI unnecessarily.

Apply this diff:

-import { Suspense, useEffect } from "react";
+import { useEffect } from "react";
 export function SelectedTeamSwitcher<AllowNull extends boolean = false>(props: SelectedTeamSwitcherProps<AllowNull>) {
-  return <Suspense fallback={<Fallback />}>
-    <Inner {...props} />
-  </Suspense>;
+  return <Inner {...props} />;
 }

If you take this route, the local Fallback component (Lines 42-44) becomes unused; consider removing it as well (see separate nit).

Also applies to: 36-40


16-24: Propagate allowNull to selectedTeam’s type (aligns consumer ergonomics with onChange/urlMap)

selectedTeam is currently typed as Team regardless of allowNull. For consistency with the conditional types on urlMap and onChange—and to let consumers pass null when AllowNull is true—make selectedTeam conditional as well.

 type SelectedTeamSwitcherProps<AllowNull extends boolean = false> = {
   urlMap?: (team: AllowNull extends true ? Team | null : Team) => string,
-  selectedTeam?: Team,
+  selectedTeam?: AllowNull extends true ? Team | null : Team,
   noUpdateSelectedTeam?: boolean,
   allowNull?: AllowNull,
   nullLabel?: string,
   onChange?: (team: AllowNull extends true ? Team | null : Team) => void,
   triggerClassName?: string,

Note: This is a type-only change that improves correctness and DX. It doesn’t alter runtime behavior.


42-44: If outer Suspense is removed, drop the now-unused Fallback component

Follow-up to the Suspense refactor: once the additional Suspense wrapper is removed, this Fallback will be unused and can be safely deleted.

apps/e2e/tests/backend/endpoints/api/v1/users.test.ts (2)

519-528: Remove unused variable in TOTP test

The local variable secret is declared but never used.

-    const secret = generateSecureRandomString(32);
     const response = await niceBackendFetch("/api/v1/users/me", {
       accessType: "client",
       method: "PATCH",
       body: {
         totp_secret_base64: "ZXhhbXBsZSB2YWx1ZQ==",
       },
     });

1900-1934: Optional: reduce snapshot brittleness by asserting shapes for selected_team

There are many repeated inline snapshots that include the same selected_team shape. Consider using expect.objectContaining or a helper matcher to reduce duplication and flakiness.

Example pattern for these assertions:

expect(response.body).toEqual(expect.objectContaining({
  selected_team_id: expect.any(String),
  selected_team: expect.objectContaining({
    id: expect.any(String),
    display_name: expect.any(String),
    client_metadata: null,
    client_read_only_metadata: null,
    profile_image_url: null,
  }),
}));

If you prefer to keep snapshots for readability, extracting a reusable helper like expectUserWithSelectedTeam(response.body) will still cut down duplication.

apps/backend/src/lib/projects.tsx (4)

15-39: Tighten query: select only teamId to reduce data transfer

The first query fetches full team rows but only teamId is used. Select just teamId to reduce payload and CPU.

-  const teams = await internalPrisma.team.findMany({
+  const teams = await internalPrisma.team.findMany({
     where: {
       tenancyId: internalTenancy.id,
       teamMembers: {
         some: {
           projectUserId: projectUser.id,
         }
       }
     },
+    select: { teamId: true },
   });
   const projectIds = await globalPrismaClient.project.findMany({
     where: {
       ownerTeamId: {
         in: teams.map((team) => team.teamId),
       },
     },
     select: {
       id: true,
     },
   });

63-66: Harden created_at handling; avoid string concatenation with "Z"

Depending on the driver, row.createdAt may already be a Date. Concatenating "Z" to a Date can produce NaN. Prefer safe parsing.

-        created_at_millis: new Date(row.createdAt + "Z").getTime(),
+        created_at_millis: (row.createdAt instanceof Date
+          ? row.createdAt
+          : new Date(row.createdAt)).getTime(),

137-147: Avoid passing undefined fields to Prisma update

When fields are omitted in options.data, you currently pass undefined, which Prisma may ignore but is brittle. Build the update payload with filterUndefined to only set explicitly provided fields and to support the “null -> empty string” behavior for description.

-      project = await tx.project.update({
-        where: {
-          id: projectFound.id,
-        },
-        data: {
-          displayName: options.data.display_name,
-          description: options.data.description === null ? "" : options.data.description,
-          isProductionMode: options.data.is_production_mode,
-          logoUrl,
-          fullLogoUrl,
-        },
-      });
+      const updateData = filterUndefined({
+        displayName: options.data.display_name,
+        description: options.data.description === null ? "" : options.data.description,
+        isProductionMode: options.data.is_production_mode,
+        logoUrl,
+        fullLogoUrl,
+      });
+      project = await tx.project.update({
+        where: { id: projectFound.id },
+        data: updateData,
+      });

28-37: Add an index on Project.ownerTeamId

I didn’t find any index on ownerTeamId in your Prisma schema or migrations. To keep lookups by ownerTeamId performant at scale, please add one. For example:

 // in apps/backend/prisma/schema.prisma

 model Project {
   id            String   @id @default(uuid())
   // …
-  ownerTeamId   String?  @db.Uuid
+  ownerTeamId   String?  @db.Uuid  @index
   // …
 }

Then run:

npx prisma migrate dev --name add_ownerTeamId_index

Let me know if you’d like me to draft the migration script.

apps/e2e/tests/backend/endpoints/api/v1/projects.test.ts (2)

1387-1387: Consider using a more descriptive test name

The test name could better reflect that it verifies team ownership alignment rather than user ownership.

-it("makes sure users own the correct projects after creating a project", async ({ expect }) => {
+it("verifies project owner_team_id matches creator's selected team", async ({ expect }) => {

1404-1404: Update test name to reflect new behavior

The test name should be updated to better describe the actual behavior being tested.

-it("removes a deleted project from a user", async ({ expect }) => {
+it("returns CURRENT_PROJECT_NOT_FOUND after project deletion", async ({ expect }) => {
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

💡 Knowledge Base configuration:

  • MCP integration is disabled by default for public repositories
  • Jira integration is disabled by default for public repositories
  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between 21c8bcf and 0f6f12b.

📒 Files selected for processing (19)
  • apps/backend/prisma/schema.prisma (1 hunks)
  • apps/backend/src/lib/projects.tsx (4 hunks)
  • apps/dashboard/src/app/(main)/(protected)/(outside-dashboard)/new-project/page-client.tsx (6 hunks)
  • apps/e2e/tests/backend/endpoints/api/v1/integrations/custom/projects/provision.test.ts (1 hunks)
  • apps/e2e/tests/backend/endpoints/api/v1/integrations/neon/oauth-providers.test.ts (1 hunks)
  • apps/e2e/tests/backend/endpoints/api/v1/integrations/neon/projects/current.test.ts (3 hunks)
  • apps/e2e/tests/backend/endpoints/api/v1/integrations/neon/projects/provision.test.ts (1 hunks)
  • apps/e2e/tests/backend/endpoints/api/v1/internal/projects.test.ts (11 hunks)
  • apps/e2e/tests/backend/endpoints/api/v1/project-permissions.test.ts (1 hunks)
  • apps/e2e/tests/backend/endpoints/api/v1/projects.test.ts (21 hunks)
  • apps/e2e/tests/backend/endpoints/api/v1/team-permissions.test.ts (1 hunks)
  • apps/e2e/tests/backend/endpoints/api/v1/users.test.ts (20 hunks)
  • examples/demo/src/app/gmail/page.tsx (2 hunks)
  • packages/stack-shared/src/interface/crud/projects.ts (2 hunks)
  • packages/template/src/components/selected-team-switcher.tsx (3 hunks)
  • packages/template/src/components/team-switcher.tsx (1 hunks)
  • packages/template/src/lib/stack-app/apps/implementations/admin-app-impl.ts (1 hunks)
  • packages/template/src/lib/stack-app/apps/implementations/client-app-impl.ts (2 hunks)
  • packages/template/src/lib/stack-app/projects/index.ts (2 hunks)
✅ Files skipped from review due to trivial changes (1)
  • examples/demo/src/app/gmail/page.tsx
🚧 Files skipped from review as they are similar to previous changes (13)
  • packages/stack-shared/src/interface/crud/projects.ts
  • apps/e2e/tests/backend/endpoints/api/v1/integrations/custom/projects/provision.test.ts
  • packages/template/src/components/team-switcher.tsx
  • apps/e2e/tests/backend/endpoints/api/v1/team-permissions.test.ts
  • apps/e2e/tests/backend/endpoints/api/v1/project-permissions.test.ts
  • apps/e2e/tests/backend/endpoints/api/v1/integrations/neon/projects/provision.test.ts
  • packages/template/src/lib/stack-app/apps/implementations/admin-app-impl.ts
  • apps/backend/prisma/schema.prisma
  • apps/e2e/tests/backend/endpoints/api/v1/integrations/neon/oauth-providers.test.ts
  • packages/template/src/lib/stack-app/projects/index.ts
  • packages/template/src/lib/stack-app/apps/implementations/client-app-impl.ts
  • apps/e2e/tests/backend/endpoints/api/v1/internal/projects.test.ts
  • apps/dashboard/src/app/(main)/(protected)/(outside-dashboard)/new-project/page-client.tsx
🧰 Additional context used
🧬 Code Graph Analysis (4)
apps/e2e/tests/backend/endpoints/api/v1/projects.test.ts (1)
apps/e2e/tests/backend/backend-helpers.ts (3)
  • backendContext (34-56)
  • InternalProjectKeys (75-80)
  • niceBackendFetch (107-165)
apps/e2e/tests/backend/endpoints/api/v1/users.test.ts (1)
packages/template/src/lib/stack-app/projects/index.ts (1)
  • Project (9-13)
packages/template/src/components/selected-team-switcher.tsx (2)
packages/template/src/components/team-switcher.tsx (1)
  • TeamSwitcher (49-53)
packages/template/src/index.ts (1)
  • TeamSwitcher (24-24)
apps/backend/src/lib/projects.tsx (2)
apps/backend/src/lib/tenancies.tsx (2)
  • getSoleTenancyFromProjectBranch (59-66)
  • DEFAULT_BRANCH_ID (17-17)
apps/backend/src/prisma-client.tsx (2)
  • getPrismaClientForTenancy (51-53)
  • globalPrismaClient (31-31)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (7)
  • GitHub Check: restart-dev-and-test
  • GitHub Check: all-good
  • GitHub Check: setup-tests
  • GitHub Check: lint_and_build (latest)
  • GitHub Check: build (22.x)
  • GitHub Check: build (22.x)
  • GitHub Check: Security Check
🔇 Additional comments (13)
packages/template/src/components/selected-team-switcher.tsx (3)

23-23: Prop threading for triggerClassName looks good

New prop is defined and passed through to TeamSwitcher. No issues.


8-8: Switch to TeamSwitcher import looks correct

Importing the new TeamSwitcher component aligns with the refactor. No concerns.


73-73: No changes needed: TeamSwitcher expects team prop

Confirmed that TeamSwitcherProps defines the controlled value as team, so the existing

team={props.selectedTeam}

is correct.

apps/e2e/tests/backend/endpoints/api/v1/integrations/neon/projects/current.test.ts (1)

46-47: Snapshots correctly include owner_team_id

The updated snapshots asserting owner_team_id look consistent across read and patch flows. This aligns with the new team-based ownership model.

Also applies to: 101-102, 172-173

apps/e2e/tests/backend/endpoints/api/v1/users.test.ts (1)

147-155: LGTM: selected_team shape added to client responses

The added selected_team object and selected_team_id fields look consistent with the new model. Good differentiation between client and server surface areas elsewhere in the file.

Also applies to: 186-194, 270-278

apps/backend/src/lib/projects.tsx (1)

101-116: LGTM: create path sets ownerTeamId and persists tenancy

Create branch correctly sets ownerTeamId and provisions tenancy. Matches the PR’s ownership shift.

apps/e2e/tests/backend/endpoints/api/v1/projects.test.ts (7)

43-43: Team creation is now enabled in the internal project

The client_team_creation_enabled field is now true for the internal project (line 43), whereas it's false in other project configurations (lines 80, 131, 179, etc.). This change appears intentional as part of the team-based ownership migration but worth noting that it enables different behavior for internal vs. regular projects.


104-104: LGTM! owner_team_id consistently added to all project responses

The addition of owner_team_id field to all project responses is consistent throughout the test file, properly reflecting the team-based ownership model introduced in this PR.

Also applies to: 154-154, 209-209, 270-270, 325-325, 423-423, 483-483, 529-529, 575-575, 635-635, 808-808, 861-861, 918-918, 970-970, 1037-1037, 1104-1104


1389-1401: Good transition to team-based ownership verification

The test correctly verifies that the project's owner_team_id matches the creator user's selected_team.id, properly validating the team-based ownership model. The use of internal endpoints with admin access is appropriate for this verification.


1410-1413: Good validation of project listing with internal client endpoint

The test correctly uses the internal endpoint with client access to list projects, which is appropriate for the authenticated user context.


1430-1446: Appropriate error handling after project deletion

The test correctly verifies that accessing a deleted project returns CURRENT_PROJECT_NOT_FOUND error, properly validating the deletion behavior in the team-based ownership model.


1467-1474: Good isolation test for project deletion

The test correctly verifies that deleting one user's project doesn't affect another user's projects, properly validating the team-based ownership isolation.


1495-1499: Project.createAndSwitch() no longer returns adminAccessToken

Based on the AI summary, Project.createAndSwitch() now returns void instead of returning an object with adminAccessToken. The test correctly adapts to this change by not expecting a return value.

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 0

♻️ Duplicate comments (12)
packages/template/src/lib/stack-app/apps/implementations/client-app-impl.ts (1)

1143-1145: Breaking change: createProject now requires teamId — ensure all call sites are updated

This is a deliberate API break. Make sure every invocation passes a teamId. Prior review already flagged the e2e helper needing an update.

Find call sites and check for teamId being supplied:

#!/bin/bash
# List all createProject calls with surrounding context
rg -n -C3 -S '\.createProject\(' -g '!**/node_modules/**'

# Heuristic: calls that construct an object literal without a teamId key
rg -n -U -P '\.createProject\(\s*\{(?:(?!teamId\b).)*\}\s*\)' -g '!**/node_modules/**' --type=ts --type=tsx
apps/e2e/tests/backend/endpoints/api/v1/users.test.ts (9)

309-316: Same invariant applies here too

Add the id equality assertion after this response as well.


853-861: Same id equality invariant for this server response

Repeat the equality assertion after this snapshot too.


1687-1696: Same invariant for updated user (by id) response

Add the id equality assertion for consistency across server responses.


1727-1735: And again after the follow-up GET

Repeat the id equality assertion after the follow-up read.


1775-1783: Server “me” update: id equality assertion

Same suggestion applies here.


1823-1831: Password update flow: assert id equality too

Add the equality check here for completeness.


1920-1928: Metadata update flow: id equality assertion

Same as above.


1980-1988: Primary email update flow: id equality assertion

Same as above.


2046-2054: Primary email removal flow: id equality assertion

Same as above.

apps/backend/src/lib/projects.tsx (2)

15-18: Correct choice: tenancy-aware access using internalPrisma

Using getSoleTenancyFromProjectBranch + getPrismaClientForTenancy is the right pattern here, consistent with prior guidance.


112-113: Missing owner_team_id in Neon provisioning handler

The Neon provisioning route at
apps/backend/src/app/api/latest/integrations/neon/projects/provision/route.tsx
invokes createOrUpdateProjectWithLegacyConfig({ type: 'create', data: { display_name, description } }) without providing owner_team_id. Since the signature now requires owner_team_id (or null), this will break at runtime.

• apps/backend/src/app/api/latest/integrations/neon/projects/provision/route.tsx (around line 44):

   data: {
     display_name: req.body.display_name,
     description: "Created with Neon",
+      owner_team_id: /* appropriate team ID or null */,
   }

Please add the missing owner_team_id here. Also verify that other “create” call sites (e.g. internal projects CRUD which spreads data, custom integration which already sets owner_team_id: null) correctly supply this field.

🧹 Nitpick comments (12)
packages/template/src/lib/stack-app/apps/implementations/client-app-impl.ts (1)

1865-1874: Add a defensive runtime guard for teamId

Even though the converter already maps teamIdowner_team_id (confirmed in packages/template/src/lib/stack-app/projects/index.ts:114), it’s safer to guard against missing values at runtime in JS call sites:

• File: packages/template/src/lib/stack-app/apps/implementations/client-app-impl.ts
• Method: _createProject

Suggested diff:

   protected async _createProject(
     session: InternalSession,
-    newProject: AdminProjectUpdateOptions & { displayName: string, teamId: string },
+    newProject: AdminProjectUpdateOptions & { displayName: string, teamId: string },
   ): Promise<AdminOwnedProject> {
     this._ensureInternalProject();
+    if (!newProject.teamId) {
+      throw new StackAssertionError("teamId is required to create a project");
+    }
     const crud = await this._interface.createProject(
       adminProjectCreateOptionsToCrud(newProject),
       session,
     );
     // …
apps/e2e/tests/backend/endpoints/api/v1/users.test.ts (6)

186-194: Add an explicit invariant: selected_team_id should equal selected_team.id

Small strengthening: assert that the id in the embedded object matches the top-level selected_team_id. This catches drift if the API ever mismatches fields.

You can append this right after the snapshot:

expect(response.body.selected_team_id).toEqual(response.body.selected_team.id);

270-278: Repeat the id equality invariant after PATCH

Same suggestion here to assert selected_team_id === selected_team.id after update. It makes the intent explicit and guards against subtle regressions.

expect(response1.body.selected_team_id).toEqual(response1.body.selected_team.id);

517-520: Remove unused variable “secret”

The local variable ‘secret’ is declared but never used in this test.

-    const secret = generateSecureRandomString(32);

744-776: Selected team update test: assert selected_team object and add negative cases

Great addition testing the full set→switch→clear flow. Two improvements:

  • Assert the embedded selected_team object is present/absent consistently and that its id matches selected_team_id.
  • Consider negative cases: setting a team the user isn’t a member of, a team from another project, or an invalid UUID.

Inline assertions you can add now:

   const response2 = await niceBackendFetch("/api/v1/users/me", {
     accessType: "client",
     method: "PATCH",
     body: {
       selected_team_id: team1Id,
     },
   });
   expect(response2.body.selected_team_id).toEqual(team1Id);
+  expect(response2.body.selected_team).toBeDefined();
+  expect(response2.body.selected_team.id).toEqual(team1Id);

   const response3 = await niceBackendFetch("/api/v1/users/me", {
     accessType: "client",
     method: "PATCH",
     body: {
       selected_team_id: team2Id,
     },
   });
   expect(response3.body.selected_team_id).toEqual(team2Id);
+  expect(response3.body.selected_team).toBeDefined();
+  expect(response3.body.selected_team.id).toEqual(team2Id);

   const response4 = await niceBackendFetch("/api/v1/users/me", {
     accessType: "client",
     method: "PATCH",
     body: {
       selected_team_id: null,
     },
   });
   expect(response4.body.selected_team_id).toEqual(null);
+  expect(response4.body.selected_team).toEqual(null);

If you want, I can draft follow-up tests for:

  • selected_team_id set to a random UUID (expect 400/404 with a specific error code)
  • selected_team_id set to a team from a different project (expect 400/403/404)
  • selected_team_id set to a team where the user isn’t a member (expect 403/404)

Tell me which error semantics you intend (e.g., TEAM_NOT_FOUND vs. INSUFFICIENT_TEAM_ACCESS), and I’ll tailor the assertions.


805-815: Server “me” response: add id equality assertion

Server path adds created_at_millis and server_metadata; also assert that selected_team_id equals the embedded id.

expect(response.body.selected_team_id).toEqual(response.body.selected_team.id);

987-996: Reading a user by id: assert selected_team_id === selected_team.id

Strengthen with the equality assertion here as well.

expect(response.body.selected_team_id).toEqual(response.body.selected_team.id);
apps/e2e/tests/backend/endpoints/api/v1/internal/projects.test.ts (2)

483-524: Add explicit assertions for owner_team_id format and stability post-update

Inline snapshots confirm presence, but it’s useful to assert:

  • It’s a valid UUID string at runtime, and
  • It remains stable across PATCH and subsequent GET for the same project.

You can append to this test:

const uuidRe = /^[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i;

// after patchResponse assertion
expect(typeof patchResponse.body.owner_team_id).toBe("string");
expect(patchResponse.body.owner_team_id).toMatch(uuidRe);

// after response (GET) assertion
expect(response.body.owner_team_id).toBe(patchResponse.body.owner_team_id);

386-432: Also assert the created owner_team_id matches the listed item

In this test, you can capture the created project’s owner_team_id and assert it appears in the list (helps catch cross-tenant listing issues):

await Auth.Otp.signIn();
const { createProjectResponse } = await Project.create();
const createdOwnerTeamId = createProjectResponse.body.owner_team_id;

const response = await niceBackendFetch("/api/v1/internal/projects", { accessType: "client" });
expect(response.body.items.length).toBeGreaterThan(0);
expect(response.body.items.some(p => p.owner_team_id === createdOwnerTeamId)).toBe(true);
apps/backend/src/lib/projects.tsx (3)

18-37: Optimize query pattern and short-circuit when no team memberships

Minor improvements:

  • Select only the needed field from teams.
  • Early return if there are no memberships to avoid the second query.
  • Reuse a computed teamIds array for clarity.

Apply:

   const teams = await internalPrisma.team.findMany({
     where: {
       tenancyId: internalTenancy.id,
       teamMembers: {
         some: {
           projectUserId: projectUser.id,
         }
       }
     },
-  });
-  const projectIds = await globalPrismaClient.project.findMany({
+    select: { teamId: true },
+  });
+  if (teams.length === 0) {
+    return [];
+  }
+  const teamIds = teams.map((t) => t.teamId);
+  const projectIds = await globalPrismaClient.project.findMany({
     where: {
       ownerTeamId: {
-        in: teams.map((team) => team.teamId),
+        in: teamIds,
       },
     },
     select: {
       id: true,
     },
   });

112-113: Optional: validate non-null owner_team_id for internal create flows

If internal flows must always attach a team, consider rejecting null here (leave null only for external/provisioning contexts that can set it later).


28-33: Add missing index on Project.ownerTeamId

I didn’t find any existing index on ownerTeamId in your Prisma schema or SQL migrations. Without it, querying projects by team will full-scan the table at scale.

• In apps/backend/prisma/schema.prisma, update the Project model:

model Project {
  id           String   @id @default(uuid())
  ownerTeamId  String?  @db.Uuid
  // … other fields …

  @@index([ownerTeamId])  // ← add this
}

• Then run npx prisma migrate dev (or your standard migration workflow) to generate a new migration that creates the index.

This will ensure SELECT … WHERE ownerTeamId IN (…) remains performant as your dataset grows.

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

💡 Knowledge Base configuration:

  • MCP integration is disabled by default for public repositories
  • Jira integration is disabled by default for public repositories
  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between 21c8bcf and 0f6f12b.

📒 Files selected for processing (19)
  • apps/backend/prisma/schema.prisma (1 hunks)
  • apps/backend/src/lib/projects.tsx (4 hunks)
  • apps/dashboard/src/app/(main)/(protected)/(outside-dashboard)/new-project/page-client.tsx (6 hunks)
  • apps/e2e/tests/backend/endpoints/api/v1/integrations/custom/projects/provision.test.ts (1 hunks)
  • apps/e2e/tests/backend/endpoints/api/v1/integrations/neon/oauth-providers.test.ts (1 hunks)
  • apps/e2e/tests/backend/endpoints/api/v1/integrations/neon/projects/current.test.ts (3 hunks)
  • apps/e2e/tests/backend/endpoints/api/v1/integrations/neon/projects/provision.test.ts (1 hunks)
  • apps/e2e/tests/backend/endpoints/api/v1/internal/projects.test.ts (11 hunks)
  • apps/e2e/tests/backend/endpoints/api/v1/project-permissions.test.ts (1 hunks)
  • apps/e2e/tests/backend/endpoints/api/v1/projects.test.ts (21 hunks)
  • apps/e2e/tests/backend/endpoints/api/v1/team-permissions.test.ts (1 hunks)
  • apps/e2e/tests/backend/endpoints/api/v1/users.test.ts (20 hunks)
  • examples/demo/src/app/gmail/page.tsx (2 hunks)
  • packages/stack-shared/src/interface/crud/projects.ts (2 hunks)
  • packages/template/src/components/selected-team-switcher.tsx (3 hunks)
  • packages/template/src/components/team-switcher.tsx (1 hunks)
  • packages/template/src/lib/stack-app/apps/implementations/admin-app-impl.ts (1 hunks)
  • packages/template/src/lib/stack-app/apps/implementations/client-app-impl.ts (2 hunks)
  • packages/template/src/lib/stack-app/projects/index.ts (2 hunks)
✅ Files skipped from review due to trivial changes (2)
  • examples/demo/src/app/gmail/page.tsx
  • apps/e2e/tests/backend/endpoints/api/v1/integrations/custom/projects/provision.test.ts
🚧 Files skipped from review as they are similar to previous changes (12)
  • apps/e2e/tests/backend/endpoints/api/v1/project-permissions.test.ts
  • apps/backend/prisma/schema.prisma
  • apps/e2e/tests/backend/endpoints/api/v1/integrations/neon/projects/provision.test.ts
  • packages/template/src/components/team-switcher.tsx
  • apps/e2e/tests/backend/endpoints/api/v1/team-permissions.test.ts
  • packages/template/src/lib/stack-app/projects/index.ts
  • apps/e2e/tests/backend/endpoints/api/v1/integrations/neon/projects/current.test.ts
  • apps/e2e/tests/backend/endpoints/api/v1/integrations/neon/oauth-providers.test.ts
  • packages/stack-shared/src/interface/crud/projects.ts
  • apps/e2e/tests/backend/endpoints/api/v1/projects.test.ts
  • apps/dashboard/src/app/(main)/(protected)/(outside-dashboard)/new-project/page-client.tsx
  • packages/template/src/components/selected-team-switcher.tsx
🧰 Additional context used
🧬 Code Graph Analysis (1)
packages/template/src/lib/stack-app/apps/implementations/client-app-impl.ts (1)
packages/template/src/lib/stack-app/projects/index.ts (2)
  • AdminProjectUpdateOptions (44-51)
  • AdminOwnedProject (40-42)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (6)
  • GitHub Check: restart-dev-and-test
  • GitHub Check: all-good
  • GitHub Check: setup-tests
  • GitHub Check: build (22.x)
  • GitHub Check: build (22.x)
  • GitHub Check: Security Check
🔇 Additional comments (7)
packages/template/src/lib/stack-app/apps/implementations/admin-app-impl.ts (1)

116-116: No action needed for ownerTeamId nullability
AdminProject.ownerTeamId is declared as string | null, matching the backend’s owner_team_id which can be null. The assignment

ownerTeamId: data.owner_team_id,

is therefore type-safe and requires no change.

apps/e2e/tests/backend/endpoints/api/v1/users.test.ts (1)

147-155: Client “me” response: selected_team shape looks correct

Good assertion of the client-visible selected_team fields; server-only fields are excluded as expected.

apps/e2e/tests/backend/endpoints/api/v1/internal/projects.test.ts (1)

99-100: LGTM: owner_team_id is asserted across create/list/update flows

Good coverage making the ownership explicit in all response shapes. This will prevent regressions where the field drops out of any pathway.

Also applies to: 152-153, 208-209, 255-256, 316-317, 379-380, 425-426, 477-478, 518-519, 569-570, 623-624

apps/backend/src/lib/projects.tsx (4)

65-66: Expose owner_team_id in project reads

The projection correctly maps ownerTeamId -> owner_team_id for API consumers.


82-83: Create path type now requires owner_team_id: correct for team ownership model

Good type-level enforcement. This prevents accidental reintroduction of user-based ownership.


227-253: Sensible defaults for RBAC in create

Preseeding team_member/team_admin permissions and default mappings on create avoids broken team flows out of the box.


31-32: No changes needed: teamId is the correct Team identifier
The Prisma schema’s Team model defines teamId (alongside tenancyId) as its primary key, and there is no standalone id field. Using team.teamId in the IN clause is therefore correct—no adjustments required.

@BilalG1 BilalG1 merged commit 9e538a0 into dev Aug 19, 2025
23 checks passed
@BilalG1 BilalG1 deleted the project-owner-team branch August 19, 2025 18:42
@coderabbitai coderabbitai Bot mentioned this pull request Aug 24, 2025
@coderabbitai coderabbitai Bot mentioned this pull request Sep 23, 2025
@coderabbitai coderabbitai Bot mentioned this pull request Oct 10, 2025
@coderabbitai coderabbitai Bot mentioned this pull request Oct 23, 2025
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.

2 participants