diff --git a/nostr/commit-signatures.jsonl b/nostr/commit-signatures.jsonl index 702a8f1..2f5601d 100644 --- a/nostr/commit-signatures.jsonl +++ b/nostr/commit-signatures.jsonl @@ -125,3 +125,4 @@ {"kind":1640,"pubkey":"573634b648634cbad10f2451776089ea21090d9407f715e83c577b4611ae6edc","created_at":1772296288,"tags":[["author","Silberengel","silberengel7@protonmail.com"],["message","administer the repos"]],"content":"Signed commit: administer the repos","id":"8825fb9bd01e099c1369f0c9ea1429dedd0a0116d103b4a640752c0a830fbc61","sig":"676f0817f817204ad910a70540399f71743a54453ae209535dcb30356d042b049138d9cfdeec08c4b7da03bb6bb51c71477bbf8d2f58bd4b602b9f69af4b3405"} {"kind":1640,"pubkey":"573634b648634cbad10f2451776089ea21090d9407f715e83c577b4611ae6edc","created_at":1772298906,"tags":[["author","Silberengel","silberengel7@protonmail.com"],["message","bug-fixes"]],"content":"Signed commit: bug-fixes","id":"6aa4dcd1b3d8a933710a6eb43321aa4faaba56598c735a634069c882c83b4f03","sig":"80ce253e890e8e84c8138e004bc2aaea402379d9aa67f62793ac7a4b344de6a7223f46fc733b240215a983a3a9b574ea8d0858a184f06df58ee66212ba58ee53"} {"kind":1640,"pubkey":"573634b648634cbad10f2451776089ea21090d9407f715e83c577b4611ae6edc","created_at":1772299137,"tags":[["author","Silberengel","silberengel7@protonmail.com"],["message","more-muted replyt-to"]],"content":"Signed commit: more-muted replyt-to","id":"fc0a91b526083b640d8116592fcac064fcf3cec9625b48dbd41c3877b2fe5444","sig":"998273d70d827ffbb939b4c149ff88e11c9f3aae3c5ddee78d860710f7fbff42c5ceed9433367b530bdc2869f9d382eb449537f813cf745c49f1a87a36926502"} +{"kind":1640,"pubkey":"573634b648634cbad10f2451776089ea21090d9407f715e83c577b4611ae6edc","created_at":1772299733,"tags":[["author","Silberengel","silberengel7@protonmail.com"],["message","make relay timeouts more efficient\nsuppress diff on initial commit"]],"content":"Signed commit: make relay timeouts more efficient\nsuppress diff on initial commit","id":"5fbc2dfb13acab011df5a394a022267e69bbe908e696c4389e0c07ba83d58a0d","sig":"daf46d563c413e2481be2cbd2b00d3015cf601e19fe0a191ffbb18c2c07508b17e34ebda5c903a1391914f991cecd7a7a4e809fcba45e1f14ebab674117eb53c"} diff --git a/src/routes/api/repos/[npub]/[repo]/clone/+server.ts b/src/routes/api/repos/[npub]/[repo]/clone/+server.ts index 42684fc..fe6ec67 100644 --- a/src/routes/api/repos/[npub]/[repo]/clone/+server.ts +++ b/src/routes/api/repos/[npub]/[repo]/clone/+server.ts @@ -303,10 +303,22 @@ export const POST: RequestHandler = async (event) => { }, 'Repository announcement clone URLs'); // Attempt to clone the repository + logger.debug({ npub, repo, preferredDefaultBranch, hasAnnouncement: !!announcementEvent }, 'Calling fetchRepoOnDemand'); const result = await repoManager.fetchRepoOnDemand(npub, repo, announcementEvent, preferredDefaultBranch); + + logger.debug({ + npub, + repo, + success: result.success, + error: result.error, + needsAnnouncement: result.needsAnnouncement, + cloneUrls: result.cloneUrls?.length || 0, + remoteUrls: result.remoteUrls?.length || 0 + }, 'fetchRepoOnDemand result'); if (!result.success) { if (result.needsAnnouncement) { + logger.error({ npub, repo }, 'Clone failed: Repository announcement is required'); throw handleValidationError( 'Repository announcement is required. Please provide an announcement event or create one.', { operation: 'cloneRepo', npub, repo } @@ -323,16 +335,18 @@ export const POST: RequestHandler = async (event) => { } else if (result.remoteUrls && result.remoteUrls.length === 0) { errorMessage += ' No accessible remote clone URLs found.'; } else if (result.cloneUrls && result.cloneUrls.length > 0) { - errorMessage += ` Attempted to clone from: ${result.cloneUrls.join(', ')}`; + errorMessage += ` Attempted to clone from: ${result.cloneUrls.slice(0, 3).join(', ')}${result.cloneUrls.length > 3 ? '...' : ''}`; } logger.error({ npub, repo, error: result.error, + errorMessage, cloneUrls: result.cloneUrls, - remoteUrls: result.remoteUrls - }, 'Failed to clone repository'); + remoteUrls: result.remoteUrls, + needsAnnouncement: result.needsAnnouncement + }, 'Failed to clone repository - fetchRepoOnDemand returned success: false'); throw handleApiError( new Error(result.error || 'Failed to clone repository from remote URLs'), diff --git a/src/routes/api/repos/list/+server.ts b/src/routes/api/repos/list/+server.ts index c9c887f..4b3f314 100644 --- a/src/routes/api/repos/list/+server.ts +++ b/src/routes/api/repos/list/+server.ts @@ -15,10 +15,16 @@ import { extractRequestContext } from '$lib/utils/api-context.js'; import logger from '$lib/services/logger.js'; import type { NostrEvent } from '$lib/types/nostr.js'; import type { RequestEvent } from '@sveltejs/kit'; +import { existsSync } from 'fs'; +import { join, resolve } from 'path'; const nostrClient = new NostrClient(DEFAULT_NOSTR_RELAYS); const maintainerService = new MaintainerService(DEFAULT_NOSTR_RELAYS); +const repoRoot = typeof process !== 'undefined' && process.env?.GIT_REPO_ROOT + ? resolve(process.env.GIT_REPO_ROOT) + : resolve('/repos'); + interface RepoListItem { event: NostrEvent; npub: string; @@ -90,6 +96,14 @@ export const GET: RequestHandler = async (event) => { npub = nip19.npubEncode(event.pubkey); } + // Only include repos that actually exist locally on the server + // This ensures deleted repos don't show up in the list + const repoPath = join(repoRoot, npub, `${dTag}.git`); + if (!existsSync(repoPath)) { + logger.debug({ npub, repoName: dTag, repoPath }, 'Skipping repo - does not exist locally'); + continue; + } + repos.push({ event, npub, diff --git a/src/routes/repos/[npub]/[repo]/+page.svelte b/src/routes/repos/[npub]/[repo]/+page.svelte index c20699f..a702626 100644 --- a/src/routes/repos/[npub]/[repo]/+page.svelte +++ b/src/routes/repos/[npub]/[repo]/+page.svelte @@ -1410,24 +1410,6 @@ {/if} - {#if state.error} -
-
- Error: {state.error} -
- {#if state.error.includes('not cloned locally') && hasUnlimitedAccess($userStore.userLevel)} -
- -
- {/if} -
- {/if} {#if repoOwnerPubkeyDerived} diff --git a/src/routes/repos/[npub]/[repo]/services/repo-operations.ts b/src/routes/repos/[npub]/[repo]/services/repo-operations.ts index 25da9f1..54d3678 100644 --- a/src/routes/repos/[npub]/[repo]/services/repo-operations.ts +++ b/src/routes/repos/[npub]/[repo]/services/repo-operations.ts @@ -225,28 +225,78 @@ export async function cloneRepository( const cloneUrl = `/api/repos/${state.npub}/${state.repo}/clone`; logger.debug({ url: cloneUrl }, '[Clone] POST request URL'); - const data = await apiPost<{ alreadyExists?: boolean }>(cloneUrl, requestBody); + const data = await apiPost<{ alreadyExists?: boolean; success?: boolean; message?: string }>(cloneUrl, requestBody); logger.debug({ data }, '[Clone] Clone request successful'); + // Check if the response indicates success + if (data.success === false) { + const errorMsg = data.message || 'Failed to clone repository'; + logger.error({ npub: state.npub, repo: state.repo, data }, '[Clone] Clone endpoint returned success: false'); + alert(`Error: ${errorMsg}`); + throw new Error(errorMsg); + } + if (data.alreadyExists) { alert('Repository already exists locally.'); // Force refresh clone status await callbacks.checkCloneStatus(true); } else { - alert('Repository cloned successfully! The repository is now available on this server.'); - // Force refresh clone status - await callbacks.checkCloneStatus(true); - // Reset API fallback status since repo is now cloned - state.clone.apiFallbackAvailable = false; - // Reload data to use the cloned repo instead of API - await Promise.all([ - callbacks.loadBranches(), - callbacks.loadFiles(state.files.currentPath), - callbacks.loadReadme(), - callbacks.loadTags(), - callbacks.loadCommitHistory() - ]); + alert('Repository clone initiated. Waiting for clone to complete...'); + + // Start polling for clone status with a delay + // Clone operation may take time, so we poll every 2 seconds + const pollInterval = 2000; // 2 seconds + const maxAttempts = 30; // Maximum 60 seconds (30 * 2s) + let attempts = 0; + let pollTimer: ReturnType | null = null; + + const pollCloneStatus = async () => { + attempts++; + logger.debug({ attempts, maxAttempts }, '[Clone] Polling clone status'); + + // Check clone status + await callbacks.checkCloneStatus(true); + + // If repo is now cloned, stop polling and reload data + if (state.clone.isCloned === true) { + if (pollTimer) { + clearInterval(pollTimer); + pollTimer = null; + } + + logger.info({ attempts }, '[Clone] Repository confirmed cloned, reloading data'); + + // Reset API fallback status since repo is now cloned + state.clone.apiFallbackAvailable = false; + + // Reload data to use the cloned repo instead of API + await Promise.all([ + callbacks.loadBranches(), + callbacks.loadFiles(state.files.currentPath), + callbacks.loadReadme(), + callbacks.loadTags(), + callbacks.loadCommitHistory() + ]); + + logger.info('[Clone] Repository data reloaded successfully'); + } else if (attempts >= maxAttempts) { + // Stop polling after max attempts + if (pollTimer) { + clearInterval(pollTimer); + pollTimer = null; + } + + logger.warn({ attempts }, '[Clone] Polling timeout - clone may still be in progress'); + alert('Clone operation is taking longer than expected. The repository may still be cloning in the background. Please refresh the page in a moment.'); + } + }; + + // Start polling after initial delay of 2 seconds + setTimeout(() => { + pollCloneStatus(); // First check immediately after delay + pollTimer = setInterval(pollCloneStatus, pollInterval); + }, pollInterval); } } catch (err) { const errorMessage = err instanceof Error ? err.message : 'Failed to clone repository'; @@ -254,9 +304,26 @@ export async function cloneRepository( error: err, npub: state.npub, repo: state.repo, - errorMessage + errorMessage, + errorStack: err instanceof Error ? err.stack : undefined }, '[Clone] Clone request failed'); - alert(`Error: ${errorMessage}`); + + // Extract more detailed error message if available + let userFriendlyMessage = errorMessage; + if (err instanceof Error) { + // Check if it's a network error + if (errorMessage.includes('fetch') || errorMessage.includes('network') || errorMessage.includes('Failed to fetch')) { + userFriendlyMessage = 'Network error: Unable to connect to the server. Please check your connection and try again.'; + } else if (errorMessage.includes('403') || errorMessage.includes('Forbidden')) { + userFriendlyMessage = 'Access denied: You do not have permission to clone this repository. Please verify you have unlimited access.'; + } else if (errorMessage.includes('404') || errorMessage.includes('Not found')) { + userFriendlyMessage = 'Repository not found: The repository announcement may not exist or the repository name may be incorrect.'; + } else if (errorMessage.includes('timeout') || errorMessage.includes('timed out')) { + userFriendlyMessage = 'Clone operation timed out. The repository may be too large or the remote server may be slow. Please try again.'; + } + } + + alert(`Error cloning repository: ${userFriendlyMessage}`); console.error('Error cloning repository:', err); } finally { state.clone.cloning = false;