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.
173 lines
5.9 KiB
173 lines
5.9 KiB
/** |
|
* API endpoint for reading and writing files in a repository |
|
*/ |
|
|
|
import { json, error } from '@sveltejs/kit'; |
|
// @ts-ignore - SvelteKit generates this type |
|
import type { RequestHandler } from './$types'; |
|
import { FileManager } from '$lib/services/git/file-manager.js'; |
|
import { MaintainerService } from '$lib/services/nostr/maintainer-service.js'; |
|
import { DEFAULT_NOSTR_RELAYS } from '$lib/config.js'; |
|
import { nip19 } from 'nostr-tools'; |
|
import { verifyNIP98Auth } from '$lib/services/nostr/nip98-auth.js'; |
|
|
|
const repoRoot = process.env.GIT_REPO_ROOT || '/repos'; |
|
const fileManager = new FileManager(repoRoot); |
|
const maintainerService = new MaintainerService(DEFAULT_NOSTR_RELAYS); |
|
|
|
export const GET: RequestHandler = async ({ params, url, request }: { params: { npub?: string; repo?: string }; url: URL; request: Request }) => { |
|
const { npub, repo } = params; |
|
const filePath = url.searchParams.get('path'); |
|
const ref = url.searchParams.get('ref') || 'HEAD'; |
|
const userPubkey = url.searchParams.get('userPubkey') || request.headers.get('x-user-pubkey'); |
|
|
|
if (!npub || !repo || !filePath) { |
|
return error(400, 'Missing npub, repo, or path parameter'); |
|
} |
|
|
|
try { |
|
if (!fileManager.repoExists(npub, repo)) { |
|
return error(404, 'Repository not found'); |
|
} |
|
|
|
// Check repository privacy |
|
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'); |
|
} |
|
|
|
const canView = await maintainerService.canView(userPubkey || null, repoOwnerPubkey, repo); |
|
if (!canView) { |
|
return error(403, 'This repository is private. Only owners and maintainers can view it.'); |
|
} |
|
|
|
const fileContent = await fileManager.getFileContent(npub, repo, filePath, ref); |
|
return json(fileContent); |
|
} catch (err) { |
|
console.error('Error reading file:', err); |
|
return error(500, err instanceof Error ? err.message : 'Failed to read file'); |
|
} |
|
}; |
|
|
|
export const POST: RequestHandler = async ({ params, url, request }: { params: { npub?: string; repo?: string }; url: URL; request: Request }) => { |
|
const { npub, repo } = params; |
|
|
|
if (!npub || !repo) { |
|
return error(400, 'Missing npub or repo parameter'); |
|
} |
|
|
|
try { |
|
const body = await request.json(); |
|
const { path, content, commitMessage, authorName, authorEmail, branch, action, userPubkey, useNIP07, nsecKey } = body; |
|
|
|
// Check for NIP-98 authentication (for git operations) |
|
const authHeader = request.headers.get('Authorization'); |
|
let nip98Event = null; |
|
if (authHeader && authHeader.startsWith('Nostr ')) { |
|
const requestUrl = `${request.headers.get('x-forwarded-proto') || (url.protocol === 'https:' ? 'https' : 'http')}://${request.headers.get('host') || url.host}${url.pathname}${url.search}`; |
|
const authResult = verifyNIP98Auth(authHeader, requestUrl, request.method); |
|
if (authResult.valid && authResult.event) { |
|
nip98Event = authResult.event; |
|
} |
|
} |
|
|
|
if (!path || !commitMessage || !authorName || !authorEmail) { |
|
return error(400, 'Missing required fields: path, commitMessage, authorName, authorEmail'); |
|
} |
|
|
|
if (!userPubkey) { |
|
return error(401, 'Authentication required. Please provide userPubkey.'); |
|
} |
|
|
|
if (!fileManager.repoExists(npub, repo)) { |
|
return error(404, 'Repository not found'); |
|
} |
|
|
|
// Check if user is a maintainer |
|
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'); |
|
} |
|
|
|
// Convert userPubkey to hex if needed |
|
let userPubkeyHex = userPubkey; |
|
try { |
|
const userDecoded = nip19.decode(userPubkey); |
|
// @ts-ignore - nip19 types are incomplete, but we know npub returns string |
|
if (userDecoded.type === 'npub') { |
|
userPubkeyHex = userDecoded.data as unknown as string; |
|
} |
|
} catch { |
|
// Assume it's already a hex pubkey |
|
} |
|
|
|
const isMaintainer = await maintainerService.isMaintainer(userPubkeyHex, repoOwnerPubkey, repo); |
|
if (!isMaintainer) { |
|
return error(403, 'Only repository maintainers can edit files directly. Please submit a pull request instead.'); |
|
} |
|
|
|
// Prepare signing options |
|
const signingOptions: { |
|
useNIP07?: boolean; |
|
nip98Event?: any; |
|
nsecKey?: string; |
|
} = {}; |
|
|
|
if (useNIP07) { |
|
signingOptions.useNIP07 = true; |
|
} else if (nip98Event) { |
|
signingOptions.nip98Event = nip98Event; |
|
} else if (nsecKey) { |
|
signingOptions.nsecKey = nsecKey; |
|
} |
|
|
|
if (action === 'delete') { |
|
await fileManager.deleteFile( |
|
npub, |
|
repo, |
|
path, |
|
commitMessage, |
|
authorName, |
|
authorEmail, |
|
branch || 'main', |
|
Object.keys(signingOptions).length > 0 ? signingOptions : undefined |
|
); |
|
return json({ success: true, message: 'File deleted and committed' }); |
|
} else if (action === 'create' || content !== undefined) { |
|
if (content === undefined) { |
|
return error(400, 'Content is required for create/update operations'); |
|
} |
|
await fileManager.writeFile( |
|
npub, |
|
repo, |
|
path, |
|
content, |
|
commitMessage, |
|
authorName, |
|
authorEmail, |
|
branch || 'main', |
|
Object.keys(signingOptions).length > 0 ? signingOptions : undefined |
|
); |
|
return json({ success: true, message: 'File saved and committed' }); |
|
} else { |
|
return error(400, 'Invalid action or missing content'); |
|
} |
|
} catch (err) { |
|
console.error('Error writing file:', err); |
|
return error(500, err instanceof Error ? err.message : 'Failed to write file'); |
|
} |
|
};
|
|
|