87 lines · 3.2 KB
import { createServerFn } from "@tanstack/react-start";
import { z } from "zod";
import { requireViewer, viewerMiddleware } from "./middleware";
import { accountsById } from "~/server/people";
import { repoDb } from "~/server/scoped-db";
import { limitBrowse, limitWrite } from "~/server/limits";
import { requireRepoAccess } from "~/server/repos";
import { subjectRepoId } from "~/server/reactions";
import { forbidden, notFoundError } from "~/server/errors";
import { REACTION_CONTENTS, REACTION_SUBJECTS, REACTORS_SHOWN } from "~/lib/reactions";
import { repoRef } from "~/lib/validation";

export const toggleReaction = createServerFn({ method: "POST" })
  .middleware([viewerMiddleware])
  .validator(
    repoRef.extend({
      subjectType: z.enum(REACTION_SUBJECTS),
      subjectId: z.string().min(1).max(64),
      content: z.enum(REACTION_CONTENTS),
    }),
  )
  .handler(async ({ data, context }) => {
    const viewer = requireViewer(context.viewer);
    await limitWrite(viewer);
    const { repo } = await requireRepoAccess(data.owner, data.repo, viewer, "read");
    const owning = await subjectRepoId(data.subjectType, data.subjectId);
    if (owning !== repo.id) throw notFoundError("Nothing to react to");
    if (repo.is_archived === 1) throw forbidden("This repository is archived");
    const db = repoDb(repo.id);
    const existing = await db.first(
      "SELECT 1 FROM reactions WHERE subject_type = ? AND subject_id = ? AND user_id = ? AND content = ?",
      data.subjectType,
      data.subjectId,
      viewer.id,
      data.content,
    );
    if (existing) {
      await db.run(
        "DELETE FROM reactions WHERE subject_type = ? AND subject_id = ? AND user_id = ? AND content = ?",
        data.subjectType,