Pre-launch — Gluecron is in final validation. Public signups and git hosting for non-owner users open after launch review.
CodeIssuesDiscussionsWikiPull RequestsProjectsCommitsActionsReleasesContributorsPulse● GatesSecuritySettingsDeploymentsPipelineInsightsAgents✨ Explain✨ Ask AI✨ Workspace✨ Spec✨ Tests▓ Debt Map✨ NL Search🏛 Archaeology
Blame · Line-by-line history

reactions.ts

Each line is annotated with the commit that last touched it. Click any SHA to jump to that commit and see the surrounding change.

reactions.tsBlame71 lines · 1 contributor
6fc53bdClaude1/**
2 * Reactions API — toggle + list reactions on issues, PRs, and their comments.
3 *
4 * POST /api/reactions/:targetType/:targetId/:emoji/toggle
5 * Body-less; auth required. Returns JSON {ok, added, counts}.
6 * If the request accepts text/html (form submission), redirects back.
7 *
8 * GET /api/reactions/:targetType/:targetId
9 * Returns the emoji -> count summary plus `reactedByMe` for the caller.
10 */
11
12import { Hono } from "hono";
13import type { AuthEnv } from "../middleware/auth";
14import { requireAuth, softAuth } from "../middleware/auth";
15import {
16 isAllowedEmoji,
17 isAllowedTarget,
18 summariseReactions,
19 toggleReaction,
20} from "../lib/reactions";
21
22const reactions = new Hono<AuthEnv>();
23
24reactions.use("/api/reactions/*", softAuth);
25
26reactions.get("/api/reactions/:targetType/:targetId", async (c) => {
27 const user = c.get("user");
28 const { targetType, targetId } = c.req.param();
29 if (!isAllowedTarget(targetType)) {
30 return c.json({ ok: false, error: "unknown target type" }, 400);
31 }
32 const rows = await summariseReactions(targetType, targetId, user?.id);
33 return c.json({ ok: true, reactions: rows });
34});
35
36reactions.post(
37 "/api/reactions/:targetType/:targetId/:emoji/toggle",
38 requireAuth,
39 async (c) => {
40 const user = c.get("user")!;
41 const { targetType, targetId, emoji } = c.req.param();
42 if (!isAllowedTarget(targetType)) {
43 return c.json({ ok: false, error: "unknown target type" }, 400);
44 }
45 if (!isAllowedEmoji(emoji)) {
46 return c.json({ ok: false, error: "unknown emoji" }, 400);
47 }
48
49 try {
50 const { added } = await toggleReaction(
51 user.id,
52 targetType,
53 targetId,
54 emoji
55 );
56 const summary = await summariseReactions(targetType, targetId, user.id);
57
58 const accept = c.req.header("accept") || "";
59 if (accept.includes("text/html")) {
60 const ref = c.req.header("referer");
61 return c.redirect(ref || "/");
62 }
63 return c.json({ ok: true, added, reactions: summary });
64 } catch (err) {
65 console.error("[reactions] toggle:", err);
66 return c.json({ ok: false, error: "server error" }, 500);
67 }
68 }
69);
70
71export default reactions;