Skip to content

Commit 1ab7d19

Browse files
feat: implement automatic repository discovery and sync
1 parent 4432ee0 commit 1ab7d19

3 files changed

Lines changed: 112 additions & 2 deletions

File tree

Lines changed: 92 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,92 @@
1+
import { Injectable, Logger, OnApplicationBootstrap } from '@nestjs/common';
2+
import { GitHubService } from '../github';
3+
import { PrismaService } from '../../prisma';
4+
import { SyncService } from './sync.service';
5+
import { ConfigService } from '@nestjs/config';
6+
7+
@Injectable()
8+
export class DiscoveryService implements OnApplicationBootstrap {
9+
private readonly logger = new Logger(DiscoveryService.name);
10+
private readonly org: string;
11+
12+
constructor(
13+
private readonly github: GitHubService,
14+
private readonly prisma: PrismaService,
15+
private readonly sync: SyncService,
16+
private readonly config: ConfigService,
17+
) {
18+
this.org = this.config.get<string>('GITHUB_ORG', 'c2siorg');
19+
}
20+
21+
onApplicationBootstrap() {
22+
this.logger.log(
23+
'Application started — triggering initial repository discovery...',
24+
);
25+
// Run discovery in background so it doesn't block startup
26+
void this.discoverAndEnqueue();
27+
}
28+
29+
/**
30+
* Fetch all repositories from the org and ensure they exist in the database.
31+
* Then enqueue sync jobs for them.
32+
*/
33+
async discoverAndEnqueue(): Promise<void> {
34+
try {
35+
this.logger.log(`Starting repository discovery for org: "${this.org}"`);
36+
37+
const remoteRepos = await this.github.listOrgRepos();
38+
this.logger.log(
39+
`Found ${remoteRepos.length} public repositories in "${this.org}"`,
40+
);
41+
42+
let newCount = 0;
43+
let updatedCount = 0;
44+
45+
for (const repo of remoteRepos) {
46+
// We use the GitHub database ID as our primary key (BigInt)
47+
const dbRepo = await this.prisma.repository.upsert({
48+
where: { id: BigInt(repo.id) },
49+
create: {
50+
id: BigInt(repo.id),
51+
name: repo.name,
52+
fullName: repo.full_name,
53+
htmlUrl: `https://github.com/${repo.full_name}`,
54+
starsCount: 0, // Will be updated by full sync
55+
forksCount: 0,
56+
openIssuesCount: 0,
57+
isActive: true,
58+
syncedAt: new Date(0), // Mark as never fully synced
59+
githubCreatedAt: new Date(), // Placeholder, updated by full sync
60+
defaultBranch: 'main',
61+
},
62+
update: {
63+
isActive: true, // Ensure it stays active if it exists
64+
},
65+
});
66+
67+
// If it's never been synced or was synced very long ago, enqueue it
68+
const neverSynced = dbRepo.syncedAt.getTime() === 0;
69+
70+
if (neverSynced) {
71+
await this.sync.enqueueRepoSync(
72+
repo.full_name,
73+
'discovery-initial',
74+
5,
75+
);
76+
newCount++;
77+
} else {
78+
updatedCount++;
79+
}
80+
}
81+
82+
this.logger.log(
83+
`Discovery complete: ${newCount} new repos enqueued, ${updatedCount} existing repos verified.`,
84+
);
85+
} catch (err) {
86+
this.logger.error(
87+
`Repository discovery failed: ${(err as Error).message}`,
88+
(err as Error).stack,
89+
);
90+
}
91+
}
92+
}

backend/src/modules/sync/sync.module.ts

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import { SyncService, SYNC_QUEUE_NAME } from './sync.service';
77
import { SyncProcessor } from './sync.processor';
88
import { ProcessingService } from './processing.service';
99
import { SyncScheduler } from './sync.scheduler';
10+
import { DiscoveryService } from './discovery.service';
1011

1112
@Module({
1213
imports: [
@@ -21,7 +22,13 @@ import { SyncScheduler } from './sync.scheduler';
2122
GitHubModule,
2223
AnalyzerModule,
2324
],
24-
providers: [SyncService, SyncProcessor, ProcessingService, SyncScheduler],
25-
exports: [SyncService],
25+
providers: [
26+
SyncService,
27+
SyncProcessor,
28+
ProcessingService,
29+
SyncScheduler,
30+
DiscoveryService,
31+
],
32+
exports: [SyncService, DiscoveryService],
2633
})
2734
export class SyncModule {}

backend/src/modules/sync/sync.scheduler.ts

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import { Injectable, Logger } from '@nestjs/common';
22
import { Cron } from '@nestjs/schedule';
33
import { SyncService } from './sync.service';
44
import { GitHubService } from '../github';
5+
import { DiscoveryService } from './discovery.service';
56

67
@Injectable()
78
export class SyncScheduler {
@@ -10,8 +11,18 @@ export class SyncScheduler {
1011
constructor(
1112
private readonly syncService: SyncService,
1213
private readonly githubService: GitHubService,
14+
private readonly discoveryService: DiscoveryService,
1315
) {}
1416

17+
/**
18+
* Every 12 hours: Discover new repositories in the organization.
19+
*/
20+
@Cron('0 */12 * * *')
21+
async discoverRepos(): Promise<void> {
22+
this.logger.log('Starting scheduled repository discovery');
23+
await this.discoveryService.discoverAndEnqueue();
24+
}
25+
1526
/**
1627
* Every 6 hours: sync repos whose syncedAt is older than 6 hours.
1728
*/

0 commit comments

Comments
 (0)