done the redirection issue

This commit is contained in:
Pradeep 2025-10-15 15:02:37 +05:30
parent ab8b8942e8
commit 5e39839d42
12 changed files with 456 additions and 100 deletions

View File

@ -256,7 +256,7 @@ services:
# - JWT_ACCESS_SECRET=access-secret-key-2024-tech4biz-secure_pipeline_2024
# - JWT_REFRESH_SECRET=refresh-secret-key-2024-tech4biz-secure_pipeline_2024
# Service URLs
- USER_AUTH_URL=http://user-auth:8011
- USER_AUTH_URL=http://pipeline_user_auth:8011
- TEMPLATE_MANAGER_URL=http://template-manager:8009
- GIT_INTEGRATION_URL=http://git-integration:8012
- REQUIREMENT_PROCESSOR_URL=http://requirement-processor:8001

View File

@ -1814,7 +1814,10 @@ app.use('/api/vcs',
},
timeout: 45000,
validateStatus: () => true,
maxRedirects: 5 // Allow following redirects for OAuth flows
maxRedirects: 0, // Don't follow redirects - handle them manually
decompress: true,
httpAgent: new http.Agent({ keepAlive: true, maxSockets: 100 }),
httpsAgent: new https.Agent({ keepAlive: true, maxSockets: 100 })
};
// Always include request body for POST/PUT/PATCH requests

View File

