255 lines
6.3 KiB
JavaScript
255 lines
6.3 KiB
JavaScript
/**
|
|
* Gitea Integration for AgentDash
|
|
* Provides real-time data from Gitea API
|
|
*/
|
|
|
|
const https = require('https');
|
|
const http = require('http');
|
|
|
|
class GiteaIntegration {
|
|
constructor(config = {}) {
|
|
this.baseUrl = config.baseUrl || 'https://gitea.tophermayor.com';
|
|
this.token = config.token || process.env.GITEA_TOKEN;
|
|
this.owner = config.owner || 'TopherMayor';
|
|
this.cache = new Map();
|
|
this.cacheTimeout = config.cacheTimeout || 30000; // 30 seconds
|
|
}
|
|
|
|
/**
|
|
* Make authenticated request to Gitea API
|
|
*/
|
|
async request(endpoint) {
|
|
return new Promise((resolve, reject) => {
|
|
const url = new URL(endpoint, this.baseUrl);
|
|
const client = url.protocol === 'https:' ? https : http;
|
|
|
|
const options = {
|
|
hostname: url.hostname,
|
|
port: url.port || (url.protocol === 'https:' ? 443 : 80),
|
|
path: url.pathname + url.search,
|
|
method: 'GET',
|
|
headers: {
|
|
'Authorization': `token ${this.token}`,
|
|
'Accept': 'application/json'
|
|
}
|
|
};
|
|
|
|
const req = client.request(options, (res) => {
|
|
let data = '';
|
|
res.on('data', chunk => data += chunk);
|
|
res.on('end', () => {
|
|
try {
|
|
resolve(JSON.parse(data));
|
|
} catch (e) {
|
|
reject(new Error(`Failed to parse response: ${e.message}`));
|
|
}
|
|
});
|
|
});
|
|
|
|
req.on('error', reject);
|
|
req.setTimeout(10000, () => {
|
|
req.destroy();
|
|
reject(new Error('Request timeout'));
|
|
});
|
|
req.end();
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Get cached data or fetch fresh
|
|
*/
|
|
async getCached(key, fetcher) {
|
|
const cached = this.cache.get(key);
|
|
if (cached && Date.now() - cached.timestamp < this.cacheTimeout) {
|
|
return cached.data;
|
|
}
|
|
|
|
const data = await fetcher();
|
|
this.cache.set(key, { data, timestamp: Date.now() });
|
|
return data;
|
|
}
|
|
|
|
/**
|
|
* Get all repositories for the owner
|
|
*/
|
|
async getRepos() {
|
|
return this.getCached('repos', () =>
|
|
this.request(`/api/v1/user/repos?limit=50`)
|
|
);
|
|
}
|
|
|
|
/**
|
|
* Get repository details
|
|
*/
|
|
async getRepo(repoName) {
|
|
return this.getCached(`repo:${repoName}`, () =>
|
|
this.request(`/api/v1/repos/${this.owner}/${repoName}`)
|
|
);
|
|
}
|
|
|
|
/**
|
|
* Get open pull requests for a repository
|
|
*/
|
|
async getPullRequests(repoName, state = 'open') {
|
|
return this.getCached(`prs:${repoName}:${state}`, () =>
|
|
this.request(`/api/v1/repos/${this.owner}/${repoName}/pulls?state=${state}&limit=20`)
|
|
);
|
|
}
|
|
|
|
/**
|
|
* Get issues for a repository
|
|
*/
|
|
async getIssues(repoName, state = 'open') {
|
|
return this.getCached(`issues:${repoName}:${state}`, () =>
|
|
this.request(`/api/v1/repos/${this.owner}/${repoName}/issues?state=${state}&limit=20`)
|
|
);
|
|
}
|
|
|
|
/**
|
|
* Get recent commits for a repository
|
|
*/
|
|
async getCommits(repoName, branch = 'main', limit = 10) {
|
|
return this.getCached(`commits:${repoName}:${branch}`, () =>
|
|
this.request(`/api/v1/repos/${this.owner}/${repoName}/commits?sha=${branch}&limit=${limit}`)
|
|
);
|
|
}
|
|
|
|
/**
|
|
* Get branches for a repository
|
|
*/
|
|
async getBranches(repoName) {
|
|
return this.getCached(`branches:${repoName}`, () =>
|
|
this.request(`/api/v1/repos/${this.owner}/${repoName}/branches`)
|
|
);
|
|
}
|
|
|
|
/**
|
|
* Get repository activity feed
|
|
*/
|
|
async getActivity(repoName) {
|
|
return this.getCached(`activity:${repoName}`, () =>
|
|
this.request(`/api/v1/repos/${this.owner}/${repoName}/activities/feeds?limit=20`)
|
|
);
|
|
}
|
|
|
|
/**
|
|
* Get user info
|
|
*/
|
|
async getUser() {
|
|
return this.getCached('user', () =>
|
|
this.request('/api/v1/user')
|
|
);
|
|
}
|
|
|
|
/**
|
|
* Get organization info (if applicable)
|
|
*/
|
|
async getOrgs() {
|
|
return this.getCached('orgs', () =>
|
|
this.request('/api/v1/user/orgs')
|
|
);
|
|
}
|
|
|
|
/**
|
|
* Clear cache
|
|
*/
|
|
clearCache() {
|
|
this.cache.clear();
|
|
}
|
|
|
|
/**
|
|
* Get swarm summary - all repos with PRs and issues
|
|
*/
|
|
async getSwarmSummary() {
|
|
const repos = await this.getRepos();
|
|
const summary = [];
|
|
|
|
for (const repo of repos.slice(0, 10)) { // Limit to 10 repos
|
|
try {
|
|
const [prs, issues, branches] = await Promise.all([
|
|
this.getPullRequests(repo.name, 'open').catch(() => []),
|
|
this.getIssues(repo.name, 'open').catch(() => []),
|
|
this.getBranches(repo.name).catch(() => [])
|
|
]);
|
|
|
|
summary.push({
|
|
name: repo.name,
|
|
full_name: repo.full_name,
|
|
stars: repo.stars_count || 0,
|
|
forks: repo.forks_count || 0,
|
|
open_prs: prs.length,
|
|
open_issues: issues.length,
|
|
branches: branches.length,
|
|
updated_at: repo.updated_at,
|
|
html_url: repo.html_url
|
|
});
|
|
} catch (e) {
|
|
console.error(`Error fetching data for ${repo.name}:`, e.message);
|
|
}
|
|
}
|
|
|
|
return summary;
|
|
}
|
|
|
|
/**
|
|
* Get pending reviews (PRs needing attention)
|
|
*/
|
|
async getPendingReviews() {
|
|
const repos = await this.getRepos();
|
|
const pending = [];
|
|
|
|
for (const repo of repos.slice(0, 10)) {
|
|
try {
|
|
const prs = await this.getPullRequests(repo.name, 'open');
|
|
for (const pr of prs) {
|
|
pending.push({
|
|
repo: repo.name,
|
|
repo_url: repo.html_url,
|
|
pr_number: pr.number,
|
|
pr_title: pr.title,
|
|
pr_url: pr.html_url,
|
|
author: pr.user?.login,
|
|
created_at: pr.created_at,
|
|
mergeable: pr.mergeable,
|
|
draft: pr.draft,
|
|
labels: pr.labels || []
|
|
});
|
|
}
|
|
} catch (e) {
|
|
console.error(`Error fetching PRs for ${repo.name}:`, e.message);
|
|
}
|
|
}
|
|
|
|
return pending;
|
|
}
|
|
|
|
/**
|
|
* Get recent activity across all repos
|
|
*/
|
|
async getRecentActivity() {
|
|
const repos = await this.getRepos();
|
|
const activities = [];
|
|
|
|
for (const repo of repos.slice(0, 5)) {
|
|
try {
|
|
const activity = await this.getActivity(repo.name);
|
|
for (const act of (activity || []).slice(0, 5)) {
|
|
activities.push({
|
|
repo: repo.name,
|
|
repo_url: repo.html_url,
|
|
...act
|
|
});
|
|
}
|
|
} catch (e) {
|
|
// Skip repos without activity access
|
|
}
|
|
}
|
|
|
|
return activities
|
|
.sort((a, b) => new Date(b.created_at) - new Date(a.created_at))
|
|
.slice(0, 20);
|
|
}
|
|
}
|
|
|
|
module.exports = GiteaIntegration;
|