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

/**
* 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');
}
};