github.com/ungtb10d/cli/v2@v2.0.0-20221110210412-98537dd9d6a1/pkg/cmd/repo/create/http.go (about)

     1  package create
     2  
     3  import (
     4  	"bytes"
     5  	"encoding/json"
     6  	"fmt"
     7  	"net/http"
     8  	"strings"
     9  
    10  	"github.com/ungtb10d/cli/v2/api"
    11  )
    12  
    13  // repoCreateInput is input parameters for the repoCreate method
    14  type repoCreateInput struct {
    15  	Name                 string
    16  	HomepageURL          string
    17  	Description          string
    18  	Visibility           string
    19  	OwnerLogin           string
    20  	TeamSlug             string
    21  	TemplateRepositoryID string
    22  	HasIssuesEnabled     bool
    23  	HasWikiEnabled       bool
    24  	GitIgnoreTemplate    string
    25  	LicenseTemplate      string
    26  	IncludeAllBranches   bool
    27  	InitReadme           bool
    28  }
    29  
    30  // createRepositoryInputV3 is the payload for the repo create REST API
    31  type createRepositoryInputV3 struct {
    32  	Name              string `json:"name"`
    33  	HomepageURL       string `json:"homepage,omitempty"`
    34  	Description       string `json:"description,omitempty"`
    35  	IsPrivate         bool   `json:"private"`
    36  	Visibility        string `json:"visibility,omitempty"`
    37  	TeamID            uint64 `json:"team_id,omitempty"`
    38  	HasIssuesEnabled  bool   `json:"has_issues"`
    39  	HasWikiEnabled    bool   `json:"has_wiki"`
    40  	GitIgnoreTemplate string `json:"gitignore_template,omitempty"`
    41  	LicenseTemplate   string `json:"license_template,omitempty"`
    42  	InitReadme        bool   `json:"auto_init,omitempty"`
    43  }
    44  
    45  // createRepositoryInput is the payload for the repo create GraphQL mutation
    46  type createRepositoryInput struct {
    47  	Name             string `json:"name"`
    48  	HomepageURL      string `json:"homepageUrl,omitempty"`
    49  	Description      string `json:"description,omitempty"`
    50  	Visibility       string `json:"visibility"`
    51  	OwnerID          string `json:"ownerId,omitempty"`
    52  	TeamID           string `json:"teamId,omitempty"`
    53  	HasIssuesEnabled bool   `json:"hasIssuesEnabled"`
    54  	HasWikiEnabled   bool   `json:"hasWikiEnabled"`
    55  }
    56  
    57  // cloneTemplateRepositoryInput is the payload for creating a repo from a template using GraphQL
    58  type cloneTemplateRepositoryInput struct {
    59  	Name               string `json:"name"`
    60  	Visibility         string `json:"visibility"`
    61  	Description        string `json:"description,omitempty"`
    62  	OwnerID            string `json:"ownerId"`
    63  	RepositoryID       string `json:"repositoryId"`
    64  	IncludeAllBranches bool   `json:"includeAllBranches"`
    65  }
    66  
    67  // repoCreate creates a new GitHub repository
    68  func repoCreate(client *http.Client, hostname string, input repoCreateInput) (*api.Repository, error) {
    69  	isOrg := false
    70  	var ownerID string
    71  	var teamID string
    72  	var teamIDv3 uint64
    73  
    74  	apiClient := api.NewClientFromHTTP(client)
    75  
    76  	if input.TeamSlug != "" {
    77  		team, err := resolveOrganizationTeam(apiClient, hostname, input.OwnerLogin, input.TeamSlug)
    78  		if err != nil {
    79  			return nil, err
    80  		}
    81  		teamIDv3 = team.ID
    82  		teamID = team.NodeID
    83  		ownerID = team.Organization.NodeID
    84  		isOrg = true
    85  	} else if input.OwnerLogin != "" {
    86  		owner, err := resolveOwner(apiClient, hostname, input.OwnerLogin)
    87  		if err != nil {
    88  			return nil, err
    89  		}
    90  		ownerID = owner.NodeID
    91  		isOrg = owner.IsOrganization()
    92  	}
    93  
    94  	if input.TemplateRepositoryID != "" {
    95  		var response struct {
    96  			CloneTemplateRepository struct {
    97  				Repository api.Repository
    98  			}
    99  		}
   100  
   101  		if ownerID == "" {
   102  			var err error
   103  			ownerID, err = api.CurrentUserID(apiClient, hostname)
   104  			if err != nil {
   105  				return nil, err
   106  			}
   107  		}
   108  
   109  		variables := map[string]interface{}{
   110  			"input": cloneTemplateRepositoryInput{
   111  				Name:               input.Name,
   112  				Description:        input.Description,
   113  				Visibility:         strings.ToUpper(input.Visibility),
   114  				OwnerID:            ownerID,
   115  				RepositoryID:       input.TemplateRepositoryID,
   116  				IncludeAllBranches: input.IncludeAllBranches,
   117  			},
   118  		}
   119  
   120  		err := apiClient.GraphQL(hostname, `
   121  		mutation CloneTemplateRepository($input: CloneTemplateRepositoryInput!) {
   122  			cloneTemplateRepository(input: $input) {
   123  				repository {
   124  					id
   125  					name
   126  					owner { login }
   127  					url
   128  				}
   129  			}
   130  		}
   131  		`, variables, &response)
   132  		if err != nil {
   133  			return nil, err
   134  		}
   135  
   136  		return api.InitRepoHostname(&response.CloneTemplateRepository.Repository, hostname), nil
   137  	}
   138  
   139  	if input.GitIgnoreTemplate != "" || input.LicenseTemplate != "" || input.InitReadme {
   140  		inputv3 := createRepositoryInputV3{
   141  			Name:              input.Name,
   142  			HomepageURL:       input.HomepageURL,
   143  			Description:       input.Description,
   144  			IsPrivate:         strings.EqualFold(input.Visibility, "PRIVATE"),
   145  			TeamID:            teamIDv3,
   146  			HasIssuesEnabled:  input.HasIssuesEnabled,
   147  			HasWikiEnabled:    input.HasWikiEnabled,
   148  			GitIgnoreTemplate: input.GitIgnoreTemplate,
   149  			LicenseTemplate:   input.LicenseTemplate,
   150  			InitReadme:        input.InitReadme,
   151  		}
   152  
   153  		path := "user/repos"
   154  		if isOrg {
   155  			path = fmt.Sprintf("orgs/%s/repos", input.OwnerLogin)
   156  			inputv3.Visibility = strings.ToLower(input.Visibility)
   157  		}
   158  
   159  		body := &bytes.Buffer{}
   160  		enc := json.NewEncoder(body)
   161  		if err := enc.Encode(inputv3); err != nil {
   162  			return nil, err
   163  		}
   164  
   165  		repo, err := api.CreateRepoTransformToV4(apiClient, hostname, "POST", path, body)
   166  		if err != nil {
   167  			return nil, err
   168  		}
   169  		return repo, nil
   170  	}
   171  
   172  	var response struct {
   173  		CreateRepository struct {
   174  			Repository api.Repository
   175  		}
   176  	}
   177  
   178  	variables := map[string]interface{}{
   179  		"input": createRepositoryInput{
   180  			Name:             input.Name,
   181  			Description:      input.Description,
   182  			HomepageURL:      input.HomepageURL,
   183  			Visibility:       strings.ToUpper(input.Visibility),
   184  			OwnerID:          ownerID,
   185  			TeamID:           teamID,
   186  			HasIssuesEnabled: input.HasIssuesEnabled,
   187  			HasWikiEnabled:   input.HasWikiEnabled,
   188  		},
   189  	}
   190  
   191  	err := apiClient.GraphQL(hostname, `
   192  	mutation RepositoryCreate($input: CreateRepositoryInput!) {
   193  		createRepository(input: $input) {
   194  			repository {
   195  				id
   196  				name
   197  				owner { login }
   198  				url
   199  			}
   200  		}
   201  	}
   202  	`, variables, &response)
   203  	if err != nil {
   204  		return nil, err
   205  	}
   206  
   207  	return api.InitRepoHostname(&response.CreateRepository.Repository, hostname), nil
   208  }
   209  
   210  type ownerResponse struct {
   211  	NodeID string `json:"node_id"`
   212  	Type   string `json:"type"`
   213  }
   214  
   215  func (r *ownerResponse) IsOrganization() bool {
   216  	return r.Type == "Organization"
   217  }
   218  
   219  func resolveOwner(client *api.Client, hostname, orgName string) (*ownerResponse, error) {
   220  	var response ownerResponse
   221  	err := client.REST(hostname, "GET", fmt.Sprintf("users/%s", orgName), nil, &response)
   222  	return &response, err
   223  }
   224  
   225  type teamResponse struct {
   226  	ID           uint64 `json:"id"`
   227  	NodeID       string `json:"node_id"`
   228  	Organization struct {
   229  		NodeID string `json:"node_id"`
   230  	}
   231  }
   232  
   233  func resolveOrganizationTeam(client *api.Client, hostname, orgName, teamSlug string) (*teamResponse, error) {
   234  	var response teamResponse
   235  	err := client.REST(hostname, "GET", fmt.Sprintf("orgs/%s/teams/%s", orgName, teamSlug), nil, &response)
   236  	return &response, err
   237  }
   238  
   239  // listGitIgnoreTemplates uses API v3 here because gitignore template isn't supported by GraphQL yet.
   240  func listGitIgnoreTemplates(httpClient *http.Client, hostname string) ([]string, error) {
   241  	var gitIgnoreTemplates []string
   242  	client := api.NewClientFromHTTP(httpClient)
   243  	err := client.REST(hostname, "GET", "gitignore/templates", nil, &gitIgnoreTemplates)
   244  	if err != nil {
   245  		return []string{}, err
   246  	}
   247  	return gitIgnoreTemplates, nil
   248  }
   249  
   250  // listLicenseTemplates uses API v3 here because license template isn't supported by GraphQL yet.
   251  func listLicenseTemplates(httpClient *http.Client, hostname string) ([]api.License, error) {
   252  	var licenseTemplates []api.License
   253  	client := api.NewClientFromHTTP(httpClient)
   254  	err := client.REST(hostname, "GET", "licenses", nil, &licenseTemplates)
   255  	if err != nil {
   256  		return nil, err
   257  	}
   258  	return licenseTemplates, nil
   259  }