import type { OAuthUserAgent } from "@atcute/oauth-browser-client"; import { createCob, eraseCob, updateCob, type CobRecord, type CobRef } from "#lib/api/cob.ts"; import { filterBlobsFromMarkdown } from "#lib/api/markupBlobs.ts"; import type { Blob } from "@atcute/lexicons/interfaces"; import { datetimeOf, nsidOf, type Did, type KnotOrigin, type LabelKey } from "#lib/api/syntax.ts"; import type { TicketRecord, TicketState } from "#lib/api/records.ts"; // a pull is a ticket with a target branch export const TICKET_COLLECTION = nsidOf("org.tangled.track.ticket"); export interface TicketProse { title: string; body: string; targetBranch?: string; tangledLabels?: readonly LabelKey[]; blobs?: readonly Blob[]; } const titleOf = (text: string): TicketRecord["title"] => ({ $type: "org.tangled.markup.markdown#inline", text }); // the knot refuses an empty markdown body, so leave a blank one out const bodyOf = ( text: string, blobs?: readonly Blob[], current?: TicketRecord["body"] ): TicketRecord["body"] => text.trim() ? { ...current, $type: "org.tangled.markup.markdown", text, blobs: filterBlobsFromMarkdown(current?.blobs, blobs, text) } : undefined; const labelsOf = (keys: readonly LabelKey[]) => keys.length ? keys.map((key) => ({ $type: "org.tangled.label.defs#labelToken" as const, key })) : undefined; export const createTicket = async ( agent: OAuthUserAgent, knot: KnotOrigin, repo: Did, { title, body, blobs, targetBranch, tangledLabels }: TicketProse ): Promise> => { const record: TicketRecord = { $type: "org.tangled.track.ticket", title: titleOf(title), body: bodyOf(body, blobs), state: "open", targetBranch, ...(tangledLabels ? { tangledLabels: labelsOf(tangledLabels) } : {}), createdAt: datetimeOf(new Date().toISOString()) }; return { ...(await createCob(agent, knot, repo, TICKET_COLLECTION, record)), value: record }; }; // a put replaces everything, so start from the stored record export const editTicket = ( agent: OAuthUserAgent, knot: KnotOrigin, from: CobRef, { title, body, blobs, targetBranch, tangledLabels }: TicketProse ): Promise> => updateCob(agent, knot, from, (current) => ({ ...current, title: titleOf(title), body: bodyOf(body, blobs, current.body), targetBranch: targetBranch ?? current.targetBranch, ...(tangledLabels ? { tangledLabels: labelsOf(tangledLabels) } : {}) })); export const setTicketState = ( agent: OAuthUserAgent, knot: KnotOrigin, from: CobRef, state: TicketState ): Promise> => updateCob(agent, knot, from, (current) => ({ ...current, state })); export const setTicketLabels = ( agent: OAuthUserAgent, knot: KnotOrigin, from: CobRef, keys: readonly LabelKey[] ): Promise> => updateCob(agent, knot, from, (current) => ({ ...current, tangledLabels: labelsOf(keys) })); export const setTicketAssignees = ( agent: OAuthUserAgent, knot: KnotOrigin, from: CobRef, assignees: readonly Did[] ): Promise> => updateCob(agent, knot, from, (current) => ({ ...current, assignees: assignees.length ? [...assignees] : undefined })); export const eraseTicket = (agent: OAuthUserAgent, knot: KnotOrigin, from: CobRef): Promise => eraseCob(agent, knot, from);