github.com/apex/up@v1.7.1/internal/cli/domains/domains.go (about)

     1  package domains
     2  
     3  import (
     4  	"fmt"
     5  	"strings"
     6  	"time"
     7  
     8  	"github.com/pkg/errors"
     9  	"github.com/tj/kingpin"
    10  	"github.com/tj/survey"
    11  
    12  	"github.com/apex/up"
    13  	"github.com/apex/up/internal/cli/root"
    14  	"github.com/apex/up/internal/colors"
    15  	"github.com/apex/up/internal/stats"
    16  	"github.com/apex/up/internal/util"
    17  	"github.com/apex/up/platform/aws/cost"
    18  )
    19  
    20  // TODO: add ability to move up/down lines more like a form
    21  // TODO: add polling of registration status (it's async)
    22  // TODO: auto-fill these details from AWS account?
    23  
    24  func init() {
    25  	cmd := root.Command("domains", "Manage domain names.")
    26  	cmd.Example(`up domains`, "List purchased domains.")
    27  	cmd.Example(`up domains check example.com`, "Check availability of a domain.")
    28  	cmd.Example(`up domains buy`, "Purchase a domain.")
    29  	list(cmd)
    30  	check(cmd)
    31  	buy(cmd)
    32  }
    33  
    34  // USD.
    35  var usd = colors.Gray("USD")
    36  
    37  // Questions.
    38  var questions = []*survey.Question{
    39  	{
    40  		Name:     "email",
    41  		Prompt:   &survey.Input{Message: "Email:"},
    42  		Validate: validateEmail,
    43  	},
    44  	{
    45  		Name:     "firstname",
    46  		Prompt:   &survey.Input{Message: "First name:"},
    47  		Validate: survey.Required,
    48  	},
    49  	{
    50  		Name:     "lastname",
    51  		Prompt:   &survey.Input{Message: "Last name:"},
    52  		Validate: survey.Required,
    53  	},
    54  	{
    55  		Name:     "countrycode",
    56  		Prompt:   &survey.Input{Message: "Country code:"},
    57  		Validate: validateCountryCode,
    58  	},
    59  	{
    60  		Name:     "city",
    61  		Prompt:   &survey.Input{Message: "City:"},
    62  		Validate: survey.Required,
    63  	},
    64  	{
    65  		Name:     "address",
    66  		Prompt:   &survey.Input{Message: "Address:"},
    67  		Validate: survey.Required,
    68  	},
    69  	{
    70  		Name:     "phonenumber",
    71  		Prompt:   &survey.Input{Message: "Phone:"},
    72  		Validate: validatePhoneNumber,
    73  	},
    74  	{
    75  		Name:     "state",
    76  		Prompt:   &survey.Input{Message: "State:"},
    77  		Validate: survey.Required,
    78  	},
    79  	{
    80  		Name:     "zipcode",
    81  		Prompt:   &survey.Input{Message: "Zip code:"},
    82  		Validate: survey.Required,
    83  	},
    84  }
    85  
    86  // buy a domain.
    87  func buy(cmd *kingpin.Cmd) {
    88  	c := cmd.Command("buy", "Purchase a domain.")
    89  
    90  	c.Action(func(_ *kingpin.ParseContext) error {
    91  		defer util.Pad()()
    92  
    93  		_, p, err := root.Init()
    94  		if err != nil {
    95  			return errors.Wrap(err, "initializing")
    96  		}
    97  
    98  		var domain string
    99  		survey.AskOne(&survey.Input{
   100  			Message: "Domain:",
   101  		}, &domain, survey.Required)
   102  
   103  		var contact up.DomainContact
   104  
   105  		if err := survey.Ask(questions, &contact); err != nil {
   106  			return errors.Wrap(err, "prompting")
   107  		}
   108  
   109  		domains := p.Domains()
   110  		if err := domains.Purchase(domain, contact); err != nil {
   111  			return errors.Wrap(err, "purchasing")
   112  		}
   113  
   114  		return nil
   115  	})
   116  }
   117  
   118  // check domain availability.
   119  func check(cmd *kingpin.Cmd) {
   120  	c := cmd.Command("check", "Check availability of a domain.")
   121  	domain := c.Arg("domain", "Domain name.").Required().String()
   122  
   123  	c.Action(func(_ *kingpin.ParseContext) error {
   124  		defer util.Pad()()
   125  
   126  		_, p, err := root.Init()
   127  		if err != nil {
   128  			return errors.Wrap(err, "initializing")
   129  		}
   130  
   131  		stats.Track("Check Domain Availability", nil)
   132  
   133  		domains := p.Domains()
   134  		d, err := domains.Availability(*domain)
   135  		if err != nil {
   136  			return errors.Wrap(err, "fetching availability")
   137  		}
   138  
   139  		state := fmt.Sprintf("Domain %s is unavailable", d.Name)
   140  		if d.Available {
   141  			state = fmt.Sprintf("Domain %s is available for %s %s", d.Name, cost.Domain(d.Name), usd)
   142  		}
   143  
   144  		fmt.Printf("  %s\n", colors.Bool(d.Available)(state))
   145  
   146  		if !d.Available {
   147  			fmt.Printf("\n  Suggestions:\n")
   148  
   149  			suggestions, err := domains.Suggestions(*domain)
   150  			if err != nil {
   151  				return errors.Wrap(err, "fetching suggestions")
   152  			}
   153  
   154  			fmt.Printf("\n")
   155  			for _, d := range suggestions {
   156  				price := cost.Domain(d.Name)
   157  				fmt.Printf("  %-40s %s %s\n", colors.Purple(d.Name), price, usd)
   158  			}
   159  		}
   160  
   161  		return nil
   162  	})
   163  }
   164  
   165  // list domains purchased.
   166  func list(cmd *kingpin.Cmd) {
   167  	c := cmd.Command("ls", "List purchased domains.").Alias("list").Default()
   168  
   169  	c.Action(func(_ *kingpin.ParseContext) error {
   170  		defer util.Pad()()
   171  
   172  		_, p, err := root.Init()
   173  		if err != nil {
   174  			return errors.Wrap(err, "initializing")
   175  		}
   176  
   177  		stats.Track("List Domains", nil)
   178  
   179  		domains, err := p.Domains().List()
   180  		if err != nil {
   181  			return errors.Wrap(err, "listing domains")
   182  		}
   183  
   184  		for _, d := range domains {
   185  			s := "expires"
   186  			if d.AutoRenew {
   187  				s = "renews"
   188  			}
   189  			util.LogName(d.Name, "%s %s", s, d.Expiry.Format(time.Stamp))
   190  		}
   191  
   192  		return nil
   193  	})
   194  }
   195  
   196  // validateEmail returns an error if the input does not look like an email.
   197  func validateEmail(v interface{}) error {
   198  	s := v.(string)
   199  	i := strings.LastIndex(s, "@")
   200  
   201  	if s == "" {
   202  		return errors.New("Email is required.")
   203  	}
   204  
   205  	if i == -1 {
   206  		return errors.New("Email is missing '@'.")
   207  	}
   208  
   209  	if i == len(s)-1 {
   210  		return errors.New("Email is missing domain.")
   211  	}
   212  
   213  	return nil
   214  }
   215  
   216  // validateCountryCode returns an error if the input does not look like a valid country code.
   217  func validateCountryCode(v interface{}) error {
   218  	s := v.(string)
   219  
   220  	if s == "" {
   221  		return errors.New("Country code is required.")
   222  	}
   223  
   224  	if len(s) != 2 {
   225  		return errors.New("Country codes must consist of two uppercase letters, such as CA or AU.")
   226  	}
   227  
   228  	return nil
   229  }
   230  
   231  // validatePhoneNumber returns an error if the input does not look like a valid phone number.
   232  func validatePhoneNumber(v interface{}) error {
   233  	s := v.(string)
   234  
   235  	if s == "" {
   236  		return errors.New("Phone number is required.")
   237  	}
   238  
   239  	if !strings.HasPrefix(s, "+") {
   240  		return errors.New("Phone number must contain the country code, for example +1.2223334444 for Canada.")
   241  	}
   242  
   243  	return nil
   244  }