Document comment/workflow APIs and fix commentId route handling
swadhinbiswas committed 8 months ago
121094d
1 parent 7a069c9
commit 121094d
3 files changed
+194 -2
Showing 3 changed files+194-2
▼src/lib/openapi.ts
+171-0
| @@ -146,6 +146,35 @@ export const openApiSpec = { | ||
146 | 146 | }, |
147 | 147 | required: ["reviewers"], |
148 | 148 | }, |
149 | + PullRequestCommentCreateRequest: { | |
150 | + type: "object", | |
151 | + properties: { | |
152 | + body: { type: "string" }, | |
153 | + path: { type: "string" }, | |
154 | + line: { type: "integer" }, | |
155 | + side: { type: "string", enum: ["LEFT", "RIGHT"] }, | |
156 | + startLine: { type: "integer" }, | |
157 | + commitSha: { type: "string" }, | |
158 | + inReplyToId: { type: "string" }, | |
159 | + suggestedChange: { type: "string" }, | |
160 | + }, | |
161 | + required: ["body"], | |
162 | + }, | |
163 | + PullRequestCommentUpdateRequest: { | |
164 | + type: "object", | |
165 | + properties: { | |
166 | + body: { type: "string" }, | |
167 | + }, | |
168 | + required: ["body"], | |
169 | + }, | |
170 | + WorkflowStateCreateRequest: { | |
171 | + type: "object", | |
172 | + properties: { | |
173 | + name: { type: "string" }, | |
174 | + description: { type: "string" }, | |
175 | + }, | |
176 | + required: ["name"], | |
177 | + }, | |
149 | 178 | }, |
150 | 179 | }, |
151 | 180 | paths: { |
| @@ -476,6 +505,148 @@ export const openApiSpec = { | ||
476 | 505 | }, |
477 | 506 | }, |
478 | 507 | }, |
508 | + "/repos/{owner}/{repo}/pulls/{number}/comments": { | |
509 | + get: { | |
510 | + tags: ["Pull Requests"], | |
511 | + summary: "List PR comments", | |
512 | + security: [{ bearerAuth: [] }], | |
513 | + parameters: [ | |
514 | + { name: "owner", in: "path", required: true, schema: { type: "string" } }, | |
515 | + { name: "repo", in: "path", required: true, schema: { type: "string" } }, | |
516 | + { name: "number", in: "path", required: true, schema: { type: "integer" } }, | |
517 | + ], | |
518 | + responses: { | |
519 | + 200: { description: "Comments returned" }, | |
520 | + 401: { description: "Unauthorized", content: { "application/json": { schema: { $ref: "#/components/schemas/Error" } } } }, | |
521 | + 404: { description: "Repository or pull request not found", content: { "application/json": { schema: { $ref: "#/components/schemas/Error" } } } }, | |
522 | + }, | |
523 | + }, | |
524 | + post: { | |
525 | + tags: ["Pull Requests"], | |
526 | + summary: "Create PR comment", | |
527 | + security: [{ bearerAuth: [] }], | |
528 | + parameters: [ | |
529 | + { name: "owner", in: "path", required: true, schema: { type: "string" } }, | |
530 | + { name: "repo", in: "path", required: true, schema: { type: "string" } }, | |
531 | + { name: "number", in: "path", required: true, schema: { type: "integer" } }, | |
532 | + ], | |
533 | + requestBody: { | |
534 | + required: true, | |
535 | + content: { | |
536 | + "application/json": { | |
537 | + schema: { $ref: "#/components/schemas/PullRequestCommentCreateRequest" }, | |
538 | + }, | |
539 | + }, | |
540 | + }, | |
541 | + responses: { | |
542 | + 200: { description: "Comment created" }, | |
543 | + 401: { description: "Unauthorized", content: { "application/json": { schema: { $ref: "#/components/schemas/Error" } } } }, | |
544 | + 403: { description: "Forbidden", content: { "application/json": { schema: { $ref: "#/components/schemas/Error" } } } }, | |
545 | + 404: { description: "Repository or pull request not found", content: { "application/json": { schema: { $ref: "#/components/schemas/Error" } } } }, | |
546 | + }, | |
547 | + }, | |
548 | + patch: { | |
549 | + tags: ["Pull Requests"], | |
550 | + summary: "Update PR comment", | |
551 | + security: [{ bearerAuth: [] }], | |
552 | + parameters: [ | |
553 | + { name: "owner", in: "path", required: true, schema: { type: "string" } }, | |
554 | + { name: "repo", in: "path", required: true, schema: { type: "string" } }, | |
555 | + { name: "number", in: "path", required: true, schema: { type: "integer" } }, | |
556 | + { name: "commentId", in: "query", required: true, schema: { type: "string" } }, | |
557 | + ], | |
558 | + requestBody: { | |
559 | + required: true, | |
560 | + content: { | |
561 | + "application/json": { | |
562 | + schema: { $ref: "#/components/schemas/PullRequestCommentUpdateRequest" }, | |
563 | + }, | |
564 | + }, | |
565 | + }, | |
566 | + responses: { | |
567 | + 200: { description: "Comment updated" }, | |
568 | + 401: { description: "Unauthorized", content: { "application/json": { schema: { $ref: "#/components/schemas/Error" } } } }, | |
569 | + 403: { description: "Forbidden", content: { "application/json": { schema: { $ref: "#/components/schemas/Error" } } } }, | |
570 | + 404: { description: "Comment or repository not found", content: { "application/json": { schema: { $ref: "#/components/schemas/Error" } } } }, | |
571 | + }, | |
572 | + }, | |
573 | + delete: { | |
574 | + tags: ["Pull Requests"], | |
575 | + summary: "Delete PR comment", | |
576 | + security: [{ bearerAuth: [] }], | |
577 | + parameters: [ | |
578 | + { name: "owner", in: "path", required: true, schema: { type: "string" } }, | |
579 | + { name: "repo", in: "path", required: true, schema: { type: "string" } }, | |
580 | + { name: "number", in: "path", required: true, schema: { type: "integer" } }, | |
581 | + { name: "commentId", in: "query", required: true, schema: { type: "string" } }, | |
582 | + ], | |
583 | + responses: { | |
584 | + 200: { description: "Comment deleted" }, | |
585 | + 401: { description: "Unauthorized", content: { "application/json": { schema: { $ref: "#/components/schemas/Error" } } } }, | |
586 | + 403: { description: "Forbidden", content: { "application/json": { schema: { $ref: "#/components/schemas/Error" } } } }, | |
587 | + 404: { description: "Comment or repository not found", content: { "application/json": { schema: { $ref: "#/components/schemas/Error" } } } }, | |
588 | + }, | |
589 | + }, | |
590 | + }, | |
591 | + "/repos/{owner}/{repo}/workflow/states": { | |
592 | + get: { | |
593 | + tags: ["Workflow"], | |
594 | + summary: "List workflow PR states", | |
595 | + security: [{ bearerAuth: [] }], | |
596 | + parameters: [ | |
597 | + { name: "owner", in: "path", required: true, schema: { type: "string" } }, | |
598 | + { name: "repo", in: "path", required: true, schema: { type: "string" } }, | |
599 | + ], | |
600 | + responses: { | |
601 | + 200: { description: "State definitions returned" }, | |
602 | + 401: { description: "Unauthorized", content: { "application/json": { schema: { $ref: "#/components/schemas/Error" } } } }, | |
603 | + 404: { description: "Repository not found", content: { "application/json": { schema: { $ref: "#/components/schemas/Error" } } } }, | |
604 | + }, | |
605 | + }, | |
606 | + post: { | |
607 | + tags: ["Workflow"], | |
608 | + summary: "Create workflow PR state", | |
609 | + security: [{ bearerAuth: [] }], | |
610 | + parameters: [ | |
611 | + { name: "owner", in: "path", required: true, schema: { type: "string" } }, | |
612 | + { name: "repo", in: "path", required: true, schema: { type: "string" } }, | |
613 | + ], | |
614 | + requestBody: { | |
615 | + required: true, | |
616 | + content: { | |
617 | + "application/json": { | |
618 | + schema: { $ref: "#/components/schemas/WorkflowStateCreateRequest" }, | |
619 | + }, | |
620 | + }, | |
621 | + }, | |
622 | + responses: { | |
623 | + 200: { description: "State created" }, | |
624 | + 400: { description: "Invalid or duplicate state payload", content: { "application/json": { schema: { $ref: "#/components/schemas/Error" } } } }, | |
625 | + 401: { description: "Unauthorized", content: { "application/json": { schema: { $ref: "#/components/schemas/Error" } } } }, | |
626 | + 403: { description: "Forbidden", content: { "application/json": { schema: { $ref: "#/components/schemas/Error" } } } }, | |
627 | + 404: { description: "Repository not found", content: { "application/json": { schema: { $ref: "#/components/schemas/Error" } } } }, | |
628 | + }, | |
629 | + }, | |
630 | + }, | |
631 | + "/repos/{owner}/{repo}/workflow/states/{id}": { | |
632 | + delete: { | |
633 | + tags: ["Workflow"], | |
634 | + summary: "Delete workflow PR state", | |
635 | + security: [{ bearerAuth: [] }], | |
636 | + parameters: [ | |
637 | + { name: "owner", in: "path", required: true, schema: { type: "string" } }, | |
638 | + { name: "repo", in: "path", required: true, schema: { type: "string" } }, | |
639 | + { name: "id", in: "path", required: true, schema: { type: "string" } }, | |
640 | + ], | |
641 | + responses: { | |
642 | + 200: { description: "State deleted" }, | |
643 | + 400: { description: "State is in use", content: { "application/json": { schema: { $ref: "#/components/schemas/Error" } } } }, | |
644 | + 401: { description: "Unauthorized", content: { "application/json": { schema: { $ref: "#/components/schemas/Error" } } } }, | |
645 | + 403: { description: "Forbidden", content: { "application/json": { schema: { $ref: "#/components/schemas/Error" } } } }, | |
646 | + 404: { description: "State or repository not found", content: { "application/json": { schema: { $ref: "#/components/schemas/Error" } } } }, | |
647 | + }, | |
648 | + }, | |
649 | + }, | |
479 | 650 | "/user/ai-config": { |
480 | 651 | get: { |
481 | 652 | tags: ["AI Review"], |
482 | 653 | |
▼src/pages/api/repos/[owner]/[repo]/pulls/[number]/comments.ts
+8-2
| @@ -201,7 +201,10 @@ export const PATCH: APIRoute = withErrorHandler(async ({ params, request }) => { | ||
201 | 201 | const parsed = await parseBody(request, updateCommentSchema); |
202 | 202 | if ("error" in parsed) return parsed.error; |
203 | 203 | |
204 | - const { owner, repo, number, commentId } = params; | |
204 | + const { owner, repo, number, commentId: commentIdFromParams } = params; | |
205 | + const requestUrl = new URL(request.url); | |
206 | + const commentId = (commentIdFromParams as string | undefined) || requestUrl.searchParams.get("commentId"); | |
207 | + if (!commentId) return badRequest("commentId is required"); | |
205 | 208 | const db = getDatabase() as NodePgDatabase<typeof schema>; |
206 | 209 | const repository = await getRepositoryByOwnerAndName(db, owner as string, repo as string); |
207 | 210 | if (!repository) return notFound("Repository not found"); |
| @@ -262,7 +265,10 @@ export const DELETE: APIRoute = withErrorHandler(async ({ params, request }) => | ||
262 | 265 | return unauthorized(); |
263 | 266 | } |
264 | 267 | |
265 | - const { owner, repo, number, commentId } = params; | |
268 | + const { owner, repo, number, commentId: commentIdFromParams } = params; | |
269 | + const requestUrl = new URL(request.url); | |
270 | + const commentId = (commentIdFromParams as string | undefined) || requestUrl.searchParams.get("commentId"); | |
271 | + if (!commentId) return badRequest("commentId is required"); | |
266 | 272 | const db = getDatabase() as NodePgDatabase<typeof schema>; |
267 | 273 | const repository = await getRepositoryByOwnerAndName(db, owner as string, repo as string); |
268 | 274 | if (!repository) return notFound("Repository not found"); |
269 | 275 | |
▼tests/integration/pr-comments-path-permissions.test.ts
+15-0
| @@ -228,4 +228,19 @@ describe("PR comments path permissions", () => { | ||
228 | 228 | expect(body?.success).toBe(true); |
229 | 229 | expect(mockDb.__mutationState.deleteCalls).toHaveLength(1); |
230 | 230 | }); |
231 | + | |
232 | + it("requires commentId for comment mutation routes", async () => { | |
233 | + const response = await updateCommentPatch({ | |
234 | + params: { owner: "owner-1", repo: "demo", number: "42" }, | |
235 | + request: new Request("http://localhost/api/repos/owner-1/demo/pulls/42/comments", { | |
236 | + method: "PATCH", | |
237 | + headers: { "content-type": "application/json" }, | |
238 | + body: JSON.stringify({ body: "updated" }), | |
239 | + }), | |
240 | + } as any); | |
241 | + | |
242 | + const body = await readJson(response); | |
243 | + expect(response.status).toBe(400); | |
244 | + expect(body?.error?.code).toBe("BAD_REQUEST"); | |
245 | + }); | |
231 | 246 | }); |
232 | 247 | |