import type { Did } from "@atcute/lexicons/syntax"; import type { OAuthUserAgent } from "@atcute/oauth-browser-client"; import { getOrCreateDelegatedSession } from "#lib/api/actAs.ts"; import type { BobbinContext } from "#lib/api/client.ts"; import { PROFILE_COLLECTION } from "#lib/api/profile.ts"; import { loadProfile, writeProfile } from "#lib/api/profileSettings.ts"; import type { ProfileRecord } from "#lib/api/records.ts"; export const PINNED_LIMIT = 6; export type PinnedKey = NonNullable[number]; // legacy repos are pinned by at-uri, which the lexicon still types as a did export const asPinnedKey = (key: string) => key as PinnedKey; export const pinKeyOf = (repo: { repoDid: string; uri?: string }): PinnedKey => asPinnedKey( repo.uri && !repo.uri.includes("/org.tangled.repo.manifest/") ? repo.uri : repo.repoDid ); export const addPinned = (keys: readonly K[], key: K): K[] => { const existing = keys.filter((entry) => entry !== key); if (existing.length >= PINNED_LIMIT) { throw new Error( `Unpin a repository first. Profiles show up to ${PINNED_LIMIT} pinned repositories.` ); } return [...existing, key]; }; export const removePinned = (keys: readonly K[], key: K): K[] => keys.filter((entry) => entry !== key); export const movePinned = (keys: readonly K[], from: number, to: number): K[] => { if (from === to || from < 0 || from >= keys.length) return [...keys]; const next = [...keys]; const [moved] = next.splice(from, 1); next.splice(Math.min(Math.max(to, 0), next.length), 0, moved); return next; }; export const pinnedWith = ( current: ProfileRecord | null, key: string, pinned: boolean ): ProfileRecord => { const keys = current?.pinnedRepositories ?? []; const pin = asPinnedKey(key); const next = pinned ? addPinned(keys, pin) : removePinned(keys, pin); const { pinnedRepositories: _pinnedRepositories, ...rest } = current ?? {}; return { ...rest, $type: PROFILE_COLLECTION, ...(next.length > 0 ? { pinnedRepositories: next } : {}) }; }; export const setRepositoryPinned = async ( ctx: BobbinContext, agent: OAuthUserAgent, ownerDid: Did, key: string, pinned: boolean ): Promise => { const ownerAgent = agent.sub === ownerDid ? agent : await getOrCreateDelegatedSession(agent, ownerDid); const current = await loadProfile(ctx, ownerDid); return writeProfile(ctx, ownerAgent, pinnedWith(current, key, pinned)); };