Skip to content

Conversation

@brendan-kellam
Copy link
Contributor

@brendan-kellam brendan-kellam commented Nov 5, 2025

This PR refactors the UserToRepoPermission join table into a new table, AccountToRepoPermission, that maps Accounts and Repos (instead of Users and Repos). The backstory here is that we are adding support for account linking where users will be able to link multiple accounts (e.g., their GitHub, GitLab, and Okta accounts). For various reasons (including linking & unlinking of Accounts), having the join table be at the Account <> Repo level makes our lives easier.

Summary by CodeRabbit

  • Chores
    • Refactored permission syncing infrastructure to track and manage repository access at the account level rather than the user level
    • Updated database schema to support account-centric permission management, including new account-level permission sync jobs and repository associations
    • Enhanced authentication system to derive and apply account-based permission scoping across the application

@github-actions

This comment has been minimized.

@coderabbitai
Copy link

coderabbitai bot commented Nov 5, 2025

Important

Review skipped

Auto reviews are disabled on this repository.

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

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

Walkthrough

Migrates permission-syncing infrastructure from user-level to account-level tracking. Renames entities (UserPermissionSyncer → AccountPermissionSyncer, UserToRepoPermission → AccountToRepoPermission, UserPermissionSyncJob → AccountPermissionSyncJob), updates database schema, and refactors data models and integration logic across backend and web packages.

Changes

Cohort / File(s) Summary
Documentation
CHANGELOG.md
Restructures Unreleased section with new "### Changed" subsection documenting internal database representation changes for repo permissions.
Permission Syncer Implementations
packages/backend/src/ee/accountPermissionSyncer.ts
Renames UserPermissionSyncer to AccountPermissionSyncer; rewires generics from UserPermissionSyncJob to AccountPermissionSyncJob; updates queue/worker creation, Prisma model references, scheduling flow, job naming, status tracking, and token validation to operate on accounts instead of users.
Repo Permission Syncer
packages/backend/src/ee/repoPermissionSyncer.ts
Replaces userToRepoPermission with accountToRepoPermission; updates field mapping from userId to accountId; renames permittedUsers to permittedAccounts; adjusts data construction and permission persistence logic.
Backend Entrypoint
packages/backend/src/index.ts
Replaces all UserPermissionSyncer references with AccountPermissionSyncer in imports, instantiation, scheduler startup, and cleanup.
Database Schema
packages/db/prisma/migrations/20251105021913_move_permission_syncing_to_account_level/migration.sql
Drops old UserPermissionSyncJob and UserToRepoPermission tables; creates new AccountPermissionSyncJob and AccountToRepoPermission tables with updated foreign key relationships; moves permissionSyncedAt from User to Account; creates AccountPermissionSyncJobStatus enum.
Prisma Schema
packages/db/prisma/schema.prisma
Renames UserToRepoPermission → AccountToRepoPermission, UserPermissionSyncJob → AccountPermissionSyncJob, UserPermissionSyncJobStatus → AccountPermissionSyncJobStatus; updates composite keys and foreign keys from userId to accountId; adds accessibleRepos, permissionSyncJobs, and permissionSyncedAt to Account model; removes accessibleRepos from User model; renames Repo.permittedUsers to Repo.permittedAccounts.
Web Prisma Extension
packages/web/src/prisma.ts
Changes userScopedPrismaClientExtension parameter from single userId to accountIds array; updates permission check logic from permittedUsers with userId to permittedAccounts with accountId.
Web Authentication
packages/web/src/withAuthV2.ts
Adds accounts inclusion in Prisma queries for both session and API key authentication paths; derives accountIds from user.accounts and passes to userScopedPrismaClientExtension.

Sequence Diagram(s)

sequenceDiagram
    participant Scheduler as Permission Scheduler
    participant Syncer as AccountPermissionSyncer
    participant DB as Database
    participant GitLab/GitHub as Git Provider
    
    Note over Scheduler,DB: New Account-Based Permission Sync
    Scheduler->>Syncer: schedulePermissionSync(accounts)
    Syncer->>DB: Create AccountPermissionSyncJob entries
    Syncer->>Syncer: startScheduler()
    
    loop For Each AccountPermissionSyncJob
        Syncer->>DB: Load account with user data
        Syncer->>Syncer: Group repos by account.provider
        Syncer->>GitLab/GitHub: Fetch permissions for account
        Syncer->>DB: Update account.permissionSyncedAt
        Syncer->>DB: Upsert AccountToRepoPermission records
        Syncer->>DB: Update AccountPermissionSyncJob status
    end
    
    Note over Syncer,DB: On Completion/Failure
    Syncer->>DB: Mark job as COMPLETED/FAILED
    Syncer->>Syncer: Log with account context
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

  • Database migration: Requires careful review of table restructuring, foreign key constraints, and data preservation strategy; data loss warnings for dropped columns.
  • Permission syncer logic: Complex refactoring from user-based to account-based structures with multiple conditional paths and provider-specific handling (GitHub/GitLab).
  • Schema changes: Significant model restructuring affecting relationships across Repo, User, and Account; composite key changes and field relocations require traceability.
  • Web layer integration: Cross-cutting changes from backend to frontend requiring validation that auth context and Prisma extension scoping align correctly.
  • Multiple heterogeneous changes: Mix of schema modifications, logic refactoring, and integration updates across different layers without obvious repetition patterns.

