import type { Did } from "@atcute/lexicons/syntax"; import { error } from "@sveltejs/kit"; import type { BobbinContext, XrpcRequestInit } from "$lib/api/client"; import { items } from "$lib/api/pagination"; export type RepoRole = "owner" | "collaborator" | "none"; export interface RepoAccess { readonly role: RepoRole; readonly canPush: boolean; readonly canAdminister: boolean; } export const NO_ACCESS: RepoAccess = { role: "none", canPush: false, canAdminister: false }; export const accessFor = (role: RepoRole): RepoAccess => ({ role, canPush: role !== "none", canAdminister: role === "owner" }); export interface RepoIdentity { readonly ownerDid: string; readonly repoDid?: string; } const COLLABORATOR_PAGES = 5; // pending invites grant nothing, the knot requires an acceptance record export const resolveRepoAccess = async ( ctx: BobbinContext, repo: RepoIdentity, viewerDid: string | undefined, init?: XrpcRequestInit ): Promise => { if (!viewerDid) return NO_ACCESS; if (viewerDid === repo.ownerDid) return accessFor("owner"); if (!repo.repoDid) return NO_ACCESS; for await (const collaborator of items( ctx, "sh.tangled.repo.listCollaborators", { subject: repo.repoDid as Did, limit: 100 }, { ...init, maxPages: COLLABORATOR_PAGES } )) { if (collaborator.subject === viewerDid) return accessFor("collaborator"); } return NO_ACCESS; }; export const requireRepoAdmin = (access: RepoAccess): void => { if (!access.canAdminister) error(403, "Only the repository owner can change this"); };