~james/core

core/web/src/lib/api/pinning.ts -rw-r--r-- 2.4 KiB
452f17b7 — james chore(import): snapshot james.dad.town/core at f70c71c7696a 15 hours ago
                                                                                
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
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<ProfileRecord["pinnedRepositories"]>[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 = <K extends string>(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 = <K extends string>(keys: readonly K[], key: K): K[] =>
	keys.filter((entry) => entry !== key);

export const movePinned = <K extends string>(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<ProfileRecord> => {
	const ownerAgent =
		agent.sub === ownerDid ? agent : await getOrCreateDelegatedSession(agent, ownerDid);
	const current = await loadProfile(ctx, ownerDid);
	return writeProfile(ctx, ownerAgent, pinnedWith(current, key, pinned));
};