"use server"; import { revalidatePath } from "next/cache"; import { redirect } from "next/navigation"; import { addCollaborator, createRepo, deleteRepo, removeCollaborator, removeRepoImage, renameRepo, RepoError, setRepoImage, setWikiEnabled, updateRepo, type Visibility, } from "@/lib/repos"; import { ActionError, repoForAction, viewerOrThrow } from "./guard"; import type { FormState } from "./types"; const visibilityOf = (v: FormDataEntryValue | null): Visibility => (v === "private" ? "private" : "public"); async function attempt(fn: () => Promise | FormState | void): Promise { try { return (await fn()) ?? undefined; } catch (e) { if (e instanceof RepoError || e instanceof ActionError) return { error: e.message }; throw e; } } export async function createRepoAction(_prev: FormState, formData: FormData): Promise { let path = ""; const result = await attempt(async () => { const viewer = await viewerOrThrow(); const repo = await createRepo(viewer, { name: String(formData.get("name") ?? ""), description: String(formData.get("description") ?? ""), visibility: visibilityOf(formData.get("visibility")), readme: formData.get("readme") === "on", }); path = `/${repo.owner}/${repo.name}`; }); if (result?.error) return result; redirect(path); } export async function updateRepoAction(owner: string, name: string, _prev: FormState, formData: FormData) { return attempt(async () => { const { repo } = await repoForAction(owner, name, "admin"); await updateRepo(repo.id, { description: String(formData.get("description") ?? ""), visibility: visibilityOf(formData.get("visibility")), }); revalidatePath(`/${repo.owner}/${repo.name}`, "layout"); return { success: "Settings saved." }; }); } export async function updateFeaturesAction(owner: string, name: string, _prev: FormState, formData: FormData) { return attempt(async () => { const { repo } = await repoForAction(owner, name, "admin"); await setWikiEnabled(repo.id, formData.get("wiki") === "on"); revalidatePath(`/${repo.owner}/${repo.name}`, "layout"); return { success: "Features saved." }; }); } export async function updateRepoImageAction(owner: string, name: string, _prev: FormState, formData: FormData) { return attempt(async () => { const { repo } = await repoForAction(owner, name, "admin"); const file = formData.get("image"); if (!(file instanceof File)) throw new RepoError("Choose an image to upload."); await setRepoImage(repo.id, new Uint8Array(await file.arrayBuffer())); // The image shows up in sidebars and lists everywhere, not just on this repository's pages. revalidatePath("/", "layout"); return { success: "Repository image updated." }; }); } export async function removeRepoImageAction(owner: string, name: string) { const { repo } = await repoForAction(owner, name, "admin"); await removeRepoImage(repo.id); revalidatePath("/", "layout"); } export async function renameRepoAction(owner: string, name: string, _prev: FormState, formData: FormData) { let path = ""; const result = await attempt(async () => { const { repo } = await repoForAction(owner, name, "admin"); const renamed = await renameRepo(repo, String(formData.get("name") ?? "")); path = `/${renamed.owner}/${renamed.name}/settings`; }); if (result?.error) return result; redirect(path); } export async function deleteRepoAction(owner: string, name: string, _prev: FormState, formData: FormData) { const result = await attempt(async () => { const { repo } = await repoForAction(owner, name, "admin"); if (formData.get("confirm") !== `${repo.owner}/${repo.name}`) { return { error: `Type ${repo.owner}/${repo.name} to confirm.` }; } await deleteRepo(repo); }); if (result?.error) return result; redirect("/"); } export async function addCollaboratorAction(owner: string, name: string, _prev: FormState, formData: FormData) { return attempt(async () => { const { repo } = await repoForAction(owner, name, "admin"); const username = String(formData.get("username") ?? "").trim(); await addCollaborator(repo, username); revalidatePath(`/${repo.owner}/${repo.name}/settings`); return { success: `${username} can now push to this repository.` }; }); } export async function removeCollaboratorAction(owner: string, name: string, userId: string) { const { repo } = await repoForAction(owner, name, "admin"); await removeCollaborator(repo.id, String(userId)); revalidatePath(`/${repo.owner}/${repo.name}/settings`); }