diff --git a/actions.go b/actions.go index 68742f1..23a69b4 100644 --- a/actions.go +++ b/actions.go @@ -203,7 +203,7 @@ func (s *ActionsService) DeleteUserSecret(ctx context.Context, name string) erro // DispatchWorkflow triggers a workflow run. func (s *ActionsService) DispatchWorkflow(ctx context.Context, owner, repo, workflow string, opts map[string]any) error { - path := ResolvePath("/api/v1/repos/{owner}/{repo}/actions/workflows/{workflow}/dispatches", pathParams("owner", owner, "repo", repo, "workflow", workflow)) + path := ResolvePath("/api/v1/repos/{owner}/{repo}/actions/workflows/{workflowname}/dispatches", pathParams("owner", owner, "repo", repo, "workflowname", workflow)) return s.client.Post(ctx, path, opts, nil) } diff --git a/admin.go b/admin.go index eb84feb..ab35e03 100644 --- a/admin.go +++ b/admin.go @@ -77,6 +77,58 @@ func (s *AdminService) CreateUser(ctx context.Context, opts *types.CreateUserOpt return &out, nil } +// CreateUserKey adds a public key on behalf of a user. +func (s *AdminService) CreateUserKey(ctx context.Context, username string, opts *types.CreateKeyOption) (*types.PublicKey, error) { + path := ResolvePath("/api/v1/admin/users/{username}/keys", Params{"username": username}) + var out types.PublicKey + if err := s.client.Post(ctx, path, opts, &out); err != nil { + return nil, err + } + return &out, nil +} + +// DeleteUserKey deletes a user's public key. +func (s *AdminService) DeleteUserKey(ctx context.Context, username string, id int64) error { + path := ResolvePath("/api/v1/admin/users/{username}/keys/{id}", Params{"username": username, "id": int64String(id)}) + return s.client.Delete(ctx, path) +} + +// CreateUserOrg creates an organisation on behalf of a user. +func (s *AdminService) CreateUserOrg(ctx context.Context, username string, opts *types.CreateOrgOption) (*types.Organization, error) { + path := ResolvePath("/api/v1/admin/users/{username}/orgs", Params{"username": username}) + var out types.Organization + if err := s.client.Post(ctx, path, opts, &out); err != nil { + return nil, err + } + return &out, nil +} + +// GetUserQuota returns a user's quota information. +func (s *AdminService) GetUserQuota(ctx context.Context, username string) (*types.QuotaInfo, error) { + path := ResolvePath("/api/v1/admin/users/{username}/quota", Params{"username": username}) + var out types.QuotaInfo + if err := s.client.Get(ctx, path, &out); err != nil { + return nil, err + } + return &out, nil +} + +// SetUserQuotaGroups sets the user's quota groups to a given list. +func (s *AdminService) SetUserQuotaGroups(ctx context.Context, username string, opts *types.SetUserQuotaGroupsOptions) error { + path := ResolvePath("/api/v1/admin/users/{username}/quota/groups", Params{"username": username}) + return s.client.Post(ctx, path, opts, nil) +} + +// CreateUserRepo creates a repository on behalf of a user. +func (s *AdminService) CreateUserRepo(ctx context.Context, username string, opts *types.CreateRepoOption) (*types.Repository, error) { + path := ResolvePath("/api/v1/admin/users/{username}/repos", Params{"username": username}) + var out types.Repository + if err := s.client.Post(ctx, path, opts, &out); err != nil { + return nil, err + } + return &out, nil +} + // EditUser edits an existing user (admin only). func (s *AdminService) EditUser(ctx context.Context, username string, opts map[string]any) error { path := ResolvePath("/api/v1/admin/users/{username}", Params{"username": username}) @@ -190,6 +242,18 @@ func (s *AdminService) DeleteQuotaGroup(ctx context.Context, quotagroup string) return s.client.Delete(ctx, path) } +// AddQuotaGroupRule adds a quota rule to a quota group. +func (s *AdminService) AddQuotaGroupRule(ctx context.Context, quotagroup, quotarule string) error { + path := ResolvePath("/api/v1/admin/quota/groups/{quotagroup}/rules/{quotarule}", Params{"quotagroup": quotagroup, "quotarule": quotarule}) + return s.client.Put(ctx, path, nil, nil) +} + +// RemoveQuotaGroupRule removes a quota rule from a quota group. +func (s *AdminService) RemoveQuotaGroupRule(ctx context.Context, quotagroup, quotarule string) error { + path := ResolvePath("/api/v1/admin/quota/groups/{quotagroup}/rules/{quotarule}", Params{"quotagroup": quotagroup, "quotarule": quotarule}) + return s.client.Delete(ctx, path) +} + // ListQuotaGroupUsers returns all users in a quota group. func (s *AdminService) ListQuotaGroupUsers(ctx context.Context, quotagroup string) ([]types.User, error) { path := ResolvePath("/api/v1/admin/quota/groups/{quotagroup}/users", Params{"quotagroup": quotagroup}) diff --git a/issues.go b/issues.go index 6c78e7d..f72d2b2 100644 --- a/issues.go +++ b/issues.go @@ -245,7 +245,7 @@ func (s *IssueService) AddLabels(ctx context.Context, owner, repo string, index // RemoveLabel removes a single label from an issue. func (s *IssueService) RemoveLabel(ctx context.Context, owner, repo string, index int64, labelID int64) error { - path := ResolvePath("/api/v1/repos/{owner}/{repo}/issues/{index}/labels/{labelID}", pathParams("owner", owner, "repo", repo, "index", int64String(index), "labelID", int64String(labelID))) + path := ResolvePath("/api/v1/repos/{owner}/{repo}/issues/{index}/labels/{id}", pathParams("owner", owner, "repo", repo, "index", int64String(index), "id", int64String(labelID))) return s.client.Delete(ctx, path) } @@ -272,6 +272,22 @@ func (s *IssueService) CreateComment(ctx context.Context, owner, repo string, in return &out, nil } +// EditComment updates an issue comment. +func (s *IssueService) EditComment(ctx context.Context, owner, repo string, index, id int64, opts *types.EditIssueCommentOption) (*types.Comment, error) { + path := ResolvePath("/api/v1/repos/{owner}/{repo}/issues/{index}/comments/{id}", pathParams("owner", owner, "repo", repo, "index", int64String(index), "id", int64String(id))) + var out types.Comment + if err := s.client.Patch(ctx, path, opts, &out); err != nil { + return nil, err + } + return &out, nil +} + +// DeleteComment deletes an issue comment. +func (s *IssueService) DeleteComment(ctx context.Context, owner, repo string, index, id int64) error { + path := ResolvePath("/api/v1/repos/{owner}/{repo}/issues/{index}/comments/{id}", pathParams("owner", owner, "repo", repo, "index", int64String(index), "id", int64String(id))) + return s.client.Delete(ctx, path) +} + // ListCommentReactions returns all reactions on an issue comment. func (s *IssueService) ListCommentReactions(ctx context.Context, owner, repo string, id int64) ([]types.Reaction, error) { path := ResolvePath("/api/v1/repos/{owner}/{repo}/issues/comments/{id}/reactions", pathParams("owner", owner, "repo", repo, "id", int64String(id))) diff --git a/labels.go b/labels.go index 341e7d3..37a12bb 100644 --- a/labels.go +++ b/labels.go @@ -91,3 +91,64 @@ func (s *LabelService) CreateOrgLabel(ctx context.Context, org string, opts *typ } return &out, nil } + +// GetOrgLabel returns a single label for an organisation. +func (s *LabelService) GetOrgLabel(ctx context.Context, org string, id int64) (*types.Label, error) { + path := ResolvePath("/api/v1/orgs/{org}/labels/{id}", pathParams("org", org, "id", int64String(id))) + var out types.Label + if err := s.client.Get(ctx, path, &out); err != nil { + return nil, err + } + return &out, nil +} + +// EditOrgLabel updates an existing label in an organisation. +func (s *LabelService) EditOrgLabel(ctx context.Context, org string, id int64, opts *types.EditLabelOption) (*types.Label, error) { + path := ResolvePath("/api/v1/orgs/{org}/labels/{id}", pathParams("org", org, "id", int64String(id))) + var out types.Label + if err := s.client.Patch(ctx, path, opts, &out); err != nil { + return nil, err + } + return &out, nil +} + +// DeleteOrgLabel deletes a label from an organisation. +func (s *LabelService) DeleteOrgLabel(ctx context.Context, org string, id int64) error { + path := ResolvePath("/api/v1/orgs/{org}/labels/{id}", pathParams("org", org, "id", int64String(id))) + return s.client.Delete(ctx, path) +} + +// ListLabelTemplates returns all available label template names. +func (s *LabelService) ListLabelTemplates(ctx context.Context) ([]string, error) { + var out []string + if err := s.client.Get(ctx, "/api/v1/label/templates", &out); err != nil { + return nil, err + } + return out, nil +} + +// IterLabelTemplates returns an iterator over all available label template names. +func (s *LabelService) IterLabelTemplates(ctx context.Context) iter.Seq2[string, error] { + return func(yield func(string, error) bool) { + items, err := s.ListLabelTemplates(ctx) + if err != nil { + yield("", err) + return + } + for _, item := range items { + if !yield(item, nil) { + return + } + } + } +} + +// GetLabelTemplate returns all labels for a label template. +func (s *LabelService) GetLabelTemplate(ctx context.Context, name string) ([]types.LabelTemplate, error) { + path := ResolvePath("/api/v1/label/templates/{name}", pathParams("name", name)) + var out []types.LabelTemplate + if err := s.client.Get(ctx, path, &out); err != nil { + return nil, err + } + return out, nil +} diff --git a/orgs.go b/orgs.go index aaf1e74..20b732a 100644 --- a/orgs.go +++ b/orgs.go @@ -79,20 +79,20 @@ func (s *OrgService) IsMember(ctx context.Context, org, username string) (bool, } // ListBlockedUsers returns all users blocked by an organisation. -func (s *OrgService) ListBlockedUsers(ctx context.Context, org string) ([]types.User, error) { - path := ResolvePath("/api/v1/orgs/{org}/blocks", pathParams("org", org)) - return ListAll[types.User](ctx, s.client, path, nil) +func (s *OrgService) ListBlockedUsers(ctx context.Context, org string) ([]types.BlockedUser, error) { + path := ResolvePath("/api/v1/orgs/{org}/list_blocked", pathParams("org", org)) + return ListAll[types.BlockedUser](ctx, s.client, path, nil) } // IterBlockedUsers returns an iterator over all users blocked by an organisation. -func (s *OrgService) IterBlockedUsers(ctx context.Context, org string) iter.Seq2[types.User, error] { - path := ResolvePath("/api/v1/orgs/{org}/blocks", pathParams("org", org)) - return ListIter[types.User](ctx, s.client, path, nil) +func (s *OrgService) IterBlockedUsers(ctx context.Context, org string) iter.Seq2[types.BlockedUser, error] { + path := ResolvePath("/api/v1/orgs/{org}/list_blocked", pathParams("org", org)) + return ListIter[types.BlockedUser](ctx, s.client, path, nil) } // IsBlocked reports whether a user is blocked by an organisation. func (s *OrgService) IsBlocked(ctx context.Context, org, username string) (bool, error) { - path := ResolvePath("/api/v1/orgs/{org}/blocks/{username}", pathParams("org", org, "username", username)) + path := ResolvePath("/api/v1/orgs/{org}/block/{username}", pathParams("org", org, "username", username)) resp, err := s.client.doJSON(ctx, "GET", path, nil, nil) if err != nil { if IsNotFound(err) { @@ -142,16 +142,116 @@ func (s *OrgService) ConcealMember(ctx context.Context, org, username string) er // Block blocks a user within an organisation. func (s *OrgService) Block(ctx context.Context, org, username string) error { - path := ResolvePath("/api/v1/orgs/{org}/blocks/{username}", pathParams("org", org, "username", username)) + path := ResolvePath("/api/v1/orgs/{org}/block/{username}", pathParams("org", org, "username", username)) return s.client.Put(ctx, path, nil, nil) } // Unblock unblocks a user within an organisation. func (s *OrgService) Unblock(ctx context.Context, org, username string) error { - path := ResolvePath("/api/v1/orgs/{org}/blocks/{username}", pathParams("org", org, "username", username)) + path := ResolvePath("/api/v1/orgs/{org}/unblock/{username}", pathParams("org", org, "username", username)) return s.client.Delete(ctx, path) } +// GetQuota returns the quota information for an organisation. +func (s *OrgService) GetQuota(ctx context.Context, org string) (*types.QuotaInfo, error) { + path := ResolvePath("/api/v1/orgs/{org}/quota", pathParams("org", org)) + var out types.QuotaInfo + if err := s.client.Get(ctx, path, &out); err != nil { + return nil, err + } + return &out, nil +} + +// CheckQuota reports whether an organisation is over quota for the current subject. +func (s *OrgService) CheckQuota(ctx context.Context, org string) (bool, error) { + path := ResolvePath("/api/v1/orgs/{org}/quota/check", pathParams("org", org)) + var out bool + if err := s.client.Get(ctx, path, &out); err != nil { + return false, err + } + return out, nil +} + +// ListQuotaArtifacts returns all artefacts counting towards an organisation's quota. +func (s *OrgService) ListQuotaArtifacts(ctx context.Context, org string) ([]types.QuotaUsedArtifact, error) { + path := ResolvePath("/api/v1/orgs/{org}/quota/artifacts", pathParams("org", org)) + return ListAll[types.QuotaUsedArtifact](ctx, s.client, path, nil) +} + +// IterQuotaArtifacts returns an iterator over all artefacts counting towards an organisation's quota. +func (s *OrgService) IterQuotaArtifacts(ctx context.Context, org string) iter.Seq2[types.QuotaUsedArtifact, error] { + path := ResolvePath("/api/v1/orgs/{org}/quota/artifacts", pathParams("org", org)) + return ListIter[types.QuotaUsedArtifact](ctx, s.client, path, nil) +} + +// ListQuotaAttachments returns all attachments counting towards an organisation's quota. +func (s *OrgService) ListQuotaAttachments(ctx context.Context, org string) ([]types.QuotaUsedAttachment, error) { + path := ResolvePath("/api/v1/orgs/{org}/quota/attachments", pathParams("org", org)) + return ListAll[types.QuotaUsedAttachment](ctx, s.client, path, nil) +} + +// IterQuotaAttachments returns an iterator over all attachments counting towards an organisation's quota. +func (s *OrgService) IterQuotaAttachments(ctx context.Context, org string) iter.Seq2[types.QuotaUsedAttachment, error] { + path := ResolvePath("/api/v1/orgs/{org}/quota/attachments", pathParams("org", org)) + return ListIter[types.QuotaUsedAttachment](ctx, s.client, path, nil) +} + +// ListQuotaPackages returns all packages counting towards an organisation's quota. +func (s *OrgService) ListQuotaPackages(ctx context.Context, org string) ([]types.QuotaUsedPackage, error) { + path := ResolvePath("/api/v1/orgs/{org}/quota/packages", pathParams("org", org)) + return ListAll[types.QuotaUsedPackage](ctx, s.client, path, nil) +} + +// IterQuotaPackages returns an iterator over all packages counting towards an organisation's quota. +func (s *OrgService) IterQuotaPackages(ctx context.Context, org string) iter.Seq2[types.QuotaUsedPackage, error] { + path := ResolvePath("/api/v1/orgs/{org}/quota/packages", pathParams("org", org)) + return ListIter[types.QuotaUsedPackage](ctx, s.client, path, nil) +} + +// GetRunnerRegistrationToken returns an organisation actions runner registration token. +func (s *OrgService) GetRunnerRegistrationToken(ctx context.Context, org string) (string, error) { + path := ResolvePath("/api/v1/orgs/{org}/actions/runners/registration-token", pathParams("org", org)) + resp, err := s.client.doJSON(ctx, http.MethodGet, path, nil, nil) + if err != nil { + return "", err + } + return resp.Header.Get("token"), nil +} + +// UpdateAvatar updates an organisation avatar. +func (s *OrgService) UpdateAvatar(ctx context.Context, org string, opts *types.UpdateUserAvatarOption) error { + path := ResolvePath("/api/v1/orgs/{org}/avatar", pathParams("org", org)) + return s.client.Post(ctx, path, opts, nil) +} + +// DeleteAvatar deletes an organisation avatar. +func (s *OrgService) DeleteAvatar(ctx context.Context, org string) error { + path := ResolvePath("/api/v1/orgs/{org}/avatar", pathParams("org", org)) + return s.client.Delete(ctx, path) +} + +// SearchTeams searches for teams within an organisation. +func (s *OrgService) SearchTeams(ctx context.Context, org, q string) ([]types.Team, error) { + path := ResolvePath("/api/v1/orgs/{org}/teams/search", pathParams("org", org)) + return ListAll[types.Team](ctx, s.client, path, map[string]string{"q": q}) +} + +// IterSearchTeams returns an iterator over teams within an organisation. +func (s *OrgService) IterSearchTeams(ctx context.Context, org, q string) iter.Seq2[types.Team, error] { + path := ResolvePath("/api/v1/orgs/{org}/teams/search", pathParams("org", org)) + return ListIter[types.Team](ctx, s.client, path, map[string]string{"q": q}) +} + +// GetUserPermissions returns a user's permissions in an organisation. +func (s *OrgService) GetUserPermissions(ctx context.Context, username, org string) (*types.OrganizationPermissions, error) { + path := ResolvePath("/api/v1/users/{username}/orgs/{org}/permissions", pathParams("username", username, "org", org)) + var out types.OrganizationPermissions + if err := s.client.Get(ctx, path, &out); err != nil { + return nil, err + } + return &out, nil +} + // ListActivityFeeds returns the organisation's activity feed entries. func (s *OrgService) ListActivityFeeds(ctx context.Context, org string, filters ...OrgActivityFeedListOptions) ([]types.Activity, error) { path := ResolvePath("/api/v1/orgs/{org}/activities/feeds", pathParams("org", org)) diff --git a/orgs_test.go b/orgs_test.go index 94bb985..81ac6d6 100644 --- a/orgs_test.go +++ b/orgs_test.go @@ -144,13 +144,13 @@ func TestOrgService_ListBlockedUsers_Good(t *testing.T) { if r.Method != http.MethodGet { t.Errorf("expected GET, got %s", r.Method) } - if r.URL.Path != "/api/v1/orgs/core/blocks" { + if r.URL.Path != "/api/v1/orgs/core/list_blocked" { t.Errorf("wrong path: %s", r.URL.Path) } w.Header().Set("X-Total-Count", "2") - json.NewEncoder(w).Encode([]types.User{ - {ID: 1, UserName: "alice"}, - {ID: 2, UserName: "bob"}, + json.NewEncoder(w).Encode([]types.BlockedUser{ + {BlockID: 1}, + {BlockID: 2}, }) })) defer srv.Close() @@ -163,8 +163,8 @@ func TestOrgService_ListBlockedUsers_Good(t *testing.T) { if len(blocked) != 2 { t.Fatalf("got %d blocked users, want 2", len(blocked)) } - if blocked[0].UserName != "alice" { - t.Errorf("got username=%q, want %q", blocked[0].UserName, "alice") + if blocked[0].BlockID != 1 { + t.Errorf("got block_id=%d, want %d", blocked[0].BlockID, 1) } } @@ -209,7 +209,7 @@ func TestOrgService_Block_Good(t *testing.T) { if r.Method != http.MethodPut { t.Errorf("expected PUT, got %s", r.Method) } - if r.URL.Path != "/api/v1/orgs/core/blocks/alice" { + if r.URL.Path != "/api/v1/orgs/core/block/alice" { t.Errorf("wrong path: %s", r.URL.Path) } w.WriteHeader(http.StatusNoContent) @@ -227,7 +227,7 @@ func TestOrgService_Unblock_Good(t *testing.T) { if r.Method != http.MethodDelete { t.Errorf("expected DELETE, got %s", r.Method) } - if r.URL.Path != "/api/v1/orgs/core/blocks/alice" { + if r.URL.Path != "/api/v1/orgs/core/unblock/alice" { t.Errorf("wrong path: %s", r.URL.Path) } w.WriteHeader(http.StatusNoContent) @@ -311,7 +311,7 @@ func TestOrgService_IsBlocked_Good(t *testing.T) { if r.Method != http.MethodGet { t.Errorf("expected GET, got %s", r.Method) } - if r.URL.Path != "/api/v1/orgs/core/blocks/alice" { + if r.URL.Path != "/api/v1/orgs/core/block/alice" { t.Errorf("wrong path: %s", r.URL.Path) } w.WriteHeader(http.StatusNoContent) diff --git a/packages.go b/packages.go index 2553349..43784a1 100644 --- a/packages.go +++ b/packages.go @@ -36,7 +36,7 @@ func (s *PackageService) Iter(ctx context.Context, owner string) iter.Seq2[types // Get returns a single package by owner, type, name, and version. func (s *PackageService) Get(ctx context.Context, owner, pkgType, name, version string) (*types.Package, error) { - path := ResolvePath("/api/v1/packages/{owner}/{pkgType}/{name}/{version}", pathParams("owner", owner, "pkgType", pkgType, "name", name, "version", version)) + path := ResolvePath("/api/v1/packages/{owner}/{type}/{name}/{version}", pathParams("owner", owner, "type", pkgType, "name", name, "version", version)) var out types.Package if err := s.client.Get(ctx, path, &out); err != nil { return nil, err @@ -46,18 +46,18 @@ func (s *PackageService) Get(ctx context.Context, owner, pkgType, name, version // Delete removes a package by owner, type, name, and version. func (s *PackageService) Delete(ctx context.Context, owner, pkgType, name, version string) error { - path := ResolvePath("/api/v1/packages/{owner}/{pkgType}/{name}/{version}", pathParams("owner", owner, "pkgType", pkgType, "name", name, "version", version)) + path := ResolvePath("/api/v1/packages/{owner}/{type}/{name}/{version}", pathParams("owner", owner, "type", pkgType, "name", name, "version", version)) return s.client.Delete(ctx, path) } // ListFiles returns all files for a specific package version. func (s *PackageService) ListFiles(ctx context.Context, owner, pkgType, name, version string) ([]types.PackageFile, error) { - path := ResolvePath("/api/v1/packages/{owner}/{pkgType}/{name}/{version}/files", pathParams("owner", owner, "pkgType", pkgType, "name", name, "version", version)) + path := ResolvePath("/api/v1/packages/{owner}/{type}/{name}/{version}/files", pathParams("owner", owner, "type", pkgType, "name", name, "version", version)) return ListAll[types.PackageFile](ctx, s.client, path, nil) } // IterFiles returns an iterator over all files for a specific package version. func (s *PackageService) IterFiles(ctx context.Context, owner, pkgType, name, version string) iter.Seq2[types.PackageFile, error] { - path := ResolvePath("/api/v1/packages/{owner}/{pkgType}/{name}/{version}/files", pathParams("owner", owner, "pkgType", pkgType, "name", name, "version", version)) + path := ResolvePath("/api/v1/packages/{owner}/{type}/{name}/{version}/files", pathParams("owner", owner, "type", pkgType, "name", name, "version", version)) return ListIter[types.PackageFile](ctx, s.client, path, nil) } diff --git a/pulls.go b/pulls.go index 5f00e98..ca5f865 100644 --- a/pulls.go +++ b/pulls.go @@ -44,6 +44,24 @@ func (s *PullService) Update(ctx context.Context, owner, repo string, index int6 return s.client.Post(ctx, path, nil, nil) } +// GetDiffOrPatch returns a pull request diff or patch as raw bytes. +func (s *PullService) GetDiffOrPatch(ctx context.Context, owner, repo string, index int64, diffType string) ([]byte, error) { + path := ResolvePath("/api/v1/repos/{owner}/{repo}/pulls/{index}.{diffType}", pathParams("owner", owner, "repo", repo, "index", int64String(index), "diffType", diffType)) + return s.client.GetRaw(ctx, path) +} + +// ListCommits returns all commits for a pull request. +func (s *PullService) ListCommits(ctx context.Context, owner, repo string, index int64) ([]types.Commit, error) { + path := ResolvePath("/api/v1/repos/{owner}/{repo}/pulls/{index}/commits", pathParams("owner", owner, "repo", repo, "index", int64String(index))) + return ListAll[types.Commit](ctx, s.client, path, nil) +} + +// IterCommits returns an iterator over all commits for a pull request. +func (s *PullService) IterCommits(ctx context.Context, owner, repo string, index int64) iter.Seq2[types.Commit, error] { + path := ResolvePath("/api/v1/repos/{owner}/{repo}/pulls/{index}/commits", pathParams("owner", owner, "repo", repo, "index", int64String(index))) + return ListIter[types.Commit](ctx, s.client, path, nil) +} + // ListReviews returns all reviews on a pull request. func (s *PullService) ListReviews(ctx context.Context, owner, repo string, index int64) ([]types.PullReview, error) { path := ResolvePath("/api/v1/repos/{owner}/{repo}/pulls/{index}/reviews", pathParams("owner", owner, "repo", repo, "index", int64String(index))) @@ -112,7 +130,7 @@ func (s *PullService) CancelReviewRequests(ctx context.Context, owner, repo stri } // SubmitReview creates a new review on a pull request. -func (s *PullService) SubmitReview(ctx context.Context, owner, repo string, index int64, review map[string]any) (*types.PullReview, error) { +func (s *PullService) SubmitReview(ctx context.Context, owner, repo string, index int64, review *types.SubmitPullReviewOptions) (*types.PullReview, error) { path := ResolvePath("/api/v1/repos/{owner}/{repo}/pulls/{index}/reviews", pathParams("owner", owner, "repo", repo, "index", int64String(index))) var out types.PullReview if err := s.client.Post(ctx, path, review, &out); err != nil { @@ -121,15 +139,69 @@ func (s *PullService) SubmitReview(ctx context.Context, owner, repo string, inde return &out, nil } +// GetReview returns a single pull request review. +func (s *PullService) GetReview(ctx context.Context, owner, repo string, index, reviewID int64) (*types.PullReview, error) { + path := ResolvePath("/api/v1/repos/{owner}/{repo}/pulls/{index}/reviews/{id}", pathParams("owner", owner, "repo", repo, "index", int64String(index), "id", int64String(reviewID))) + var out types.PullReview + if err := s.client.Get(ctx, path, &out); err != nil { + return nil, err + } + return &out, nil +} + +// DeleteReview deletes a pull request review. +func (s *PullService) DeleteReview(ctx context.Context, owner, repo string, index, reviewID int64) error { + path := ResolvePath("/api/v1/repos/{owner}/{repo}/pulls/{index}/reviews/{id}", pathParams("owner", owner, "repo", repo, "index", int64String(index), "id", int64String(reviewID))) + return s.client.Delete(ctx, path) +} + +// ListReviewComments returns all comments on a pull request review. +func (s *PullService) ListReviewComments(ctx context.Context, owner, repo string, index, reviewID int64) ([]types.PullReviewComment, error) { + path := ResolvePath("/api/v1/repos/{owner}/{repo}/pulls/{index}/reviews/{id}/comments", pathParams("owner", owner, "repo", repo, "index", int64String(index), "id", int64String(reviewID))) + return ListAll[types.PullReviewComment](ctx, s.client, path, nil) +} + +// IterReviewComments returns an iterator over all comments on a pull request review. +func (s *PullService) IterReviewComments(ctx context.Context, owner, repo string, index, reviewID int64) iter.Seq2[types.PullReviewComment, error] { + path := ResolvePath("/api/v1/repos/{owner}/{repo}/pulls/{index}/reviews/{id}/comments", pathParams("owner", owner, "repo", repo, "index", int64String(index), "id", int64String(reviewID))) + return ListIter[types.PullReviewComment](ctx, s.client, path, nil) +} + +// GetReviewComment returns a single comment on a pull request review. +func (s *PullService) GetReviewComment(ctx context.Context, owner, repo string, index, reviewID, commentID int64) (*types.PullReviewComment, error) { + path := ResolvePath("/api/v1/repos/{owner}/{repo}/pulls/{index}/reviews/{id}/comments/{comment}", pathParams("owner", owner, "repo", repo, "index", int64String(index), "id", int64String(reviewID), "comment", int64String(commentID))) + var out types.PullReviewComment + if err := s.client.Get(ctx, path, &out); err != nil { + return nil, err + } + return &out, nil +} + +// CreateReviewComment creates a new comment on a pull request review. +func (s *PullService) CreateReviewComment(ctx context.Context, owner, repo string, index, reviewID int64, opts *types.CreatePullReviewCommentOptions) (*types.PullReviewComment, error) { + path := ResolvePath("/api/v1/repos/{owner}/{repo}/pulls/{index}/reviews/{id}/comments", pathParams("owner", owner, "repo", repo, "index", int64String(index), "id", int64String(reviewID))) + var out types.PullReviewComment + if err := s.client.Post(ctx, path, opts, &out); err != nil { + return nil, err + } + return &out, nil +} + +// DeleteReviewComment deletes a comment on a pull request review. +func (s *PullService) DeleteReviewComment(ctx context.Context, owner, repo string, index, reviewID, commentID int64) error { + path := ResolvePath("/api/v1/repos/{owner}/{repo}/pulls/{index}/reviews/{id}/comments/{comment}", pathParams("owner", owner, "repo", repo, "index", int64String(index), "id", int64String(reviewID), "comment", int64String(commentID))) + return s.client.Delete(ctx, path) +} + // DismissReview dismisses a pull request review. func (s *PullService) DismissReview(ctx context.Context, owner, repo string, index, reviewID int64, msg string) error { - path := ResolvePath("/api/v1/repos/{owner}/{repo}/pulls/{index}/reviews/{reviewID}/dismissals", pathParams("owner", owner, "repo", repo, "index", int64String(index), "reviewID", int64String(reviewID))) + path := ResolvePath("/api/v1/repos/{owner}/{repo}/pulls/{index}/reviews/{id}/dismissals", pathParams("owner", owner, "repo", repo, "index", int64String(index), "id", int64String(reviewID))) body := map[string]string{"message": msg} return s.client.Post(ctx, path, body, nil) } // UndismissReview undismisses a pull request review. func (s *PullService) UndismissReview(ctx context.Context, owner, repo string, index, reviewID int64) error { - path := ResolvePath("/api/v1/repos/{owner}/{repo}/pulls/{index}/reviews/{reviewID}/undismissals", pathParams("owner", owner, "repo", repo, "index", int64String(index), "reviewID", int64String(reviewID))) + path := ResolvePath("/api/v1/repos/{owner}/{repo}/pulls/{index}/reviews/{id}/undismissals", pathParams("owner", owner, "repo", repo, "index", int64String(index), "id", int64String(reviewID))) return s.client.Post(ctx, path, nil, nil) } diff --git a/repos.go b/repos.go index b40af7f..bb94868 100644 --- a/repos.go +++ b/repos.go @@ -55,6 +55,30 @@ func (o ActivityFeedListOptions) queryParams() map[string]string { } } +// RepoTimeListOptions controls filtering for repository tracked times. +type RepoTimeListOptions struct { + User string + Since *time.Time + Before *time.Time +} + +func (o RepoTimeListOptions) queryParams() map[string]string { + query := make(map[string]string, 3) + if o.User != "" { + query["user"] = o.User + } + if o.Since != nil { + query["since"] = o.Since.Format(time.RFC3339) + } + if o.Before != nil { + query["before"] = o.Before.Format(time.RFC3339) + } + if len(query) == 0 { + return nil + } + return query +} + func newRepoService(c *Client) *RepoService { return &RepoService{ Resource: *NewResource[types.Repository, types.CreateRepoOption, types.EditRepoOption]( @@ -72,6 +96,16 @@ func (s *RepoService) Migrate(ctx context.Context, opts *types.MigrateRepoOption return &out, nil } +// CreateOrgRepo creates a repository in an organisation. +func (s *RepoService) CreateOrgRepo(ctx context.Context, org string, opts *types.CreateRepoOption) (*types.Repository, error) { + path := ResolvePath("/api/v1/org/{org}/repos", pathParams("org", org)) + var out types.Repository + if err := s.client.Post(ctx, path, opts, &out); err != nil { + return nil, err + } + return &out, nil +} + // ListOrgRepos returns all repositories for an organisation. func (s *RepoService) ListOrgRepos(ctx context.Context, org string) ([]types.Repository, error) { path := ResolvePath("/api/v1/orgs/{org}/repos", pathParams("org", org)) @@ -635,6 +669,31 @@ func (s *RepoService) DeleteTopic(ctx context.Context, owner, repo, topic string return s.client.Delete(ctx, path) } +// AddFlag adds a flag to a repository. +func (s *RepoService) AddFlag(ctx context.Context, owner, repo, flag string) error { + path := ResolvePath("/api/v1/repos/{owner}/{repo}/flags/{flag}", pathParams("owner", owner, "repo", repo, "flag", flag)) + return s.client.Put(ctx, path, nil, nil) +} + +// HasFlag reports whether a repository has a given flag. +func (s *RepoService) HasFlag(ctx context.Context, owner, repo, flag string) (bool, error) { + path := ResolvePath("/api/v1/repos/{owner}/{repo}/flags/{flag}", pathParams("owner", owner, "repo", repo, "flag", flag)) + resp, err := s.client.doJSON(ctx, http.MethodGet, path, nil, nil) + if err != nil { + if IsNotFound(err) { + return false, nil + } + return false, err + } + return resp.StatusCode == http.StatusNoContent, nil +} + +// RemoveFlag removes a flag from a repository. +func (s *RepoService) RemoveFlag(ctx context.Context, owner, repo, flag string) error { + path := ResolvePath("/api/v1/repos/{owner}/{repo}/flags/{flag}", pathParams("owner", owner, "repo", repo, "flag", flag)) + return s.client.Delete(ctx, path) +} + // GetNewPinAllowed returns whether new issue pins are allowed for a repository. func (s *RepoService) GetNewPinAllowed(ctx context.Context, owner, repo string) (*types.NewIssuePinsAllowed, error) { path := ResolvePath("/api/v1/repos/{owner}/{repo}/new_pin_allowed", pathParams("owner", owner, "repo", repo)) @@ -812,6 +871,84 @@ func (s *RepoService) SyncPushMirrors(ctx context.Context, owner, repo string) e return s.client.Post(ctx, path, nil, nil) } +// GetBlob returns the blob content for a repository object. +func (s *RepoService) GetBlob(ctx context.Context, owner, repo, sha string) (*types.GitBlobResponse, error) { + path := ResolvePath("/api/v1/repos/{owner}/{repo}/git/blobs/{sha}", pathParams("owner", owner, "repo", repo, "sha", sha)) + var out types.GitBlobResponse + if err := s.client.Get(ctx, path, &out); err != nil { + return nil, err + } + return &out, nil +} + +// ListGitRefs returns all git references for a repository. +func (s *RepoService) ListGitRefs(ctx context.Context, owner, repo string) ([]types.Reference, error) { + path := ResolvePath("/api/v1/repos/{owner}/{repo}/git/refs", pathParams("owner", owner, "repo", repo)) + return ListAll[types.Reference](ctx, s.client, path, nil) +} + +// IterGitRefs returns an iterator over all git references for a repository. +func (s *RepoService) IterGitRefs(ctx context.Context, owner, repo string) iter.Seq2[types.Reference, error] { + path := ResolvePath("/api/v1/repos/{owner}/{repo}/git/refs", pathParams("owner", owner, "repo", repo)) + return ListIter[types.Reference](ctx, s.client, path, nil) +} + +// ListGitRefsByRef returns all git references matching a ref prefix. +func (s *RepoService) ListGitRefsByRef(ctx context.Context, owner, repo, ref string) ([]types.Reference, error) { + path := ResolvePath("/api/v1/repos/{owner}/{repo}/git/refs/{ref}", pathParams("owner", owner, "repo", repo, "ref", ref)) + return ListAll[types.Reference](ctx, s.client, path, nil) +} + +// IterGitRefsByRef returns an iterator over all git references matching a ref prefix. +func (s *RepoService) IterGitRefsByRef(ctx context.Context, owner, repo, ref string) iter.Seq2[types.Reference, error] { + path := ResolvePath("/api/v1/repos/{owner}/{repo}/git/refs/{ref}", pathParams("owner", owner, "repo", repo, "ref", ref)) + return ListIter[types.Reference](ctx, s.client, path, nil) +} + +// GetAnnotatedTag returns the annotated tag object for a tag SHA. +func (s *RepoService) GetAnnotatedTag(ctx context.Context, owner, repo, sha string) (*types.AnnotatedTag, error) { + path := ResolvePath("/api/v1/repos/{owner}/{repo}/git/tags/{sha}", pathParams("owner", owner, "repo", repo, "sha", sha)) + var out types.AnnotatedTag + if err := s.client.Get(ctx, path, &out); err != nil { + return nil, err + } + return &out, nil +} + +// GetTree returns the git tree for a repository object. +func (s *RepoService) GetTree(ctx context.Context, owner, repo, sha string) (*types.GitTreeResponse, error) { + path := ResolvePath("/api/v1/repos/{owner}/{repo}/git/trees/{sha}", pathParams("owner", owner, "repo", repo, "sha", sha)) + var out types.GitTreeResponse + if err := s.client.Get(ctx, path, &out); err != nil { + return nil, err + } + return &out, nil +} + +// ListTimes returns all tracked times for a repository. +func (s *RepoService) ListTimes(ctx context.Context, owner, repo string, filters ...RepoTimeListOptions) ([]types.TrackedTime, error) { + path := ResolvePath("/api/v1/repos/{owner}/{repo}/times", pathParams("owner", owner, "repo", repo)) + return ListAll[types.TrackedTime](ctx, s.client, path, repoTimeQuery(filters...)) +} + +// IterTimes returns an iterator over all tracked times for a repository. +func (s *RepoService) IterTimes(ctx context.Context, owner, repo string, filters ...RepoTimeListOptions) iter.Seq2[types.TrackedTime, error] { + path := ResolvePath("/api/v1/repos/{owner}/{repo}/times", pathParams("owner", owner, "repo", repo)) + return ListIter[types.TrackedTime](ctx, s.client, path, repoTimeQuery(filters...)) +} + +// ListUserTimes returns all tracked times for a user in a repository. +func (s *RepoService) ListUserTimes(ctx context.Context, owner, repo, username string) ([]types.TrackedTime, error) { + path := ResolvePath("/api/v1/repos/{owner}/{repo}/times/{user}", pathParams("owner", owner, "repo", repo, "user", username)) + return ListAll[types.TrackedTime](ctx, s.client, path, nil) +} + +// IterUserTimes returns an iterator over all tracked times for a user in a repository. +func (s *RepoService) IterUserTimes(ctx context.Context, owner, repo, username string) iter.Seq2[types.TrackedTime, error] { + path := ResolvePath("/api/v1/repos/{owner}/{repo}/times/{user}", pathParams("owner", owner, "repo", repo, "user", username)) + return ListIter[types.TrackedTime](ctx, s.client, path, nil) +} + func repoKeyQuery(filters ...RepoKeyListOptions) map[string]string { if len(filters) == 0 { return nil @@ -832,6 +969,23 @@ func repoKeyQuery(filters ...RepoKeyListOptions) map[string]string { return query } +func repoTimeQuery(filters ...RepoTimeListOptions) map[string]string { + if len(filters) == 0 { + return nil + } + + query := make(map[string]string, 3) + for _, filter := range filters { + for key, value := range filter.queryParams() { + query[key] = value + } + } + if len(query) == 0 { + return nil + } + return query +} + func activityFeedQuery(filters ...ActivityFeedListOptions) map[string]string { if len(filters) == 0 { return nil diff --git a/teams.go b/teams.go index c37a94a..0baa9fe 100644 --- a/teams.go +++ b/teams.go @@ -27,25 +27,25 @@ func newTeamService(c *Client) *TeamService { // ListMembers returns all members of a team. func (s *TeamService) ListMembers(ctx context.Context, teamID int64) ([]types.User, error) { - path := ResolvePath("/api/v1/teams/{teamID}/members", pathParams("teamID", int64String(teamID))) + path := ResolvePath("/api/v1/teams/{id}/members", pathParams("id", int64String(teamID))) return ListAll[types.User](ctx, s.client, path, nil) } // IterMembers returns an iterator over all members of a team. func (s *TeamService) IterMembers(ctx context.Context, teamID int64) iter.Seq2[types.User, error] { - path := ResolvePath("/api/v1/teams/{teamID}/members", pathParams("teamID", int64String(teamID))) + path := ResolvePath("/api/v1/teams/{id}/members", pathParams("id", int64String(teamID))) return ListIter[types.User](ctx, s.client, path, nil) } // AddMember adds a user to a team. func (s *TeamService) AddMember(ctx context.Context, teamID int64, username string) error { - path := ResolvePath("/api/v1/teams/{teamID}/members/{username}", pathParams("teamID", int64String(teamID), "username", username)) + path := ResolvePath("/api/v1/teams/{id}/members/{username}", pathParams("id", int64String(teamID), "username", username)) return s.client.Put(ctx, path, nil, nil) } // GetMember returns a particular member of a team. func (s *TeamService) GetMember(ctx context.Context, teamID int64, username string) (*types.User, error) { - path := ResolvePath("/api/v1/teams/{teamID}/members/{username}", pathParams("teamID", int64String(teamID), "username", username)) + path := ResolvePath("/api/v1/teams/{id}/members/{username}", pathParams("id", int64String(teamID), "username", username)) var out types.User if err := s.client.Get(ctx, path, &out); err != nil { return nil, err @@ -55,34 +55,44 @@ func (s *TeamService) GetMember(ctx context.Context, teamID int64, username stri // RemoveMember removes a user from a team. func (s *TeamService) RemoveMember(ctx context.Context, teamID int64, username string) error { - path := ResolvePath("/api/v1/teams/{teamID}/members/{username}", pathParams("teamID", int64String(teamID), "username", username)) + path := ResolvePath("/api/v1/teams/{id}/members/{username}", pathParams("id", int64String(teamID), "username", username)) return s.client.Delete(ctx, path) } // ListRepos returns all repositories managed by a team. func (s *TeamService) ListRepos(ctx context.Context, teamID int64) ([]types.Repository, error) { - path := ResolvePath("/api/v1/teams/{teamID}/repos", pathParams("teamID", int64String(teamID))) + path := ResolvePath("/api/v1/teams/{id}/repos", pathParams("id", int64String(teamID))) return ListAll[types.Repository](ctx, s.client, path, nil) } // IterRepos returns an iterator over all repositories managed by a team. func (s *TeamService) IterRepos(ctx context.Context, teamID int64) iter.Seq2[types.Repository, error] { - path := ResolvePath("/api/v1/teams/{teamID}/repos", pathParams("teamID", int64String(teamID))) + path := ResolvePath("/api/v1/teams/{id}/repos", pathParams("id", int64String(teamID))) return ListIter[types.Repository](ctx, s.client, path, nil) } // AddRepo adds a repository to a team. func (s *TeamService) AddRepo(ctx context.Context, teamID int64, org, repo string) error { - path := ResolvePath("/api/v1/teams/{teamID}/repos/{org}/{repo}", pathParams("teamID", int64String(teamID), "org", org, "repo", repo)) + path := ResolvePath("/api/v1/teams/{id}/repos/{org}/{repo}", pathParams("id", int64String(teamID), "org", org, "repo", repo)) return s.client.Put(ctx, path, nil, nil) } // RemoveRepo removes a repository from a team. func (s *TeamService) RemoveRepo(ctx context.Context, teamID int64, org, repo string) error { - path := ResolvePath("/api/v1/teams/{teamID}/repos/{org}/{repo}", pathParams("teamID", int64String(teamID), "org", org, "repo", repo)) + path := ResolvePath("/api/v1/teams/{id}/repos/{org}/{repo}", pathParams("id", int64String(teamID), "org", org, "repo", repo)) return s.client.Delete(ctx, path) } +// GetRepo returns a particular repository managed by a team. +func (s *TeamService) GetRepo(ctx context.Context, teamID int64, org, repo string) (*types.Repository, error) { + path := ResolvePath("/api/v1/teams/{id}/repos/{org}/{repo}", pathParams("id", int64String(teamID), "org", org, "repo", repo)) + var out types.Repository + if err := s.client.Get(ctx, path, &out); err != nil { + return nil, err + } + return &out, nil +} + // ListOrgTeams returns all teams in an organisation. func (s *TeamService) ListOrgTeams(ctx context.Context, org string) ([]types.Team, error) { path := ResolvePath("/api/v1/orgs/{org}/teams", pathParams("org", org)) @@ -94,3 +104,15 @@ func (s *TeamService) IterOrgTeams(ctx context.Context, org string) iter.Seq2[ty path := ResolvePath("/api/v1/orgs/{org}/teams", pathParams("org", org)) return ListIter[types.Team](ctx, s.client, path, nil) } + +// ListActivityFeeds returns a team's activity feed entries. +func (s *TeamService) ListActivityFeeds(ctx context.Context, teamID int64) ([]types.Activity, error) { + path := ResolvePath("/api/v1/teams/{id}/activities/feeds", pathParams("id", int64String(teamID))) + return ListAll[types.Activity](ctx, s.client, path, nil) +} + +// IterActivityFeeds returns an iterator over a team's activity feed entries. +func (s *TeamService) IterActivityFeeds(ctx context.Context, teamID int64) iter.Seq2[types.Activity, error] { + path := ResolvePath("/api/v1/teams/{id}/activities/feeds", pathParams("id", int64String(teamID))) + return ListIter[types.Activity](ctx, s.client, path, nil) +} diff --git a/users.go b/users.go index a9de6ed..a097792 100644 --- a/users.go +++ b/users.go @@ -525,6 +525,55 @@ func (s *UserService) IterMyStarred(ctx context.Context) iter.Seq2[types.Reposit return ListIter[types.Repository](ctx, s.client, "/api/v1/user/starred", nil) } +// ListMyFollowers returns all followers of the authenticated user. +func (s *UserService) ListMyFollowers(ctx context.Context) ([]types.User, error) { + return ListAll[types.User](ctx, s.client, "/api/v1/user/followers", nil) +} + +// IterMyFollowers returns an iterator over all followers of the authenticated user. +func (s *UserService) IterMyFollowers(ctx context.Context) iter.Seq2[types.User, error] { + return ListIter[types.User](ctx, s.client, "/api/v1/user/followers", nil) +} + +// ListMyTeams returns all teams the authenticated user belongs to. +func (s *UserService) ListMyTeams(ctx context.Context) ([]types.Team, error) { + return ListAll[types.Team](ctx, s.client, "/api/v1/user/teams", nil) +} + +// IterMyTeams returns an iterator over all teams the authenticated user belongs to. +func (s *UserService) IterMyTeams(ctx context.Context) iter.Seq2[types.Team, error] { + return ListIter[types.Team](ctx, s.client, "/api/v1/user/teams", nil) +} + +// ListMyTrackedTimes returns all tracked times logged by the authenticated user. +func (s *UserService) ListMyTrackedTimes(ctx context.Context) ([]types.TrackedTime, error) { + return ListAll[types.TrackedTime](ctx, s.client, "/api/v1/user/times", nil) +} + +// IterMyTrackedTimes returns an iterator over all tracked times logged by the authenticated user. +func (s *UserService) IterMyTrackedTimes(ctx context.Context) iter.Seq2[types.TrackedTime, error] { + return ListIter[types.TrackedTime](ctx, s.client, "/api/v1/user/times", nil) +} + +// CheckQuota reports whether the authenticated user is over quota. +func (s *UserService) CheckQuota(ctx context.Context) (bool, error) { + var out bool + if err := s.client.Get(ctx, "/api/v1/user/quota/check", &out); err != nil { + return false, err + } + return out, nil +} + +// GetRunnerRegistrationToken returns the authenticated user's actions runner registration token. +func (s *UserService) GetRunnerRegistrationToken(ctx context.Context) (string, error) { + path := "/api/v1/user/actions/runners/registration-token" + resp, err := s.client.doJSON(ctx, http.MethodGet, path, nil, nil) + if err != nil { + return "", err + } + return resp.Header.Get("token"), nil +} + // ListFollowers returns all followers of a user. func (s *UserService) ListFollowers(ctx context.Context, username string) ([]types.User, error) { path := ResolvePath("/api/v1/users/{username}/followers", pathParams("username", username)) @@ -561,6 +610,30 @@ func (s *UserService) IterFollowing(ctx context.Context, username string) iter.S return ListIter[types.User](ctx, s.client, path, nil) } +// ListActivityFeeds returns a user's activity feed entries. +func (s *UserService) ListActivityFeeds(ctx context.Context, username string) ([]types.Activity, error) { + path := ResolvePath("/api/v1/users/{username}/activities/feeds", pathParams("username", username)) + return ListAll[types.Activity](ctx, s.client, path, nil) +} + +// IterActivityFeeds returns an iterator over a user's activity feed entries. +func (s *UserService) IterActivityFeeds(ctx context.Context, username string) iter.Seq2[types.Activity, error] { + path := ResolvePath("/api/v1/users/{username}/activities/feeds", pathParams("username", username)) + return ListIter[types.Activity](ctx, s.client, path, nil) +} + +// ListRepos returns all repositories owned by a user. +func (s *UserService) ListRepos(ctx context.Context, username string) ([]types.Repository, error) { + path := ResolvePath("/api/v1/users/{username}/repos", pathParams("username", username)) + return ListAll[types.Repository](ctx, s.client, path, nil) +} + +// IterRepos returns an iterator over all repositories owned by a user. +func (s *UserService) IterRepos(ctx context.Context, username string) iter.Seq2[types.Repository, error] { + path := ResolvePath("/api/v1/users/{username}/repos", pathParams("username", username)) + return ListIter[types.Repository](ctx, s.client, path, nil) +} + // Follow follows a user as the authenticated user. func (s *UserService) Follow(ctx context.Context, username string) error { path := ResolvePath("/api/v1/user/following/{username}", pathParams("username", username))