From 7bb1c8d66b2790c887f617705c728fb1ba4ce5e7 Mon Sep 17 00:00:00 2001 From: Silberengel Date: Sat, 28 Feb 2026 16:45:50 +0100 Subject: [PATCH] remove polling Nostr-Signature: 40f01e84f96661bb7fea13aa63c7da428118061b0a1470a11890d4f9cd6d685b 573634b648634cbad10f2451776089ea21090d9407f715e83c577b4611ae6edc dbb6947defac6c7f92a3cf6f72352a94ffe2c4b33e65f8410518a40406c93f1f5a3e13e81f2f04f676d826e6cf03ec802328f5228300f80a8114fa3fd26eaeff --- docs/api-and-cli.md | 1 - docs/editing-repos.md | 10 +- nostr/commit-signatures.jsonl | 1 + src/hooks.server.ts | 50 +--- src/lib/services/git/repo-manager.ts | 5 +- src/lib/services/nostr/repo-polling.ts | 278 ----------------------- src/lib/services/service-registry.ts | 16 -- src/lib/utils/nostr-utils.ts | 1 - src/lib/utils/repo-poll-trigger.ts | 30 --- src/routes/api/openapi.json/openapi.json | 43 ---- src/routes/api/repos/local/+server.ts | 2 +- src/routes/api/repos/poll/+server.ts | 33 --- src/routes/api/user/level/+server.ts | 7 - src/routes/repos/+page.svelte | 29 +-- 14 files changed, 12 insertions(+), 494 deletions(-) delete mode 100644 src/lib/services/nostr/repo-polling.ts delete mode 100644 src/lib/utils/repo-poll-trigger.ts delete mode 100644 src/routes/api/repos/poll/+server.ts diff --git a/docs/api-and-cli.md b/docs/api-and-cli.md index 01a6e45..bc4110e 100644 --- a/docs/api-and-cli.md +++ b/docs/api-and-cli.md @@ -116,7 +116,6 @@ View interactive documentation at `/api/openapi.json` or use any OpenAPI viewer. - `GET /api/config` - Get server configuration - `GET /api/tor/onion` - Get Tor .onion address -- `POST /api/repos/poll` - Trigger repository polling (provisions new repos from Nostr) - `GET /api/transfers/pending` - Get pending ownership transfers #### Git HTTP Backend diff --git a/docs/editing-repos.md b/docs/editing-repos.md index 7597310..1d12583 100644 --- a/docs/editing-repos.md +++ b/docs/editing-repos.md @@ -2,18 +2,18 @@ This page covers all aspects of editing repositories: branch management, file management, auto-provisioning, file-editing permissions, and event-creation permissions. -## Auto-Provisioning +## Adding Repositories -When you create a repository announcement, GitRepublic automatically: +Repositories must be explicitly added to the server using the clone endpoint. When you clone a repository: -1. **Polls Nostr relays** for new announcements +1. **Fetches the repository announcement** from Nostr relays 2. **Creates a bare git repository** at `/repos/{npub}/{repo-name}.git` -3. **Fetches self-transfer event** for ownership verification +3. **Fetches self-transfer event** for ownership verification (if available) 4. **Creates initial commit** with README.md (if provided) 5. **Saves announcement and transfer events** to `nostr/repo-events.jsonl` 6. **Syncs from other remotes** if clone URLs are configured -The repository is ready to use immediately after announcement. +The repository is ready to use immediately after cloning. ## Branch Management diff --git a/nostr/commit-signatures.jsonl b/nostr/commit-signatures.jsonl index 43d00a0..5f36c57 100644 --- a/nostr/commit-signatures.jsonl +++ b/nostr/commit-signatures.jsonl @@ -120,3 +120,4 @@ {"kind":1640,"pubkey":"573634b648634cbad10f2451776089ea21090d9407f715e83c577b4611ae6edc","created_at":1772269280,"tags":[["author","Silberengel","silberengel7@protonmail.com"],["message","api refactor part 2"]],"content":"Signed commit: api refactor part 2","id":"ece894a60057bba46ebd4ac0dca2aca55ffce05e44671fe07b29516809fc86f6","sig":"176706a271659834e441ea5eab4bb1480667dad4468fe8315803284f4a183debf595523dd33d0d3cabe0c35013f4a72b9169b5f10afefaf8a82a721d8b0f3b08"} {"kind":1640,"pubkey":"573634b648634cbad10f2451776089ea21090d9407f715e83c577b4611ae6edc","created_at":1772270859,"tags":[["author","Silberengel","silberengel7@protonmail.com"],["message","bug-fixes and fallback relay"]],"content":"Signed commit: bug-fixes and fallback relay","id":"1d85d0c5e1451c90bca5d59e08043f29adeaad4db4ac5495c8e9a4247775780f","sig":"a1960b76c78db9f64dad20378d26f500ffc09f1f6d137314db548470202712222a1d391f682146ba281fd23355c574fcbb260310db61b3458bba3dec0c724a18"} {"kind":1640,"pubkey":"573634b648634cbad10f2451776089ea21090d9407f715e83c577b4611ae6edc","created_at":1772271656,"tags":[["author","Silberengel","silberengel7@protonmail.com"],["message","bug-fixes"]],"content":"Signed commit: bug-fixes","id":"f4a5e0d3e2aa7d0d99803f26008ab68e40551e36362bb6d04acf639c5b78d959","sig":"59da9e59a6fb5648f4c889e0045b571e0d2d66a555100d60dec373455309a640bea89e4bb3a42a0e502aa4d2091e4b698203721e79b346ff30e6b2bcdc5f48b3"} +{"kind":1640,"pubkey":"573634b648634cbad10f2451776089ea21090d9407f715e83c577b4611ae6edc","created_at":1772274086,"tags":[["author","Silberengel","silberengel7@protonmail.com"],["message","bug-fixes"]],"content":"Signed commit: bug-fixes","id":"32794e047f06902ad610f918834efb113f41eace26a53a3f0fad083b9d8323dc","sig":"3859f0de3de0f8a742b6fbe7709c5a5625f4d5612a936fd81f38a7e1231ee810b50a69c1ed5d23c8a6670b4cbc9ea3d4bd39d6fa9e6207802f45995689b924a9"} diff --git a/src/hooks.server.ts b/src/hooks.server.ts index deffc5e..49b600d 100644 --- a/src/hooks.server.ts +++ b/src/hooks.server.ts @@ -1,23 +1,14 @@ /** * Server-side hooks for gitrepublic-web - * Initializes repo polling service and security middleware + * Initializes security middleware */ import type { Handle } from '@sveltejs/kit'; import { error } from '@sveltejs/kit'; -import { RepoPollingService } from './lib/services/nostr/repo-polling.js'; -import { GIT_DOMAIN, DEFAULT_NOSTR_RELAYS } from './lib/config.js'; -import { setRepoPollingService } from './lib/services/service-registry.js'; import { rateLimiter } from './lib/services/security/rate-limiter.js'; import { auditLogger } from './lib/services/security/audit-logger.js'; import logger from './lib/services/logger.js'; -// Initialize polling service -const repoRoot = process.env.GIT_REPO_ROOT || '/repos'; -const domain = GIT_DOMAIN; - -let pollingService: RepoPollingService | null = null; - if (typeof process !== 'undefined') { // Handle unhandled promise rejections to prevent crashes from relay errors process.on('unhandledRejection', (reason, promise) => { @@ -29,29 +20,9 @@ if (typeof process !== 'undefined') { } }); - pollingService = new RepoPollingService(DEFAULT_NOSTR_RELAYS, repoRoot, domain); - - // Register with service registry so it can be accessed from API endpoints - setRepoPollingService(pollingService); - - // Start polling - the initial poll will complete asynchronously - // The local repos endpoint will skip cache for the first 10 seconds after startup - pollingService.start().then(() => { - logger.info({ service: 'repo-polling', relays: DEFAULT_NOSTR_RELAYS.length }, 'Repo polling service ready (initial poll completed)'); - }).catch((err) => { - logger.error({ error: err, service: 'repo-polling' }, 'Initial repo poll failed, but continuing'); - }); - - logger.info({ service: 'repo-polling', relays: DEFAULT_NOSTR_RELAYS.length }, 'Started repo polling service (initial poll in progress)'); - // Cleanup on server shutdown const cleanup = (signal: string) => { logger.info({ signal }, 'Received shutdown signal, cleaning up...'); - if (pollingService) { - logger.info('Stopping repo polling service...'); - pollingService.stop(); - pollingService = null; - } // Give a moment for cleanup, then exit setTimeout(() => { process.exit(0); @@ -68,25 +39,6 @@ if (typeof process !== 'undefined') { process.exit(0); }, 2000); }); - - // Also cleanup on process exit (last resort) - process.on('exit', () => { - if (pollingService) { - pollingService.stop(); - } - }); - - // Periodic zombie process cleanup check - // This helps catch any processes that weren't properly cleaned up - if (typeof setInterval !== 'undefined') { - setInterval(() => { - // Check for zombie processes by attempting to reap them - // Node.js handles this automatically via 'close' events, but this is a safety net - // We can't directly check for zombies, but we can ensure our cleanup is working - // The real cleanup happens in process handlers, this is just monitoring - logger.debug('Zombie cleanup check (process handlers should prevent zombies)'); - }, 60000); // Check every minute - } } export const handle: Handle = async ({ event, resolve }) => { diff --git a/src/lib/services/git/repo-manager.ts b/src/lib/services/git/repo-manager.ts index 315eb7b..ca088a6 100644 --- a/src/lib/services/git/repo-manager.ts +++ b/src/lib/services/git/repo-manager.ts @@ -182,11 +182,10 @@ export class RepoManager { } else { // For existing repos, check if announcement exists in repo // If not, try to fetch from relays and save it - // Note: We have the announcement from polling (event parameter), so we can use that - // Non-blocking: fire and forget - we have the announcement from relays, so this is just for offline papertrail + // Note: We have the announcement from the clone request (event parameter), so we can use that const hasAnnouncement = await this.announcementManager.hasAnnouncementInRepoFile(repoPath.fullPath); if (!hasAnnouncement) { - // We have the event from polling, so use it directly (no need to fetch from relays again) + // We have the event from the clone request, so use it directly (no need to fetch from relays again) // Save announcement to repo asynchronously this.announcementManager.ensureAnnouncementInRepo(repoPath.fullPath, event, selfTransferEvent) .catch((err) => { diff --git a/src/lib/services/nostr/repo-polling.ts b/src/lib/services/nostr/repo-polling.ts deleted file mode 100644 index 85b96cd..0000000 --- a/src/lib/services/nostr/repo-polling.ts +++ /dev/null @@ -1,278 +0,0 @@ -/** - * Service for polling NIP-34 repo announcements and auto-provisioning repos - */ - -import { NostrClient } from './nostr-client.js'; -import { KIND } from '../../types/nostr.js'; -import type { NostrEvent } from '../../types/nostr.js'; -import { RepoManager } from '../git/repo-manager.js'; -import { OwnershipTransferService } from './ownership-transfer-service.js'; -import { getCachedUserLevel } from '../security/user-level-cache.js'; -import logger from '../logger.js'; -import { extractCloneUrls } from '../../utils/nostr-utils.js'; - -export class RepoPollingService { - private nostrClient: NostrClient; - private repoManager: RepoManager; - private pollingInterval: number; - private intervalId: NodeJS.Timeout | null = null; - private domain: string; - private relays: string[]; - private initialPollPromise: Promise | null = null; - private isInitialPollComplete: boolean = false; - - constructor( - relays: string[], - repoRoot: string, - domain: string, - pollingInterval: number = 60000 // 1 minute - ) { - this.relays = relays; - this.nostrClient = new NostrClient(relays); - this.repoManager = new RepoManager(repoRoot, domain); - this.pollingInterval = pollingInterval; - this.domain = domain; - } - - /** - * Start polling for repo announcements - * Returns a promise that resolves when the initial poll completes - */ - start(): Promise { - if (this.intervalId) { - this.stop(); - } - - // Poll immediately and wait for it to complete - this.initialPollPromise = this.poll(); - - // Then poll at intervals - this.intervalId = setInterval(() => { - this.poll(); - }, this.pollingInterval); - - return this.initialPollPromise; - } - - /** - * Wait for initial poll to complete (useful for server startup) - */ - async waitForInitialPoll(): Promise { - if (this.initialPollPromise) { - await this.initialPollPromise; - } - } - - /** - * Check if initial poll has completed - */ - isReady(): boolean { - return this.isInitialPollComplete; - } - - /** - * Stop polling and cleanup resources - */ - stop(): void { - if (this.intervalId) { - clearInterval(this.intervalId); - this.intervalId = null; - } - // Close Nostr client connections - if (this.nostrClient) { - this.nostrClient.close(); - } - } - - /** - * Trigger a manual poll (useful after user verification) - */ - async triggerPoll(): Promise { - logger.info('Manual poll triggered'); - return this.poll(); - } - - /** - * Poll for new repo announcements and provision repos - */ - private async poll(): Promise { - try { - logger.debug('Starting repo poll...'); - const events = await this.nostrClient.fetchEvents([ - { - kinds: [KIND.REPO_ANNOUNCEMENT], - limit: 100 - } - ]); - - // Filter for repos that list our domain - const relevantEvents = events.filter(event => { - // Skip local-only forks (synthetic announcements not published to Nostr) - const isLocalOnly = event.tags.some(t => t[0] === 'local-only' && t[1] === 'true'); - if (isLocalOnly) { - return false; - } - - const cloneUrls = this.extractCloneUrls(event); - const listsDomain = cloneUrls.some(url => url.includes(this.domain)); - if (listsDomain) { - logger.debug({ - eventId: event.id, - pubkey: event.pubkey.slice(0, 16) + '...', - cloneUrls: cloneUrls.slice(0, 3) // Log first 3 URLs - }, 'Found repo announcement that lists this domain'); - } - return listsDomain; - }); - - logger.info({ - totalEvents: events.length, - relevantEvents: relevantEvents.length, - domain: this.domain - }, 'Filtered repo announcements'); - - // Provision each repo - for (const event of relevantEvents) { - try { - // Extract repo ID from d-tag - const dTag = event.tags.find(t => t[0] === 'd')?.[1]; - if (!dTag) { - logger.warn({ eventId: event.id }, 'Repo announcement missing d-tag'); - continue; - } - - // Check if this is an existing repo or new repo - const cloneUrls = this.extractCloneUrls(event); - const domainUrl = cloneUrls.find(url => url.includes(this.domain)); - if (!domainUrl) continue; - - const repoPath = this.repoManager.parseRepoUrl(domainUrl); - if (!repoPath) continue; - - const repoExists = this.repoManager.repoExists(repoPath.fullPath); - const isExistingRepo = repoExists; - - // Fetch self-transfer event for this repo - const ownershipService = new OwnershipTransferService(this.relays); - const repoTag = `${KIND.REPO_ANNOUNCEMENT}:${event.pubkey}:${dTag}`; - - const selfTransferEvents = await this.nostrClient.fetchEvents([ - { - kinds: [KIND.OWNERSHIP_TRANSFER], - '#a': [repoTag], - authors: [event.pubkey], - limit: 10 - } - ]); - - // Find self-transfer event (from owner to themselves) - let selfTransferEvent: NostrEvent | undefined; - for (const transferEvent of selfTransferEvents) { - const pTag = transferEvent.tags.find(t => t[0] === 'p'); - if (pTag && pTag[1] === event.pubkey) { - // Decode npub if needed - let toPubkey = pTag[1]; - try { - const { nip19 } = await import('nostr-tools'); - const decoded = nip19.decode(toPubkey); - if (decoded.type === 'npub') { - toPubkey = decoded.data as string; - } - } catch { - // Assume it's already hex - } - - if (transferEvent.pubkey === event.pubkey && toPubkey === event.pubkey) { - selfTransferEvent = transferEvent; - break; - } - } - } - - // For existing repos without self-transfer, create one retroactively - if (isExistingRepo && !selfTransferEvent) { - // Security: Truncate pubkey in logs - const truncatedPubkey = event.pubkey.length > 16 ? `${event.pubkey.slice(0, 8)}...${event.pubkey.slice(-4)}` : event.pubkey; - logger.info({ repoId: dTag, pubkey: truncatedPubkey }, 'Existing repo has no self-transfer event. Creating template for owner to sign and publish.'); - - try { - // Create a self-transfer event template for the existing repo - // The owner will need to sign and publish this to relays - const initialOwnershipEvent = ownershipService.createInitialOwnershipEvent(event.pubkey, dTag); - - // Create an unsigned event template that can be included in the repo - // This serves as a reference and the owner can use it to create the actual event - const selfTransferTemplate = { - ...initialOwnershipEvent, - id: '', // Will be computed when signed - sig: '', // Needs owner signature - _note: 'This is a template. The owner must sign and publish this event to relays for it to be valid.' - } as NostrEvent & { _note?: string }; - - // Use the template (even though it's unsigned, it will be included in the repo) - selfTransferEvent = selfTransferTemplate; - - logger.warn({ repoId: dTag, pubkey: event.pubkey }, 'Self-transfer event template created. Owner should sign and publish it to relays.'); - } catch (err) { - logger.error({ error: err, repoId: dTag }, 'Failed to create self-transfer event template'); - } - } - - // Check if user has unlimited access before provisioning new repos - // This prevents spam and abuse - if (!isExistingRepo) { - const userLevel = getCachedUserLevel(event.pubkey); - const { hasUnlimitedAccess } = await import('../../utils/user-access.js'); - const hasAccess = hasUnlimitedAccess(userLevel?.level); - - logger.debug({ - eventId: event.id, - pubkey: event.pubkey.slice(0, 16) + '...', - cachedLevel: userLevel?.level || 'none', - hasAccess, - isExistingRepo - }, 'Checking user access for repo provisioning'); - - if (!hasAccess) { - logger.warn({ - eventId: event.id, - pubkey: event.pubkey.slice(0, 16) + '...', - level: userLevel?.level || 'none', - cacheExists: !!userLevel - }, 'Skipping repo provisioning: user does not have unlimited access'); - continue; - } - } - - // Provision the repo with self-transfer event if available - await this.repoManager.provisionRepo(event, selfTransferEvent, isExistingRepo); - logger.info({ eventId: event.id, isExistingRepo }, 'Provisioned repo from announcement'); - } catch (error) { - logger.error({ error, eventId: event.id }, 'Failed to provision repo from announcement'); - } - } - - // Mark initial poll as complete - if (!this.isInitialPollComplete) { - this.isInitialPollComplete = true; - logger.info('Initial repo poll completed'); - } - } catch (error) { - logger.error({ error }, 'Error polling for repo announcements'); - - // Still mark as complete even on error (to prevent blocking) - if (!this.isInitialPollComplete) { - this.isInitialPollComplete = true; - logger.warn('Initial repo poll completed with errors'); - } - } - } - - /** - * Extract clone URLs from a NIP-34 repo announcement - * Uses shared utility (without normalization) - */ - private extractCloneUrls(event: NostrEvent): string[] { - return extractCloneUrls(event, false); - } -} diff --git a/src/lib/services/service-registry.ts b/src/lib/services/service-registry.ts index 2e3432f..8325d60 100644 --- a/src/lib/services/service-registry.ts +++ b/src/lib/services/service-registry.ts @@ -15,7 +15,6 @@ import { ForkCountService } from './nostr/fork-count-service.js'; import { PRsService } from './nostr/prs-service.js'; import { HighlightsService } from './nostr/highlights-service.js'; import { ReleasesService } from './nostr/releases-service.js'; -import { RepoPollingService } from './nostr/repo-polling.js'; import { DEFAULT_NOSTR_RELAYS, DEFAULT_NOSTR_SEARCH_RELAYS, GIT_DOMAIN } from '../config.js'; // Get repo root from environment or use default @@ -36,7 +35,6 @@ let _forkCountService: ForkCountService | null = null; let _prsService: PRsService | null = null; let _highlightsService: HighlightsService | null = null; let _releasesService: ReleasesService | null = null; -let _repoPollingService: RepoPollingService | null = null; /** * Get singleton FileManager instance @@ -158,20 +156,6 @@ export function getReleasesService(): ReleasesService { return _releasesService; } -/** - * Get singleton RepoPollingService instance - * Note: This should be initialized in hooks.server.ts on startup - */ -export function getRepoPollingService(): RepoPollingService | null { - return _repoPollingService; -} - -/** - * Set the RepoPollingService instance (called from hooks.server.ts) - */ -export function setRepoPollingService(service: RepoPollingService): void { - _repoPollingService = service; -} // Convenience exports for direct access (common pattern) export const fileManager = getFileManager(); diff --git a/src/lib/utils/nostr-utils.ts b/src/lib/utils/nostr-utils.ts index 3253356..5d1b42b 100644 --- a/src/lib/utils/nostr-utils.ts +++ b/src/lib/utils/nostr-utils.ts @@ -12,7 +12,6 @@ import { KIND } from '../types/nostr.js'; * This is a shared utility to avoid code duplication across: * - RepoManager (with URL normalization) * - Git API endpoint (for performance, without normalization) - * - RepoPollingService * * @param event - The Nostr repository announcement event * @param normalize - Whether to normalize URLs (add .git suffix if needed). Default: false diff --git a/src/lib/utils/repo-poll-trigger.ts b/src/lib/utils/repo-poll-trigger.ts deleted file mode 100644 index 5dbb9e3..0000000 --- a/src/lib/utils/repo-poll-trigger.ts +++ /dev/null @@ -1,30 +0,0 @@ -/** - * Shared utility for triggering repo polls - * This provides a consistent interface for triggering polls from anywhere in the codebase - */ - -import { getRepoPollingService } from '../services/service-registry.js'; -import logger from '../services/logger.js'; - -/** - * Trigger a repo poll - * This is the single source of truth for triggering polls - * @param context Optional context string for logging (e.g., 'user-verification', 'manual-refresh') - * @returns Promise that resolves when poll is triggered (not when it completes) - */ -export async function triggerRepoPoll(context?: string): Promise { - const pollingService = getRepoPollingService(); - - if (!pollingService) { - logger.warn({ context }, 'Poll request received but polling service not initialized'); - throw new Error('Polling service not available'); - } - - // Trigger poll asynchronously (non-blocking) - // The poll will complete in the background - pollingService.triggerPoll().catch((err) => { - logger.error({ error: err, context }, 'Failed to trigger poll'); - }); - - logger.info({ context }, 'Repo poll triggered'); -} diff --git a/src/routes/api/openapi.json/openapi.json b/src/routes/api/openapi.json/openapi.json index caf5ca5..f6b1fc9 100644 --- a/src/routes/api/openapi.json/openapi.json +++ b/src/routes/api/openapi.json/openapi.json @@ -286,49 +286,6 @@ } } }, - "/api/repos/poll": { - "post": { - "summary": "Trigger repository polling", - "description": "Manually trigger repository polling to provision new repos from Nostr announcements. This endpoint fetches NIP-34 repo announcements from relays and provisions repositories that list this server's domain in their clone URLs. The poll runs asynchronously and does not block the request.", - "tags": ["Infrastructure"], - "responses": { - "200": { - "description": "Poll triggered successfully", - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "success": {"type": "boolean"}, - "message": {"type": "string"} - } - } - } - } - }, - "503": { - "description": "Polling service not available", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/Error" - } - } - } - }, - "500": { - "description": "Error triggering poll", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/Error" - } - } - } - } - } - } - }, "/api/repos/{npub}/{repo}/files": { "get": { "summary": "Get file content, list files, or get raw file", diff --git a/src/routes/api/repos/local/+server.ts b/src/routes/api/repos/local/+server.ts index 7dd77cb..ba612a9 100644 --- a/src/routes/api/repos/local/+server.ts +++ b/src/routes/api/repos/local/+server.ts @@ -35,7 +35,7 @@ let cache: CacheEntry | null = null; // Track server startup time to invalidate cache on first request after startup let serverStartTime = Date.now(); -const STARTUP_GRACE_PERIOD = 10000; // 10 seconds - allow time for initial poll +const STARTUP_GRACE_PERIOD = 1000; // 1 second - minimal grace period for cache /** * Invalidate cache (internal use only - not exported to avoid SvelteKit build errors) diff --git a/src/routes/api/repos/poll/+server.ts b/src/routes/api/repos/poll/+server.ts deleted file mode 100644 index 87e4ff9..0000000 --- a/src/routes/api/repos/poll/+server.ts +++ /dev/null @@ -1,33 +0,0 @@ -/** - * API endpoint for manually triggering a repo poll - * This allows users to refresh the repo list and trigger provisioning of new repos - * - * This is the public API interface for triggering polls. - * All poll triggers should go through this endpoint or the shared triggerRepoPoll utility. - */ - -import { json } from '@sveltejs/kit'; -import type { RequestHandler } from './$types'; -import { triggerRepoPoll } from '$lib/utils/repo-poll-trigger.js'; -import { extractRequestContext } from '$lib/utils/api-context.js'; - -export const POST: RequestHandler = async (event) => { - const requestContext = extractRequestContext(event); - const clientIp = requestContext.clientIp || 'unknown'; - - try { - await triggerRepoPoll('api-endpoint'); - - return json({ - success: true, - message: 'Poll triggered successfully' - }); - } catch (err) { - const errorMessage = err instanceof Error ? err.message : String(err); - - return json({ - success: false, - error: errorMessage - }, { status: err instanceof Error && errorMessage.includes('not available') ? 503 : 500 }); - } -}; diff --git a/src/routes/api/user/level/+server.ts b/src/routes/api/user/level/+server.ts index 3341aad..8a58ac3 100644 --- a/src/routes/api/user/level/+server.ts +++ b/src/routes/api/user/level/+server.ts @@ -17,7 +17,6 @@ import { extractRequestContext } from '$lib/utils/api-context.js'; import { sanitizeError } from '$lib/utils/security.js'; import { verifyEvent } from 'nostr-tools'; import logger from '$lib/services/logger.js'; -import { triggerRepoPoll } from '$lib/utils/repo-poll-trigger.js'; export const POST: RequestHandler = async (event) => { const requestContext = extractRequestContext(event); @@ -144,12 +143,6 @@ export const POST: RequestHandler = async (event) => { // Cache the successful verification cacheUserLevel(userPubkeyHex, 'unlimited'); - // Trigger a repo poll to provision repos now that user is verified - // This is non-blocking - we don't wait for it to complete - triggerRepoPoll('user-verification').catch((err) => { - logger.warn({ error: err, userPubkeyHex }, 'Failed to trigger poll after user verification (non-blocking)'); - }); - auditLogger.logAuth( userPubkeyHex, clientIp, diff --git a/src/routes/repos/+page.svelte b/src/routes/repos/+page.svelte index 81d858f..68aed13 100644 --- a/src/routes/repos/+page.svelte +++ b/src/routes/repos/+page.svelte @@ -308,7 +308,7 @@ } } - async function loadRepos(triggerPoll = false) { + async function loadRepos() { loading = true; error = null; @@ -357,31 +357,6 @@ loadForkCounts(registeredRepos.map(r => r.event)).catch(err => { console.warn('[RepoList] Failed to load some fork counts:', err); }); - - // If triggerPoll is true, trigger a poll and then refresh the list - if (triggerPoll) { - try { - // Trigger poll (non-blocking) - const pollResponse = await fetch('/api/repos/poll', { - method: 'POST', - headers: userPubkeyHex ? { - 'X-User-Pubkey': userPubkeyHex - } : {} - }); - - if (pollResponse.ok) { - // Wait a bit for the poll to process (lazy - don't wait for full completion) - // Give it 2-3 seconds to provision repos - await new Promise(resolve => setTimeout(resolve, 2500)); - - // Refresh the list after poll - await loadRepos(false); - } - } catch (pollErr) { - // Don't fail the whole operation if poll fails - console.warn('[RepoList] Failed to trigger poll:', pollErr); - } - } } catch (e) { error = String(e); console.error('[RepoList] Failed to load repos:', e); @@ -802,7 +777,7 @@

Repositories on {$page.data.gitDomain || 'localhost:6543'}

-