package main import ( "bytes" "context" "encoding/json" "fmt" "log" "net/http" "sort" "time" "suah.dev/gostart/data" "tailscale.com/tailcfg" ) const gqEndPoint = "https://api.github.com/graphql" const graphQuery = ` { search( query: "is:open is:public archived:false repo:%s in:title %s", type: ISSUE, first: 20 ) { issueCount edges { node { ... on Issue { number title url repository { nameWithOwner } createdAt } ... on PullRequest { number title repository { nameWithOwner } createdAt url } } } } rateLimit { remaining resetAt } } ` type GQLQuery struct { Query string `json:"query"` } func getData(q GQLQuery, token string) (*WatchResult, error) { var req *http.Request var err error var re = &WatchResult{} client := &http.Client{} buf := new(bytes.Buffer) if err := json.NewEncoder(buf).Encode(q); err != nil { return nil, err } req, err = http.NewRequest("POST", gqEndPoint, buf) if err != nil { return nil, err } req.Header.Set("Authorization", fmt.Sprintf("bearer %s", token)) res, err := client.Do(req) if err != nil { return nil, err } defer func() { err := res.Body.Close() if err != nil { log.Fatal("can't close body: ", err) } }() if err = json.NewDecoder(res.Body).Decode(re); err != nil { return nil, err } return re, nil } type Page struct { Title string PullRequests []data.PullRequest Links []data.Link Node tailcfg.Node Watches WatchResults CurrentLimits *RateLimit Ignores []data.PullRequestIgnore } func (p *Page) Sort() { sort.Slice(p.Links, func(i, j int) bool { return p.Links[i].Name < p.Links[j].Name }) sort.Slice(p.PullRequests, func(i, j int) bool { return p.PullRequests[i].Number > p.PullRequests[j].Number }) sort.Slice(p.Watches, func(i, j int) bool { return p.Watches[i].Name < p.Watches[j].Name }) for _, w := range p.Watches { sort.Slice(w.Data.Search.Edges, func(i, j int) bool { return w.Data.Search.Edges[i].Node.CreatedAt.After(w.Data.Search.Edges[j].Node.CreatedAt) }) } } type WatchResults []WatchResult func (w *WatchResults) forID(ownerID int64) WatchResults { newResults := WatchResults{} for _, r := range *w { if r.OwnerID == ownerID { newResults = append(newResults, r) } } return newResults } func (w WatchResults) GetLimits() *RateLimit { rl := &RateLimit{} sort.Slice(w, func(i, j int) bool { return w[i].Data.RateLimit.Remaining < w[j].Data.RateLimit.Remaining }) if len(w) > 0 { rl = &w[0].Data.RateLimit } return rl } func (w *WatchResults) Update(ghToken string) error { ctx := context.Background() watches, err := app.queries.GetAllWatchItems(ctx) if err != nil { return err } for _, watch := range watches { qd := GQLQuery{Query: fmt.Sprintf(graphQuery, watch.Repo, watch.Name)} wr, err := getData(qd, ghToken) if err != nil { return err } // TODO: cross ref the list of ignores and prune the wr accordingly wr.OwnerID = watch.OwnerID wr.Name = watch.Name wr.Repo = watch.Repo *w = append(*w, *wr) } return nil } type WatchResult struct { Data Data `json:"data,omitempty"` OwnerID int64 Name string Repo string } type Repository struct { NameWithOwner string `json:"nameWithOwner,omitempty"` } type Node struct { Number int `json:"number,omitempty"` Title string `json:"title,omitempty"` Repository Repository `json:"repository,omitempty"` CreatedAt time.Time `json:"createdAt,omitempty"` URL string `json:"url,omitempty"` } type Edges struct { Node Node `json:"node,omitempty"` } type Search struct { IssueCount int `json:"issueCount,omitempty"` Edges []Edges `json:"edges,omitempty"` } type RateLimit struct { Remaining int `json:"remaining,omitempty"` ResetAt time.Time `json:"resetAt,omitempty"` } type Data struct { Search Search `json:"search,omitempty"` RateLimit RateLimit `json:"rateLimit,omitempty"` }