import { parseCanonicalResourceUri } from "@atcute/lexicons/syntax"; import type { OAuthUserAgent } from "@atcute/oauth-browser-client"; import { mintServiceAuth, serviceDidForHost } from "$lib/auth/agent"; import { createCob, knotProcedure, type CobRef } from "$lib/api/cob"; import { createTicket, eraseTicket, type TicketProse } from "$lib/api/ticket"; import { datetimeOf, didOf, nsidOf, type AtUri, type Did, type GitOid, type KnotOrigin, type Nsid } from "$lib/api/syntax"; import { buildUrl, toResponseError } from "$lib/api/_request"; import type { PatchRecord } from "$lib/api/records"; const PATCH_COLLECTION = nsidOf("org.tangled.review.patch"); export interface PatchRange { base: GitOid; head: GitOid; } // both commits have to be in the target repo already, so fork pulls don't work yet export const createPatch = async ( agent: OAuthUserAgent, knot: KnotOrigin, ticket: AtUri, { base, head }: PatchRange ): Promise => { const { repo, rkey } = parseCanonicalResourceUri(ticket); const record: PatchRecord = { $type: "org.tangled.review.patch", ticket: rkey, base: { oid: base }, head: { oid: head }, createdAt: datetimeOf(new Date().toISOString()) }; return createCob(agent, knot, didOf(repo), PATCH_COLLECTION, record); }; // if the knot refuses the patch, erase the ticket again so there's no pull without a diff export const openPull = async ( agent: OAuthUserAgent, knot: KnotOrigin, repo: Did, prose: TicketProse, targetBranch: string, range: PatchRange ): Promise => { const ticket = await createTicket(agent, knot, repo, { ...prose, targetBranch }); try { await createPatch(agent, knot, ticket.uri, range); } catch (cause) { await eraseTicket(agent, knot, ticket).catch(() => {}); throw cause; } return ticket; }; const PULL_MERGE_NSID = nsidOf("sh.tangled.repo.pull.merge"); export const mergePull = async ( agent: OAuthUserAgent, knot: KnotOrigin, patch: AtUri ): Promise => { const { repo, rkey } = parseCanonicalResourceUri(patch); await knotProcedure(agent, knot, didOf(repo), PULL_MERGE_NSID, { rkey }); }; const DELETE_BRANCH_NSID = nsidOf("org.tangled.temp.git.deleteBranch"); const REPO_PUSH_NSID = nsidOf("sh.tangled.repo.push"); const mirrorPush = async ( agent: OAuthUserAgent, bobbin: string, knot: string, nsid: Nsid, body: unknown, fetch: typeof globalThis.fetch ): Promise => { const token = await mintServiceAuth(agent, { aud: serviceDidForHost(knot), // git write operations expect `sh.tangled.repo.push` lxm token lxm: REPO_PUSH_NSID }); const response = await fetch(buildUrl(bobbin, nsid), { method: "POST", headers: { "content-type": "application/json", accept: "application/json", authorization: `Bearer ${token}` }, body: JSON.stringify(body) }); if (!response.ok) throw await toResponseError(response); }; export const deleteBranch = ( agent: OAuthUserAgent, bobbin: string, knot: string, input: { repo: string; branch: string }, fetch: typeof globalThis.fetch = globalThis.fetch ): Promise => mirrorPush(agent, bobbin, knot, DELETE_BRANCH_NSID, input, fetch);