codenuk_backend_mine/services/git-integration/src/routes/github-integration.routes.js
2025-09-04 15:29:50 +05:30

562 lines
16 KiB
JavaScript

// Updated routes/github-integration.js
const express = require('express');
const router = express.Router();
const GitHubIntegrationService = require('../services/github-integration.service');
const GitHubOAuthService = require('../services/github-oauth');
const FileStorageService = require('../services/file-storage.service');
const database = require('../config/database');
const fs = require('fs');
const path = require('path');
const githubService = new GitHubIntegrationService();
const oauthService = new GitHubOAuthService();
const fileStorageService = new FileStorageService();
// Attach GitHub repository to template
router.post('/attach-repository', async (req, res) => {
try {
const { template_id, repository_url, branch_name } = req.body;
// Validate input
if (!template_id || !repository_url) {
return res.status(400).json({
success: false,
message: 'Template ID and repository URL are required'
});
}
// Check if template exists
const templateQuery = 'SELECT * FROM templates WHERE id = $1 AND is_active = true';
const templateResult = await database.query(templateQuery, [template_id]);
if (templateResult.rows.length === 0) {
return res.status(404).json({
success: false,
message: 'Template not found'
});
}
// Parse GitHub URL
const { owner, repo, branch } = githubService.parseGitHubUrl(repository_url);
// Check repository access
const accessCheck = await githubService.checkRepositoryAccess(owner, repo);
if (!accessCheck.hasAccess) {
if (accessCheck.requiresAuth) {
// Check if we have OAuth token
const tokenRecord = await oauthService.getToken();
if (!tokenRecord) {
return res.status(401).json({
success: false,
message: 'GitHub authentication required for this repository',
requires_auth: true,
auth_url: `/api/github/auth/github`
});
}
}
return res.status(404).json({
success: false,
message: accessCheck.error || 'Repository not accessible'
});
}
// Get repository information from GitHub
const repositoryData = await githubService.fetchRepositoryMetadata(owner, repo);
// Analyze the codebase
const codebaseAnalysis = await githubService.analyzeCodebase(owner, repo, branch || branch_name);
// Store everything in PostgreSQL
const insertQuery = `
INSERT INTO github_repositories (
template_id, repository_url, repository_name, owner_name,
branch_name, is_public, metadata, codebase_analysis, sync_status,
requires_auth
) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10)
RETURNING *
`;
const insertValues = [
template_id,
repository_url,
repo,
owner,
branch || branch_name || 'main',
repositoryData.visibility === 'public',
JSON.stringify(repositoryData),
JSON.stringify(codebaseAnalysis),
'synced',
accessCheck.requiresAuth
];
const insertResult = await database.query(insertQuery, insertValues);
const repositoryRecord = insertResult.rows[0];
// Download repository with file storage
console.log('Downloading repository with storage...');
const downloadResult = await githubService.downloadRepositoryWithStorage(
owner, repo, branch || branch_name || 'main', repositoryRecord.id
);
if (!downloadResult.success) {
// If download failed, still return the repository record but mark the storage issue
console.warn('Repository download failed:', downloadResult.error);
}
// Create feature-codebase mappings
const featureQuery = 'SELECT id FROM template_features WHERE template_id = $1';
const featureResult = await database.query(featureQuery, [template_id]);
if (featureResult.rows.length > 0) {
const mappingValues = [];
const mappingParams = [];
let paramIndex = 1;
for (const feature of featureResult.rows) {
mappingValues.push(`(uuid_generate_v4(), $${paramIndex++}, $${paramIndex++}, $${paramIndex++}, $${paramIndex++})`);
mappingParams.push(
feature.id,
repositoryRecord.id,
'[]', // Empty paths for now
'{}' // Empty snippets for now
);
}
const mappingQuery = `
INSERT INTO feature_codebase_mappings (id, feature_id, repository_id, code_paths, code_snippets)
VALUES ${mappingValues.join(', ')}
`;
await database.query(mappingQuery, mappingParams);
}
// Get storage information
const storageInfo = await githubService.getRepositoryStorage(repositoryRecord.id);
res.status(201).json({
success: true,
message: 'Repository attached successfully',
data: {
repository_id: repositoryRecord.id,
template_id: repositoryRecord.template_id,
repository_name: repositoryRecord.repository_name,
owner_name: repositoryRecord.owner_name,
branch_name: repositoryRecord.branch_name,
is_public: repositoryRecord.is_public,
requires_auth: repositoryRecord.requires_auth,
metadata: repositoryData,
codebase_analysis: codebaseAnalysis,
storage_info: storageInfo,
download_result: downloadResult
}
});
} catch (error) {
console.error('Error attaching repository:', error);
res.status(500).json({
success: false,
message: error.message || 'Failed to attach repository'
});
}
});
// Get repository information for a template
router.get('/template/:id/repository', async (req, res) => {
try {
const { id } = req.params;
const query = `
SELECT gr.*, rs.local_path, rs.storage_status, rs.total_files_count,
rs.total_directories_count, rs.total_size_bytes, rs.download_completed_at
FROM github_repositories gr
LEFT JOIN repository_storage rs ON gr.id = rs.repository_id
WHERE gr.template_id = $1
ORDER BY gr.created_at DESC
LIMIT 1
`;
const result = await database.query(query, [id]);
if (result.rows.length === 0) {
return res.status(404).json({
success: false,
message: 'No repository found for this template'
});
}
const repository = result.rows[0];
const parseMaybe = (v) => {
if (v == null) return {};
if (typeof v === 'string') {
try { return JSON.parse(v); } catch { return {}; }
}
return v; // already an object from jsonb
};
res.json({
success: true,
data: {
...repository,
metadata: parseMaybe(repository.metadata),
codebase_analysis: parseMaybe(repository.codebase_analysis)
}
});
} catch (error) {
console.error('Error fetching repository:', error);
res.status(500).json({
success: false,
message: error.message || 'Failed to fetch repository'
});
}
});
// Get repository file structure
router.get('/repository/:id/structure', async (req, res) => {
try {
const { id } = req.params;
const { path: directoryPath } = req.query;
// Get repository info
const repoQuery = 'SELECT * FROM github_repositories WHERE id = $1';
const repoResult = await database.query(repoQuery, [id]);
if (repoResult.rows.length === 0) {
return res.status(404).json({
success: false,
message: 'Repository not found'
});
}
const structure = await fileStorageService.getRepositoryStructure(id, directoryPath);
res.json({
success: true,
data: {
repository_id: id,
directory_path: directoryPath || '',
structure: structure
}
});
} catch (error) {
console.error('Error fetching repository structure:', error);
res.status(500).json({
success: false,
message: error.message || 'Failed to fetch repository structure'
});
}
});
// Get files in a directory
router.get('/repository/:id/files', async (req, res) => {
try {
const { id } = req.params;
const { directory_path = '' } = req.query;
// Get repository info
const repoQuery = 'SELECT * FROM github_repositories WHERE id = $1';
const repoResult = await database.query(repoQuery, [id]);
if (repoResult.rows.length === 0) {
return res.status(404).json({
success: false,
message: 'Repository not found'
});
}
const files = await fileStorageService.getDirectoryFiles(id, directory_path);
res.json({
success: true,
data: {
repository_id: id,
directory_path: directory_path,
files: files
}
});
} catch (error) {
console.error('Error fetching directory files:', error);
res.status(500).json({
success: false,
message: error.message || 'Failed to fetch directory files'
});
}
});
// Get file content
router.get('/repository/:id/file-content', async (req, res) => {
try {
const { id } = req.params;
const { file_path } = req.query;
if (!file_path) {
return res.status(400).json({
success: false,
message: 'File path is required'
});
}
const query = `
SELECT rf.*, rfc.content_text, rfc.content_preview, rfc.language_detected,
rfc.line_count, rfc.char_count
FROM repository_files rf
LEFT JOIN repository_file_contents rfc ON rf.id = rfc.file_id
WHERE rf.repository_id = $1 AND rf.relative_path = $2
`;
const result = await database.query(query, [id, file_path]);
if (result.rows.length === 0) {
return res.status(404).json({
success: false,
message: 'File not found'
});
}
const file = result.rows[0];
res.json({
success: true,
data: {
file_info: {
id: file.id,
filename: file.filename,
file_extension: file.file_extension,
relative_path: file.relative_path,
file_size_bytes: file.file_size_bytes,
mime_type: file.mime_type,
is_binary: file.is_binary,
language_detected: file.language_detected,
line_count: file.line_count,
char_count: file.char_count
},
content: file.is_binary ? null : file.content_text,
preview: file.content_preview
}
});
} catch (error) {
console.error('Error fetching file content:', error);
res.status(500).json({
success: false,
message: error.message || 'Failed to fetch file content'
});
}
});
// Search repository files
router.get('/repository/:id/search', async (req, res) => {
try {
const { id } = req.params;
const { q: query } = req.query;
if (!query) {
return res.status(400).json({
success: false,
message: 'Search query is required'
});
}
const results = await fileStorageService.searchFileContent(id, query);
res.json({
success: true,
data: {
repository_id: id,
search_query: query,
results: results,
total_results: results.length
}
});
} catch (error) {
console.error('Error searching repository:', error);
res.status(500).json({
success: false,
message: error.message || 'Failed to search repository'
});
}
});
// List all repositories for a template
router.get('/template/:id/repositories', async (req, res) => {
try {
const { id } = req.params;
const query = `
SELECT gr.*, rs.local_path, rs.storage_status, rs.total_files_count,
rs.total_directories_count, rs.total_size_bytes, rs.download_completed_at
FROM github_repositories gr
LEFT JOIN repository_storage rs ON gr.id = rs.repository_id
WHERE gr.template_id = $1
ORDER BY gr.created_at DESC
`;
const result = await database.query(query, [id]);
const repositories = result.rows.map(repo => ({
...repo,
metadata: JSON.parse(repo.metadata || '{}'),
codebase_analysis: JSON.parse(repo.codebase_analysis || '{}')
}));
res.json({
success: true,
data: repositories
});
} catch (error) {
console.error('Error fetching repositories:', error);
res.status(500).json({
success: false,
message: error.message || 'Failed to fetch repositories'
});
}
});
// Download repository files (legacy endpoint for backward compatibility)
router.post('/download', async (req, res) => {
try {
const { repository_url, branch_name } = req.body;
if (!repository_url) {
return res.status(400).json({
success: false,
message: 'Repository URL is required'
});
}
const { owner, repo, branch } = githubService.parseGitHubUrl(repository_url);
const targetBranch = branch || branch_name || 'main';
const result = await githubService.downloadRepository(owner, repo, targetBranch);
if (result.success) {
res.json({
success: true,
message: 'Repository downloaded successfully',
data: result
});
} else {
res.status(500).json({
success: false,
message: 'Failed to download repository',
error: result.error
});
}
} catch (error) {
console.error('Error downloading repository:', error);
res.status(500).json({
success: false,
message: error.message || 'Failed to download repository'
});
}
});
// Re-sync repository (re-download and update database)
router.post('/repository/:id/sync', async (req, res) => {
try {
const { id } = req.params;
// Get repository info
const repoQuery = 'SELECT * FROM github_repositories WHERE id = $1';
const repoResult = await database.query(repoQuery, [id]);
if (repoResult.rows.length === 0) {
return res.status(404).json({
success: false,
message: 'Repository not found'
});
}
const repository = repoResult.rows[0];
const { owner, repo, branch } = githubService.parseGitHubUrl(repository.repository_url);
// Clean up existing storage
await githubService.cleanupRepositoryStorage(id);
// Re-download with storage
const downloadResult = await githubService.downloadRepositoryWithStorage(
owner, repo, branch || repository.branch_name, id
);
// Update sync status
await database.query(
'UPDATE github_repositories SET sync_status = $1, updated_at = NOW() WHERE id = $2',
[downloadResult.success ? 'synced' : 'error', id]
);
res.json({
success: downloadResult.success,
message: downloadResult.success ? 'Repository synced successfully' : 'Failed to sync repository',
data: downloadResult
});
} catch (error) {
console.error('Error syncing repository:', error);
res.status(500).json({
success: false,
message: error.message || 'Failed to sync repository'
});
}
});
// Remove repository from template
router.delete('/repository/:id', async (req, res) => {
try {
const { id } = req.params;
// Get repository info before deletion
const getQuery = 'SELECT * FROM github_repositories WHERE id = $1';
const getResult = await database.query(getQuery, [id]);
if (getResult.rows.length === 0) {
return res.status(404).json({
success: false,
message: 'Repository not found'
});
}
const repository = getResult.rows[0];
// Clean up file storage
await githubService.cleanupRepositoryStorage(id);
// Delete feature mappings first
await database.query(
'DELETE FROM feature_codebase_mappings WHERE repository_id = $1',
[id]
);
// Delete repository record
await database.query(
'DELETE FROM github_repositories WHERE id = $1',
[id]
);
res.json({
success: true,
message: 'Repository removed successfully',
data: {
removed_repository: repository.repository_name,
template_id: repository.template_id
}
});
} catch (error) {
console.error('Error removing repository:', error);
res.status(500).json({
success: false,
message: error.message || 'Failed to remove repository'
});
}
});
module.exports = router;