Public: anyone can read this; the crew can change it.
A repository's label routes leave api.ts for src/http/api/labels.ts #6931
Check · Review · Land · Live done — Landed and live in production.
mason/forge:mason/6303-a-repository-s-label-routes-leave-api-ts → main 1 commit fork
2 files
changed +52
−37
src/http/api.ts +2 −37
| @@ -168,10 +168,8 @@ import { search, searchWithin } from "../actions/search.ts"; | ||
| 168 | 168 | import { runRepos } from "./run_gate.ts"; |
| 169 | 169 | import { |
| 170 | 170 | commitToBranch, |
| 171 | createLabel, | |
| 172 | 171 | createRepo, |
| 173 | 172 | createWebhook, |
| 174 | deleteLabel, | |
| 175 | 173 | deleteRepo, |
| 176 | 174 | deleteWebhook, |
| 177 | 175 | FORKS_PER_DAY, |
| @@ -208,7 +206,6 @@ import { | ||
| 208 | 206 | labelsForIssues, |
| 209 | 207 | listComments, |
| 210 | 208 | listIssues, |
| 211 | listLabels, | |
| 212 | 209 | LIST_LIMIT, |
| 213 | 210 | LIST_LIMIT_MAX, |
| 214 | 211 | pageLimit, |
| @@ -454,6 +451,7 @@ import { frontInput, frontJson, setFront } from "../actions/front.ts"; | ||
| 454 | 451 | import { COPIES_PER_DAY, makeItYours } from "../actions/make_it_yours.ts"; |
| 455 | 452 | import { passportRoutes } from "./passport_edge.ts"; |
| 456 | 453 | import { egressGrantRoutes } from "./api/egress_grants.ts"; |
| 454 | import { labelsRoutes } from "./api/labels.ts"; | |
| 457 | 455 | import { secretRoutes } from "./api/secrets.ts"; |
| 458 | 456 | import type { Router } from "./router.ts"; |
| 459 | 457 | import { clientAddress, clientBucket } from "./peer.ts"; |
| @@ -3174,40 +3172,7 @@ export function apiRoutes(router: Router<Handler>): void { | ||
| 3174 | 3172 | |
| 3175 | 3173 | // ── labels ─────────────────────────────────────────────────────────── |
| 3176 | 3174 | |
| 3177 | router.add("GET", "/api/v1/repos/:owner/:name/labels", (ctx) => { | |
| 3178 | notePhase(ctx.request, "session", takeSessionTime(ctx.request)); | |
| 3179 | const repo = loadRepo(ctx); | |
| 3180 | if (!repo) return notFound(); | |
| 3181 | const labels = timePhase(ctx.request, "labels", () => listLabels(ctx.app.db, repo.id)); | |
| 3182 | return timePhase(ctx.request, "render", () => json(labels)); | |
| 3183 | }); | |
| 3184 | ||
| 3185 | router.add("POST", "/api/v1/repos/:owner/:name/labels", async (ctx) => { | |
| 3186 | const denied = requireScope(ctx, "admin"); | |
| 3187 | if (denied) return denied; | |
| 3188 | const repo = loadRepo(ctx); | |
| 3189 | if (!repo) return notFound(); | |
| 3190 | const input = await body(ctx.request); | |
| 3191 | const created = createLabel( | |
| 3192 | ctx.app, | |
| 3193 | ctx.auth.user, | |
| 3194 | repo, | |
| 3195 | str(input.name), | |
| 3196 | str(input.color) || "6b7280", | |
| 3197 | ); | |
| 3198 | if (!created.ok) return respondAccess(created.error, 422); | |
| 3199 | return json(created.value, 201); | |
| 3200 | }); | |
| 3201 | ||
| 3202 | router.add("DELETE", "/api/v1/repos/:owner/:name/labels/:id", (ctx) => { | |
| 3203 | const denied = requireScope(ctx, "admin"); | |
| 3204 | if (denied) return denied; | |
| 3205 | const repo = loadRepo(ctx); | |
| 3206 | if (!repo) return notFound(); | |
| 3207 | const removed = deleteLabel(ctx.app, ctx.auth.user, repo, ctx.params.id!); | |
| 3208 | if (!removed.ok) return respondAccess(removed.error, 422); | |
| 3209 | return new Response(null, { status: 204 }); | |
| 3210 | }); | |
| 3175 | labelsRoutes(router); | |
| 3211 | 3176 | |
| 3212 | 3177 | // Replace the label set of an issue or a pull — both live in one table, so |
| 3213 | 3178 | // either number works here. Names or ids; an unknown label rejects the set. |
src/http/api/labels.ts added +50
| @@ -0,0 +1,50 @@ | ||
| 1 | /** | |
| 2 | * A repository's labels over the API: `GET` and `POST` | |
| 3 | * `/api/v1/repos/:owner/:name/labels`, and `DELETE` `…/labels/:id`. Moved | |
| 4 | * out of `api.ts` (#6303); the rules live in `actions/repos.ts`. | |
| 5 | */ | |
| 6 | ||
| 7 | import { createLabel, deleteLabel } from "../../actions/repos.ts"; | |
| 8 | import { listLabels } from "../../core/issues.ts"; | |
| 9 | import { notePhase, takeSessionTime, timePhase } from "../../core/route_timing.ts"; | |
| 10 | import { loadRepo } from "../api.ts"; | |
| 11 | import type { Handler } from "../context.ts"; | |
| 12 | import { body, json, notFound, requireScope, respondAccess, str } from "../respond.ts"; | |
| 13 | import type { Router } from "../router.ts"; | |
| 14 | ||
| 15 | export function labelsRoutes(router: Router<Handler>): void { | |
| 16 | router.add("GET", "/api/v1/repos/:owner/:name/labels", (ctx) => { | |
| 17 | notePhase(ctx.request, "session", takeSessionTime(ctx.request)); | |
| 18 | const repo = loadRepo(ctx); | |
| 19 | if (!repo) return notFound(); | |
| 20 | const labels = timePhase(ctx.request, "labels", () => listLabels(ctx.app.db, repo.id)); | |
| 21 | return timePhase(ctx.request, "render", () => json(labels)); | |
| 22 | }); | |
| 23 | ||
| 24 | router.add("POST", "/api/v1/repos/:owner/:name/labels", async (ctx) => { | |
| 25 | const denied = requireScope(ctx, "admin"); | |
| 26 | if (denied) return denied; | |
| 27 | const repo = loadRepo(ctx); | |
| 28 | if (!repo) return notFound(); | |
| 29 | const input = await body(ctx.request); | |
| 30 | const created = createLabel( | |
| 31 | ctx.app, | |
| 32 | ctx.auth.user, | |
| 33 | repo, | |
| 34 | str(input.name), | |
| 35 | str(input.color) || "6b7280", | |
| 36 | ); | |
| 37 | if (!created.ok) return respondAccess(created.error, 422); | |
| 38 | return json(created.value, 201); | |
| 39 | }); | |
| 40 | ||
| 41 | router.add("DELETE", "/api/v1/repos/:owner/:name/labels/:id", (ctx) => { | |
| 42 | const denied = requireScope(ctx, "admin"); | |
| 43 | if (denied) return denied; | |
| 44 | const repo = loadRepo(ctx); | |
| 45 | if (!repo) return notFound(); | |
| 46 | const removed = deleteLabel(ctx.app, ctx.auth.user, repo, ctx.params.id!); | |
| 47 | if (!removed.ok) return respondAccess(removed.error, 422); | |
| 48 | return new Response(null, { status: 204 }); | |
| 49 | }); | |
| 50 | } |
new above
offline