Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
171 changes: 171 additions & 0 deletions apps/loopover-ui/public/openapi.json
Original file line number Diff line number Diff line change
Expand Up @@ -16290,6 +16290,52 @@
"nullable": true
}
}
},
"ContributorWatchesResponse": {
"type": "object",
"properties": {
"watching": {
"type": "array",
"items": {
"type": "object",
"properties": {
"repoFullName": {
"type": "string"
},
"labels": {
"type": "array",
"items": {
"type": "string"
}
}
},
"required": [
"repoFullName",
"labels"
]
}
},
"changed": {
"type": "string"
}
}
},
"ContributorWatchRequest": {
"type": "object",
"properties": {
"repoFullName": {
"type": "string"
},
"labels": {
"type": "array",
"items": {
"type": "string"
}
}
},
"required": [
"repoFullName"
]
}
},
"parameters": {},
Expand Down Expand Up @@ -22172,6 +22218,131 @@
}
]
}
},
"/v1/contributors/{login}/watches": {
"get": {
"summary": "List a contributor's own issue-watch subscriptions — REST mirror of loopover_watch_issues (GET=list) (#9306)",
"parameters": [
{
"schema": {
"type": "string"
},
"required": true,
"name": "login",
"in": "path"
}
],
"responses": {
"200": {
"description": "The contributor's own watch subscriptions (self-scoped): each repoFullName with its watched labels.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ContributorWatchesResponse"
}
}
}
}
},
"security": [
{
"LoopOverBearer": []
},
{
"LoopOverSessionCookie": []
}
]
},
"post": {
"summary": "Watch an issue label set for a contributor — REST mirror of loopover_watch_issues (POST=watch) (#9306)",
"parameters": [
{
"schema": {
"type": "string"
},
"required": true,
"name": "login",
"in": "path"
}
],
"requestBody": {
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ContributorWatchRequest"
}
}
}
},
"responses": {
"200": {
"description": "The updated watch subscription list after adding the repo/label watch, with a changed marker.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ContributorWatchesResponse"
}
}
}
},
"400": {
"description": "Invalid watch subscription body"
}
},
"security": [
{
"LoopOverBearer": []
},
{
"LoopOverSessionCookie": []
}
]
},
"delete": {
"summary": "Unwatch a contributor's issue subscription — REST mirror of loopover_watch_issues (DELETE=unwatch) (#9306)",
"parameters": [
{
"schema": {
"type": "string"
},
"required": true,
"name": "login",
"in": "path"
}
],
"requestBody": {
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ContributorWatchRequest"
}
}
}
},
"responses": {
"200": {
"description": "The updated watch subscription list after removing the repo watch, with a changed marker.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ContributorWatchesResponse"
}
}
}
},
"400": {
"description": "Invalid watch subscription body"
}
},
"security": [
{
"LoopOverBearer": []
},
{
"LoopOverSessionCookie": []
}
]
}
}
},
"servers": [
Expand Down
23 changes: 23 additions & 0 deletions src/openapi/schemas.ts
Original file line number Diff line number Diff line change
Expand Up @@ -539,6 +539,29 @@ export const NotificationsMarkedSchema = z
})
.openapi("NotificationsMarked");

/**
* Response for /v1/contributors/{login}/watches (GET list + POST watch + DELETE unwatch). Field-level parity
* with `watchIssuesOutputSchema` (the `loopover_watch_issues` MCP tool `outputSchema`) in src/mcp/server.ts —
* #9306. GET returns just `watching`; a POST/DELETE mutation also echoes a `changed` marker.
*/
export const ContributorWatchesResponseSchema = z
.object({
watching: z.array(z.object({ repoFullName: z.string(), labels: z.array(z.string()) })).optional(),
changed: z.string().optional(),
})
.openapi("ContributorWatchesResponse");

/**
* Request body for POST/DELETE /v1/contributors/{login}/watches. Mirrors `watchSubscriptionBodySchema`
* (src/api/routes.ts) — repoFullName plus POST-only labels; a DELETE ignores labels. #9306.
*/
export const ContributorWatchRequestSchema = z
.object({
repoFullName: z.string(),
labels: z.array(z.string()).optional(),
})
.openapi("ContributorWatchRequest");

export const ContributorOpportunitySchema = z
.object({
repoFullName: z.string(),
Expand Down
52 changes: 52 additions & 0 deletions src/openapi/spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,8 @@ import {
IssueQualityReportSchema,
IssueQualityResponseSchema,
GateConfigEffectiveResponseSchema,
ContributorWatchesResponseSchema,
ContributorWatchRequestSchema,
EligibilityPlanResponseSchema,
ScoreBreakdownResponseSchema,
EvaluateEscalationRequestSchema,
Expand Down Expand Up @@ -209,6 +211,8 @@ export function buildOpenApiSpec() {
registry.register("IssueQualityReport", IssueQualityReportSchema);
registry.register("IssueQualityResponse", IssueQualityResponseSchema);
registry.register("GateConfigEffectiveResponse", GateConfigEffectiveResponseSchema);
registry.register("ContributorWatchesResponse", ContributorWatchesResponseSchema);
registry.register("ContributorWatchRequest", ContributorWatchRequestSchema);
registry.register("SelftuneOverrideAuditResponse", SelftuneOverrideAuditResponseSchema);
registry.register("ClearSelftuneOverrideResponse", ClearSelftuneOverrideResponseSchema);
registry.register("EligibilityPlanResponse", EligibilityPlanResponseSchema);
Expand Down Expand Up @@ -1379,6 +1383,54 @@ export function buildOpenApiSpec() {
400: { description: "Invalid mark-read body" },
},
});
registry.registerPath({
method: "get",
path: "/v1/contributors/{login}/watches",
summary: "List a contributor's own issue-watch subscriptions — REST mirror of loopover_watch_issues (GET=list) (#9306)",
request: { params: z.object({ login: z.string() }) },
responses: {
200: {
description: "The contributor's own watch subscriptions (self-scoped): each repoFullName with its watched labels.",
content: { "application/json": { schema: ContributorWatchesResponseSchema } },
},
},
});
registry.registerPath({
method: "post",
path: "/v1/contributors/{login}/watches",
summary: "Watch an issue label set for a contributor — REST mirror of loopover_watch_issues (POST=watch) (#9306)",
request: {
params: z.object({ login: z.string() }),
body: {
content: { "application/json": { schema: ContributorWatchRequestSchema } },
},
},
responses: {
200: {
description: "The updated watch subscription list after adding the repo/label watch, with a changed marker.",
content: { "application/json": { schema: ContributorWatchesResponseSchema } },
},
400: { description: "Invalid watch subscription body" },
},
});
registry.registerPath({
method: "delete",
path: "/v1/contributors/{login}/watches",
summary: "Unwatch a contributor's issue subscription — REST mirror of loopover_watch_issues (DELETE=unwatch) (#9306)",
request: {
params: z.object({ login: z.string() }),
body: {
content: { "application/json": { schema: ContributorWatchRequestSchema } },
},
},
responses: {
200: {
description: "The updated watch subscription list after removing the repo watch, with a changed marker.",
content: { "application/json": { schema: ContributorWatchesResponseSchema } },
},
400: { description: "Invalid watch subscription body" },
},
});
registry.registerPath({
method: "get",
path: "/v1/contributors/{login}/repos/{owner}/{repo}/decision",
Expand Down
Loading