Skip to content

feature: repo sync to gh account connected#1

Open
20407002036 wants to merge 1 commit into
SpaceyaTech:devfrom
20407002036:feature/repo_sync_for_GH_accounts
Open

feature: repo sync to gh account connected#1
20407002036 wants to merge 1 commit into
SpaceyaTech:devfrom
20407002036:feature/repo_sync_for_GH_accounts

Conversation

@20407002036

Copy link
Copy Markdown

🚀 Feature: GitHub Repository Sync (SPA-13)

This PR implements the backend synchronization flow to fetch and store a user's GitHub repositories. This allows users to import their projects into Colabs and selectively mark public repositories for collaboration.

🛠️ Key Changes

🗄️ Database Schema

  • User Model: Added githubAccessToken, githubRefreshToken, and lastSyncedAt to support authenticated API requests and enforce sync rate limits.
  • Project Model: Added isCollaborative boolean flag (default: false) to control public visibility in the project gallery.

🔐 Authentication

  • Updated the GitHub OAuth strategy (auth.strategy.ts) to capture and persist access and refresh tokens during the user login/upsert process.

🌐 GitHub API Integration (src/lib/github.ts)

  • Dynamic Client: Refactored the API client to support per-user authorization tokens instead of relying solely on a global system token.
  • Pagination: Implemented robust link-based pagination in fetchUserRepositories to ensure all repositories are fetched regardless of count.

🔄 Sync Logic & API

  • New Endpoint: POST /api/projects/sync-repos — allows users to manually trigger a repository sync.
  • Rate Limiting: Implemented a user-based cooldown period (15 minutes) using the lastSyncedAt timestamp to prevent API abuse.
  • Idempotent Upserts: Used a "Fetch $\to$ Map $\to$ Upsert" pattern. Repositories are upserted based on their githubRepoUrl to prevent duplicates while keeping metadata (stars, forks, etc.) up to date.
  • Visibility Control: Updated listProjects to filter the public gallery by isCollaborative: true, while ensuring users can still see all their own projects regardless of the flag.

🧪 Verification Plan (For Reviewer)

  • Auth Flow: Log in via GitHub and verify that the access token is correctly stored in the User table.
  • Sync Trigger: Call POST /api/projects/sync-repos and verify that repositories are created/updated in the Project table.
  • Rate Limit: Attempt to trigger the sync twice within 15 minutes; verify the second request returns a 429 Too Many Requests error.
  • Public Visibility:
    • Verify that a project with isCollaborative: false is hidden from the public GET /api/projects list.
    • Verify that setting isCollaborative: true makes it visible.
  • Pagination: Test with an account having >100 repositories to ensure the pagination logic captures everything.

⚠️ Deployment Note

This PR includes a database migration. Please ensure npx prisma migrate dev is run to apply the new columns to the User and Project tables.

Comment thread prisma/schema.prisma
Comment on lines +51 to +75
githubAccessToken String?
githubRefreshToken String?
lastSyncedAt DateTime?
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt

projects Project[]
issueClaims IssueClaim[]
gigsPosted Gig[] @relation("GigClient")
proposals Proposal[]
teamsOwned Team[] @relation("TeamOwner")
teamMemberships TeamMember[]
}

