返回 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, VersionQuerySchema } 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 page = parseQuery(c, VersionQuerySchema);
27 const { packages: repo } = repos(c.env);
28 const row = await repo.bySlug(slug);
29 if (!row || row.status !== "active") throw new ApiError(404, "not_found", "No such package.");
30 const versions = await repo.versions(row.id, page);
31 return c.json({ package: toPackageDTO(row), ...versions });
32 });
33
34 packages.post("/", writeRateLimit, requireAuth, async (c) => {
35 const user = currentUser(c);
36 if (!user.emailVerified) {
37 throw new ApiError(403, "email_unverified", "Verify your email at id.reasonix.io before publishing.");
38 }
39 const input = await parseBody(c, PublishSchema);
40 const { packages: repo, events } = repos(c.env);
41 const { row, created, version } = await repo.publish(user, input, now());
42 // Announce only what is public. A pending submission waits for an admin to
43 // approve it before it surfaces in the feed or the listing.
44 if (row.status === "active") {
45 await events.log({
46 type: created ? "publish" : "update",
47 packageId: row.id,
48 actorHandle: user.handle,
49 summary: `${created ? "published" : "updated"} ${row.slug}@${version}`,
50 now: now(),
51 });
52 }
53 return c.json({ package: toPackageDTO(row), created, version }, created ? 201 : 200);
54 });
55
56 packages.post("/:handle/:name/installed", writeRateLimit, async (c) => {
57 const slug = `${c.req.param("handle")}/${c.req.param("name")}`;
58 const { packages: repo, events } = repos(c.env);
59 const result = await repo.recordInstall(slug, now());
60 if (result === null) throw new ApiError(404, "not_found", "No such package.");
61 if (isMilestone(result.count)) {
62 await events.log({
63 type: "milestone",
64 packageId: result.packageId,
65 actorHandle: result.scopeHandle,
66 summary: `${slug} reached ${result.count} installs`,
67 now: now(),
68 });
69 }
70 return c.json({ ok: true, installCount: result.count });
71 });
72
73 packages.post("/:handle/:name/star", writeRateLimit, requireAuth, async (c) => {
74 const slug = `${c.req.param("handle")}/${c.req.param("name")}`;
75 const user = currentUser(c);
76 const { packages: repo, events } = repos(c.env);
77 const result = await repo.toggleStar(slug, user.id, now());
78 if (result === null) throw new ApiError(404, "not_found", "No such package.");
79 if (result.starred) {
80 await events.log({ type: "star", packageId: null, actorHandle: user.handle, summary: `starred ${slug}`, now: now() });
81 }
82 return c.json(result);
83 });
84
85 export default packages;
86
86 lines TYPESCRIPT