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 }