Redirecting to repository...
-diff --git a/nostr/commit-signatures.jsonl b/nostr/commit-signatures.jsonl index 1af4693..b07e3cf 100644 --- a/nostr/commit-signatures.jsonl +++ b/nostr/commit-signatures.jsonl @@ -7,3 +7,4 @@ {"kind":1640,"pubkey":"573634b648634cbad10f2451776089ea21090d9407f715e83c577b4611ae6edc","created_at":1771520422,"tags":[["author","Silberengel","silberengel7@protonmail.com"],["message","correct icons"]],"content":"Signed commit: correct icons","id":"3d630436d21542aa097b574829ba03f9700db4d707f3d7065bc24000321d0ba2","sig":"6e345bb8ca6fef352400dd10a801d1f41b8798b7a0307eba9af84ea3b4045235b50510905ab2cc9cbdd2894b56a0d1524560a9347c137f39cf756c43ca72c326"} {"kind":1640,"pubkey":"573634b648634cbad10f2451776089ea21090d9407f715e83c577b4611ae6edc","created_at":1771520523,"tags":[["author","Silberengel","silberengel7@protonmail.com"],["message","fix contrast"]],"content":"Signed commit: fix contrast","id":"210177972a67b45a8c56494f2423987ffd30fc5594c539ed6a9f23c0f0992d21","sig":"c3122ebc0055f5a7145d394b9461b811b6e37a7423493d62a6debf7078c006435352e2e2a4259fce6a8a13486bdd137e2e7a49bfdf512a37a73d0c36d405ff2f"} {"kind":1640,"pubkey":"573634b648634cbad10f2451776089ea21090d9407f715e83c577b4611ae6edc","created_at":1771522633,"tags":[["author","Silberengel","silberengel7@protonmail.com"],["message","adjusting api for themes"]],"content":"Signed commit: adjusting api for themes","id":"c6125da849827ef6481eed3588231630470289db0176066fc9c1e044f839976b","sig":"7a943b493af9d7108a26fb3bad8166e58ba2ed08eb6c24c178775387620601e6a130ce8a0f344a79e637fc4e75ed2e6d308a242101b14bdb38ccb901c09ff13f"} +{"kind":1640,"pubkey":"573634b648634cbad10f2451776089ea21090d9407f715e83c577b4611ae6edc","created_at":1771529356,"tags":[["author","Silberengel","silberengel7@protonmail.com"],["message","update transfer workflow"]],"content":"Signed commit: update transfer workflow","id":"5d6d6909666a881f88f240389d30f5bedd36dba5d69a9d24dca86557b0098867","sig":"d13caca8b3e1009469e28c352bdfacf5eb78e2e9f5ac80c8511a9e2c6c5ac7031b83374d2d91b93b8018b5a3402e3e9c7114332da89ee2cb039f64aa3207f3f4"} diff --git a/src/hooks.server.ts b/src/hooks.server.ts index 8dcb417..2a188f3 100644 --- a/src/hooks.server.ts +++ b/src/hooks.server.ts @@ -73,7 +73,6 @@ export const handle: Handle = async ({ event, resolve }) => { !url.pathname.includes('/file') && // File operations are rate limited separately !url.pathname.includes('/delete') && !url.pathname.includes('/transfer') && - !url.pathname.includes('/settings') && // Settings might be write operations (url.pathname.endsWith('/fork') || // GET /fork is read-only url.pathname.endsWith('/verify') || // GET /verify is read-only url.pathname.endsWith('/readme') || // GET /readme is read-only diff --git a/src/routes/api/openapi.json/openapi.json b/src/routes/api/openapi.json/openapi.json index b5681fb..7959e6c 100644 --- a/src/routes/api/openapi.json/openapi.json +++ b/src/routes/api/openapi.json/openapi.json @@ -130,45 +130,6 @@ } } }, - "RepositorySettings": { - "type": "object", - "properties": { - "name": { - "type": "string" - }, - "description": { - "type": "string" - }, - "cloneUrls": { - "type": "array", - "items": { - "type": "string" - } - }, - "maintainers": { - "type": "array", - "items": { - "type": "string" - } - }, - "chatRelays": { - "type": "array", - "items": { - "type": "string" - } - }, - "isPrivate": { - "type": "boolean" - }, - "owner": { - "type": "string", - "format": "hex" - }, - "npub": { - "type": "string" - } - } - }, "FileContent": { "type": "object", "properties": { @@ -331,149 +292,6 @@ } } }, - "/api/repos/{npub}/{repo}/settings": { - "get": { - "summary": "Get repository settings", - "description": "Get repository settings. Requires owner authentication.", - "tags": ["Repositories"], - "security": [{"NIP98": []}], - "parameters": [ - { - "name": "npub", - "in": "path", - "required": true, - "schema": { - "type": "string" - }, - "description": "Repository owner's npub" - }, - { - "name": "repo", - "in": "path", - "required": true, - "schema": { - "type": "string" - }, - "description": "Repository name" - } - ], - "responses": { - "200": { - "description": "Repository settings", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/RepositorySettings" - } - } - } - }, - "403": { - "description": "Not authorized", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/Error" - } - } - } - } - } - }, - "post": { - "summary": "Update repository settings", - "description": "Update repository settings. Requires owner authentication.", - "tags": ["Repositories"], - "security": [{"NIP98": []}], - "parameters": [ - { - "name": "npub", - "in": "path", - "required": true, - "schema": { - "type": "string" - } - }, - { - "name": "repo", - "in": "path", - "required": true, - "schema": { - "type": "string" - } - } - ], - "requestBody": { - "required": true, - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "name": { - "type": "string" - }, - "description": { - "type": "string" - }, - "cloneUrls": { - "type": "array", - "items": { - "type": "string" - } - }, - "maintainers": { - "type": "array", - "items": { - "type": "string" - } - }, - "chatRelays": { - "type": "array", - "items": { - "type": "string" - } - }, - "isPrivate": { - "type": "boolean" - } - } - } - } - } - }, - "responses": { - "200": { - "description": "Settings updated", - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "success": { - "type": "boolean" - }, - "event": { - "$ref": "#/components/schemas/NostrEvent" - } - } - } - } - } - }, - "403": { - "description": "Not authorized", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/Error" - } - } - } - } - } - } - }, "/api/repos/{npub}/{repo}/file": { "get": { "summary": "Get file content", diff --git a/src/routes/api/repos/[npub]/[repo]/settings/+server.ts b/src/routes/api/repos/[npub]/[repo]/settings/+server.ts deleted file mode 100644 index b6649df..0000000 --- a/src/routes/api/repos/[npub]/[repo]/settings/+server.ts +++ /dev/null @@ -1,222 +0,0 @@ -/** - * API endpoint for repository settings - */ - -import { json, error } from '@sveltejs/kit'; -import type { RequestHandler } from './$types'; -import { nostrClient, maintainerService, ownershipTransferService, fileManager } from '$lib/services/service-registry.js'; -import { DEFAULT_NOSTR_RELAYS, combineRelays } from '$lib/config.js'; -import { getUserRelays } from '$lib/services/nostr/user-relays.js'; -import { KIND } from '$lib/types/nostr.js'; -import { signEventWithNIP07 } from '$lib/services/nostr/nip07-signer.js'; -import { createRepoGetHandler, withRepoValidation } from '$lib/utils/api-handlers.js'; -import type { RepoRequestContext, RequestEvent } from '$lib/utils/api-context.js'; -import { handleApiError, handleValidationError, handleNotFoundError, handleAuthorizationError } from '$lib/utils/error-handler.js'; -import { generateVerificationFile, VERIFICATION_FILE_PATH } from '$lib/services/nostr/repo-verification.js'; -import { nip19 } from 'nostr-tools'; -import logger from '$lib/services/logger.js'; - -/** - * GET - Get repository settings - */ -export const GET: RequestHandler = createRepoGetHandler( - async (context: RepoRequestContext) => { - // Check if user is owner - if (!context.userPubkeyHex) { - throw handleApiError(new Error('Authentication required'), { operation: 'getSettings', npub: context.npub, repo: context.repo }, 'Authentication required'); - } - - const currentOwner = await ownershipTransferService.getCurrentOwner(context.repoOwnerPubkey, context.repo); - if (context.userPubkeyHex !== currentOwner) { - throw handleAuthorizationError('Only the repository owner can access settings', { operation: 'getSettings', npub: context.npub, repo: context.repo }); - } - - // Get repository announcement - const events = await nostrClient.fetchEvents([ - { - kinds: [KIND.REPO_ANNOUNCEMENT], - authors: [currentOwner], - '#d': [context.repo], - limit: 1 - } - ]); - - if (events.length === 0) { - throw handleNotFoundError('Repository announcement not found', { operation: 'getSettings', npub: context.npub, repo: context.repo }); - } - - const announcement = events[0]; - const name = announcement.tags.find(t => t[0] === 'name')?.[1] || context.repo; - const description = announcement.tags.find(t => t[0] === 'description')?.[1] || ''; - const cloneUrls = announcement.tags - .filter(t => t[0] === 'clone') - .flatMap(t => t.slice(1)) - .filter(url => url && typeof url === 'string') as string[]; - const maintainers = announcement.tags - .filter(t => t[0] === 'maintainers') - .flatMap(t => t.slice(1)) - .filter(m => m && typeof m === 'string') as string[]; - const chatRelays = announcement.tags - .filter(t => t[0] === 'chat-relay') - .flatMap(t => t.slice(1)) - .filter(url => url && typeof url === 'string') as string[]; - const privacyInfo = await maintainerService.getPrivacyInfo(currentOwner, context.repo); - const isPrivate = privacyInfo.isPrivate; - - return json({ - name, - description, - cloneUrls, - maintainers, - chatRelays, - isPrivate, - owner: currentOwner, - npub: context.npub - }); - }, - { operation: 'getSettings', requireRepoAccess: false } // Override to check owner instead -); - -/** - * POST - Update repository settings - */ -export const POST: RequestHandler = withRepoValidation( - async ({ repoContext, requestContext, event }) => { - if (!requestContext.userPubkeyHex) { - throw handleApiError(new Error('Authentication required'), { operation: 'updateSettings', npub: repoContext.npub, repo: repoContext.repo }, 'Authentication required'); - } - - const body = await event.request.json(); - const { name, description, cloneUrls, maintainers, chatRelays, isPrivate } = body; - - // Check if user is owner - const currentOwner = await ownershipTransferService.getCurrentOwner(repoContext.repoOwnerPubkey, repoContext.repo); - if (requestContext.userPubkeyHex !== currentOwner) { - throw handleAuthorizationError('Only the repository owner can update settings', { operation: 'updateSettings', npub: repoContext.npub, repo: repoContext.repo }); - } - - // Get existing announcement - const events = await nostrClient.fetchEvents([ - { - kinds: [KIND.REPO_ANNOUNCEMENT], - authors: [currentOwner], - '#d': [repoContext.repo], - limit: 1 - } - ]); - - if (events.length === 0) { - throw handleNotFoundError('Repository announcement not found', { operation: 'updateSettings', npub: repoContext.npub, repo: repoContext.repo }); - } - - const existingAnnouncement = events[0]; - - // Build updated tags - const gitDomain = process.env.GIT_DOMAIN || 'localhost:6543'; - const isLocalhost = gitDomain.startsWith('localhost') || gitDomain.startsWith('127.0.0.1'); - const protocol = isLocalhost ? 'http' : 'https'; - const gitUrl = `${protocol}://${gitDomain}/${repoContext.npub}/${repoContext.repo}.git`; - - // Get Tor .onion URL if available - const { getTorGitUrl } = await import('$lib/services/tor/hidden-service.js'); - const torOnionUrl = await getTorGitUrl(repoContext.npub, repoContext.repo); - - // Filter user-provided clone URLs (exclude localhost and .onion duplicates) - const userCloneUrls = (cloneUrls || []).filter((url: string) => { - if (!url || !url.trim()) return false; - // Exclude if it's our domain or already a .onion - if (url.includes(gitDomain)) return false; - if (url.includes('.onion')) return false; - return true; - }); - - // Build clone URLs - NEVER include localhost, only include public domain or Tor .onion - const cloneUrlList: string[] = []; - - // Add our domain URL only if it's NOT localhost (explicitly check the URL) - if (!isLocalhost && !gitUrl.includes('localhost') && !gitUrl.includes('127.0.0.1')) { - cloneUrlList.push(gitUrl); - } - - // Add Tor .onion URL if available (always useful, even with localhost) - if (torOnionUrl) { - cloneUrlList.push(torOnionUrl); - } - - // Add user-provided clone URLs - cloneUrlList.push(...userCloneUrls); - - // Validate: If using localhost, require either Tor .onion URL or at least one other clone URL - if (isLocalhost && !torOnionUrl && userCloneUrls.length === 0) { - throw error(400, 'Cannot update with only localhost. You need either a Tor .onion address or at least one other clone URL.'); - } - - const tags: string[][] = [ - ['d', repoContext.repo], - ['name', name || repoContext.repo], - ...(description ? [['description', description]] : []), - ['clone', ...cloneUrlList], - ['relays', ...DEFAULT_NOSTR_RELAYS], - ...(isPrivate ? [['private', 'true']] : []), - ...(maintainers || []).map((m: string) => ['maintainers', m]), - ...(chatRelays && chatRelays.length > 0 ? [['chat-relay', ...chatRelays]] : []) - ]; - - // Preserve other tags from original announcement - const preserveTags = ['r', 'web', 't']; - for (const tag of existingAnnouncement.tags) { - if (preserveTags.includes(tag[0]) && !tags.some(t => t[0] === tag[0])) { - tags.push(tag); - } - } - - // Create updated announcement - const updatedAnnouncement = { - kind: KIND.REPO_ANNOUNCEMENT, - pubkey: currentOwner, - created_at: Math.floor(Date.now() / 1000), - content: '', - tags - }; - - // Sign and publish - const signedEvent = await signEventWithNIP07(updatedAnnouncement); - - const { outbox } = await getUserRelays(currentOwner, nostrClient); - const combinedRelays = combineRelays(outbox); - - const result = await nostrClient.publishEvent(signedEvent, combinedRelays); - - if (result.success.length === 0) { - throw error(500, 'Failed to publish updated announcement to relays'); - } - - // Save updated announcement to repo (offline papertrail) - try { - const announcementFileContent = generateVerificationFile(signedEvent, currentOwner); - - // Save to repo if it exists locally - if (fileManager.repoExists(repoContext.npub, repoContext.repo)) { - await fileManager.writeFile( - repoContext.npub, - repoContext.repo, - VERIFICATION_FILE_PATH, - announcementFileContent, - `Update repository announcement: ${signedEvent.id.slice(0, 16)}...`, - 'Nostr', - `${currentOwner}@nostr`, - 'main' - ).catch(err => { - // Log but don't fail - publishing to relays is more important - logger.warn({ error: err, npub: repoContext.npub, repo: repoContext.repo }, 'Failed to save updated announcement to repo'); - }); - } - } catch (err) { - // Log but don't fail - publishing to relays is more important - logger.warn({ error: err, npub: repoContext.npub, repo: repoContext.repo }, 'Failed to save updated announcement to repo'); - } - - return json({ success: true, event: signedEvent }); - }, - { operation: 'updateSettings', requireRepoAccess: false } // Override to check owner instead -); diff --git a/src/routes/repos/[npub]/[repo]/+page.svelte b/src/routes/repos/[npub]/[repo]/+page.svelte index 9040dd2..8d8cbc8 100644 --- a/src/routes/repos/[npub]/[repo]/+page.svelte +++ b/src/routes/repos/[npub]/[repo]/+page.svelte @@ -2987,7 +2987,7 @@ {/if} {#if isMaintainer} - Settings + Settings {/if} {#if pageData.repoOwnerPubkey && userPubkeyHex === pageData.repoOwnerPubkey} {#if verificationStatus?.verified !== true} diff --git a/src/routes/repos/[npub]/[repo]/settings/+page.svelte b/src/routes/repos/[npub]/[repo]/settings/+page.svelte deleted file mode 100644 index 5cb05d9..0000000 --- a/src/routes/repos/[npub]/[repo]/settings/+page.svelte +++ /dev/null @@ -1,237 +0,0 @@ - - -
Redirecting to repository...
-