1
1
package cmd
2
2
3
3
import (
4
+ "bytes"
5
+ "encoding/json"
4
6
"fmt"
7
+ "io"
8
+ "net/http"
5
9
"os"
10
+ "path"
11
+ "time"
6
12
7
13
"github.com/spf13/cobra"
8
14
"github.com/spf13/viper"
9
15
)
10
16
11
17
var cfgFile string
12
18
13
- // rootCmd represents the base command when called without any subcommands
14
19
var rootCmd = & cobra.Command {
15
- Use : "v2" ,
16
- Short : "A brief description of your application" ,
17
- Long : `A longer description that spans multiple lines and likely contains
18
- examples and usage of using your application. For example:
19
-
20
- Cobra is a CLI library for Go that empowers applications.
21
- This application is a tool to generate the needed files
22
- to quickly create a Cobra application.` ,
23
- // Uncomment the following line if your bare application
24
- // has an action associated with it:
25
- // Run: func(cmd *cobra.Command, args []string) { },
20
+ Use : "bootdev" ,
21
+ Short : "The official boot.dev CLI" ,
22
+ Long : `The official CLI for boot.dev. This program is meant
23
+ to be a companion app (not a replacement) for the website.` ,
26
24
}
27
25
28
26
// Execute adds all child commands to the root command and sets flags appropriately.
@@ -37,37 +35,93 @@ func Execute() {
37
35
func init () {
38
36
cobra .OnInitialize (initConfig )
39
37
40
- // Here you will define your flags and configuration settings.
41
- // Cobra supports persistent flags, which, if defined here,
42
- // will be global for your application.
43
-
44
- rootCmd .PersistentFlags ().StringVar (& cfgFile , "config" , "" , "config file (default is $HOME/.v2.yaml)" )
45
-
46
- // Cobra also supports local flags, which will only run
47
- // when this action is called directly.
48
- rootCmd .Flags ().BoolP ("toggle" , "t" , false , "Help message for toggle" )
38
+ rootCmd .PersistentFlags ().StringVar (& cfgFile , "config" , "" , "config file (default is $HOME/.bootdev.yaml)" )
49
39
}
50
40
51
41
// initConfig reads in config file and ENV variables if set.
52
42
func initConfig () {
43
+ viper .SetDefault ("base_url" , "https://boot.dev" )
44
+ viper .SetDefault ("api_url" , "https://api.boot.dev" )
45
+ viper .SetDefault ("access_token" , "" )
46
+ viper .SetDefault ("refresh_token" , "" )
47
+ viper .SetDefault ("last_refresh" , 0 )
53
48
if cfgFile != "" {
54
49
// Use config file from the flag.
55
50
viper .SetConfigFile (cfgFile )
51
+ err := viper .ReadInConfig ()
52
+ cobra .CheckErr (err )
56
53
} else {
57
54
// Find home directory.
58
55
home , err := os .UserHomeDir ()
59
56
cobra .CheckErr (err )
60
57
61
- // Search config in home directory with name ".v2 " (without extension).
58
+ // Search config in home directory with name ".bootdev " (without extension).
62
59
viper .AddConfigPath (home )
63
60
viper .SetConfigType ("yaml" )
64
- viper .SetConfigName (".v2" )
61
+ viper .SetConfigName (".bootdev" )
62
+ if err := viper .ReadInConfig (); err != nil {
63
+ home , err := os .UserHomeDir ()
64
+ cobra .CheckErr (err )
65
+ viper .SafeWriteConfigAs (path .Join (home , ".bootdev.yaml" ))
66
+ viper .ReadInConfig ()
67
+ cobra .CheckErr (err )
68
+ }
65
69
}
66
70
71
+ viper .SetEnvPrefix ("bd" )
67
72
viper .AutomaticEnv () // read in environment variables that match
73
+ }
74
+
75
+ func promptLoginAndExitIf (condition bool ) {
76
+ if condition {
77
+ fmt .Println ("You must be logged in to use that command." )
78
+ fmt .Println ("Please run 'bootdev login' first." )
79
+ os .Exit (1 )
80
+ }
81
+ }
82
+
83
+ // Call this function at the beginning of a command handler
84
+ // if you need to make authenticated requests. This will
85
+ // automatically refresh the tokens, if necessary, and prompt
86
+ // the user to re-login if anything goes wrong.
87
+ func requireAuth () {
88
+ access_token := viper .GetString ("access_token" )
89
+ promptLoginAndExitIf (access_token == "" )
90
+
91
+ // We only refresh if our token is getting stale.
92
+ last_refresh := viper .GetInt64 ("last_refresh" )
93
+ if time .Now ().Add (- time .Minute * 55 ).Unix () <= last_refresh {
94
+ return
95
+ }
96
+
97
+ api_url := viper .GetString ("api_url" )
98
+
99
+ client := & http.Client {}
100
+ r , err := http .NewRequest ("POST" , api_url + "/v1/auth/refresh" , bytes .NewBuffer ([]byte {}))
101
+ r .Header .Add ("X-Refresh-Token" , viper .GetString ("refresh_token" ))
102
+ promptLoginAndExitIf (err != nil )
103
+ resp , err := client .Do (r )
104
+ promptLoginAndExitIf (err != nil )
105
+
106
+ defer resp .Body .Close ()
107
+ promptLoginAndExitIf (err != nil )
108
+
109
+ if resp .StatusCode != 200 {
110
+ promptLoginAndExitIf (err != nil )
111
+ }
112
+
113
+ body , err := io .ReadAll (resp .Body )
114
+ promptLoginAndExitIf (err != nil )
68
115
69
- // If a config file is found, read it in.
70
- if err := viper .ReadInConfig (); err == nil {
71
- fmt .Fprintln (os .Stderr , "Using config file:" , viper .ConfigFileUsed ())
116
+ var creds LoginResponse
117
+ err = json .Unmarshal (body , & creds )
118
+ promptLoginAndExitIf (err != nil )
119
+ if creds .AccessToken == "" || creds .RefreshToken == "" {
120
+ promptLoginAndExitIf (err != nil )
72
121
}
122
+ viper .Set ("access_token" , creds .AccessToken )
123
+ viper .Set ("refresh_token" , creds .RefreshToken )
124
+ viper .Set ("last_refresh" , time .Now ().Unix ())
125
+ err = viper .WriteConfig ()
126
+ promptLoginAndExitIf (err != nil )
73
127
}
0 commit comments