Skip to content

Commit 46084c3

Browse files
feedback
1 parent 0209342 commit 46084c3

File tree

15 files changed

+403
-370
lines changed

15 files changed

+403
-370
lines changed
Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
11

22
<Warning>
3-
This is an experimental feature. Certain functionality may be buggy or incomplete, and breaking changes may ship in non-major releases. Have feedback? Submit a [issue](https://github.com/sourcebot-dev/sourcebot/issues) on GitHub.
3+
This is an experimental feature. Certain functionality may be incomplete and breaking changes may ship in non-major releases. Have feedback? Submit a [issue](https://github.com/sourcebot-dev/sourcebot/issues) on GitHub.
44
</Warning>

packages/backend/src/connectionManager.ts

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ export class ConnectionManager {
2828
private worker: Worker;
2929
private queue: Queue<JobPayload>;
3030
private logger = createLogger('connection-manager');
31+
private interval?: NodeJS.Timeout;
3132

3233
constructor(
3334
private db: PrismaClient,
@@ -71,7 +72,7 @@ export class ConnectionManager {
7172

7273
public startScheduler() {
7374
this.logger.debug('Starting scheduler');
74-
return setInterval(async () => {
75+
this.interval = setInterval(async () => {
7576
const thresholdDate = new Date(Date.now() - this.settings.resyncConnectionIntervalMs);
7677
const connections = await this.db.connection.findMany({
7778
where: {
@@ -364,6 +365,9 @@ export class ConnectionManager {
364365
}
365366

366367
public dispose() {
368+
if (this.interval) {
369+
clearInterval(this.interval);
370+
}
367371
this.worker.close();
368372
this.queue.close();
369373
}

packages/backend/src/ee/repoPermissionSyncer.ts

Lines changed: 20 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,14 @@
11
import * as Sentry from "@sentry/node";
22
import { PrismaClient, Repo, RepoPermissionSyncJobStatus } from "@sourcebot/db";
33
import { createLogger } from "@sourcebot/logger";
4-
import { BitbucketConnectionConfig } from "@sourcebot/schemas/v3/bitbucket.type";
5-
import { GiteaConnectionConfig } from "@sourcebot/schemas/v3/gitea.type";
6-
import { GithubConnectionConfig } from "@sourcebot/schemas/v3/github.type";
7-
import { GitlabConnectionConfig } from "@sourcebot/schemas/v3/gitlab.type";
4+
import { hasEntitlement } from "@sourcebot/shared";
85
import { Job, Queue, Worker } from 'bullmq';
96
import { Redis } from 'ioredis';
10-
import { env } from "../env.js";
11-
import { createOctokitFromConfig, getUserIdsWithReadAccessToRepo } from "../github.js";
12-
import { RepoWithConnections, Settings } from "../types.js";
137
import { PERMISSION_SYNC_SUPPORTED_CODE_HOST_TYPES } from "../constants.js";
14-
import { hasEntitlement } from "@sourcebot/shared";
8+
import { env } from "../env.js";
9+
import { createOctokitFromToken, getRepoCollaborators } from "../github.js";
10+
import { Settings } from "../types.js";
11+
import { getAuthCredentialsForRepo } from "../utils.js";
1512

1613
type RepoPermissionSyncJob = {
1714
jobId: string;
@@ -25,6 +22,7 @@ const logger = createLogger('repo-permission-syncer');
2522
export class RepoPermissionSyncer {
2623
private queue: Queue<RepoPermissionSyncJob>;
2724
private worker: Worker<RepoPermissionSyncJob>;
25+
private interval?: NodeJS.Timeout;
2826

2927
constructor(
3028
private db: PrismaClient,
@@ -49,7 +47,7 @@ export class RepoPermissionSyncer {
4947

5048
logger.debug('Starting scheduler');
5149

52-
return setInterval(async () => {
50+
this.interval = setInterval(async () => {
5351
// @todo: make this configurable
5452
const thresholdDate = new Date(Date.now() - this.settings.experiment_repoDrivenPermissionSyncIntervalMs);
5553

@@ -104,6 +102,9 @@ export class RepoPermissionSyncer {
104102
}
105103

106104
public dispose() {
105+
if (this.interval) {
106+
clearInterval(this.interval);
107+
}
107108
this.worker.close();
108109
this.queue.close();
109110
}
@@ -157,15 +158,17 @@ export class RepoPermissionSyncer {
157158

158159
logger.info(`Syncing permissions for repo ${repo.displayName}...`);
159160

160-
const connection = getFirstConnectionWithToken(repo);
161-
if (!connection) {
162-
throw new Error(`No connection with token found for repo ${id}`);
161+
const credentials = await getAuthCredentialsForRepo(repo, this.db, logger);
162+
if (!credentials) {
163+
throw new Error(`No credentials found for repo ${id}`);
163164
}
164165

165166
const userIds = await (async () => {
166-
if (connection.connectionType === 'github') {
167-
const config = connection.config as unknown as GithubConnectionConfig;
168-
const { octokit } = await createOctokitFromConfig(config, repo.orgId, this.db);
167+
if (repo.external_codeHostType === 'github') {
168+
const { octokit } = await createOctokitFromToken({
169+
token: credentials.token,
170+
url: credentials.hostUrl,
171+
});
169172

170173
// @note: this is a bit of a hack since the displayName _might_ not be set..
171174
// however, this property was introduced many versions ago and _should_ be set
@@ -176,7 +179,8 @@ export class RepoPermissionSyncer {
176179

177180
const [owner, repoName] = repo.displayName.split('/');
178181

179-
const githubUserIds = await getUserIdsWithReadAccessToRepo(owner, repoName, octokit);
182+
const collaborators = await getRepoCollaborators(owner, repoName, octokit);
183+
const githubUserIds = collaborators.map(collaborator => collaborator.id.toString());
180184

181185
const accounts = await this.db.account.findMany({
182186
where: {
@@ -268,34 +272,3 @@ export class RepoPermissionSyncer {
268272
}
269273
}
270274
}
271-
272-
const getFirstConnectionWithToken = (repo: RepoWithConnections) => {
273-
for (const { connection } of repo.connections) {
274-
if (connection.connectionType === 'github') {
275-
const config = connection.config as unknown as GithubConnectionConfig;
276-
if (config.token) {
277-
return connection;
278-
}
279-
}
280-
if (connection.connectionType === 'gitlab') {
281-
const config = connection.config as unknown as GitlabConnectionConfig;
282-
if (config.token) {
283-
return connection;
284-
}
285-
}
286-
if (connection.connectionType === 'gitea') {
287-
const config = connection.config as unknown as GiteaConnectionConfig;
288-
if (config.token) {
289-
return connection;
290-
}
291-
}
292-
if (connection.connectionType === 'bitbucket') {
293-
const config = connection.config as unknown as BitbucketConnectionConfig;
294-
if (config.token) {
295-
return connection;
296-
}
297-
}
298-
}
299-
300-
return undefined;
301-
}

packages/backend/src/ee/userPermissionSyncer.ts

Lines changed: 45 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,11 @@
1-
import { Octokit } from "@octokit/rest";
21
import * as Sentry from "@sentry/node";
32
import { PrismaClient, User, UserPermissionSyncJobStatus } from "@sourcebot/db";
43
import { createLogger } from "@sourcebot/logger";
54
import { Job, Queue, Worker } from "bullmq";
65
import { Redis } from "ioredis";
76
import { PERMISSION_SYNC_SUPPORTED_CODE_HOST_TYPES } from "../constants.js";
87
import { env } from "../env.js";
9-
import { createOctokitFromOAuthToken, getReposForAuthenticatedUser } from "../github.js";
8+
import { createOctokitFromToken, getReposForAuthenticatedUser } from "../github.js";
109
import { hasEntitlement } from "@sourcebot/shared";
1110
import { Settings } from "../types.js";
1211

@@ -22,6 +21,7 @@ type UserPermissionSyncJob = {
2221
export class UserPermissionSyncer {
2322
private queue: Queue<UserPermissionSyncJob>;
2423
private worker: Worker<UserPermissionSyncJob>;
24+
private interval?: NodeJS.Timeout;
2525

2626
constructor(
2727
private db: PrismaClient,
@@ -46,7 +46,7 @@ export class UserPermissionSyncer {
4646

4747
logger.debug('Starting scheduler');
4848

49-
return setInterval(async () => {
49+
this.interval = setInterval(async () => {
5050
const thresholdDate = new Date(Date.now() - this.settings.experiment_userDrivenPermissionSyncIntervalMs);
5151

5252
const users = await this.db.user.findMany({
@@ -102,6 +102,9 @@ export class UserPermissionSyncer {
102102
}
103103

104104
public dispose() {
105+
if (this.interval) {
106+
clearInterval(this.interval);
107+
}
105108
this.worker.close();
106109
this.queue.close();
107110
}
@@ -151,50 +154,61 @@ export class UserPermissionSyncer {
151154

152155
logger.info(`Syncing permissions for user ${user.email}...`);
153156

154-
for (const account of user.accounts) {
155-
const repoIds = await (async () => {
157+
// Get a list of all repos that the user has access to from all connected accounts.
158+
const repoIds = await (async () => {
159+
const aggregatedRepoIds: Set<number> = new Set();
160+
161+
for (const account of user.accounts) {
156162
if (account.provider === 'github') {
157-
const octokit = await createOctokitFromOAuthToken(account.access_token);
163+
if (!account.access_token) {
164+
throw new Error(`User '${user.email}' does not have an GitHub OAuth access token associated with their GitHub account.`);
165+
}
166+
167+
const { octokit } = await createOctokitFromToken({
168+
token: account.access_token,
169+
url: env.AUTH_EE_GITHUB_BASE_URL,
170+
});
158171
// @note: we only care about the private repos since we don't need to build a mapping
159172
// for public repos.
160173
// @see: packages/web/src/prisma.ts
161-
const repoIds = await getReposForAuthenticatedUser(/* visibility = */ 'private', octokit);
174+
const githubRepos = await getReposForAuthenticatedUser(/* visibility = */ 'private', octokit);
175+
const gitHubRepoIds = githubRepos.map(repo => repo.id.toString());
162176

163177
const repos = await this.db.repo.findMany({
164178
where: {
165179
external_codeHostType: 'github',
166180
external_id: {
167-
in: repoIds,
181+
in: gitHubRepoIds,
168182
}
169183
}
170184
});
171185

172-
return repos.map(repo => repo.id);
186+
repos.forEach(repo => aggregatedRepoIds.add(repo.id));
173187
}
188+
}
174189

175-
return [];
176-
})();
177-
190+
return Array.from(aggregatedRepoIds);
191+
})();
178192

179-
await this.db.$transaction([
180-
this.db.user.update({
181-
where: {
182-
id: user.id,
183-
},
184-
data: {
185-
accessibleRepos: {
186-
deleteMany: {},
187-
}
193+
await this.db.$transaction([
194+
this.db.user.update({
195+
where: {
196+
id: user.id,
197+
},
198+
data: {
199+
accessibleRepos: {
200+
deleteMany: {},
188201
}
189-
}),
190-
this.db.userToRepoPermission.createMany({
191-
data: repoIds.map(repoId => ({
192-
userId: user.id,
193-
repoId,
194-
}))
195-
})
196-
]);
197-
}
202+
}
203+
}),
204+
this.db.userToRepoPermission.createMany({
205+
data: repoIds.map(repoId => ({
206+
userId: user.id,
207+
repoId,
208+
})),
209+
skipDuplicates: true,
210+
})
211+
]);
198212
}
199213

200214
private async onJobCompleted(job: Job<UserPermissionSyncJob>) {
@@ -226,7 +240,7 @@ export class UserPermissionSyncer {
226240
queue: QUEUE_NAME,
227241
}
228242
});
229-
243+
230244
const errorMessage = (email: string) => `User permission sync job failed for user ${email}: ${err.message}`;
231245

232246
if (job) {

packages/backend/src/git.ts

Lines changed: 20 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -5,9 +5,15 @@ import { env } from './env.js';
55
type onProgressFn = (event: SimpleGitProgressEvent) => void;
66

77
export const cloneRepository = async (
8-
remoteUrl: URL,
9-
path: string,
10-
onProgress?: onProgressFn
8+
{
9+
cloneUrl,
10+
path,
11+
onProgress,
12+
}: {
13+
cloneUrl: string,
14+
path: string,
15+
onProgress?: onProgressFn
16+
}
1117
) => {
1218
try {
1319
await mkdir(path, { recursive: true });
@@ -19,7 +25,7 @@ export const cloneRepository = async (
1925
})
2026

2127
await git.clone(
22-
remoteUrl.toString(),
28+
cloneUrl,
2329
path,
2430
[
2531
"--bare",
@@ -42,9 +48,15 @@ export const cloneRepository = async (
4248
};
4349

4450
export const fetchRepository = async (
45-
remoteUrl: URL,
46-
path: string,
47-
onProgress?: onProgressFn
51+
{
52+
cloneUrl,
53+
path,
54+
onProgress,
55+
}: {
56+
cloneUrl: string,
57+
path: string,
58+
onProgress?: onProgressFn
59+
}
4860
) => {
4961
try {
5062
const git = simpleGit({
@@ -54,7 +66,7 @@ export const fetchRepository = async (
5466
})
5567

5668
await git.fetch([
57-
remoteUrl.toString(),
69+
cloneUrl,
5870
"+refs/heads/*:refs/heads/*",
5971
"--prune",
6072
"--progress"

0 commit comments

Comments
 (0)