返回 DeepSeek-Reasonix
packages.ts
根目录 / workers / crash-report / src / registry / routes / packages.ts
1 import { Hono } from "hono";
2 import type { AppEnv } from "../env";
3 import { toPackageDTO } from "../types";
4 import { repos } from "../db";
5 import { requireAuth, currentUser } from "../http/auth";
6 import { writeRateLimit } from "../http/ratelimit";
7 import { ApiError } from "../http/errors";
8 import { parseBody, parseQuery, PublishSchema, ListQuerySchema } from "../lib/validation";
9
10 const packages = new Hono<AppEnv>();
11
12 const now = () => new Date().toISOString();
13
14 // Install-count thresholds worth announcing in the activity feed.
15 const MILESTONES = new Set([10, 50, 100, 500, 1000]);
16 const isMilestone = (n: number) => MILESTONES.has(n) || (n >= 1000 && n % 1000 === 0);
17
18 packages.get("/", async (c) => {
19 const q = parseQuery(c, ListQuerySchema);
20 const rows = await repos(c.env).packages.list({ ...q, now: now() });
21 return c.json({ packages: rows.map(toPackageDTO), limit: q.limit, offset: q.offset });
22 });
23
24 packages.get("/:handle/:name", async (c) => {
25 const slug = `${c.req.param("handle")}/${c.req.param("name")}`;
26 const { packages: repo } = repos(c.env);
27 const row = await repo.bySlug(slug);
28 if (!row || row.status !== "active") throw new ApiError(404, "not_found", "No such package.");
29 const versions = await repo.versions(row.id);
30 return c.json({ package: toPackageDTO(row), versions });
31 });
32
33 packages.post("/", writeRateLimit, requireAuth, async (c) => {
34 const user = currentUser(c);
35 if (!user.emailVerified) {
36 throw new ApiError(403, "email_unverified", "Verify your email at id.reasonix.io before publishing.");
37 }
38 const input = await parseBody(c, PublishSchema);
39 const { packages: repo, events } = repos(c.env);
40 const { row, created, version } = await repo.publish(user, input, now());
41 // Announce only what is public. A pending submission waits for an admin to
42 // approve it before it surfaces in the feed or the listing.
43 if (row.status === "active") {
44 await events.log({
45 type: created ? "publish" : "update",
46 packageId: row.id,
47 actorHandle: user.handle,
48 summary: `${created ? "published" : "updated"} ${row.slug}@${version}`,
49 now: now(),
50 });
51 }
52 return c.json({ package: toPackageDTO(row), created, version }, created ? 201 : 200);
53 });
54
55 packages.post("/:handle/:name/installed", writeRateLimit, async (c) => {
56 const slug = `${c.req.param("handle")}/${c.req.param("name")}`;
57 const { packages: repo, events } = repos(c.env);
58 const count = await repo.recordInstall(slug);
59 if (count === null) throw new ApiError(404, "not_found", "No such package.");
60 const row = await repo.bySlug(slug);
61 await events.log({ type: "install", packageId: row?.id ?? null, actorHandle: "", summary: `installed ${slug}`, now: now() });
62 if (isMilestone(count) && row) {
63 await events.log({
64 type: "milestone",
65 packageId: row.id,
66 actorHandle: row.scope_handle,
67 summary: `${slug} reached ${count} installs`,
68 now: now(),
69 });
70 }
71 return c.json({ ok: true, installCount: count });
72 });
73
74 packages.post("/:handle/:name/star", writeRateLimit, requireAuth, async (c) => {
75 const slug = `${c.req.param("handle")}/${c.req.param("name")}`;
76 const user = currentUser(c);
77 const { packages: repo, events } = repos(c.env);
78 const result = await repo.toggleStar(slug, user.id, now());
79 if (result === null) throw new ApiError(404, "not_found", "No such package.");
80 if (result.starred) {
81 await events.log({ type: "star", packageId: null, actorHandle: user.handle, summary: `starred ${slug}`, now: now() });
82 }
83 return c.json(result);
84 });
85
86 export default packages;
87
87 lines TYPESCRIPT