go-forge/issues.go
Virgil 36181a7335
All checks were successful
Security Scan / security (push) Successful in 11s
Test / test (push) Successful in 1m4s
Add issue timeline listing
Co-Authored-By: Virgil <virgil@lethean.io>
2026-04-01 22:40:32 +00:00

195 lines
9.3 KiB
Go

package forge
import (
"context"
"iter"
"time"
"dappco.re/go/core/forge/types"
)
// IssueService handles issue operations within a repository.
//
// Usage:
//
// f := forge.NewForge("https://forge.lthn.ai", "token")
// _, err := f.Issues.ListAll(ctx, forge.Params{"owner": "core", "repo": "go-forge"})
type IssueService struct {
Resource[types.Issue, types.CreateIssueOption, types.EditIssueOption]
}
func newIssueService(c *Client) *IssueService {
return &IssueService{
Resource: *NewResource[types.Issue, types.CreateIssueOption, types.EditIssueOption](
c, "/api/v1/repos/{owner}/{repo}/issues/{index}",
),
}
}
// Pin pins an issue.
func (s *IssueService) Pin(ctx context.Context, owner, repo string, index int64) error {
path := ResolvePath("/api/v1/repos/{owner}/{repo}/issues/{index}/pin", pathParams("owner", owner, "repo", repo, "index", int64String(index)))
return s.client.Post(ctx, path, nil, nil)
}
// MovePin moves a pinned issue to a new position.
func (s *IssueService) MovePin(ctx context.Context, owner, repo string, index, position int64) error {
path := ResolvePath("/api/v1/repos/{owner}/{repo}/issues/{index}/pin/{position}", pathParams("owner", owner, "repo", repo, "index", int64String(index), "position", int64String(position)))
return s.client.Patch(ctx, path, nil, nil)
}
// Unpin unpins an issue.
func (s *IssueService) Unpin(ctx context.Context, owner, repo string, index int64) error {
path := ResolvePath("/api/v1/repos/{owner}/{repo}/issues/{index}/pin", pathParams("owner", owner, "repo", repo, "index", int64String(index)))
return s.client.Delete(ctx, path)
}
// SetDeadline sets or updates the deadline on an issue.
func (s *IssueService) SetDeadline(ctx context.Context, owner, repo string, index int64, deadline string) error {
path := ResolvePath("/api/v1/repos/{owner}/{repo}/issues/{index}/deadline", pathParams("owner", owner, "repo", repo, "index", int64String(index)))
body := map[string]string{"due_date": deadline}
return s.client.Post(ctx, path, body, nil)
}
// AddReaction adds a reaction to an issue.
func (s *IssueService) AddReaction(ctx context.Context, owner, repo string, index int64, reaction string) error {
path := ResolvePath("/api/v1/repos/{owner}/{repo}/issues/{index}/reactions", pathParams("owner", owner, "repo", repo, "index", int64String(index)))
body := map[string]string{"content": reaction}
return s.client.Post(ctx, path, body, nil)
}
// DeleteReaction removes a reaction from an issue.
func (s *IssueService) DeleteReaction(ctx context.Context, owner, repo string, index int64, reaction string) error {
path := ResolvePath("/api/v1/repos/{owner}/{repo}/issues/{index}/reactions", pathParams("owner", owner, "repo", repo, "index", int64String(index)))
body := map[string]string{"content": reaction}
return s.client.DeleteWithBody(ctx, path, body)
}
// StartStopwatch starts the stopwatch on an issue.
func (s *IssueService) StartStopwatch(ctx context.Context, owner, repo string, index int64) error {
path := ResolvePath("/api/v1/repos/{owner}/{repo}/issues/{index}/stopwatch/start", pathParams("owner", owner, "repo", repo, "index", int64String(index)))
return s.client.Post(ctx, path, nil, nil)
}
// StopStopwatch stops the stopwatch on an issue.
func (s *IssueService) StopStopwatch(ctx context.Context, owner, repo string, index int64) error {
path := ResolvePath("/api/v1/repos/{owner}/{repo}/issues/{index}/stopwatch/stop", pathParams("owner", owner, "repo", repo, "index", int64String(index)))
return s.client.Post(ctx, path, nil, nil)
}
// DeleteStopwatch deletes an issue's existing stopwatch.
func (s *IssueService) DeleteStopwatch(ctx context.Context, owner, repo string, index int64) error {
path := ResolvePath("/api/v1/repos/{owner}/{repo}/issues/{index}/stopwatch/delete", pathParams("owner", owner, "repo", repo, "index", int64String(index)))
return s.client.Delete(ctx, path)
}
// AddLabels adds labels to an issue.
func (s *IssueService) AddLabels(ctx context.Context, owner, repo string, index int64, labelIDs []int64) error {
path := ResolvePath("/api/v1/repos/{owner}/{repo}/issues/{index}/labels", pathParams("owner", owner, "repo", repo, "index", int64String(index)))
body := types.IssueLabelsOption{Labels: toAnySlice(labelIDs)}
return s.client.Post(ctx, path, body, nil)
}
// 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)))
return s.client.Delete(ctx, path)
}
// ListComments returns all comments on an issue.
func (s *IssueService) ListComments(ctx context.Context, owner, repo string, index int64) ([]types.Comment, error) {
path := ResolvePath("/api/v1/repos/{owner}/{repo}/issues/{index}/comments", pathParams("owner", owner, "repo", repo, "index", int64String(index)))
return ListAll[types.Comment](ctx, s.client, path, nil)
}
// IterComments returns an iterator over all comments on an issue.
func (s *IssueService) IterComments(ctx context.Context, owner, repo string, index int64) iter.Seq2[types.Comment, error] {
path := ResolvePath("/api/v1/repos/{owner}/{repo}/issues/{index}/comments", pathParams("owner", owner, "repo", repo, "index", int64String(index)))
return ListIter[types.Comment](ctx, s.client, path, nil)
}
// CreateComment creates a comment on an issue.
func (s *IssueService) CreateComment(ctx context.Context, owner, repo string, index int64, body string) (*types.Comment, error) {
path := ResolvePath("/api/v1/repos/{owner}/{repo}/issues/{index}/comments", pathParams("owner", owner, "repo", repo, "index", int64String(index)))
opts := types.CreateIssueCommentOption{Body: body}
var out types.Comment
if err := s.client.Post(ctx, path, opts, &out); err != nil {
return nil, err
}
return &out, nil
}
// ListTimeline returns all comments and events on an issue.
func (s *IssueService) ListTimeline(ctx context.Context, owner, repo string, index int64, since, before *time.Time) ([]types.TimelineComment, error) {
path := ResolvePath("/api/v1/repos/{owner}/{repo}/issues/{index}/timeline", pathParams("owner", owner, "repo", repo, "index", int64String(index)))
query := make(map[string]string, 2)
if since != nil {
query["since"] = since.Format(time.RFC3339)
}
if before != nil {
query["before"] = before.Format(time.RFC3339)
}
if len(query) == 0 {
query = nil
}
return ListAll[types.TimelineComment](ctx, s.client, path, query)
}
// IterTimeline returns an iterator over all comments and events on an issue.
func (s *IssueService) IterTimeline(ctx context.Context, owner, repo string, index int64, since, before *time.Time) iter.Seq2[types.TimelineComment, error] {
path := ResolvePath("/api/v1/repos/{owner}/{repo}/issues/{index}/timeline", pathParams("owner", owner, "repo", repo, "index", int64String(index)))
query := make(map[string]string, 2)
if since != nil {
query["since"] = since.Format(time.RFC3339)
}
if before != nil {
query["before"] = before.Format(time.RFC3339)
}
if len(query) == 0 {
query = nil
}
return ListIter[types.TimelineComment](ctx, s.client, path, query)
}
// ListSubscriptions returns all users subscribed to an issue.
func (s *IssueService) ListSubscriptions(ctx context.Context, owner, repo string, index int64) ([]types.User, error) {
path := ResolvePath("/api/v1/repos/{owner}/{repo}/issues/{index}/subscriptions", pathParams("owner", owner, "repo", repo, "index", int64String(index)))
return ListAll[types.User](ctx, s.client, path, nil)
}
// IterSubscriptions returns an iterator over all users subscribed to an issue.
func (s *IssueService) IterSubscriptions(ctx context.Context, owner, repo string, index int64) iter.Seq2[types.User, error] {
path := ResolvePath("/api/v1/repos/{owner}/{repo}/issues/{index}/subscriptions", pathParams("owner", owner, "repo", repo, "index", int64String(index)))
return ListIter[types.User](ctx, s.client, path, nil)
}
// CheckSubscription returns the authenticated user's subscription state for an issue.
func (s *IssueService) CheckSubscription(ctx context.Context, owner, repo string, index int64) (*types.WatchInfo, error) {
path := ResolvePath("/api/v1/repos/{owner}/{repo}/issues/{index}/subscriptions/check", pathParams("owner", owner, "repo", repo, "index", int64String(index)))
var out types.WatchInfo
if err := s.client.Get(ctx, path, &out); err != nil {
return nil, err
}
return &out, nil
}
// SubscribeUser subscribes a user to an issue.
func (s *IssueService) SubscribeUser(ctx context.Context, owner, repo string, index int64, user string) error {
path := ResolvePath("/api/v1/repos/{owner}/{repo}/issues/{index}/subscriptions/{user}", pathParams("owner", owner, "repo", repo, "index", int64String(index), "user", user))
return s.client.Put(ctx, path, nil, nil)
}
// UnsubscribeUser unsubscribes a user from an issue.
func (s *IssueService) UnsubscribeUser(ctx context.Context, owner, repo string, index int64, user string) error {
path := ResolvePath("/api/v1/repos/{owner}/{repo}/issues/{index}/subscriptions/{user}", pathParams("owner", owner, "repo", repo, "index", int64String(index), "user", user))
return s.client.Delete(ctx, path)
}
// toAnySlice converts a slice of int64 to a slice of any for IssueLabelsOption.
func toAnySlice(ids []int64) []any {
out := make([]any, len(ids))
for i, id := range ids {
out[i] = id
}
return out
}