github.com/goreleaser/goreleaser@v1.25.1/internal/pipe/opencollective/opencollective.go (about)

     1  package opencollective
     2  
     3  import (
     4  	"bytes"
     5  	"encoding/json"
     6  	"fmt"
     7  	"net/http"
     8  
     9  	"github.com/caarlos0/env/v9"
    10  	"github.com/caarlos0/log"
    11  	"github.com/goreleaser/goreleaser/internal/tmpl"
    12  	"github.com/goreleaser/goreleaser/pkg/context"
    13  )
    14  
    15  const (
    16  	defaultTitleTemplate   = `{{ .Tag }}`
    17  	defaultMessageTemplate = `{{ .ProjectName }} {{ .Tag }} is out!<br/>Check it out at <a href="{{ .ReleaseURL }}">{{ .ReleaseURL }}</a>`
    18  	endpoint               = "https://api.opencollective.com/graphql/v2"
    19  )
    20  
    21  type Pipe struct{}
    22  
    23  func (Pipe) String() string { return "opencollective" }
    24  
    25  func (Pipe) Skip(ctx *context.Context) bool {
    26  	return !ctx.Config.Announce.OpenCollective.Enabled || ctx.Config.Announce.OpenCollective.Slug == ""
    27  }
    28  
    29  type Config struct {
    30  	Token string `env:"OPENCOLLECTIVE_TOKEN,notEmpty"`
    31  }
    32  
    33  func (Pipe) Default(ctx *context.Context) error {
    34  	if ctx.Config.Announce.OpenCollective.TitleTemplate == "" {
    35  		ctx.Config.Announce.OpenCollective.TitleTemplate = defaultTitleTemplate
    36  	}
    37  	if ctx.Config.Announce.OpenCollective.MessageTemplate == "" {
    38  		ctx.Config.Announce.OpenCollective.MessageTemplate = defaultMessageTemplate
    39  	}
    40  	return nil
    41  }
    42  
    43  func (Pipe) Announce(ctx *context.Context) error {
    44  	title, err := tmpl.New(ctx).Apply(ctx.Config.Announce.OpenCollective.TitleTemplate)
    45  	if err != nil {
    46  		return fmt.Errorf("opencollective: %w", err)
    47  	}
    48  	html, err := tmpl.New(ctx).Apply(ctx.Config.Announce.OpenCollective.MessageTemplate)
    49  	if err != nil {
    50  		return fmt.Errorf("opencollective: %w", err)
    51  	}
    52  
    53  	var cfg Config
    54  	if err := env.Parse(&cfg); err != nil {
    55  		return fmt.Errorf("opencollective: %w", err)
    56  	}
    57  
    58  	log.Infof("posting: %q | %q", title, html)
    59  
    60  	id, err := createUpdate(ctx, title, html, ctx.Config.Announce.OpenCollective.Slug, cfg.Token)
    61  	if err != nil {
    62  		return fmt.Errorf("opencollective: %w", err)
    63  	}
    64  
    65  	if err := publishUpdate(ctx, id, cfg.Token); err != nil {
    66  		return fmt.Errorf("opencollective: %w", err)
    67  	}
    68  
    69  	return nil
    70  }
    71  
    72  type payload struct {
    73  	Query     string         `json:"query"`
    74  	Variables map[string]any `json:"variables"`
    75  }
    76  
    77  func createUpdate(ctx *context.Context, title, html, slug, token string) (string, error) {
    78  	mutation := `mutation (
    79    $update: UpdateCreateInput!
    80  ) {
    81    createUpdate(update: $update) {
    82      id
    83    }
    84  }`
    85  	payload := payload{
    86  		Query: mutation,
    87  		Variables: map[string]any{
    88  			"update": map[string]any{
    89  				"title": title,
    90  				"html":  html,
    91  				"account": map[string]any{
    92  					"slug": slug,
    93  				},
    94  			},
    95  		},
    96  	}
    97  
    98  	resp, err := doMutation(ctx, payload, token)
    99  	if err != nil {
   100  		return "", err
   101  	}
   102  	defer resp.Body.Close()
   103  
   104  	//nolint:tagliatelle
   105  	var envelope struct {
   106  		Data struct {
   107  			CreateUpdate struct {
   108  				ID string `json:"id"`
   109  			} `json:"createUpdate"`
   110  		} `json:"data"`
   111  	}
   112  	if err := json.NewDecoder(resp.Body).Decode(&envelope); err != nil {
   113  		return "", fmt.Errorf("could not decode JSON response: %w", err)
   114  	}
   115  
   116  	return envelope.Data.CreateUpdate.ID, nil
   117  }
   118  
   119  func publishUpdate(ctx *context.Context, id, token string) error {
   120  	mutation := `mutation (
   121    $id: String!
   122    $audience: UpdateAudience
   123  ) {
   124    publishUpdate(id: $id, notificationAudience: $audience) {
   125      id
   126    }
   127  }`
   128  	payload := payload{
   129  		Query: mutation,
   130  		Variables: map[string]any{
   131  			"id":       id,
   132  			"audience": "ALL",
   133  		},
   134  	}
   135  
   136  	resp, err := doMutation(ctx, payload, token)
   137  	if err != nil {
   138  		return err
   139  	}
   140  	defer resp.Body.Close()
   141  
   142  	return err
   143  }
   144  
   145  func doMutation(ctx *context.Context, payload payload, token string) (*http.Response, error) {
   146  	p, err := json.Marshal(payload)
   147  	if err != nil {
   148  		return nil, fmt.Errorf("could not marshal payload: %w", err)
   149  	}
   150  
   151  	req, err := http.NewRequestWithContext(ctx, http.MethodPost, endpoint, bytes.NewReader(p))
   152  	if err != nil {
   153  		return nil, fmt.Errorf("could not create request: %w", err)
   154  	}
   155  	req.Header.Set("Personal-Token", token)
   156  	req.Header.Set("Content-Type", "application/json")
   157  
   158  	resp, err := http.DefaultClient.Do(req)
   159  	if err != nil {
   160  		return nil, fmt.Errorf("could not send request to opencollective: %w", err)
   161  	}
   162  
   163  	if resp.StatusCode != http.StatusOK {
   164  		return resp, fmt.Errorf("incorrect response from opencollective: %s", resp.Status)
   165  	}
   166  
   167  	return resp, nil
   168  }