Possibly related PRs

Suggested labels

sourcebot-team

Pre-merge checks and finishing touches

✅ Passed checks (2 passed)
Check name Status Explanation
Title check ✅ Passed The title accurately summarizes the main change: refactoring the permission syncing join table from User-Repo to Account-Repo, which is the core architectural shift throughout the changeset.
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.

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

❤️ Share

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

@brendan-kellam
Copy link
Contributor Author

@coderabbitai review

@coderabbitai
Copy link

coderabbitai bot commented Nov 5, 2025

✅ Actions performed

Review triggered.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

Copy link

@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: 1

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 26ec7af and 0a33cc9.

📒 Files selected for processing (8)
  • CHANGELOG.md (1 hunks)
  • packages/backend/src/ee/accountPermissionSyncer.ts (8 hunks)
  • packages/backend/src/ee/repoPermissionSyncer.ts (4 hunks)
  • packages/backend/src/index.ts (4 hunks)
  • packages/db/prisma/migrations/20251105021913_move_permission_syncing_to_account_level/migration.sql (1 hunks)
  • packages/db/prisma/schema.prisma (3 hunks)
  • packages/web/src/prisma.ts (2 hunks)
  • packages/web/src/withAuthV2.ts (3 hunks)
🧰 Additional context used
📓 Path-based instructions (1)
**/*

📄 CodeRabbit inference engine (.cursor/rules/style.mdc)

Filenames should always be camelCase. Exception: if there are filenames in the same directory with a format other than camelCase, use that format to keep things consistent.

Files:

  • packages/db/prisma/migrations/20251105021913_move_permission_syncing_to_account_level/migration.sql
  • packages/backend/src/ee/repoPermissionSyncer.ts
  • packages/web/src/prisma.ts
  • packages/backend/src/ee/accountPermissionSyncer.ts
  • packages/backend/src/index.ts
  • packages/db/prisma/schema.prisma
  • CHANGELOG.md
  • packages/web/src/withAuthV2.ts
🪛 GitHub Actions: Test Web
packages/web/src/withAuthV2.ts

[error] 91-91: TypeError: Cannot read properties of undefined (reading 'map') at Module.getAuthContext (src/withAuthV2.ts:91). This occurs during test run of 'yarn workspace @sourcebot/web test' with 18 failing tests in withAuthV2.*

🪛 GitHub Check: build
packages/web/src/withAuthV2.ts

[failure] 91-91: src/withAuthV2.test.ts > withOptionalAuthV2 > should call the callback with the auth context object if a valid session is present and the user is a member of the organization
TypeError: Cannot read properties of undefined (reading 'map')
❯ getAuthContext src/withAuthV2.ts:91:30
❯ Module.withOptionalAuthV2 src/withAuthV2.ts:44:25
❯ src/withAuthV2.test.ts:483:24


[failure] 91-91: src/withAuthV2.test.ts > withAuthV2 > should return a service error if the user is not a member of the organization (guest role)
TypeError: Cannot read properties of undefined (reading 'map')
❯ getAuthContext src/withAuthV2.ts:91:30
❯ Module.withAuthV2 src/withAuthV2.ts:28:25
❯ src/withAuthV2.test.ts:458:24


[failure] 91-91: src/withAuthV2.test.ts > withAuthV2 > should return a service error if the user is a guest of the organization
TypeError: Cannot read properties of undefined (reading 'map')
❯ getAuthContext src/withAuthV2.ts:91:30
❯ Module.withAuthV2 src/withAuthV2.ts:28:25
❯ src/withAuthV2.test.ts:439:24


[failure] 91-91: src/withAuthV2.test.ts > withAuthV2 > should call the callback with the auth context object if a valid session is present and the user is a member of the organization with OWNER role (api key)
TypeError: Cannot read properties of undefined (reading 'map')
❯ getAuthContext src/withAuthV2.ts:91:30
❯ Module.withAuthV2 src/withAuthV2.ts:28:25
❯ src/withAuthV2.test.ts:386:24


[failure] 91-91: src/withAuthV2.test.ts > withAuthV2 > should call the callback with the auth context object if a valid session is present and the user is a member of the organization (api key)
TypeError: Cannot read properties of undefined (reading 'map')
❯ getAuthContext src/withAuthV2.ts:91:30
❯ Module.withAuthV2 src/withAuthV2.ts:28:25
❯ src/withAuthV2.test.ts:351:24


[failure] 91-91: src/withAuthV2.test.ts > withAuthV2 > should call the callback with the auth context object if a valid session is present and the user is a member of the organization with OWNER role
TypeError: Cannot read properties of undefined (reading 'map')
❯ getAuthContext src/withAuthV2.ts:91:30
❯ Module.withAuthV2 src/withAuthV2.ts:28:25
❯ src/withAuthV2.test.ts:316:24


[failure] 91-91: src/withAuthV2.test.ts > withAuthV2 > should call the callback with the auth context object if a valid session is present and the user is a member of the organization
TypeError: Cannot read properties of undefined (reading 'map')
❯ getAuthContext src/withAuthV2.ts:91:30
❯ Module.withAuthV2 src/withAuthV2.ts:28:25
❯ src/withAuthV2.test.ts:286:24


[failure] 91-91: src/withAuthV2.test.ts > getAuthContext > should return a auth context object if a valid session is present and the user is not a member of the organization. The role should be GUEST.
TypeError: Cannot read properties of undefined (reading 'map')
❯ Module.getAuthContext src/withAuthV2.ts:91:30
❯ src/withAuthV2.test.ts:237:29


[failure] 91-91: src/withAuthV2.test.ts > getAuthContext > should return a auth context object if a valid session is present and the user is a member of the organization with OWNER role
TypeError: Cannot read properties of undefined (reading 'map')
❯ Module.getAuthContext src/withAuthV2.ts:91:30
❯ src/withAuthV2.test.ts:212:29


[failure] 91-91: src/withAuthV2.test.ts > getAuthContext > should return a auth context object if a valid session is present and the user is a member of the organization
TypeError: Cannot read properties of undefined (reading 'map')
❯ Module.getAuthContext src/withAuthV2.ts:91:30
❯ src/withAuthV2.test.ts:182:29

⏰ 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). (1)
  • GitHub Check: build
🔇 Additional comments (8)
packages/db/prisma/schema.prisma (1)

62-62: LGTM! Schema migration is well-structured.

The refactoring from user-level to account-level permission tracking is implemented correctly:

  • Relationships properly updated (permittedUsers → permittedAccounts)
  • Foreign keys with appropriate cascade rules
  • Composite primary key on AccountToRepoPermission ensures uniqueness

Also applies to: 365-395, 412-416

CHANGELOG.md (1)

21-26: LGTM! Documentation accurately describes the change.

The CHANGELOG entry clearly documents the internal representation change from user-based to account-based permissions.

packages/backend/src/index.ts (1)

14-14: LGTM! Syncer references updated consistently.

All references from UserPermissionSyncer to AccountPermissionSyncer are properly updated across import, instantiation, and lifecycle methods.

Also applies to: 55-55, 68-68, 84-84

packages/web/src/prisma.ts (1)

23-64: LGTM! Prisma extension correctly updated for account-based scoping.

The function signature and query logic are properly updated to:

  • Accept an array of accountIds instead of a single userId
  • Use the in operator for filtering against multiple accounts
  • Reference permittedAccounts instead of permittedUsers
packages/backend/src/ee/repoPermissionSyncer.ts (1)

171-247: LGTM! Repo permission syncer correctly refactored for accounts.

The changes properly:

  • Map collaborators/members to account IDs instead of user IDs
  • Update the relation from permittedUsers to permittedAccounts
  • Use accountId in the accountToRepoPermission records
packages/db/prisma/migrations/20251105021913_move_permission_syncing_to_account_level/migration.sql (1)

1-65: Verify migration and data loss implications.

The migration correctly implements the account-level permission model, but note:

  1. Old data in UserPermissionSyncJob and UserToRepoPermission will be lost
  2. PR description indicates "TODO: test migration path"

Ensure the migration path is tested before merging, especially if there's existing production data.

Consider:

  • Testing the migration on a copy of production data
  • Documenting the data loss implications for users
  • Potentially creating a data migration script to preserve historical permission records if needed
packages/backend/src/ee/accountPermissionSyncer.ts (1)

2-2: LGTM! Account permission syncer is comprehensively refactored.

The refactoring from UserPermissionSyncer to AccountPermissionSyncer is thorough and correct:

  • Database operations properly updated to use account entities
  • Provider-based filtering aligns with account model
  • Logging context updated to account-centric messages
  • Transaction logic correctly maps repos to accounts

Also applies to: 17-25, 54-98, 110-130, 136-237, 240-303

packages/web/src/withAuthV2.ts (1)

110-114: No issues found - all user queries correctly include accounts relation.

Verification confirms both user.findUnique calls in the file (lines 107-114 and 128-135) include accounts: true. The accounts relation is consistently loaded across all code paths that fetch users, and line 91 demonstrates the relation is actively used in the code.

msukkari
msukkari previously approved these changes Nov 5, 2025
@brendan-kellam brendan-kellam merged commit 5fde901 into main Nov 5, 2025
9 checks passed
@brendan-kellam brendan-kellam deleted the bkellam/account_syncing branch November 5, 2025 04:12
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.

3 participants