import { Gitlab } from '@gitbeaker/rest';
import { GitLabActivity } from '../types/index.js';
import { startOfDay, endOfDay } from 'date-fns';
export class GitLabIntegration {
private client: InstanceType<typeof Gitlab> | null = null;
private userId: number | null = null;
async initialize(token: string, gitlabUrl: string = 'https://gitlab.com'): Promise<void> {
this.client = new Gitlab({
token,
host: gitlabUrl,
});
// Get current user ID
const user = await this.client.Users.showCurrentUser();
this.userId = user.id;
}
async getActivityForDate(date: Date): Promise<GitLabActivity> {
if (!this.client || !this.userId) {
throw new Error('GitLab client not initialized');
}
const dayStart = startOfDay(date);
const dayEnd = endOfDay(date);
const activity: GitLabActivity = {
date,
commits: [],
mergeRequests: [],
issues: [],
};
try {
// Get user's events for the day
// GitLab API: after means >= date, before means <= date
const events = await this.client.Events.all({
after: dayStart.toISOString().split('T')[0],
});
for (const event of events) {
if (event.author_id !== this.userId) continue;
// Filter events to only include those within the target day
const eventDate = new Date(event.created_at as string);
if (eventDate < dayStart || eventDate > dayEnd) continue;
const projectName = event.project_id && typeof event.project_id === 'number'
? await this.getProjectName(event.project_id)
: 'Unknown';
switch (event.action_name) {
case 'pushed to':
case 'pushed new':
if (event.push_data && typeof event.push_data === 'object') {
const pushData = event.push_data as any;
activity.commits.push({
message: String(pushData.commit_title || 'Commit'),
project: projectName,
branch: String(pushData.ref || 'unknown'),
});
}
break;
case 'opened':
if (event.target_type === 'MergeRequest') {
activity.mergeRequests.push({
action: 'created',
title: String(event.target_title || 'MR'),
project: projectName,
});
} else if (event.target_type === 'Issue') {
activity.issues.push({
action: 'status_changed',
title: String(event.target_title || 'Issue'),
project: projectName,
details: 'opened',
});
}
break;
case 'commented on':
if (event.target_type === 'MergeRequest') {
activity.mergeRequests.push({
action: 'commented',
title: String(event.target_title || 'MR'),
project: projectName,
});
} else if (event.target_type === 'Issue') {
activity.issues.push({
action: 'commented',
title: String(event.target_title || 'Issue'),
project: projectName,
});
}
break;
case 'accepted':
if (event.target_type === 'MergeRequest') {
activity.mergeRequests.push({
action: 'approved',
title: String(event.target_title || 'MR'),
project: projectName,
});
}
break;
case 'approved':
if (event.target_type === 'MergeRequest') {
activity.mergeRequests.push({
action: 'approved',
title: String(event.target_title || 'MR'),
project: projectName,
});
}
break;
}
}
// Also fetch commits directly for more detailed information
await this.fetchCommitsForDate(date, activity);
// Fetch MR reviews
await this.fetchMergeRequestActivity(date, activity);
} catch (error) {
console.error('Error fetching GitLab activity:', error);
}
return activity;
}
private async fetchCommitsForDate(date: Date, activity: GitLabActivity): Promise<void> {
if (!this.client || !this.userId) return;
try {
// Get all projects the user has access to
const projects = await this.client.Projects.all({
membership: true,
simple: true,
perPage: 100,
});
const dayStart = startOfDay(date);
const dayEnd = endOfDay(date);
for (const project of projects) {
try {
const commits = await this.client.Commits.all(project.id, {
since: dayStart.toISOString(),
until: dayEnd.toISOString(),
author: await this.getCurrentUserEmail(),
});
for (const commit of commits) {
// Avoid duplicates
const exists = activity.commits.some(c => c.message === commit.title);
if (!exists) {
activity.commits.push({
message: commit.title,
project: project.name,
branch: 'main', // GitLab API doesn't always provide branch in commit list
});
}
}
} catch (error) {
// Skip projects we can't access
continue;
}
}
} catch (error) {
console.error('Error fetching commits:', error);
}
}
private async fetchMergeRequestActivity(date: Date, activity: GitLabActivity): Promise<void> {
if (!this.client || !this.userId) return;
try {
const dayStart = startOfDay(date);
const dayEnd = endOfDay(date);
// Get MRs created by user
const createdMRs = await this.client.MergeRequests.all({
authorId: this.userId,
createdAfter: dayStart.toISOString(),
createdBefore: dayEnd.toISOString(),
scope: 'all',
});
for (const mr of createdMRs) {
const projectId = typeof mr.project_id === 'number' ? mr.project_id : 0;
const projectName = await this.getProjectName(projectId);
activity.mergeRequests.push({
action: 'created',
title: mr.title,
project: projectName,
});
}
// Get MRs where user is reviewer
const reviewMRs = await this.client.MergeRequests.all({
reviewerId: this.userId,
updatedAfter: dayStart.toISOString(),
updatedBefore: dayEnd.toISOString(),
scope: 'all',
});
for (const mr of reviewMRs) {
const projectId = typeof mr.project_id === 'number' ? mr.project_id : 0;
const projectName = await this.getProjectName(projectId);
// Check if already added
const exists = activity.mergeRequests.some(
m => m.title === mr.title && m.project === projectName
);
if (!exists) {
activity.mergeRequests.push({
action: 'reviewed',
title: mr.title,
project: projectName,
});
}
}
} catch (error) {
console.error('Error fetching MR activity:', error);
}
}
private projectNameCache = new Map<number, string>();
private async getProjectName(projectId: number): Promise<string> {
if (this.projectNameCache.has(projectId)) {
return this.projectNameCache.get(projectId)!;
}
try {
const project = await this.client!.Projects.show(projectId);
this.projectNameCache.set(projectId, project.name);
return project.name;
} catch {
return `Project ${projectId}`;
}
}
private async getCurrentUserEmail(): Promise<string> {
if (!this.client) throw new Error('Client not initialized');
const user = await this.client.Users.showCurrentUser();
return user.email;
}
}