Vectrum
Sign In
91 lines · 3.3 KB · TypeScript
"use server";

import { revalidatePath } from "next/cache";
import { addSshKey, deleteSshKey, KeyError } from "@/lib/keys";
import { canWrite, findRepoById, repoAccess } from "@/lib/repos";
import { createToken, deleteToken, TokenError, type TokenPermission } from "@/lib/tokens";
import { setPassword, updateProfile, UserError, validatePassword, verifyUserPassword } from "@/lib/users";
import { viewerOrThrow } from "./guard";
import type { FormState } from "./types";

export async function updateProfileAction(_prev: FormState, formData: FormData): Promise<FormState> {
  const viewer = await viewerOrThrow();
  try {
    updateProfile(viewer.id, {
      name: String(formData.get("name") ?? ""),
      email: String(formData.get("email") ?? ""),
      bio: String(formData.get("bio") ?? ""),
    });
  } catch (e) {
    if (e instanceof UserError) return { error: e.message };
    throw e;
  }
  revalidatePath("/", "layout");
  return { success: "Profile updated." };
}

export async function changePasswordAction(_prev: FormState, formData: FormData): Promise<FormState> {
  const viewer = await viewerOrThrow();
  const current = String(formData.get("current") ?? "");
  const next = String(formData.get("password") ?? "");
  if (!(await verifyUserPassword(viewer.id, current))) return { error: "Your current password is incorrect." };
  if (next !== formData.get("confirm")) return { error: "The new passwords don't match." };
  const problem = validatePassword(next);
  if (problem) return { error: problem };
  await setPassword(viewer.id, next);
  return { success: "Password changed." };
}

export async function addSshKeyAction(_prev: FormState, formData: FormData): Promise<FormState> {
  const viewer = await viewerOrThrow();
  try {
    addSshKey(viewer.id, String(formData.get("title") ?? ""), String(formData.get("key") ?? ""));
  } catch (e) {
    if (e instanceof KeyError) return { error: e.message };
    throw e;
  }
  revalidatePath("/settings/keys");
  return { success: "SSH key added." };
}

export async function deleteSshKeyAction(keyId: number) {
  const viewer = await viewerOrThrow();
  deleteSshKey(viewer.id, keyId);
  revalidatePath("/settings/keys");
}

export async function createTokenAction(_prev: FormState, formData: FormData): Promise<FormState> {
  const viewer = await viewerOrThrow();
  const permission: TokenPermission = formData.get("permission") === "read" ? "read" : "write";
  const expiry = String(formData.get("expiry") ?? "30");
  let repoIds: number[] | null = null;
  if (formData.get("scope") === "selected") {
    repoIds = formData
      .getAll("repos")
      .map(Number)
      .filter((id) => {
        const repo = findRepoById(id);
        const access = repo ? repoAccess(repo, viewer.id) : null;
        return access && (permission === "read" || canWrite(access));
      });
  }
  try {
    const token = createToken(viewer.id, {
      name: String(formData.get("name") ?? ""),
      permission,
      expiresInDays: expiry === "never" ? null : Number(expiry) || 30,
      repoIds,
    });
    revalidatePath("/settings/tokens");
    return { token };
  } catch (e) {
    if (e instanceof TokenError) return { error: e.message };
    throw e;
  }
}

export async function deleteTokenAction(tokenId: number) {
  const viewer = await viewerOrThrow();
  deleteToken(viewer.id, tokenId);
  revalidatePath("/settings/tokens");
}