@ -64,7 +64,13 @@ router.get('/auth/github/callback', async (req, res) => {
(req.session && req.session.user && (req.session.user.id || req.session.user.userId)) ||
(req.user && (req.user.id || req.user.userId));
if (!user_id && typeof state === 'string' && state.includes('|uid=')) {
try { user_id = state.split('|uid=')[1]; } catch {}
try {
const stateParts = state.split('|');
const uidPart = stateParts.find(part => part.startsWith('uid='));
if (uidPart) {
user_id = uidPart.split('=')[1];
}
} catch {}
}
if (!user_id) {
@ -113,11 +119,20 @@ router.get('/auth/github/callback', async (req, res) => {
}
// Redirect back to frontend IMMEDIATELY (before heavy cloning operation)
const frontendUrl = process.env.FRONTEND_URL || 'https://dashboard.codenuk.com';
const frontendUrl = 'http://localhost:3001';
try {
const redirectUrl = `${frontendUrl}?github_connected=1&user=${encodeURIComponent(githubUser.login)}&processing=1`;
console.log('[GitHub OAuth] Redirecting to:', redirectUrl);
// Check if this is a private repository OAuth flow
const isPrivateRepo = typeof state === 'string' && state.includes('private_repo=true');
let redirectUrl = `${frontendUrl}/project-builder?oauth_success=true&provider=github&user_id=${encodeURIComponent(user_id)}`;
if (isPrivateRepo && repoContext) {
// Add private repo sync parameters
redirectUrl += `&sync_private_repo=true&repository_url=${encodeURIComponent(repoContext.repoUrl)}&branch_name=${encodeURIComponent(repoContext.branchName || 'main')}&sync_status=authenticating`;
console.log('[GitHub OAuth] Private repository OAuth completed, starting sync:', repoContext.repoUrl);
}
console.log('[GitHub OAuth] Redirecting to:', redirectUrl);
// Send redirect response immediately
res.redirect(302, redirectUrl);

View File

@ -40,6 +40,109 @@ function extractEventType(providerKey, payload) {
}
}
// Background sync method for private repositories
async function startPrivateRepoSync(providerKey, repoUrl, branchName, userId) {
try {
console.log(`🔄 [Private Repo Sync] Starting background sync for ${providerKey}: ${repoUrl}`);
const provider = getProvider({ params: { provider: providerKey } });
const { owner, repo, branch } = provider.parseRepoUrl(repoUrl);
// Update sync status to syncing
const database = require('../config/database');
await database.query(`
UPDATE all_repositories
SET sync_status = 'syncing', updated_at = NOW()
WHERE repository_url = $1 AND user_id = $2
`, [repoUrl, userId]);
// Fetch repository metadata with authentication
const repositoryData = await provider.fetchRepositoryMetadata(owner, repo);
let actualBranch = branch || branchName || repositoryData.default_branch || 'main';
// Analyze codebase
const codebaseAnalysis = await provider.analyzeCodebase(owner, repo, actualBranch, false);
// Prepare insert values
const insertValues = [
repoUrl,
repo,
owner,
actualBranch,
repositoryData.visibility !== 'private',
JSON.stringify(repositoryData),
JSON.stringify(codebaseAnalysis),
'synced',
repositoryData.visibility === 'private',
userId
];
// Check if repository already exists
const existingRepo = await database.query(
'SELECT id FROM all_repositories WHERE repository_url = $1 AND user_id = $2',
[repoUrl, userId]
);
let repositoryRecord;
if (existingRepo.rows.length > 0) {
// Update existing repository
const updateQuery = `
UPDATE all_repositories SET
metadata = $1,
codebase_analysis = $2,
sync_status = $3,
updated_at = NOW()
WHERE id = $4
RETURNING *
`;
const updateResult = await database.query(updateQuery, [
JSON.stringify(repositoryData),
JSON.stringify(codebaseAnalysis),
'synced',
existingRepo.rows[0].id
]);
repositoryRecord = updateResult.rows[0];
} else {
// Insert new repository
const insertQuery = `
INSERT INTO all_repositories (
repository_url, repository_name, owner_name,
branch_name, is_public, metadata, codebase_analysis, sync_status,
requires_auth, user_id
) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10)
RETURNING *
`;
const insertResult = await database.query(insertQuery, insertValues);
repositoryRecord = insertResult.rows[0];
}
// Sync repository files (clone/download)
const downloadResult = await provider.syncRepositoryWithFallback(owner, repo, actualBranch, repositoryRecord.id, repositoryData.visibility !== 'private');
// Update final sync status
const finalSyncStatus = downloadResult.success ? 'synced' : 'error';
await database.query('UPDATE all_repositories SET sync_status = $1, updated_at = NOW() WHERE id = $2', [finalSyncStatus, repositoryRecord.id]);
// Set up webhook if possible
const publicBaseUrl = process.env.PUBLIC_BASE_URL || null;
const callbackUrl = publicBaseUrl ? `${publicBaseUrl}/api/vcs/${providerKey}/webhook` : null;
try { await provider.ensureRepositoryWebhook(owner, repo, callbackUrl); } catch (_) {}
console.log(`✅ [Private Repo Sync] Completed sync for ${repoUrl} - Status: ${finalSyncStatus}`);
} catch (error) {
console.error(`❌ [Private Repo Sync] Failed for ${repoUrl}:`, error);
// Update status to error
const database = require('../config/database');
await database.query(`
UPDATE all_repositories
SET sync_status = 'error', updated_at = NOW()
WHERE repository_url = $1 AND user_id = $2
`, [repoUrl, userId]);
}
}
// Attach repository (provider-agnostic)
router.post('/:provider/attach-repository', async (req, res) => {
try {
@ -53,33 +156,86 @@ router.post('/:provider/attach-repository', async (req, res) => {
}
const { owner, repo, branch } = provider.parseRepoUrl(repository_url);
// Enhanced flow: Detect private repos and redirect to OAuth immediately
const providerKey = (req.params.provider || '').toLowerCase();
console.log(`[VCS Attach] Processing ${providerKey} repository: ${repository_url}`);
console.log(`[VCS Attach] Parsed - owner: ${owner}, repo: ${repo}`);
// First, try to determine if this is a private repository without authentication
let isPrivateRepo = false;
let visibilityError = null;
try {
console.log(`[VCS Attach] Fetching metadata without auth for ${providerKey}/${owner}/${repo}`);
console.log(`[VCS Attach] Provider method:`, typeof provider.fetchRepositoryMetadata);
console.log(`[VCS Attach] Calling with skipAuth=true`);
// Try to fetch repo metadata without authentication to check visibility
const repositoryData = await provider.fetchRepositoryMetadata(owner, repo, true);
console.log(`[VCS Attach] Metadata response:`, repositoryData);
isPrivateRepo = repositoryData.visibility === 'private';
console.log(`[VCS Attach] Repository visibility: ${repositoryData.visibility}, isPrivate: ${isPrivateRepo}`);
} catch (error) {
console.log(`[VCS Attach] Error fetching metadata:`, error.message);
// If we can't determine visibility, check error type
if (error.message && (
error.message.includes('Authentication required') ||
error.message.includes('404') ||
error.message.includes('Not Found') ||
error.message.includes('Repository not found') ||
error.message.includes('No GitHub token found') ||
error.message.includes('Please authenticate') ||
error.message.includes('authentication')
)) {
console.log(`[VCS Attach] Detected as private repository requiring auth`);
// Could be a private repository (404/Not Found/Repository not found) or auth required
// In either case, we need authentication to proceed
isPrivateRepo = true;
visibilityError = 'Authentication required to access repository';
} else {
console.log(`[VCS Attach] Non-auth error, returning 404:`, error.message);
return res.status(404).json({ success: false, message: error.message || 'Repository not accessible' });
}
}
// If private repo detected, check authentication
if (isPrivateRepo) {
const oauthService = getOAuthService(providerKey);
if (oauthService) {
let tokenRecord = null;
if (userId) {
tokenRecord = await oauthService.getTokenForUser(userId);
}
if (!tokenRecord) {
tokenRecord = await oauthService.getToken();
}
if (!tokenRecord) {
// Redirect to OAuth for private repo authentication
console.log(`🔒 [VCS Attach] Private ${providerKey} repository detected, redirecting to OAuth: ${repository_url}`);
// Generate OAuth URL with repository context in state
const stateBase = Math.random().toString(36).substring(7);
const state = `${stateBase}|uid=${userId}|repo=${encodeURIComponent(repository_url)}|branch=${encodeURIComponent(branch_name || 'main')}|private_repo=true`;
const authUrl = oauthService.getAuthUrl(state, userId);
return res.json({
success: false,
message: `${providerKey.charAt(0).toUpperCase() + providerKey.slice(1)} authentication required for private repository`,
requires_auth: true,
is_private_repo: true,
auth_url: authUrl,
state: state
});
}
}
}
// For public repos or authenticated private repos, proceed with normal flow
const accessCheck = await provider.checkRepositoryAccess(owner, repo, userId);
if (!accessCheck.hasAccess) {
if (accessCheck.requiresAuth) {
// Check if we have OAuth token for this provider
const providerKey = (req.params.provider || '').toLowerCase();
const oauthService = getOAuthService(providerKey);
if (oauthService) {
let tokenRecord = null;
if (userId) {
tokenRecord = await oauthService.getTokenForUser(userId);
}
if (!tokenRecord) {
tokenRecord = await oauthService.getToken();
}
if (!tokenRecord) {
return res.status(401).json({
success: false,
message: `${providerKey.charAt(0).toUpperCase() + providerKey.slice(1)} authentication required for this repository`,
requires_auth: true,
auth_url: `/api/vcs/${providerKey}/auth/start?user_id=${encodeURIComponent(userId)}`
});
}
}
}
return res.status(404).json({ success: false, message: accessCheck.error || 'Repository not accessible' });
}
@ -484,11 +640,14 @@ router.get('/:provider/repositories', async (req, res) => {
}
if (!tokenRecord) {
return res.status(401).json({
success: false,
message: `${providerKey.charAt(0).toUpperCase() + providerKey.slice(1)} authentication required`,
// Return empty list instead of 401 error for better UX
return res.status(200).json({
success: true,
data: [],
message: `${providerKey.charAt(0).toUpperCase() + providerKey.slice(1)} authentication required to view repositories`,
requires_auth: true,
auth_url: `/api/vcs/${providerKey}/auth/start?user_id=${encodeURIComponent(userId)}`
auth_url: `/api/vcs/${providerKey}/auth/start?user_id=${encodeURIComponent(userId)}`,
count: 0
});
}
@ -570,48 +729,110 @@ router.get('/:provider/auth/callback', (req, res) => {
}
const accessToken = await oauth.exchangeCodeForToken(code);
const user = await oauth.getUserInfo(accessToken);
const userId =
req.query.user_id ||
(req.body && req.body.user_id) ||
req.headers['x-user-id'] ||
(req.cookies && (req.cookies.user_id || req.cookies.uid)) ||
(req.session && req.session.user && (req.session.user.id || req.session.user.userId)) ||
(req.user && (req.user.id || req.user.userId));
// Extract userId from state parameter (embedded in OAuth state)
let userId = null;
const state = req.query.state;
if (state && state.includes('|uid=')) {
const stateParts = state.split('|');
const uidPart = stateParts.find(part => part.startsWith('uid='));
if (uidPart) {
userId = uidPart.split('=')[1];
}
}
// Fallback to other sources if not found in state
if (!userId) {
userId = req.query.user_id ||
(req.body && req.body.user_id) ||
req.headers['x-user-id'] ||
(req.cookies && (req.cookies.user_id || req.cookies.uid)) ||
(req.session && req.session.user && (req.session.user.id || req.session.user.userId)) ||
(req.user && (req.user.id || req.user.userId));
}
if (providerKey === 'github' && !userId) {
return res.status(400).json({ success: false, message: 'user_id is required to complete GitHub authentication' });
}
console.log('[VCS OAuth] callback provider=%s resolved user_id = %s', providerKey, userId || null);
const tokenRecord = await oauth.storeToken(accessToken, user, userId || null);
res.json({ success: true, provider: providerKey, user, token: { id: tokenRecord.id || null } });
// Enhanced redirect logic for private repository flow
const frontendUrl = 'http://localhost:3001';
// Check if there was a pending repository attachment from private repo flow
let redirectUrl = `${frontendUrl}/project-builder?oauth_success=true&provider=${providerKey}&user_id=${encodeURIComponent(userId || '')}`;
// Extract repository context from OAuth state
if (state && (state.includes('|repo=') || state.includes('|private_repo=true'))) {
const stateParts = state.split('|');
const repoUrlPart = stateParts.find(part => part.startsWith('repo='));
const branchPart = stateParts.find(part => part.startsWith('branch='));
const privateRepoPart = stateParts.find(part => part.startsWith('private_repo='));
if (repoUrlPart) {
const repoUrl = decodeURIComponent(repoUrlPart.split('=')[1]);
const branchName = branchPart ? decodeURIComponent(branchPart.split('=')[1]) : 'main';
const isPrivateRepo = privateRepoPart ? privateRepoPart.split('=')[1] === 'true' : false;
if (isPrivateRepo) {
// For private repos, redirect to project-builder with sync progress tracking
redirectUrl += `&sync_private_repo=true&repository_url=${encodeURIComponent(repoUrl)}&branch_name=${encodeURIComponent(branchName)}&sync_status=authenticating`;
console.log(`🔒 [VCS OAUTH] Private repository OAuth completed, starting sync: ${repoUrl}`);
// Start background sync process
setImmediate(async () => {
try {
await startPrivateRepoSync(providerKey, repoUrl, branchName, userId);
} catch (error) {
console.error(`❌ [VCS OAUTH] Background sync failed for ${repoUrl}:`, error);
}
});
} else {
// For regular repos, include attachment parameters
redirectUrl += `&attach_repo=true&repository_url=${encodeURIComponent(repoUrl)}&branch_name=${encodeURIComponent(branchName)}`;
}
}
}
console.log(`✅ [VCS OAUTH] Redirecting to frontend: ${redirectUrl}`);
res.redirect(redirectUrl);
} catch (e) {
console.error(`❌ [VCS OAUTH] Callback error for ${req.params.provider}:`, e);
// Always redirect to frontend even on errors, so user sees proper error message
const frontendUrl = 'http://localhost:3001';
const providerKey = (req.params.provider || '').toLowerCase();
// Extract userId from state if available
let userId = null;
const state = req.query.state;
if (state && state.includes('|uid=')) {
const stateParts = state.split('|');
const uidPart = stateParts.find(part => part.startsWith('uid='));
if (uidPart) {
userId = uidPart.split('=')[1];
}
}
// Provide more specific error messages
let errorMessage = e.message || 'OAuth callback failed';
let statusCode = 500;
if (e.message.includes('not configured')) {
statusCode = 500;
errorMessage = `OAuth configuration error: ${e.message}`;
} else if (e.message.includes('timeout')) {
statusCode = 504;
errorMessage = `OAuth timeout: ${e.message}`;
} else if (e.message.includes('network error') || e.message.includes('Cannot connect')) {
statusCode = 502;
errorMessage = `Network error: ${e.message}`;
} else if (e.message.includes('HTTP error')) {
statusCode = 502;
errorMessage = `OAuth provider error: ${e.message}`;
}
res.status(statusCode).json({
success: false,
message: errorMessage,
provider: req.params.provider,
error: e.message,
details: process.env.NODE_ENV === 'development' ? e.stack : undefined
});
// Redirect to frontend with error parameters
const redirectUrl = `${frontendUrl}/project-builder?oauth_error=true&provider=${providerKey}&error_message=${encodeURIComponent(errorMessage)}&user_id=${encodeURIComponent(userId || '')}`;
console.log(`❌ [VCS OAUTH] Redirecting to frontend with error: ${redirectUrl}`);
res.redirect(redirectUrl);
}
})();
});
@ -1393,7 +1614,4 @@ async function getMinimalFileTree(repositoryId, filePath) {
// Return null tree as fallback
return null;
}
}
module.exports = router;
}

View File

@ -18,12 +18,8 @@ class BitbucketOAuthService {
throw new Error('Bitbucket OAuth not configured');
}
// If a userId is provided, append it to the redirect_uri
let redirectUri = this.redirectUri;
if (userId) {
const hasQuery = redirectUri.includes('?');
redirectUri = `${redirectUri}${hasQuery ? '&' : '?'}user_id=${encodeURIComponent(userId)}`;
}
// Use the base redirect URI without user_id parameter
const redirectUri = this.redirectUri;
// Embed userId into the OAuth state for fallback extraction
const stateWithUser = userId ? `${state}|uid=${userId}` : state;

View File

@ -21,12 +21,8 @@ class GiteaOAuthService {
throw new Error('Gitea OAuth not configured');
}
// If a userId is provided, append it to the redirect_uri
let redirectUri = this.redirectUri;
if (userId) {
const hasQuery = redirectUri.includes('?');
redirectUri = `${redirectUri}${hasQuery ? '&' : '?'}user_id=${encodeURIComponent(userId)}`;
}
// Use the base redirect URI without user_id parameter
const redirectUri = this.redirectUri;
// Embed userId into the OAuth state for fallback extraction
const stateWithUser = userId ? `${state}|uid=${userId}` : state;

View File

@ -289,22 +289,54 @@ class GitHubIntegrationService {
}
// Get repository information from GitHub
async fetchRepositoryMetadata(owner, repo) {
const octokit = await this.getAuthenticatedOctokit();
async fetchRepositoryMetadata(owner, repo, skipAuth = false) {
// If skipAuth is true, try with unauthenticated octokit first to check visibility
let octokit;
if (skipAuth) {
octokit = this.octokit; // Use unauthenticated instance
} else {
octokit = await this.getAuthenticatedOctokit();
}
const safe = async (fn, fallback) => {
try {
return await fn();
} catch (error) {
try {
return await fn();
} catch (error) {
console.warn(`API call failed: ${error.message}`);
return fallback;
return fallback;
}
};
const repoData = await safe(
async () => (await octokit.repos.get({ owner, repo })).data,
{}
);
let repoData;
try {
const response = await octokit.repos.get({ owner, repo });
if (skipAuth) {
if (response.status === 401 || response.status === 403) {
throw new Error('Authentication required to access repository');
} else if (response.status === 404) {
throw new Error('Repository not found');
}
}
repoData = response.data;
} catch (error) {
console.log(`🔍 [GitHub] Error in fetchRepositoryMetadata:`, error.message, error.status);
if (skipAuth) {
// For GitHub, any error when skipAuth=true likely means private repo
if (error.status === 401 || error.status === 403 || error.status === 404) {
throw new Error('Authentication required to access repository');
}
// For other errors, also assume private repo
throw new Error('Authentication required to access repository');
}
// For other errors, use safe fallback
repoData = await safe(
async () => {
const response = await octokit.repos.get({ owner, repo });
return response.data;
},
{}
);
}
const languages = await safe(
async () => (await octokit.repos.listLanguages({ owner, repo })).data,

View File

@ -19,14 +19,10 @@ class GitHubOAuthService {
throw new Error('GitHub OAuth not configured');
}
// If a userId is provided, append it to the redirect_uri so the callback can link token to that user
let redirectUri = this.redirectUri;
if (userId) {
const hasQuery = redirectUri.includes('?');
redirectUri = `${redirectUri}${hasQuery ? '&' : '?'}user_id=${encodeURIComponent(userId)}`;
}
// Use the base redirect URI without user_id parameter
const redirectUri = this.redirectUri;
// Also embed userId into the OAuth state for fallback extraction in callback
// Embed userId into the OAuth state for fallback extraction in callback
const stateWithUser = userId ? `${state}|uid=${userId}` : state;
const params = new URLSearchParams({

View File

@ -19,12 +19,8 @@ class GitLabOAuthService {
throw new Error('GitLab OAuth not configured');
}
// If a userId is provided, append it to the redirect_uri
let redirectUri = this.redirectUri;
if (userId) {
const hasQuery = redirectUri.includes('?');
redirectUri = `${redirectUri}${hasQuery ? '&' : '?'}user_id=${encodeURIComponent(userId)}`;
}
// Use the base redirect URI without user_id parameter
const redirectUri = this.redirectUri;
// Embed userId into the OAuth state for fallback extraction
const stateWithUser = userId ? `${state}|uid=${userId}` : state;
@ -33,7 +29,7 @@ class GitLabOAuthService {
client_id: this.clientId,
redirect_uri: redirectUri,
response_type: 'code',
scope: 'read_api api read_user read_repository',
scope: 'read_api read_user read_repository',
state: stateWithUser
});

View File

@ -117,7 +117,34 @@ class BitbucketAdapter extends VcsProviderInterface {
}
}
async fetchRepositoryMetadata(owner, repo) {
async fetchRepositoryMetadata(owner, repo, skipAuth = false) {
// If skipAuth is true, try without authentication first to check visibility
if (skipAuth) {
try {
const resp = await fetch(`https://api.bitbucket.org/2.0/repositories/${owner}/${repo}`);
if (resp.ok) {
const d = await resp.json();
// Check if the response contains an error message
if (d.error || (d.message && d.message.includes('Not Found'))) {
throw new Error('Repository not found');
}
return { full_name: d.full_name, visibility: d.is_private ? 'private' : 'public', default_branch: d.mainbranch?.name || 'main', updated_at: d.updated_on };
} else if (resp.status === 404) {
// For Bitbucket, 404 can mean either "not found" or "private repo"
// We'll assume it's private and require authentication
throw new Error('Authentication required to access repository');
} else if (resp.status === 401 || resp.status === 403) {
throw new Error('Authentication required to access repository');
}
} catch (error) {
if (error.message.includes('Authentication required') || error.message.includes('Repository not found')) {
throw error;
}
// If other error, fall through to authenticated attempt
}
}
// Try with authentication (original behavior)
const token = await this.oauth.getToken();
if (token?.access_token) {
try {
@ -129,6 +156,8 @@ class BitbucketAdapter extends VcsProviderInterface {
}
} catch (_) {}
}
// Fallback
return { full_name: `${owner}/${repo}`, visibility: 'public', default_branch: 'main', updated_at: new Date().toISOString() };
}

View File

@ -175,9 +175,44 @@ class GiteaAdapter extends VcsProviderInterface {
}
}
async fetchRepositoryMetadata(owner, repo) {
const token = await this.oauth.getToken();
async fetchRepositoryMetadata(owner, repo, skipAuth = false) {
const base = (process.env.GITEA_BASE_URL || 'https://gitea.com').replace(/\/$/, '');
// If skipAuth is true, try without authentication first to check visibility
if (skipAuth) {
try {
const response = await axios.get(`${base}/api/v1/repos/${owner}/${repo}`, {
httpsAgent: new https.Agent({
keepAlive: true,
timeout: 15000,
family: 4 // Force IPv4 to avoid IPv6 connectivity issues
}),
timeout: 15000
});
if (response.status === 200) {
const d = response.data;
// Check if the response contains an error message
if (d.message && d.message.includes('Not Found')) {
throw new Error('Repository not found');
}
return { full_name: d.full_name || `${owner}/${repo}`, visibility: d.private ? 'private' : 'public', default_branch: d.default_branch || 'main', updated_at: d.updated_at };
}
} catch (error) {
if (error.response && (error.response.status === 401 || error.response.status === 404)) {
if (error.response.status === 404) {
// For Gitea, 404 can mean either "not found" or "private repo"
// We'll assume it's private and require authentication
throw new Error('Authentication required to access repository');
} else {
throw new Error('Authentication required to access repository');
}
}
// If other error, fall through to authenticated attempt
}
}
// Try with authentication (original behavior)
const token = await this.oauth.getToken();
if (token?.access_token) {
try {
const response = await axios.get(`${base}/api/v1/repos/${owner}/${repo}`, {
@ -197,6 +232,8 @@ class GiteaAdapter extends VcsProviderInterface {
console.log(`❌ [GITEA] Failed to fetch repository metadata: ${error.message}`);
}
}
// Fallback
return { full_name: `${owner}/${repo}`, visibility: 'public', default_branch: 'main', updated_at: new Date().toISOString() };
}

View File

@ -98,9 +98,45 @@ class GitlabAdapter extends VcsProviderInterface {
};
}
async fetchRepositoryMetadata(owner, repo) {
const token = await this.oauth.getToken();
async fetchRepositoryMetadata(owner, repo, skipAuth = false) {
const base = (process.env.GITLAB_BASE_URL || 'https://gitlab.com').replace(/\/$/, '');
// If skipAuth is true, try without authentication first to check visibility
if (skipAuth) {
try {
const url = `${base}/api/v4/projects/${encodeURIComponent(`${owner}/${repo}`)}`;
console.log(`🔍 [GitLab Adapter] Fetching metadata without auth: ${url}`);
const resp = await fetch(url);
console.log(`🔍 [GitLab Adapter] Response status: ${resp.status}`);
if (resp.ok) {
const d = await resp.json();
console.log(`🔍 [GitLab Adapter] Response data:`, JSON.stringify(d, null, 2));
// Check if the response contains an error message
if (d.message && (d.message.includes('404') || d.message.includes('Not Found'))) {
throw new Error('Repository not found');
}
return { full_name: d.path_with_namespace, visibility: d.visibility === 'public' ? 'public' : 'private', default_branch: d.default_branch || 'main', updated_at: d.last_activity_at };
} else if (resp.status === 404) {
// For GitLab, 404 can mean either "not found" or "private repo"
// We'll assume it's private and require authentication
console.log(`🔍 [GitLab Adapter] 404 response - assuming private repo`);
throw new Error('Authentication required to access repository');
} else if (resp.status === 401 || resp.status === 403) {
console.log(`🔍 [GitLab Adapter] ${resp.status} response - authentication required`);
throw new Error('Authentication required to access repository');
}
} catch (error) {
console.log(`🔍 [GitLab Adapter] Error in skipAuth:`, error.message);
if (error.message.includes('Authentication required') || error.message.includes('Repository not found')) {
throw error;
}
// If other error, fall through to authenticated attempt
}
}
// Try with authentication (original behavior)
const token = await this.oauth.getToken();
if (token?.access_token) {
try {
const resp = await fetch(`${base}/api/v4/projects/${encodeURIComponent(`${owner}/${repo}`)}`, { headers: { Authorization: `Bearer ${token.access_token}` } });
@ -110,6 +146,8 @@ class GitlabAdapter extends VcsProviderInterface {
}
} catch (_) {}
}
// Fallback
return { full_name: `${owner}/${repo}`, visibility: 'public', default_branch: 'main', updated_at: new Date().toISOString() };
}