Browse Source
Nostr-Signature: e036526abc826e4435a562f1f334e594577d78a7a50a02cb78f8e5565ea68872 573634b648634cbad10f2451776089ea21090d9407f715e83c577b4611ae6edc 12642202ef028dfbac68ce53e9cf9f7a64ce3242d2dd995fd0b4c4014c9aa2b18891b72dc281fa5aadacd636646ebd8d2b69fd29bf36407658dff9725b779be5main
11 changed files with 1017 additions and 30 deletions
@ -0,0 +1,88 @@ |
|||||||
|
/** |
||||||
|
* API endpoint for merging Pull Requests |
||||||
|
*/ |
||||||
|
|
||||||
|
import { json } from '@sveltejs/kit'; |
||||||
|
import type { RequestHandler } from './$types'; |
||||||
|
import { withRepoValidation } from '$lib/utils/api-handlers.js'; |
||||||
|
import type { RepoRequestContext } from '$lib/utils/api-context.js'; |
||||||
|
import { handleValidationError, handleApiError } from '$lib/utils/error-handler.js'; |
||||||
|
import { DEFAULT_NOSTR_RELAYS } from '$lib/config.js'; |
||||||
|
import { RepoManager } from '$lib/services/git/repo-manager.js'; |
||||||
|
import { FileManager } from '$lib/services/git/file-manager.js'; |
||||||
|
import { MaintainerService } from '$lib/services/nostr/maintainer-service.js'; |
||||||
|
import { prsService } from '$lib/services/service-registry.js'; |
||||||
|
import { simpleGit } from 'simple-git'; |
||||||
|
import { join, resolve } from 'path'; |
||||||
|
import { existsSync } from 'fs'; |
||||||
|
import logger from '$lib/services/logger.js'; |
||||||
|
|
||||||
|
const repoRoot = process.env.GIT_REPO_ROOT || '/repos'; |
||||||
|
const repoManager = new RepoManager(repoRoot); |
||||||
|
const fileManager = new FileManager(repoRoot); |
||||||
|
const maintainerService = new MaintainerService(DEFAULT_NOSTR_RELAYS); |
||||||
|
|
||||||
|
export const POST: RequestHandler = withRepoValidation( |
||||||
|
async ({ repoContext, requestContext, event }) => { |
||||||
|
const body = await event.request.json(); |
||||||
|
const { prId, prAuthor, prCommitId, targetBranch = 'main', mergeMessage } = body; |
||||||
|
|
||||||
|
if (!prId || !prAuthor || !prCommitId) { |
||||||
|
throw handleValidationError('Missing required fields: prId, prAuthor, prCommitId', { operation: 'mergePR', npub: repoContext.npub, repo: repoContext.repo }); |
||||||
|
} |
||||||
|
|
||||||
|
// Check if user is maintainer
|
||||||
|
const isMaintainer = await maintainerService.isMaintainer(requestContext.userPubkeyHex || '', repoContext.repoOwnerPubkey, repoContext.repo); |
||||||
|
|
||||||
|
if (!isMaintainer && requestContext.userPubkeyHex !== repoContext.repoOwnerPubkey) { |
||||||
|
throw handleApiError(new Error('Only repository owners and maintainers can merge PRs'), { operation: 'mergePR', npub: repoContext.npub, repo: repoContext.repo }, 'Unauthorized'); |
||||||
|
} |
||||||
|
|
||||||
|
// Check if repo exists locally
|
||||||
|
const repoPath = join(repoRoot, repoContext.npub, `${repoContext.repo}.git`); |
||||||
|
if (!existsSync(repoPath)) { |
||||||
|
throw handleApiError(new Error('Repository not cloned locally. Please clone the repository first.'), { operation: 'mergePR', npub: repoContext.npub, repo: repoContext.repo }, 'Repository not found'); |
||||||
|
} |
||||||
|
|
||||||
|
// Get user info for commit
|
||||||
|
const authorName = requestContext.userName || 'GitRepublic User'; |
||||||
|
const authorEmail = requestContext.userEmail || `${requestContext.userPubkeyHex?.slice(0, 20)}@gitrepublic.web`; |
||||||
|
|
||||||
|
try { |
||||||
|
const git = simpleGit(repoPath); |
||||||
|
|
||||||
|
// Fetch latest changes
|
||||||
|
await git.fetch(['origin']).catch(() => {}); // Ignore errors if no remote
|
||||||
|
|
||||||
|
// Checkout target branch
|
||||||
|
await git.checkout(targetBranch); |
||||||
|
|
||||||
|
// Merge the PR commit
|
||||||
|
const mergeMessageText = mergeMessage || `Merge pull request ${prId.slice(0, 7)}`; |
||||||
|
await git.merge([prCommitId, '--no-ff', '-m', mergeMessageText]); |
||||||
|
|
||||||
|
// Get the merge commit ID
|
||||||
|
const mergeCommitId = (await git.revparse(['HEAD'])).trim(); |
||||||
|
|
||||||
|
// Update PR status to merged
|
||||||
|
const statusEvent = await prsService.updatePRStatus( |
||||||
|
prId, |
||||||
|
prAuthor, |
||||||
|
repoContext.repoOwnerPubkey, |
||||||
|
repoContext.repo, |
||||||
|
'merged', |
||||||
|
mergeCommitId |
||||||
|
); |
||||||
|
|
||||||
|
return json({
|
||||||
|
success: true,
|
||||||
|
mergeCommitId, |
||||||
|
statusEvent
|
||||||
|
}); |
||||||
|
} catch (err) { |
||||||
|
logger.error({ error: err, npub: repoContext.npub, repo: repoContext.repo, prId, prCommitId }, 'Error merging PR'); |
||||||
|
throw handleApiError(err instanceof Error ? err : new Error('Failed to merge PR'), { operation: 'mergePR', npub: repoContext.npub, repo: repoContext.repo }, 'Failed to merge pull request'); |
||||||
|
} |
||||||
|
}, |
||||||
|
{ operation: 'mergePR', requireRepoAccess: true } |
||||||
|
); |
||||||
@ -0,0 +1,43 @@ |
|||||||
|
/** |
||||||
|
* API endpoint for updating Pull Requests (kind 1619) |
||||||
|
*/ |
||||||
|
|
||||||
|
import { json } from '@sveltejs/kit'; |
||||||
|
// @ts-ignore - SvelteKit generates this type
|
||||||
|
import type { RequestHandler } from './$types'; |
||||||
|
import { withRepoValidation } from '$lib/utils/api-handlers.js'; |
||||||
|
import type { RepoRequestContext } from '$lib/utils/api-context.js'; |
||||||
|
import { handleValidationError, handleApiError } from '$lib/utils/error-handler.js'; |
||||||
|
import { DEFAULT_NOSTR_RELAYS } from '$lib/config.js'; |
||||||
|
import { prsService } from '$lib/services/service-registry.js'; |
||||||
|
import { getGitUrl } from '$lib/config.js'; |
||||||
|
|
||||||
|
export const POST: RequestHandler = withRepoValidation( |
||||||
|
async ({ repoContext, requestContext, event }) => { |
||||||
|
const body = await event.request.json(); |
||||||
|
const { prId, prAuthor, newCommitId, mergeBase } = body; |
||||||
|
|
||||||
|
if (!prId || !prAuthor || !newCommitId) { |
||||||
|
throw handleValidationError('Missing required fields: prId, prAuthor, newCommitId', { operation: 'updatePR', npub: repoContext.npub, repo: repoContext.repo }); |
||||||
|
} |
||||||
|
|
||||||
|
// Only PR author can update their PR
|
||||||
|
if (requestContext.userPubkeyHex !== prAuthor) { |
||||||
|
throw handleApiError(new Error('Only the PR author can update the PR'), { operation: 'updatePR', npub: repoContext.npub, repo: repoContext.repo }, 'Unauthorized'); |
||||||
|
} |
||||||
|
|
||||||
|
const cloneUrl = getGitUrl(repoContext.npub, repoContext.repo); |
||||||
|
const updateEvent = await prsService.updatePullRequest( |
||||||
|
prId, |
||||||
|
prAuthor, |
||||||
|
repoContext.repoOwnerPubkey, |
||||||
|
repoContext.repo, |
||||||
|
newCommitId, |
||||||
|
cloneUrl, |
||||||
|
mergeBase |
||||||
|
); |
||||||
|
|
||||||
|
return json({ success: true, event: updateEvent }); |
||||||
|
}, |
||||||
|
{ operation: 'updatePR', requireRepoAccess: false } |
||||||
|
); |
||||||
Loading…
Reference in new issue