You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
219 lines
6.8 KiB
219 lines
6.8 KiB
/** |
|
* API endpoint for repository settings |
|
*/ |
|
|
|
import { json, error } from '@sveltejs/kit'; |
|
import type { RequestHandler } from './$types'; |
|
import { NostrClient } from '$lib/services/nostr/nostr-client.js'; |
|
import { DEFAULT_NOSTR_RELAYS, combineRelays, getGitUrl } from '$lib/config.js'; |
|
import { getUserRelays } from '$lib/services/nostr/user-relays.js'; |
|
import { KIND } from '$lib/types/nostr.js'; |
|
import { nip19 } from 'nostr-tools'; |
|
import { signEventWithNIP07 } from '$lib/services/nostr/nip07-signer.js'; |
|
import { MaintainerService } from '$lib/services/nostr/maintainer-service.js'; |
|
import { OwnershipTransferService } from '$lib/services/nostr/ownership-transfer-service.js'; |
|
|
|
const nostrClient = new NostrClient(DEFAULT_NOSTR_RELAYS); |
|
const maintainerService = new MaintainerService(DEFAULT_NOSTR_RELAYS); |
|
const ownershipTransferService = new OwnershipTransferService(DEFAULT_NOSTR_RELAYS); |
|
|
|
/** |
|
* GET - Get repository settings |
|
*/ |
|
export const GET: RequestHandler = async ({ params, url, request }) => { |
|
const { npub, repo } = params; |
|
const userPubkey = url.searchParams.get('userPubkey') || request.headers.get('x-user-pubkey'); |
|
|
|
if (!npub || !repo) { |
|
return error(400, 'Missing npub or repo parameter'); |
|
} |
|
|
|
try { |
|
// Decode npub to get pubkey |
|
let repoOwnerPubkey: string; |
|
try { |
|
const decoded = nip19.decode(npub); |
|
if (decoded.type === 'npub') { |
|
repoOwnerPubkey = decoded.data as string; |
|
} else { |
|
return error(400, 'Invalid npub format'); |
|
} |
|
} catch { |
|
return error(400, 'Invalid npub format'); |
|
} |
|
|
|
// Check if user is owner |
|
if (!userPubkey) { |
|
return error(401, 'Authentication required'); |
|
} |
|
|
|
let userPubkeyHex = userPubkey; |
|
try { |
|
const userDecoded = nip19.decode(userPubkey); |
|
if (userDecoded.type === 'npub') { |
|
userPubkeyHex = userDecoded.data as string; |
|
} |
|
} catch { |
|
// Assume it's already hex |
|
} |
|
|
|
const currentOwner = await ownershipTransferService.getCurrentOwner(repoOwnerPubkey, repo); |
|
if (userPubkeyHex !== currentOwner) { |
|
return error(403, 'Only the repository owner can access settings'); |
|
} |
|
|
|
// Get repository announcement |
|
const events = await nostrClient.fetchEvents([ |
|
{ |
|
kinds: [KIND.REPO_ANNOUNCEMENT], |
|
authors: [currentOwner], |
|
'#d': [repo], |
|
limit: 1 |
|
} |
|
]); |
|
|
|
if (events.length === 0) { |
|
return error(404, 'Repository announcement not found'); |
|
} |
|
|
|
const announcement = events[0]; |
|
const name = announcement.tags.find(t => t[0] === 'name')?.[1] || 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 isPrivate = await maintainerService.isRepoPrivate(currentOwner, repo); |
|
|
|
return json({ |
|
name, |
|
description, |
|
cloneUrls, |
|
maintainers, |
|
isPrivate, |
|
owner: currentOwner, |
|
npub |
|
}); |
|
} catch (err) { |
|
console.error('Error getting repository settings:', err); |
|
return error(500, err instanceof Error ? err.message : 'Failed to get repository settings'); |
|
} |
|
}; |
|
|
|
/** |
|
* POST - Update repository settings |
|
*/ |
|
export const POST: RequestHandler = async ({ params, request }) => { |
|
const { npub, repo } = params; |
|
|
|
if (!npub || !repo) { |
|
return error(400, 'Missing npub or repo parameter'); |
|
} |
|
|
|
try { |
|
const body = await request.json(); |
|
const { userPubkey, name, description, cloneUrls, maintainers, isPrivate } = body; |
|
|
|
if (!userPubkey) { |
|
return error(401, 'Authentication required'); |
|
} |
|
|
|
// Decode npub to get pubkey |
|
let repoOwnerPubkey: string; |
|
try { |
|
const decoded = nip19.decode(npub); |
|
if (decoded.type === 'npub') { |
|
repoOwnerPubkey = decoded.data as string; |
|
} else { |
|
return error(400, 'Invalid npub format'); |
|
} |
|
} catch { |
|
return error(400, 'Invalid npub format'); |
|
} |
|
|
|
let userPubkeyHex = userPubkey; |
|
try { |
|
const userDecoded = nip19.decode(userPubkey); |
|
if (userDecoded.type === 'npub') { |
|
userPubkeyHex = userDecoded.data as string; |
|
} |
|
} catch { |
|
// Assume it's already hex |
|
} |
|
|
|
// Check if user is owner |
|
const currentOwner = await ownershipTransferService.getCurrentOwner(repoOwnerPubkey, repo); |
|
if (userPubkeyHex !== currentOwner) { |
|
return error(403, 'Only the repository owner can update settings'); |
|
} |
|
|
|
// Get existing announcement |
|
const events = await nostrClient.fetchEvents([ |
|
{ |
|
kinds: [KIND.REPO_ANNOUNCEMENT], |
|
authors: [currentOwner], |
|
'#d': [repo], |
|
limit: 1 |
|
} |
|
]); |
|
|
|
if (events.length === 0) { |
|
return error(404, 'Repository announcement not found'); |
|
} |
|
|
|
const existingAnnouncement = events[0]; |
|
|
|
// Build updated tags |
|
const gitDomain = process.env.GIT_DOMAIN || 'localhost:6543'; |
|
const protocol = gitDomain.startsWith('localhost') ? 'http' : 'https'; |
|
const gitUrl = `${protocol}://${gitDomain}/${npub}/${repo}.git`; |
|
|
|
const tags: string[][] = [ |
|
['d', repo], |
|
['name', name || repo], |
|
...(description ? [['description', description]] : []), |
|
['clone', gitUrl, ...(cloneUrls || []).filter((url: string) => url && !url.includes(gitDomain))], |
|
['relays', ...DEFAULT_NOSTR_RELAYS], |
|
...(isPrivate ? [['private', 'true']] : []), |
|
...(maintainers || []).map((m: string) => ['maintainers', m]) |
|
]; |
|
|
|
// 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) { |
|
return error(500, 'Failed to publish updated announcement to relays'); |
|
} |
|
|
|
return json({ success: true, event: signedEvent }); |
|
} catch (err) { |
|
console.error('Error updating repository settings:', err); |
|
return error(500, err instanceof Error ? err.message : 'Failed to update repository settings'); |
|
} |
|
};
|
|
|