bpist-squalk-forum/src/lib/admins.svelte.ts

65 lines
1.8 KiB
TypeScript

import { MODE } from "$lib/config";
import { groupStore } from "$lib/group.svelte";
import { queryForum } from "$lib/relay";
// room id -> admin pubkeys (NIP-29 kind 39001 `p` tags), across all rooms.
let byRoom = $state<Record<string, string[]>>({});
let loaded = $state(false);
let loadedKey = "";
export const roomAdminsStore = {
get byRoom() {
return byRoom;
},
get loaded() {
return loaded;
},
};
// Single source of truth for "who is an admin": the union of every room's
// admins in full mode, or the one group's admins in simple mode.
export const adminPubkeys = {
get list(): string[] {
return MODE === "full"
? [...new Set(Object.values(byRoom).flat())]
: (groupStore.data?.admins ?? []);
},
get loaded(): boolean {
return MODE === "full" ? loaded : groupStore.loaded;
},
};
// Per-group admin check. NIP-29 roles are scoped to a group, so a delete
// button must gate on the post's own group, not the cross-room union above.
export function isGroupAdmin(
pubkey: string | undefined,
groupId: string | undefined,
): boolean {
if (!pubkey || !groupId) return false;
return MODE === "full"
? (byRoom[groupId]?.includes(pubkey) ?? false)
: (groupStore.data?.admins?.includes(pubkey) ?? false);
}
export async function loadRoomAdmins(roomIds: string[]) {
if (roomIds.length === 0) return;
const key = [...roomIds].sort().join(",");
if (key === loadedKey) return;
loadedKey = key;
try {
const events = await queryForum({
kinds: [39001],
"#d": roomIds,
});
const map: Record<string, string[]> = {};
for (const e of events) {
const d = e.tags.find((t) => t[0] === "d")?.[1];
if (!d) continue;
map[d] = e.tags.filter((t) => t[0] === "p" && t[1]).map((t) => t[1]);
}
byRoom = map;
} finally {
loaded = true;
}
}