diff --git a/internal/forgejo/client.go b/internal/forgejo/client.go index 14b8b29..14b5a6a 100644 --- a/internal/forgejo/client.go +++ b/internal/forgejo/client.go @@ -429,6 +429,42 @@ func (c *Client) CreateLabel(owner, repo, labelName, color string) (*Label, erro return &label, nil } +// CheckRepoPermission verifies the bot user has write (push) access to the repo. +func (c *Client) CheckRepoPermission(owner, repo string) error { + reqURL := fmt.Sprintf("%s/api/v1/repos/%s/%s", c.baseURL, owner, repo) + httpReq, err := http.NewRequest("GET", reqURL, nil) + if err != nil { + return err + } + httpReq.Header.Set("Authorization", "token "+c.apiToken) + + resp, err := c.httpClient.Do(httpReq) + if err != nil { + return fmt.Errorf("forgejo API request failed: %w", err) + } + defer resp.Body.Close() + + if resp.StatusCode != http.StatusOK { + respBody, _ := io.ReadAll(resp.Body) + return fmt.Errorf("forgejo API returned %d: %s", resp.StatusCode, string(respBody)) + } + + var result struct { + Permissions struct { + Admin bool `json:"admin"` + Push bool `json:"push"` + Pull bool `json:"pull"` + } `json:"permissions"` + } + if err := json.NewDecoder(resp.Body).Decode(&result); err != nil { + return err + } + if !result.Permissions.Push { + return fmt.Errorf("bot user does not have write access to %s/%s — add it as a collaborator with Write permission", owner, repo) + } + return nil +} + // GetOrCreateLabel looks up a label by name, creating it if it doesn't exist. func (c *Client) GetOrCreateLabel(owner, repo, labelName, color string) (*Label, error) { label, err := c.GetLabel(owner, repo, labelName) diff --git a/internal/handlers/admin/repos.go b/internal/handlers/admin/repos.go index 22f2bc8..a3ba5a9 100644 --- a/internal/handlers/admin/repos.go +++ b/internal/handlers/admin/repos.go @@ -51,6 +51,19 @@ func (h *RepoHandler) Create(c *gin.Context) { return } + // Verify the bot user has write access to the Forgejo repo + if err := h.deps.ForgejoClient.CheckRepoPermission(forgejoOwner, forgejoRepo); err != nil { + log.Error().Err(err).Msg("repo permission check failed") + h.deps.Renderer.Render(c.Writer, c.Request, "admin/repos/new", map[string]interface{}{ + "Error": "Forgejo repo check failed: " + err.Error(), + "Name": name, + "Slug": slug, + "ForgejoOwner": forgejoOwner, + "ForgejoRepo": forgejoRepo, + }) + return + } + webhookSecret, err := forgejo.GenerateWebhookSecret() if err != nil { log.Error().Err(err).Msg("generate webhook secret error") @@ -105,10 +118,17 @@ func (h *RepoHandler) EditForm(c *gin.Context) { return } + // Check bot write access to the Forgejo repo + var repoPermErr string + if err := h.deps.ForgejoClient.CheckRepoPermission(repo.ForgejoOwner, repo.ForgejoRepo); err != nil { + repoPermErr = err.Error() + } + h.deps.Renderer.Render(c.Writer, c.Request, "admin/repos/edit", map[string]interface{}{ - "Repo": repo, - "BaseURL": h.deps.Config.BaseURL, - "ForgejoURL": h.deps.Config.ForgejoURL, + "Repo": repo, + "BaseURL": h.deps.Config.BaseURL, + "ForgejoURL": h.deps.Config.ForgejoURL, + "RepoPermErr": repoPermErr, }) } diff --git a/web/templates/pages/admin/repos/edit.html b/web/templates/pages/admin/repos/edit.html index 161a056..c353ed3 100644 --- a/web/templates/pages/admin/repos/edit.html +++ b/web/templates/pages/admin/repos/edit.html @@ -9,6 +9,27 @@
Bot write access missing
+{{.RepoPermErr}}
+Bot write access verified
+