model Project {
id String @id @default(cuid())
githubRepoUrl String @unique
name String
description String?
language String?
stars Int @default(0)
forks Int @default(0)
topics String[]
logoUrl String?
isCollaborative Boolean @default(false)

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Schema adds githubAccessToken, githubRefreshToken, lastSyncedAt, isCollaborative, but no migration SQL is included in the PR. PR description says to run prisma migrate dev, but reviewers/CI cannot apply changes reliably.

Suggested Fix: Commit the generated migration:

npx prisma migrate dev --name add_github_sync_fields

Comment on lines +135 to +144
return prisma.project.upsert({
where: { githubRepoUrl },
update: {
name: repo.full_name,
description: repo.description,
language: repo.language,
stars: repo.stargazers_count,
forks: repo.forks_count,
topics: repo.topics || [],
// Preserve isCollaborative flag on update

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Problem: Upsert is keyed only on githubRepoUrl. If User B already registered facebook/react, User A's sync runs the update branch and refreshes metadata on User B's project — no ownerId check.

Suggesting fix:

const existing = await prisma.project.findUnique({ where: { githubRepoUrl } });
if (existing && existing.ownerId !== userId) {
  return null; // skip repos owned by others
}
// OR upsert with where: { githubRepoUrl, ownerId: userId } if you add a composite unique

Comment on lines +16 to +21

// If not requesting own projects, only show collaborative public ones
if (!req.user || req.user.id !== req.query.ownerId) {
where.isCollaborative = true;
}

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Problem: GET /api/projects has no auth middleware, so req.user is always undefined. The filter isCollaborative: true always applies, including for owners trying to view their own repos. This contradicts the PR verification plan (“users can still see all their own projects”).

Suggesting Fix:
Add optional auth middleware, then:

const ownerId = req.query.ownerId as string | undefined;
const isOwnList = req.user && ownerId && req.user.id === ownerId;

if (!isOwnList) {
  where.isCollaborative = true;
}
if (ownerId) where.ownerId = ownerId;

Or add a dedicated authenticated route: GET /api/projects/mine.

Comment thread src/lib/github.ts
Comment on lines +51 to +60
while (hasNext) {
const { data, headers } = await client.get(`/user/repos?per_page=100&page=${page}`);
repos = [...repos, ...data];

const linkHeader = headers['link'];
hasNext = linkHeader && linkHeader.includes('rel="next"');
page++;
}
return repos;
};

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Problem: No try/catch around axios. GitHub 401 (expired token), 403 (missing scope), or rate-limit → unhandled rejection → process exit.

Also add import 'express-async-errors' in app.ts so controller errors reach errorHandler.

Comment on lines +24 to +35
githubAccessToken: _accessToken,
githubRefreshToken: _refreshToken,
},
create: {
githubId: String(profile.id),
username: profile.username,
name: profile.displayName || profile.username,
email: profile.emails?.[0]?.value,
avatarUrl: profile.photos?.[0]?.value,
githubUrl: profile.profileUrl,
githubAccessToken: _accessToken,
githubRefreshToken: _refreshToken,

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Problem: githubAccessToken stored as plain text on User. DB leak = full GitHub account access.

Fix (minimum for review): Encrypt at rest (e.g. AES with TOKEN_ENCRYPTION_KEY env var) or store in a separate secrets table. Document risk if deferring encryption to a follow-up ticket.

Comment on lines +159 to +161

await Promise.all(upserts);

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

User with 200+ repos fires 200 parallel upserts — can exhaust Prisma connection pool.

Fix: Batch in chunks (for (const chunk of chunks(repos, 20))) or use $transaction with batch size limits.

@SimonGideon SimonGideon left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Good progress on SPA-13: token persistence + repo sync endpoint is the right foundation. However, OAuth scopes, upsert ownership, and listProjects filtering need fixes before merge, and GitHub failures can still crash the server. Please address the inline comments and checklist below, then re-request review.

  • Prisma migration committed and tested
  • OAuth scopes include repo (or documented public_repo limitation)
  • Upsert ownership guard — cannot modify another user's project
  • listProjects visibility works for owners vs public gallery
  • fetchUserRepositories error handling + async error middleware
  • Endpoint to toggle isCollaborative (or updated verification plan)
  • /sync-repos route registered before /:id
  • Token encryption planned or implemented

Functional gaps vs SPA-13 & PR description

No endpoint to toggle isCollaborative
Problem: Verification plan says “setting isCollaborative: true makes it visible”, but the PR adds no PATCH endpoint. Cannot complete manual QA as written.

Fix: Add e.g. PATCH /api/projects/:id/collaboration with owner auth.

Happy 💯 to re-review once those are addressed.

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