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<CobRecord<TicketRecord>> => {
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<CobRecord<TicketRecord>> =>
updateCob<TicketRecord>(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<CobRecord<TicketRecord>> =>
updateCob<TicketRecord>(agent, knot, from, (current) => ({ ...current, state }));
export const setTicketLabels = (
agent: OAuthUserAgent,
knot: KnotOrigin,
from: CobRef,
keys: readonly LabelKey[]
): Promise<CobRecord<TicketRecord>> =>
updateCob<TicketRecord>(agent, knot, from, (current) => ({
...current,
tangledLabels: labelsOf(keys)
}));
export const setTicketAssignees = (
agent: OAuthUserAgent,
knot: KnotOrigin,
from: CobRef,
assignees: readonly Did[]
): Promise<CobRecord<TicketRecord>> =>
updateCob<TicketRecord>(agent, knot, from, (current) => ({
...current,
assignees: assignees.length ? [...assignees] : undefined
}));
export const eraseTicket = (agent: OAuthUserAgent, knot: KnotOrigin, from: CobRef): Promise<void> =>
eraseCob(agent, knot, from);