repo_description: A curated list of awesome Go frameworks, libraries and software
repo_topics: awesome awesome-list go golang golang-library hacktoberfest
code: package main
  
  import (
  	"bytes"
  	"context"
  	"encoding/json"
  	"fmt"
  	"log"
  	"net/http"
  	"os"
  	"regexp"
  	"strings"
  	"testing"
  	"text/template"
  	"time"
  
  	"github.com/PuerkitoBio/goquery"
  	"golang.org/x/oauth2"
  )
  
  const issueTemplateContent = `
  {{range .}}
  - [ ] {{.}}
  {{end}}
  `
  
  var issueTemplate = template.Must(template.New("issue").Parse(issueTemplateContent))
  
  // FIXME: use official github client
  var reGithubRepo = regexp.MustCompile("https://github.com/([a-zA-Z0-9-._]+)/([a-zA-Z0-9-._]+)$")
  var githubGETREPO = "https://api.github.com/repos%s"
  var githubGETCOMMITS = "https://api.github.com/repos%s/commits"
  var githubPOSTISSUES = "https://api.github.com/repos/avelino/awesome-go/issues"
  
  // FIXME: use https
  var awesomeGoGETISSUES = "http://api.github.com/repos/avelino/awesome-go/issues" //only returns open issues
  // FIXME: variable has type Duration, but contains a number. we should use
  //
  //	time.Hour * ... or change type of variable
  var numberOfYears time.Duration = 1
  var timeNow = time.Now()
  var issueTitle = fmt.Sprintf("Investigate repositories with more than 1 year without update - %s", timeNow.Format(time.DateOnly))
  
  const deadLinkMessage = " this repository might no longer exist! (status code >= 400 returned)"
  const movedPermanently = " status code 301 received"
  const status302 = " status code 302 received"
  const archived = " repository has been archived"
  
  // LIMIT specifies the max number of repositories that are added in a single run of the script
  var LIMIT = 10
  var ctr = 0
  
  type tokenSource struct {
  	AccessToken string
  }
  
  type issue struct {
  	Title string `json:"title"`
  	Body  string `json:"body"`
  }
  
  func (t *tokenSource) Token() (*oauth2.Token, error) {
  	return &oauth2.Token{
  		AccessToken: t.AccessToken,
  	}, nil
  }
  
  func getRepositoriesFromBody(body string) []string {
  	links := strings.Split(body, "- ")
  	for i, link := range links {
  		link = strings.ReplaceAll(link, "\r", "")
  		link = strings.ReplaceAll(link, "[ ]", "")
  		link = strings.ReplaceAll(link, "[x]", "")
  		link = strings.ReplaceAll(link, " ", "")
  		link = strings.ReplaceAll(link, "\n", "")
  		link = strings.ReplaceAll(link, deadLinkMessage, "")
  		link = strings.ReplaceAll(link, movedPermanently, "")
  		link = strings.ReplaceAll(link, status302, "")
  		link = strings.ReplaceAll(link, archived, "")
  		links[i] = link
  	}
  
  	return links
  }
  
  func generateIssueBody(t *testing.T, repositories []string) (string, error) {
  	t.Helper()
  
  	buf := bytes.NewBuffer(nil)
  	err := issueTemplate.Execute(buf, repositories)
  	requireNoErr(t, err, "Failed to generate template")
  
  	return buf.String(), nil
  }
  
  func createIssue(t *testing.T, staleRepos []string, client *http.Client) {
  	t.Helper()
  
  	if len(staleRepos) == 0 {
  		log.Print("NO STALE REPOSITORIES")
  		return
  	}
  
  	body, err := generateIssueBody(t, staleRepos)
  	requireNoErr(t, err, "failed to generate issue body")
  
  	newIssue := &issue{
  		Title: issueTitle,
  		Body:  body,
  	}
  	buf := bytes.NewBuffer(nil)
  	requireNoErr(t, json.NewEncoder(buf).Encode(newIssue), "failed to encode json req")
  
  	req, err := http.NewRequest(http.MethodPost, githubPOSTISSUES, buf)
  	requireNoErr(t, err, "failed to create request")
  
  	_, roundTripErr := client.Do(req)
  	requireNoErr(t, roundTripErr, "failed to send request")
  }
  
  func getAllFlaggedRepositories(t *testing.T, client *http.Client) map[string]bool {
  	t.Helper()
  
  	req, err := http.NewRequest(http.MethodGet, awesomeGoGETISSUES, nil)
  	requireNoErr(t, err, "failed to create request")
  
  	res, err := client.Do(req)
  	requireNoErr(t, err, "failed to send request")
  
  	defer res.Body.Close()
  
  	var issues []issue
  	requireNoErr(t, json.NewDecoder(res.Body).Decode(&issues), "failed to unmarshal response")
  
  	addressedRepositories := make(map[string]bool)
  	for _, issue := range issues {
  		if issue.Title != issueTitle {
  			continue
  		}
  
  		repos := getRepositoriesFromBody(issue.Body)
  		for _, repo := range repos {
  			addressedRepositories[repo] = true
  		}
  	}
  
  	return addressedRepositories
  }
  
  func checkRepoAvailability(toRun bool, href string, client *http.Client) ([]string, bool) {
  	if !toRun {
  		return nil, false
  	}
  
  	ownerRepo := strings.ReplaceAll(href, "https://github.com", "")
  	apiCall := fmt.Sprintf(githubGETREPO, ownerRepo)
  	req, err := http.NewRequest(http.MethodGet, apiCall, nil)
  	if err != nil {
  		log.Printf("Failed at repository %s\n", href)
  		return nil, false
  	}
  
  	resp, err := client.Do(req)
  	if err != nil {
  		log.Printf("Failed at repository %s\n", href)
  		return nil, false
  	}
  
  	defer resp.Body.Close()
  
  	var repoResp struct {
  		Archived bool `json:"archived"`
  	}
  
  	if err := json.NewDecoder(resp.Body).Decode(&repoResp); err != nil {
  		return nil, false
  	}
  
  	var isRepoAdded bool
  
  	var warnings []string
  	if resp.StatusCode == http.StatusMovedPermanently {
  		warnings = append(warnings, href+movedPermanently)
  		log.Printf("%s returned %d", href, resp.StatusCode)
  		isRepoAdded = true
  	}
  
  	if resp.StatusCode == http.StatusFound && !isRepoAdded {
  		warnings = append(warnings, href+status302)
  		log.Printf("%s returned %d", href, resp.StatusCode)
  		isRepoAdded = true
  	}
  
  	if resp.StatusCode >= http.StatusBadRequest && !isRepoAdded {
  		warnings = append(warnings, href+deadLinkMessage)
  		log.Printf("%s might not exist!", href)
  		isRepoAdded = true
  	}
  
  	if repoResp.Archived && !isRepoAdded {
  		warnings = append(warnings, href+archived)
  		log.Printf("%s is archived!", href)
  		isRepoAdded = true
  	}
  
  	// FIXME: expression `(len(warnings) > 0) == isRepoAdded` is always true.
  	return warnings, isRepoAdded
  }
  
  func checkRepoCommitActivity(toRun bool, href string, client *http.Client) ([]string, bool) {
  	if !toRun {
  		return nil, false
  	}
  
  	ownerRepo := strings.ReplaceAll(href, "https://github.com", "")
  	apiCall := fmt.Sprintf(githubGETCOMMITS, ownerRepo)
  	req, err := http.NewRequest(http.MethodGet, apiCall, nil)
  	if err != nil {
  		log.Printf("Failed at repository %s\n", href)
  		return nil, false
  	}
  
  	since := timeNow.Add(-1 * 365 * 24 * numberOfYears * time.Hour)
  	sinceQuery := since.Format(time.RFC3339)
  
  	q := req.URL.Query()
  	q.Add("since", sinceQuery)
  	req.URL.RawQuery = q.Encode()
  
  	resp, err := client.Do(req)
  	if err != nil {
  		log.Printf("Failed at repository %s\n", href)
  		return nil, false
  	}
  
  	defer resp.Body.Close()
  
  	var respObj []map[string]interface{}
  	// FIXME: handle error in all that cases
  	if err := json.NewDecoder(resp.Body).Decode(&respObj); err != nil {
  		return nil, false
  	}
  
  	var warnings []string
  	var isRepoAdded bool
  	isAged := len(respObj) == 0
  	if isAged {
  		log.Printf("%s has not had a commit in a while", href)
  		warnings = append(warnings, href)
  		isRepoAdded = true
  	}
  
  	// FIXME: expression `(len(warnings) > 0) == isRepoAdded` is always true.
  	return warnings, isRepoAdded
  }
  
  func TestStaleRepository(t *testing.T) {
  	doc := goqueryFromReadme(t)
  
  	oauth := os.Getenv("OAUTH_TOKEN")
  	client := &http.Client{
  		Transport: &http.Transport{},
  	}
  
  	if oauth == "" {
  		log.Print("No oauth token found. Using unauthenticated client ...")
  	} else {
  		tokenSource := &tokenSource{
  			AccessToken: oauth,
  		}
  		client = oauth2.NewClient(context.Background(), tokenSource)
  	}
  
  	// FIXME: return addressedRepositories, no need to pass
  	addressedRepositories := getAllFlaggedRepositories(t, client)
  
  	var staleRepos []string
  	doc.
  		Find("body li > a:first-child").
  		EachWithBreak(func(_ int, s *goquery.Selection) bool {
  			href, ok := s.Attr("href")
  			if !ok {
  				log.Println("expected to have href")
  				return true
  			}
  
  			if ctr >= LIMIT && LIMIT != -1 {
  				log.Print("Max number of issues created")
  				return false
  			}
  
  			if _, issueExists := addressedRepositories[href]; issueExists {
  				log.Printf("issue already exists for %s\n", href)
  				return true
  			}
  
  			if !reGithubRepo.MatchString(href) {
  				log.Printf("%s non-github repo not currently handled", href)
  			}
  
  			// FIXME: this is `or` expres24sion. Probably we need `and`?
  			warnings, isRepoAdded := checkRepoAvailability(true, href, client)
  			staleRepos = append(staleRepos, warnings...)
  
  			warnings, isRepoAdded = checkRepoCommitActivity(!isRepoAdded, href, client)
  			staleRepos = append(staleRepos, warnings...)
  
  			if isRepoAdded {
  				ctr++
  			}
  
  			return true
  		})
  
  	createIssue(t, staleRepos, client)
  }
  