Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

feat(server): serve repos at /repos/{owner}/{repo} path routes #1323

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
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
217 changes: 172 additions & 45 deletions api/openapi/stackit.yaml
View file Open in desktop
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,54 @@ info:
servers:
- url: /
paths:
/api/v1/view:
/api/v1/repos:
get:
summary: List repositories the server is serving (filtered to the caller).
operationId: listRepos
responses:
"200":
description: Repository index.
content:
application/json:
schema:
$ref: "#/components/schemas/ReposListResponse"
post:
summary: Onboard a GitHub repository (clone and start serving it).
description: >
Clones a GitHub repository the authenticated user can access and starts
serving it, recorded against that user so only they see it. Requires an
authenticated, writable, DB-backed server with a repos root configured;
read-only servers refuse with 405.
operationId: onboardRepo
requestBody:
required: true
content:
application/json:
schema:
$ref: "#/components/schemas/OnboardRepoRequest"
responses:
"201":
description: The newly served repository.
content:
application/json:
schema:
$ref: "#/components/schemas/RepoSummary"
"400":
description: Missing or invalid owner/name.
"401":
description: Authentication required.
"404":
description: Repository not found or not accessible to the caller.
"405":
description: Server is read-only.
"409":
description: Repository already onboarded.
"503":
description: Onboarding not available (no auth, database, or repos root).
/api/v1/repos/{owner}/{repo}/view:
parameters:
- $ref: "#/components/parameters/Owner"
- $ref: "#/components/parameters/Repo"
get:
summary: Combined view payload for the dashboard.
operationId: getView
Expand All @@ -17,7 +64,12 @@ paths:
application/json:
schema:
$ref: "#/components/schemas/ViewResponse"
/api/v1/repo:
"404":
description: Repository not found or not visible to the caller.
/api/v1/repos/{owner}/{repo}/repo:
parameters:
- $ref: "#/components/parameters/Owner"
- $ref: "#/components/parameters/Repo"
get:
summary: Repository metadata.
operationId: getRepo
Expand All @@ -28,7 +80,12 @@ paths:
application/json:
schema:
$ref: "#/components/schemas/RepoResponse"
/api/v1/stacks:
"404":
description: Repository not found or not visible to the caller.
/api/v1/repos/{owner}/{repo}/stacks:
parameters:
- $ref: "#/components/parameters/Owner"
- $ref: "#/components/parameters/Repo"
get:
summary: List discovered stacks.
operationId: listStacks
Expand All @@ -41,7 +98,10 @@ paths:
type: array
items:
$ref: "#/components/schemas/StackSummary"
/api/v1/stacks/{rootBranch}:
/api/v1/repos/{owner}/{repo}/stacks/{rootBranch}:
parameters:
- $ref: "#/components/parameters/Owner"
- $ref: "#/components/parameters/Repo"
get:
summary: Get detailed stack by root branch.
operationId: getStack
Expand All @@ -60,7 +120,46 @@ paths:
$ref: "#/components/schemas/StackDetail"
"404":
description: Stack not found.
/api/v1/branches:
/api/v1/repos/{owner}/{repo}/stacks/{rootBranch}/submit:
parameters:
- $ref: "#/components/parameters/Owner"
- $ref: "#/components/parameters/Repo"
post:
summary: Submit a stack as stacked pull requests.
operationId: submitStack
parameters:
- in: path
name: rootBranch
required: true
schema:
type: string
responses:
"200":
description: Submit result.
"404":
description: Stack not found.
"405":
description: Server is read-only.
/api/v1/repos/{owner}/{repo}/sync:
parameters:
- $ref: "#/components/parameters/Owner"
- $ref: "#/components/parameters/Repo"
post:
summary: Force an immediate refresh of one repository.
operationId: syncRepo
responses:
"204":
description: Refresh completed.
"404":
description: Repository not found or not visible to the caller.
"405":
description: Server is read-only.
"502":
description: Mirror fetch failed.
/api/v1/repos/{owner}/{repo}/branches:
parameters:
- $ref: "#/components/parameters/Owner"
- $ref: "#/components/parameters/Repo"
get:
summary: List tracked branches.
operationId: listBranches
Expand All @@ -73,7 +172,10 @@ paths:
type: array
items:
$ref: "#/components/schemas/BranchResponse"
/api/v1/branches/{branchName}:
/api/v1/repos/{owner}/{repo}/branches/{branchName}:
parameters:
- $ref: "#/components/parameters/Owner"
- $ref: "#/components/parameters/Repo"
get:
summary: Get one tracked branch.
operationId: getBranch
Expand All @@ -92,13 +194,16 @@ paths:
$ref: "#/components/schemas/BranchResponse"
"404":
description: Branch not found.
/api/v1/branch-diff:
/api/v1/repos/{owner}/{repo}/branch-diff/{branchName}:
parameters:
- $ref: "#/components/parameters/Owner"
- $ref: "#/components/parameters/Repo"
get:
summary: Get raw patch diff for a tracked branch.
operationId: getBranchDiff
parameters:
- in: query
name: branch
- in: path
name: branchName
required: true
schema:
type: string
Expand All @@ -110,10 +215,13 @@ paths:
schema:
$ref: "#/components/schemas/BranchDiffResponse"
"400":
description: Missing branch query parameter.
description: Missing branch.
"404":
description: Branch not found.
/api/v1/events:
/api/v1/repos/{owner}/{repo}/events:
parameters:
- $ref: "#/components/parameters/Owner"
- $ref: "#/components/parameters/Repo"
get:
summary: Server-sent event stream for repository refresh events.
operationId: streamEvents
Expand All @@ -124,51 +232,66 @@ paths:
text/event-stream:
schema:
type: string
/api/v1/repos:
get:
summary: List repositories the server is serving (filtered to the caller).
operationId: listRepos
responses:
"200":
description: Repository index.
content:
application/json:
schema:
$ref: "#/components/schemas/ReposListResponse"
/api/v1/webhooks/github:
post:
summary: Onboard a GitHub repository (clone and start serving it).
description: >
Clones a GitHub repository the authenticated user can access and starts
serving it, recorded against that user so only they see it. Requires an
authenticated, writable, DB-backed server with a repos root configured;
read-only servers refuse with 405.
operationId: onboardRepo
summary: Receive a GitHub webhook delivery and trigger a repo refresh.
description: >-
Authenticated solely by the X-Hub-Signature-256 HMAC over the raw body;
it carries no session or CSRF token. Fails closed: when no webhook secret
is configured the endpoint returns 404. A verified push is acked
immediately and the matching managed repo is mirror-fetched off the
request path (a burst for one repo coalesces into a single fetch). It is
unaffected by read-only mode, since a refresh is a read-side operation.
operationId: receiveGitHubWebhook
parameters:
- in: header
name: X-Hub-Signature-256
required: true
description: HMAC-SHA256 of the raw request body, keyed by the webhook secret.
schema:
type: string
- in: header
name: X-GitHub-Event
required: true
description: GitHub event name; only "push" triggers a refresh and "ping" is acked.
schema:
type: string
requestBody:
required: true
content:
application/json:
schema:
$ref: "#/components/schemas/OnboardRepoRequest"
type: object
description: The GitHub webhook event payload (e.g. a push event).
responses:
"201":
description: The newly served repository.
content:
application/json:
schema:
$ref: "#/components/schemas/RepoSummary"
"202":
description: Push accepted; a refresh is scheduled for the matching managed repo.
"204":
description: >-
Verified but no action taken — a ping, a non-push event, or a push
that did not resolve to a managed repo.
"400":
description: Missing or invalid owner/name.
description: Could not read the request body.
"401":
description: Authentication required.
description: Missing or invalid signature.
"404":
description: Repository not found or not accessible to the caller.
"405":
description: Server is read-only.
"409":
description: Repository already onboarded.
"503":
description: Onboarding not available (no auth, database, or repos root).
description: Webhook receiver is disabled (no secret configured).
components:
parameters:
Owner:
in: path
name: owner
required: true
description: GitHub repository owner (login), matched case-insensitively.
schema:
type: string
Repo:
in: path
name: repo
required: true
description: GitHub repository name, matched case-insensitively.
schema:
type: string
schemas:
ViewResponse:
type: object
Expand Down Expand Up @@ -373,6 +496,10 @@ components:
properties:
id:
type: string
owner:
type: string
repo:
type: string
displayName:
type: string
trunk:
Expand Down
4 changes: 2 additions & 2 deletions docs/deploy.md
View file Open in desktop
Original file line number Diff line number Diff line change
Expand Up @@ -230,7 +230,7 @@ clients refetch. Three things trigger it:
(below). The reliable backstop.
2. **Webhooks** — an immediate, push-driven refresh of a single repo
([below](#evented-refresh-webhooks)). The low-latency path.
3. **Manual sync** — `POST /api/v1/repos/{repoID}/sync`, an on-demand refresh
3. **Manual sync** — `POST /api/v1/repos/{owner}/{repo}/sync`, an on-demand refresh
(below). The fallback for local servers and for forcing a pull.

The interval loop is the floor: webhooks and manual sync make refreshes faster
Expand Down Expand Up @@ -284,7 +284,7 @@ point a GitHub webhook at the server:

### Manual sync

`POST /api/v1/repos/{repoID}/sync` forces a refresh of one repo on demand. It is
`POST /api/v1/repos/{owner}/{repo}/sync` forces a refresh of one repo on demand. It is
session-gated like submit (and refused in read-only mode), so it is never an
anonymous trigger. For a managed mirror it mirror-fetches then rebuilds; for a
local `-cwd` working repo it only re-reads on-disk refs (it never mirror-fetches
Expand Down
4 changes: 2 additions & 2 deletions internal/api/auth/middleware_test.go
View file Open in desktop
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,7 @@ func TestRequireCSRFHeader_BlocksMutatingMethodsWithoutHeader(t *testing.T) {
}))

for _, m := range []string{http.MethodPost, http.MethodPut, http.MethodPatch, http.MethodDelete} {
req := httptest.NewRequest(m, "/api/v1/repos/default/stacks/main/submit", nil)
req := httptest.NewRequest(m, "/api/v1/repos/acme/demo/stacks/main/submit", nil)
rr := httptest.NewRecorder()
h.ServeHTTP(rr, req)
require.Equalf(t, http.StatusForbidden, rr.Code, "method %s without header should be 403", m)
Expand All @@ -89,7 +89,7 @@ func TestRequireCSRFHeader_AllowsMutatingMethodsWithHeader(t *testing.T) {
w.WriteHeader(http.StatusOK)
}))

req := httptest.NewRequest(http.MethodPost, "/api/v1/repos/default/stacks/main/submit", nil)
req := httptest.NewRequest(http.MethodPost, "/api/v1/repos/acme/demo/stacks/main/submit", nil)
req.Header.Set(CSRFHeader, "1")
rr := httptest.NewRecorder()
h.ServeHTTP(rr, req)
Expand Down
4 changes: 2 additions & 2 deletions internal/api/handlers/branch_diff.go
View file Open in desktop
Original file line number Diff line number Diff line change
Expand Up @@ -46,9 +46,9 @@ func (h *BranchDiffHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
return
}

branchName := r.URL.Query().Get("branch")
branchName := r.PathValue("name")
if branchName == "" {
http.Error(w, "missing branch query parameter", http.StatusBadRequest)
http.Error(w, "missing branch", http.StatusBadRequest)
return
}
if !validateBranchName(w, branchName) {
Expand Down
Loading
Loading

AltStyle によって変換されたページ (->オリジナル) /