detached feb5f45

Beginning of the CLI client.

- Create endpoint(s) on the backend to handle cli client requests
- Add a middleware token who compare from the one in the config.yaml
- Create basic CLI client with cobra
- Create payload structure
- Create basic hello world command
commit
feb5f457604aead59a836c1c08fece150e03dfd4
author
bnema · · 2023-10-23 17:24 UTC
parent
c79b816
signature
unsigned
20 files · +361 -189
M cmd/cli/main.go +3 -2
@@ -1,10 +1,11 @@
11 package main
22
33 import (
4+ "github.com/bnema/gordon/internal/app"
45 "github.com/bnema/gordon/internal/cli"
56 )
67
78 func main() {
8- cli.InitCli()
9-
9+ a := app.NewServerApp()
10+ cli.Execute(a)
1011 }
@@ -1,10 +1,11 @@
1 package main1 package main
2 2
3 import (3 import (
4+ "github.com/bnema/gordon/internal/app"
4 "github.com/bnema/gordon/internal/cli"5 "github.com/bnema/gordon/internal/cli"
5 )6 )
6 7
7 func main() {8 func main() {
8- cli.InitCli()9+ a := app.NewServerApp()
9-10+ cli.Execute(a)
10 }11 }
M cmd/srv/main.go +1 -1
@@ -21,7 +21,7 @@ func cleanup(a *app.App) {
2121 }
2222
2323 func main() {
24- a := app.NewApp()
24+ a := app.NewServerApp()
2525
2626 _, err := app.InitializeDB(a)
2727 if err != nil {
@@ -21,7 +21,7 @@ func cleanup(a *app.App) {
21 }21 }
22 22
23 func main() {23 func main() {
24- a := app.NewApp()24+ a := app.NewServerApp()
25 25
26 _, err := app.InitializeDB(a)26 _, err := app.InitializeDB(a)
27 if err != nil {27 if err != nil {
M go.mod +3 -13
@@ -4,7 +4,6 @@ go 1.21.3
44
55 require (
66 github.com/PuerkitoBio/goquery v1.8.1
7- github.com/charmbracelet/bubbletea v0.24.2
87 github.com/docker/docker v24.0.6+incompatible
98 github.com/docker/go-connections v0.4.0
109 github.com/gorilla/sessions v1.2.1
@@ -13,24 +12,15 @@ require (
1312 github.com/labstack/echo/v4 v4.11.1
1413 github.com/mattn/go-sqlite3 v1.14.17
1514 github.com/microcosm-cc/bluemonday v1.0.25
15+ github.com/spf13/cobra v1.7.0
1616 gopkg.in/yaml.v3 v3.0.1
1717 )
1818
1919 require (
20- github.com/aymanbagabas/go-osc52/v2 v2.0.1 // indirect
21- github.com/containerd/console v1.0.4-0.20230313162750-1ae8d489ac81 // indirect
20+ github.com/inconshreveable/mousetrap v1.1.0 // indirect
2221 github.com/kr/text v0.2.0 // indirect
23- github.com/lucasb-eyer/go-colorful v1.2.0 // indirect
24- github.com/mattn/go-localereader v0.0.1 // indirect
25- github.com/mattn/go-runewidth v0.0.14 // indirect
26- github.com/muesli/ansi v0.0.0-20211018074035-2e021307bc4b // indirect
27- github.com/muesli/cancelreader v0.2.2 // indirect
28- github.com/muesli/reflow v0.3.0 // indirect
29- github.com/muesli/termenv v0.15.1 // indirect
30- github.com/rivo/uniseg v0.2.0 // indirect
22+ github.com/spf13/pflag v1.0.5 // indirect
3123 github.com/stretchr/testify v1.8.2 // indirect
32- golang.org/x/sync v0.4.0 // indirect
33- golang.org/x/term v0.13.0 // indirect
3424 )
3525
3626 require (
@@ -4,7 +4,6 @@ go 1.21.3
4 4
5 require (5 require (
6 github.com/PuerkitoBio/goquery v1.8.16 github.com/PuerkitoBio/goquery v1.8.1
7- github.com/charmbracelet/bubbletea v0.24.2
8 github.com/docker/docker v24.0.6+incompatible7 github.com/docker/docker v24.0.6+incompatible
9 github.com/docker/go-connections v0.4.08 github.com/docker/go-connections v0.4.0
10 github.com/gorilla/sessions v1.2.19 github.com/gorilla/sessions v1.2.1
@@ -13,24 +12,15 @@ require (
13 github.com/labstack/echo/v4 v4.11.112 github.com/labstack/echo/v4 v4.11.1
14 github.com/mattn/go-sqlite3 v1.14.1713 github.com/mattn/go-sqlite3 v1.14.17
15 github.com/microcosm-cc/bluemonday v1.0.2514 github.com/microcosm-cc/bluemonday v1.0.25
15+ github.com/spf13/cobra v1.7.0
16 gopkg.in/yaml.v3 v3.0.116 gopkg.in/yaml.v3 v3.0.1
17 )17 )
18 18
19 require (19 require (
20- github.com/aymanbagabas/go-osc52/v2 v2.0.1 // indirect20+ github.com/inconshreveable/mousetrap v1.1.0 // indirect
21- github.com/containerd/console v1.0.4-0.20230313162750-1ae8d489ac81 // indirect
22 github.com/kr/text v0.2.0 // indirect21 github.com/kr/text v0.2.0 // indirect
23- github.com/lucasb-eyer/go-colorful v1.2.0 // indirect22+ github.com/spf13/pflag v1.0.5 // indirect
24- github.com/mattn/go-localereader v0.0.1 // indirect
25- github.com/mattn/go-runewidth v0.0.14 // indirect
26- github.com/muesli/ansi v0.0.0-20211018074035-2e021307bc4b // indirect
27- github.com/muesli/cancelreader v0.2.2 // indirect
28- github.com/muesli/reflow v0.3.0 // indirect
29- github.com/muesli/termenv v0.15.1 // indirect
30- github.com/rivo/uniseg v0.2.0 // indirect
31 github.com/stretchr/testify v1.8.2 // indirect23 github.com/stretchr/testify v1.8.2 // indirect
32- golang.org/x/sync v0.4.0 // indirect
33- golang.org/x/term v0.13.0 // indirect
34 )24 )
35 25
36 require (26 require (
M go.sum +8 -27 generated file: collapsed
M internal/app/app.go +29 -48
@@ -3,14 +3,10 @@ package app
33 import (
44 "database/sql"
55 "fmt"
6- "io"
76 "io/fs"
8- "log"
97 "os"
108
119 "github.com/bnema/gordon/internal/db"
12- "github.com/bnema/gordon/internal/templating"
13- "github.com/bnema/gordon/internal/webui"
1410 "github.com/bnema/gordon/pkg/docker"
1511 _ "github.com/joho/godotenv/autoload"
1612 "gopkg.in/yaml.v3"
@@ -97,50 +93,6 @@ func LoadConfig(config *Config) (*Config, error) {
9793 return config, nil
9894 }
9995
100-func NewApp() *App {
101- // Initialize AppConfig
102- config := &Config{}
103- _, err := LoadConfig(config)
104- if err != nil {
105- log.Fatalf("Failed to load configuration: %v", err)
106- }
107- // Append build dir to storage dir
108- config.General.StorageDir = fmt.Sprintf("%s/%s", config.General.BuildDir, config.General.StorageDir)
109-
110- // Open the strings.yml file containing the strings for the current language
111- file, err := templating.TemplateFS.Open("locstrings.yml")
112- if err != nil {
113- log.Fatalf("Failed to open strings.yml: %v", err)
114- }
115-
116- // Read the file content into a byte slice
117- bytes, err := io.ReadAll(file)
118- if err != nil {
119- log.Fatalf("Failed to read strings.yml: %v", err)
120- }
121-
122- file.Close()
123-
124- // Initialize App
125- a := &App{
126- TemplateFS: templating.TemplateFS,
127- PublicFS: webui.PublicFS,
128- LocYML: bytes,
129- DBDir: DBDir,
130- DBFilename: DBFilename,
131- Config: *config,
132- }
133-
134- // DBTables: DBTables{
135- // User: db.User{},
136- // Account: db.Account{},
137- // Sessions: db.Sessions{},
138- // Provider: db.Provider{},
139-
140- OauthCallbackURL = config.GenerateOauthCallbackURL()
141- return a
142-}
143-
14496 // NewDockerConfig creates and returns a new Docker client configuration
14597 func (config *Config) NewDockerConfig() *docker.Config {
14698 if config.ContainerEngine.Podman {
@@ -175,6 +127,25 @@ func (config *Config) GenerateOauthCallbackURL() string {
175127 return fmt.Sprintf("%s://%s%s%s/login/oauth/callback", scheme, domain, port, config.Admin.Path)
176128 }
177129
130+func (config *Config) GenerateAPIURL() string {
131+ var scheme, port string
132+
133+ if config.General.RunEnv == "dev" {
134+ scheme = "http"
135+ port = fmt.Sprintf(":%d", config.Http.Port)
136+ } else { // Assuming "prod"
137+ scheme = "https"
138+ port = ""
139+ }
140+
141+ domain := config.Http.TopDomain
142+ if config.Http.SubDomain != "" {
143+ domain = fmt.Sprintf("%s.%s", config.Http.SubDomain, config.Http.TopDomain)
144+ }
145+
146+ return fmt.Sprintf("%s://%s%s/api", scheme, domain, port)
147+}
148+
178149 // UpdateConfig updates the config file
179150 func (config *Config) UpdateConfig() error {
180151 // Marshal the config struct into YAML
@@ -191,3 +162,13 @@ func (config *Config) UpdateConfig() error {
191162
192163 return nil
193164 }
165+
166+func (config *Config) GetToken() (string, error) {
167+ token := config.General.GordonToken
168+ if token == "" {
169+ return "", fmt.Errorf("no token found in config.yml")
170+ }
171+
172+ return token, nil
173+
174+}
@@ -3,14 +3,10 @@ package app
3 import (3 import (
4 "database/sql"4 "database/sql"
5 "fmt"5 "fmt"
6- "io"
7 "io/fs"6 "io/fs"
8- "log"
9 "os"7 "os"
10 8
11 "github.com/bnema/gordon/internal/db"9 "github.com/bnema/gordon/internal/db"
12- "github.com/bnema/gordon/internal/templating"
13- "github.com/bnema/gordon/internal/webui"
14 "github.com/bnema/gordon/pkg/docker"10 "github.com/bnema/gordon/pkg/docker"
15 _ "github.com/joho/godotenv/autoload"11 _ "github.com/joho/godotenv/autoload"
16 "gopkg.in/yaml.v3"12 "gopkg.in/yaml.v3"
@@ -97,50 +93,6 @@ func LoadConfig(config *Config) (*Config, error) {
97 return config, nil93 return config, nil
98 }94 }
99 95
100-func NewApp() *App {
101- // Initialize AppConfig
102- config := &Config{}
103- _, err := LoadConfig(config)
104- if err != nil {
105- log.Fatalf("Failed to load configuration: %v", err)
106- }
107- // Append build dir to storage dir
108- config.General.StorageDir = fmt.Sprintf("%s/%s", config.General.BuildDir, config.General.StorageDir)
109-
110- // Open the strings.yml file containing the strings for the current language
111- file, err := templating.TemplateFS.Open("locstrings.yml")
112- if err != nil {
113- log.Fatalf("Failed to open strings.yml: %v", err)
114- }
115-
116- // Read the file content into a byte slice
117- bytes, err := io.ReadAll(file)
118- if err != nil {
119- log.Fatalf("Failed to read strings.yml: %v", err)
120- }
121-
122- file.Close()
123-
124- // Initialize App
125- a := &App{
126- TemplateFS: templating.TemplateFS,
127- PublicFS: webui.PublicFS,
128- LocYML: bytes,
129- DBDir: DBDir,
130- DBFilename: DBFilename,
131- Config: *config,
132- }
133-
134- // DBTables: DBTables{
135- // User: db.User{},
136- // Account: db.Account{},
137- // Sessions: db.Sessions{},
138- // Provider: db.Provider{},
139-
140- OauthCallbackURL = config.GenerateOauthCallbackURL()
141- return a
142-}
143-
144 // NewDockerConfig creates and returns a new Docker client configuration96 // NewDockerConfig creates and returns a new Docker client configuration
145 func (config *Config) NewDockerConfig() *docker.Config {97 func (config *Config) NewDockerConfig() *docker.Config {
146 if config.ContainerEngine.Podman {98 if config.ContainerEngine.Podman {
@@ -175,6 +127,25 @@ func (config *Config) GenerateOauthCallbackURL() string {
175 return fmt.Sprintf("%s://%s%s%s/login/oauth/callback", scheme, domain, port, config.Admin.Path)127 return fmt.Sprintf("%s://%s%s%s/login/oauth/callback", scheme, domain, port, config.Admin.Path)
176 }128 }
177 129
130+func (config *Config) GenerateAPIURL() string {
131+ var scheme, port string
132+
133+ if config.General.RunEnv == "dev" {
134+ scheme = "http"
135+ port = fmt.Sprintf(":%d", config.Http.Port)
136+ } else { // Assuming "prod"
137+ scheme = "https"
138+ port = ""
139+ }
140+
141+ domain := config.Http.TopDomain
142+ if config.Http.SubDomain != "" {
143+ domain = fmt.Sprintf("%s.%s", config.Http.SubDomain, config.Http.TopDomain)
144+ }
145+
146+ return fmt.Sprintf("%s://%s%s/api", scheme, domain, port)
147+}
148+
178 // UpdateConfig updates the config file149 // UpdateConfig updates the config file
179 func (config *Config) UpdateConfig() error {150 func (config *Config) UpdateConfig() error {
180 // Marshal the config struct into YAML151 // Marshal the config struct into YAML
@@ -191,3 +162,13 @@ func (config *Config) UpdateConfig() error {
191 162
192 return nil163 return nil
193 }164 }
165+
166+func (config *Config) GetToken() (string, error) {
167+ token := config.General.GordonToken
168+ if token == "" {
169+ return "", fmt.Errorf("no token found in config.yml")
170+ }
171+
172+ return token, nil
173+
174+}
A internal/app/newserver.go +48 -0
@@ -0,0 +1,48 @@
1+package app
2+
3+import (
4+ "fmt"
5+ "io"
6+ "log"
7+
8+ "github.com/bnema/gordon/internal/templating"
9+ "github.com/bnema/gordon/internal/webui"
10+)
11+
12+func NewServerApp() *App {
13+ // Initialize AppConfig
14+ config := &Config{}
15+ _, err := LoadConfig(config)
16+ if err != nil {
17+ log.Fatalf("Failed to load configuration: %v", err)
18+ }
19+ // Append build dir to storage dir
20+ config.General.StorageDir = fmt.Sprintf("%s/%s", config.General.BuildDir, config.General.StorageDir)
21+
22+ // Open the strings.yml file containing the strings for the current language
23+ file, err := templating.TemplateFS.Open("locstrings.yml")
24+ if err != nil {
25+ log.Fatalf("Failed to open strings.yml: %v", err)
26+ }
27+
28+ // Read the file content into a byte slice
29+ bytes, err := io.ReadAll(file)
30+ if err != nil {
31+ log.Fatalf("Failed to read strings.yml: %v", err)
32+ }
33+
34+ file.Close()
35+
36+ // Initialize App
37+ a := &App{
38+ TemplateFS: templating.TemplateFS,
39+ PublicFS: webui.PublicFS,
40+ LocYML: bytes,
41+ DBDir: DBDir,
42+ DBFilename: DBFilename,
43+ Config: *config,
44+ }
45+
46+ OauthCallbackURL = config.GenerateOauthCallbackURL()
47+ return a
48+}
@@ -0,0 +1,48 @@
1+package app
2+
3+import (
4+ "fmt"
5+ "io"
6+ "log"
7+
8+ "github.com/bnema/gordon/internal/templating"
9+ "github.com/bnema/gordon/internal/webui"
10+)
11+
12+func NewServerApp() *App {
13+ // Initialize AppConfig
14+ config := &Config{}
15+ _, err := LoadConfig(config)
16+ if err != nil {
17+ log.Fatalf("Failed to load configuration: %v", err)
18+ }
19+ // Append build dir to storage dir
20+ config.General.StorageDir = fmt.Sprintf("%s/%s", config.General.BuildDir, config.General.StorageDir)
21+
22+ // Open the strings.yml file containing the strings for the current language
23+ file, err := templating.TemplateFS.Open("locstrings.yml")
24+ if err != nil {
25+ log.Fatalf("Failed to open strings.yml: %v", err)
26+ }
27+
28+ // Read the file content into a byte slice
29+ bytes, err := io.ReadAll(file)
30+ if err != nil {
31+ log.Fatalf("Failed to read strings.yml: %v", err)
32+ }
33+
34+ file.Close()
35+
36+ // Initialize App
37+ a := &App{
38+ TemplateFS: templating.TemplateFS,
39+ PublicFS: webui.PublicFS,
40+ LocYML: bytes,
41+ DBDir: DBDir,
42+ DBFilename: DBFilename,
43+ Config: *config,
44+ }
45+
46+ OauthCallbackURL = config.GenerateOauthCallbackURL()
47+ return a
48+}
M internal/cli/cli.go +9 -9
@@ -1,18 +1,18 @@
11 package cli
22
33 import (
4- "log"
5-
4+ "github.com/bnema/gordon/internal/app"
65 "github.com/bnema/gordon/internal/cli/cmd"
7- "github.com/bnema/gordon/internal/cli/mvu"
8- tea "github.com/charmbracelet/bubbletea"
6+ "github.com/spf13/cobra"
97 )
108
9+var rootCmd = &cobra.Command{Use: "gordon"}
1110
11+func InitializeCommands(a *app.App) {
12+ rootCmd.AddCommand(cmd.NewHelloCommand(a))
13+}
1214
13-func InitCli(a *app.App, m *mvu.Model)) {
14- p := tea.NewProgram(Init, mvu.Init, mvu.Update, mvu.View)
15- if _, err := p.Run(); err != nil {
16- log.Fatal(err)
17- }
15+func Execute(a *app.App) {
16+ InitializeCommands(a)
17+ cobra.CheckErr(rootCmd.Execute())
1818 }
@@ -1,18 +1,18 @@
1 package cli1 package cli
2 2
3 import (3 import (
4- "log"4+ "github.com/bnema/gordon/internal/app"
5-
6 "github.com/bnema/gordon/internal/cli/cmd"5 "github.com/bnema/gordon/internal/cli/cmd"
7- "github.com/bnema/gordon/internal/cli/mvu"6+ "github.com/spf13/cobra"
8- tea "github.com/charmbracelet/bubbletea"
9 )7 )
10 8
9+var rootCmd = &cobra.Command{Use: "gordon"}
11 10
11+func InitializeCommands(a *app.App) {
12+ rootCmd.AddCommand(cmd.NewHelloCommand(a))
13+}
12 14
13-func InitCli(a *app.App, m *mvu.Model)) {15+func Execute(a *app.App) {
14- p := tea.NewProgram(Init, mvu.Init, mvu.Update, mvu.View)16+ InitializeCommands(a)
15- if _, err := p.Run(); err != nil {17+ cobra.CheckErr(rootCmd.Execute())
16- log.Fatal(err)
17- }
18 }18 }
R internal/cli/cmd/update.go → internal/cli/cmd/_update.go +0 -0 renamed, 100% similar

renamed, 100% similar

M internal/cli/cmd/checkserver.go +0 -27
@@ -1,28 +1,1 @@
11 package cmd
2-
3-import (
4- "fmt"
5- "net/http"
6- "time"
7-
8- "github.com/bnema/gordon/internal/app"
9- tea "github.com/charmbracelet/bubbletea"
10-)
11-
12-type statusMsg int
13-type timeout time.Duration
14-
15-func CheckServer(a *app.App) tea.Msg {
16- url := a.Config.Admin.Path
17- c := &http.Client{
18- Timeout: 10 * time.Second,
19- }
20- res, err := c.Get(url)
21- if err != nil {
22- fmt.Println(err)
23- return mvu.ErrMsg{Err: err}
24- }
25- defer res.Body.Close() // nolint:errcheck
26-
27- return statusMsg(res.StatusCode)
28-}
@@ -1,28 +1,1 @@
1 package cmd1 package cmd
2-
3-import (
4- "fmt"
5- "net/http"
6- "time"
7-
8- "github.com/bnema/gordon/internal/app"
9- tea "github.com/charmbracelet/bubbletea"
10-)
11-
12-type statusMsg int
13-type timeout time.Duration
14-
15-func CheckServer(a *app.App) tea.Msg {
16- url := a.Config.Admin.Path
17- c := &http.Client{
18- Timeout: 10 * time.Second,
19- }
20- res, err := c.Get(url)
21- if err != nil {
22- fmt.Println(err)
23- return mvu.ErrMsg{Err: err}
24- }
25- defer res.Body.Close() // nolint:errcheck
26-
27- return statusMsg(res.StatusCode)
28-}
A internal/cli/cmd/hello.go +33 -0
@@ -0,0 +1,33 @@
1+package cmd
2+
3+import (
4+ "fmt"
5+
6+ "github.com/bnema/gordon/internal/app"
7+ "github.com/bnema/gordon/internal/cli/handler"
8+ "github.com/bnema/gordon/internal/common"
9+ "github.com/spf13/cobra"
10+)
11+
12+func NewHelloCommand(a *app.App) *cobra.Command {
13+ return &cobra.Command{
14+ Use: "hello",
15+ Short: "Send a hello request to the backend",
16+ Run: func(cmd *cobra.Command, args []string) {
17+ data := map[string]interface{}{
18+ "message": "Hello, World!",
19+ }
20+ payload, err := common.CreatePayload("hello", data)
21+ if err != nil {
22+ fmt.Println("Error creating payload:", err)
23+ return
24+ }
25+
26+ // Use your handler.SendHTTPRequest function here
27+ // Assuming that the SendHTTPRequest is moved to a package that can be imported here
28+ handler.SendHTTPRequest(a, payload)
29+
30+ fmt.Printf("Sending payload to backend: %+v\n", payload)
31+ },
32+ }
33+}
@@ -0,0 +1,33 @@
1+package cmd
2+
3+import (
4+ "fmt"
5+
6+ "github.com/bnema/gordon/internal/app"
7+ "github.com/bnema/gordon/internal/cli/handler"
8+ "github.com/bnema/gordon/internal/common"
9+ "github.com/spf13/cobra"
10+)
11+
12+func NewHelloCommand(a *app.App) *cobra.Command {
13+ return &cobra.Command{
14+ Use: "hello",
15+ Short: "Send a hello request to the backend",
16+ Run: func(cmd *cobra.Command, args []string) {
17+ data := map[string]interface{}{
18+ "message": "Hello, World!",
19+ }
20+ payload, err := common.CreatePayload("hello", data)
21+ if err != nil {
22+ fmt.Println("Error creating payload:", err)
23+ return
24+ }
25+
26+ // Use your handler.SendHTTPRequest function here
27+ // Assuming that the SendHTTPRequest is moved to a package that can be imported here
28+ handler.SendHTTPRequest(a, payload)
29+
30+ fmt.Printf("Sending payload to backend: %+v\n", payload)
31+ },
32+ }
33+}
M internal/cli/cmd/serve.go +18 -0
@@ -1,3 +1,21 @@
11 // the serve command is used to start the gordon server
22 // Path: internal/cli/cmd/serve.go
33 package cmd
4+
5+import (
6+ "github.com/bnema/gordon/internal/app"
7+ "github.com/bnema/gordon/internal/cli/handler"
8+ "github.com/spf13/cobra"
9+)
10+
11+// execute cmd/srv/main.go main function
12+
13+func NewServeCommand(a *app.App) *cobra.Command {
14+ return &cobra.Command{
15+ Use: "serve",
16+ Short: "Start the gordon server",
17+ Run: func(cmd *cobra.Command, args []string) {
18+ handler.StartServer(a)
19+ },
20+ }
21+}
@@ -1,3 +1,21 @@
1 // the serve command is used to start the gordon server1 // the serve command is used to start the gordon server
2 // Path: internal/cli/cmd/serve.go2 // Path: internal/cli/cmd/serve.go
3 package cmd3 package cmd
4+
5+import (
6+ "github.com/bnema/gordon/internal/app"
7+ "github.com/bnema/gordon/internal/cli/handler"
8+ "github.com/spf13/cobra"
9+)
10+
11+// execute cmd/srv/main.go main function
12+
13+func NewServeCommand(a *app.App) *cobra.Command {
14+ return &cobra.Command{
15+ Use: "serve",
16+ Short: "Start the gordon server",
17+ Run: func(cmd *cobra.Command, args []string) {
18+ handler.StartServer(a)
19+ },
20+ }
21+}
A internal/cli/handler/http.go +70 -0
@@ -0,0 +1,70 @@
1+package handler
2+
3+import (
4+ "bytes"
5+ "encoding/json"
6+ "fmt"
7+ "net/http"
8+
9+ "github.com/bnema/gordon/internal/app"
10+ "github.com/bnema/gordon/internal/common"
11+)
12+
13+// GenerateAPIURL creates the API URL
14+func GenerateAPIURL(a *app.App) string {
15+ fmt.Println("Generating API URL:", a.Config.GenerateAPIURL())
16+ return a.Config.GenerateAPIURL()
17+}
18+
19+// PrepareJSONPayload marshals the payload into JSON
20+func PrepareJSONPayload(payload common.Payload) ([]byte, error) {
21+ return json.Marshal(payload)
22+}
23+
24+// CreateNewRequest creates a new HTTP request
25+func CreateNewRequest(method, url string, body []byte) (*http.Request, error) {
26+ return http.NewRequest(method, url, bytes.NewBuffer(body))
27+}
28+
29+// SetRequestHeaders sets the headers for the request
30+func SetRequestHeaders(req *http.Request, token string) {
31+ req.Header.Set("Content-Type", "application/json")
32+ req.Header.Set("Authorization", "Bearer "+token)
33+}
34+
35+// SendRequest sends the HTTP request and returns the response
36+func SendRequest(req *http.Request) (*http.Response, error) {
37+ client := &http.Client{}
38+ return client.Do(req)
39+}
40+
41+// SendHTTPRequest sends the HTTP request
42+func SendHTTPRequest(a *app.App, payload common.Payload) {
43+ apiUrl := GenerateAPIURL(a)
44+ fmt.Println("Sending request to:", apiUrl)
45+
46+ token := a.Config.General.GordonToken
47+
48+ jsonPayload, err := PrepareJSONPayload(payload)
49+ if err != nil {
50+ fmt.Println("Error preparing JSON payload:", err)
51+ return
52+ }
53+
54+ req, err := CreateNewRequest("POST", apiUrl, jsonPayload)
55+ if err != nil {
56+ fmt.Println("Error creating new HTTP request:", err)
57+ return
58+ }
59+
60+ SetRequestHeaders(req, token)
61+
62+ resp, err := SendRequest(req)
63+ if err != nil {
64+ fmt.Println("Error sending HTTP request:", err)
65+ return
66+ }
67+ defer resp.Body.Close()
68+
69+ fmt.Println("Response Status:", resp.Status)
70+}
@@ -0,0 +1,70 @@
1+package handler
2+
3+import (
4+ "bytes"
5+ "encoding/json"
6+ "fmt"
7+ "net/http"
8+
9+ "github.com/bnema/gordon/internal/app"
10+ "github.com/bnema/gordon/internal/common"
11+)
12+
13+// GenerateAPIURL creates the API URL
14+func GenerateAPIURL(a *app.App) string {
15+ fmt.Println("Generating API URL:", a.Config.GenerateAPIURL())
16+ return a.Config.GenerateAPIURL()
17+}
18+
19+// PrepareJSONPayload marshals the payload into JSON
20+func PrepareJSONPayload(payload common.Payload) ([]byte, error) {
21+ return json.Marshal(payload)
22+}
23+
24+// CreateNewRequest creates a new HTTP request
25+func CreateNewRequest(method, url string, body []byte) (*http.Request, error) {
26+ return http.NewRequest(method, url, bytes.NewBuffer(body))
27+}
28+
29+// SetRequestHeaders sets the headers for the request
30+func SetRequestHeaders(req *http.Request, token string) {
31+ req.Header.Set("Content-Type", "application/json")
32+ req.Header.Set("Authorization", "Bearer "+token)
33+}
34+
35+// SendRequest sends the HTTP request and returns the response
36+func SendRequest(req *http.Request) (*http.Response, error) {
37+ client := &http.Client{}
38+ return client.Do(req)
39+}
40+
41+// SendHTTPRequest sends the HTTP request
42+func SendHTTPRequest(a *app.App, payload common.Payload) {
43+ apiUrl := GenerateAPIURL(a)
44+ fmt.Println("Sending request to:", apiUrl)
45+
46+ token := a.Config.General.GordonToken
47+
48+ jsonPayload, err := PrepareJSONPayload(payload)
49+ if err != nil {
50+ fmt.Println("Error preparing JSON payload:", err)
51+ return
52+ }
53+
54+ req, err := CreateNewRequest("POST", apiUrl, jsonPayload)
55+ if err != nil {
56+ fmt.Println("Error creating new HTTP request:", err)
57+ return
58+ }
59+
60+ SetRequestHeaders(req, token)
61+
62+ resp, err := SendRequest(req)
63+ if err != nil {
64+ fmt.Println("Error sending HTTP request:", err)
65+ return
66+ }
67+ defer resp.Body.Close()
68+
69+ fmt.Println("Response Status:", resp.Status)
70+}
A internal/cli/handler/startserver.go +9 -0
@@ -0,0 +1,9 @@
1+package handler
2+
3+import "github.com/bnema/gordon/internal/app"
4+
5+// execute cmd/srv/main.go main function
6+
7+func StartServer(a *app.App) error {
8+ return nil
9+}
@@ -0,0 +1,9 @@
1+package handler
2+
3+import "github.com/bnema/gordon/internal/app"
4+
5+// execute cmd/srv/main.go main function
6+
7+func StartServer(a *app.App) error {
8+ return nil
9+}
D internal/cli/mvu/mvu.go +0 -59
@@ -1,59 +0,0 @@
1-package mvu
2-
3-import (
4- "fmt"
5- "net/http"
6-
7- tea "github.com/charmbracelet/bubbletea"
8-)
9-
10-type Model struct {
11- status int
12- err error
13- url string
14-}
15-
16-type statusMsg int
17-
18-type ErrMsg struct {
19- Err error
20-}
21-
22-func (m Model) Init() tea.Cmd {
23- return nil
24-}
25-
26-func Error(e ErrMsg) string { return e.Err.Error() }
27-
28-func Update(msg tea.Msg, m Model) (tea.Model, tea.Cmd) {
29- switch msg := msg.(type) {
30- case tea.KeyMsg:
31- switch msg.String() {
32- case "q", "ctrl+c", "esc":
33- return m, tea.Quit
34- default:
35- return nil
36- }
37-
38- case statusMsg:
39- m.status = int(msg)
40- return m, tea.Quit
41-
42- case ErrMsg:
43- m.err = msg
44- return nil
45-
46- default:
47- return nil
48- }
49-}
50-
51-func (m Model) View() string {
52- s := fmt.Sprintf("Checking %s...", m.url)
53- if m.err != nil {
54- s += fmt.Sprintf("something went wrong: %s", m.err)
55- } else if m.status != 0 {
56- s += fmt.Sprintf("%d %s", m.status, http.StatusText(m.status))
57- }
58- return s + "\n"
59-}
@@ -1,59 +0,0 @@
1-package mvu
2-
3-import (
4- "fmt"
5- "net/http"
6-
7- tea "github.com/charmbracelet/bubbletea"
8-)
9-
10-type Model struct {
11- status int
12- err error
13- url string
14-}
15-
16-type statusMsg int
17-
18-type ErrMsg struct {
19- Err error
20-}
21-
22-func (m Model) Init() tea.Cmd {
23- return nil
24-}
25-
26-func Error(e ErrMsg) string { return e.Err.Error() }
27-
28-func Update(msg tea.Msg, m Model) (tea.Model, tea.Cmd) {
29- switch msg := msg.(type) {
30- case tea.KeyMsg:
31- switch msg.String() {
32- case "q", "ctrl+c", "esc":
33- return m, tea.Quit
34- default:
35- return nil
36- }
37-
38- case statusMsg:
39- m.status = int(msg)
40- return m, tea.Quit
41-
42- case ErrMsg:
43- m.err = msg
44- return nil
45-
46- default:
47- return nil
48- }
49-}
50-
51-func (m Model) View() string {
52- s := fmt.Sprintf("Checking %s...", m.url)
53- if m.err != nil {
54- s += fmt.Sprintf("something went wrong: %s", m.err)
55- } else if m.status != 0 {
56- s += fmt.Sprintf("%d %s", m.status, http.StatusText(m.status))
57- }
58- return s + "\n"
59-}
A internal/common/payload.go +61 -0
@@ -0,0 +1,61 @@
1+package common
2+
3+import (
4+ "errors"
5+)
6+
7+// RequestPayload holds the type and payload of a client request
8+type RequestPayload struct {
9+ Type string `json:"type"`
10+ Payload Payload `json:"payload"`
11+}
12+
13+// Request is the client request type
14+type Request struct {
15+ Token string `json:"token"`
16+ Request RequestPayload `json:"request"`
17+}
18+
19+// Payload interface exposes the type and allows for extensible payloads
20+type Payload interface {
21+ GetType() string
22+}
23+
24+// HelloWorldPayload for testing
25+type HelloWorldPayload struct {
26+ Message string `json:"message"`
27+}
28+
29+// GetType implementation for HelloWorldPayload
30+func (p HelloWorldPayload) GetType() string {
31+ return "hello"
32+}
33+
34+// PushPayload for pushing container images
35+type PushPayload struct {
36+ Image string `json:"image"`
37+ Tag string `json:"tag"`
38+}
39+
40+// GetType implementation for PushPayload
41+func (p PushPayload) GetType() string {
42+ return "push"
43+}
44+
45+// CreatePayload is a factory function to create a Payload based on the type
46+func CreatePayload(payloadType string, data map[string]interface{}) (Payload, error) {
47+ switch payloadType {
48+ case "hello":
49+ message, ok := data["message"].(string)
50+ if !ok {
51+ return nil, errors.New("invalid data for HelloWorldPayload")
52+ }
53+ return HelloWorldPayload{Message: message}, nil
54+ case "push":
55+ // TODO: implement
56+ default:
57+ return nil, errors.New("unknown payload type")
58+ }
59+
60+ return nil, nil
61+}
@@ -0,0 +1,61 @@
1+package common
2+
3+import (
4+ "errors"
5+)
6+
7+// RequestPayload holds the type and payload of a client request
8+type RequestPayload struct {
9+ Type string `json:"type"`
10+ Payload Payload `json:"payload"`
11+}
12+
13+// Request is the client request type
14+type Request struct {
15+ Token string `json:"token"`
16+ Request RequestPayload `json:"request"`
17+}
18+
19+// Payload interface exposes the type and allows for extensible payloads
20+type Payload interface {
21+ GetType() string
22+}
23+
24+// HelloWorldPayload for testing
25+type HelloWorldPayload struct {
26+ Message string `json:"message"`
27+}
28+
29+// GetType implementation for HelloWorldPayload
30+func (p HelloWorldPayload) GetType() string {
31+ return "hello"
32+}
33+
34+// PushPayload for pushing container images
35+type PushPayload struct {
36+ Image string `json:"image"`
37+ Tag string `json:"tag"`
38+}
39+
40+// GetType implementation for PushPayload
41+func (p PushPayload) GetType() string {
42+ return "push"
43+}
44+
45+// CreatePayload is a factory function to create a Payload based on the type
46+func CreatePayload(payloadType string, data map[string]interface{}) (Payload, error) {
47+ switch payloadType {
48+ case "hello":
49+ message, ok := data["message"].(string)
50+ if !ok {
51+ return nil, errors.New("invalid data for HelloWorldPayload")
52+ }
53+ return HelloWorldPayload{Message: message}, nil
54+ case "push":
55+ // TODO: implement
56+ default:
57+ return nil, errors.New("unknown payload type")
58+ }
59+
60+ return nil, nil
61+}
A internal/httpserve/handler/endpoints.go +14 -0
@@ -0,0 +1,14 @@
1+package handler
2+
3+import (
4+ "net/http"
5+
6+ "github.com/bnema/gordon/internal/app"
7+ "github.com/labstack/echo/v4"
8+)
9+
10+// Handle GET on /api endpoint
11+
12+func GetHello(c echo.Context, a *app.App) error {
13+ return c.JSON(http.StatusOK, "Hello, World!")
14+}
@@ -0,0 +1,14 @@
1+package handler
2+
3+import (
4+ "net/http"
5+
6+ "github.com/bnema/gordon/internal/app"
7+ "github.com/labstack/echo/v4"
8+)
9+
10+// Handle GET on /api endpoint
11+
12+func GetHello(c echo.Context, a *app.App) error {
13+ return c.JSON(http.StatusOK, "Hello, World!")
14+}
M internal/httpserve/handler/oauth.go +2 -2
@@ -21,7 +21,7 @@ import (
2121 var urlToken string
2222
2323 // compareGordonToken compares the token from the URL query parameter with the one from the config.yml
24-func compareGordonToken(c echo.Context, a *app.App) error {
24+func CompareGordonToken(c echo.Context, a *app.App) error {
2525 configToken := a.Config.General.GordonToken
2626 if urlToken != configToken {
2727 // if token is not present or does not match the one from the config.yml
@@ -158,7 +158,7 @@ func handleUser(c echo.Context, a *app.App, accessToken, browserInfo string, use
158158
159159 if !userExists {
160160 // if it is a new user creation we compare the gordon token
161- err := compareGordonToken(c, a)
161+ err := CompareGordonToken(c, a)
162162 if err != nil {
163163 return nil, echo.NewHTTPError(http.StatusUnauthorized, "Token is empty or not valid")
164164 }
@@ -21,7 +21,7 @@ import (
21 var urlToken string21 var urlToken string
22 22
23 // compareGordonToken compares the token from the URL query parameter with the one from the config.yml23 // compareGordonToken compares the token from the URL query parameter with the one from the config.yml
24-func compareGordonToken(c echo.Context, a *app.App) error {24+func CompareGordonToken(c echo.Context, a *app.App) error {
25 configToken := a.Config.General.GordonToken25 configToken := a.Config.General.GordonToken
26 if urlToken != configToken {26 if urlToken != configToken {
27 // if token is not present or does not match the one from the config.yml27 // if token is not present or does not match the one from the config.yml
@@ -158,7 +158,7 @@ func handleUser(c echo.Context, a *app.App, accessToken, browserInfo string, use
158 158
159 if !userExists {159 if !userExists {
160 // if it is a new user creation we compare the gordon token160 // if it is a new user creation we compare the gordon token
161- err := compareGordonToken(c, a)161+ err := CompareGordonToken(c, a)
162 if err != nil {162 if err != nil {
163 return nil, echo.NewHTTPError(http.StatusUnauthorized, "Token is empty or not valid")163 return nil, echo.NewHTTPError(http.StatusUnauthorized, "Token is empty or not valid")
164 }164 }
M internal/httpserve/middleware/commondata.go +1 -1
@@ -8,7 +8,7 @@ import (
88 func SetCommonDataMiddleware(a *app.App) echo.MiddlewareFunc {
99 return func(next echo.HandlerFunc) echo.HandlerFunc {
1010 return func(c echo.Context) error {
11- // Will pass data to the renderer
11+ // TODO: set common data
1212 return next(c)
1313 }
1414 }
@@ -8,7 +8,7 @@ import (
8 func SetCommonDataMiddleware(a *app.App) echo.MiddlewareFunc {8 func SetCommonDataMiddleware(a *app.App) echo.MiddlewareFunc {
9 return func(next echo.HandlerFunc) echo.HandlerFunc {9 return func(next echo.HandlerFunc) echo.HandlerFunc {
10 return func(c echo.Context) error {10 return func(c echo.Context) error {
11- // Will pass data to the renderer11+ // TODO: set common data
12 return next(c)12 return next(c)
13 }13 }
14 }14 }
A internal/httpserve/middleware/requiretoken.go +40 -0
@@ -0,0 +1,40 @@
1+package middleware
2+
3+import (
4+ "fmt"
5+ "strings"
6+
7+ "github.com/bnema/gordon/internal/app"
8+ "github.com/labstack/echo/v4"
9+)
10+
11+// RequireToken is a middleware that checks for a valid token in the request
12+func RequireToken(a *app.App) echo.MiddlewareFunc {
13+ return func(next echo.HandlerFunc) echo.HandlerFunc {
14+ return func(c echo.Context) error {
15+ if err := validateToken(c, a); err != nil {
16+ return err
17+ }
18+ return next(c)
19+ }
20+ }
21+}
22+
23+func validateToken(c echo.Context, a *app.App) error {
24+ token := c.Request().Header.Get("Authorization")
25+ if token == "" {
26+ return echo.ErrUnauthorized
27+ }
28+ fmt.Println(token)
29+ token = strings.Replace(token, "Bearer ", "", 1)
30+ configToken, err := a.Config.GetToken()
31+ if err != nil {
32+ return fmt.Errorf("failed to get token from config: %w", err)
33+ }
34+
35+ if token != configToken {
36+ return echo.ErrUnauthorized
37+ }
38+
39+ return nil
40+}
@@ -0,0 +1,40 @@
1+package middleware
2+
3+import (
4+ "fmt"
5+ "strings"
6+
7+ "github.com/bnema/gordon/internal/app"
8+ "github.com/labstack/echo/v4"
9+)
10+
11+// RequireToken is a middleware that checks for a valid token in the request
12+func RequireToken(a *app.App) echo.MiddlewareFunc {
13+ return func(next echo.HandlerFunc) echo.HandlerFunc {
14+ return func(c echo.Context) error {
15+ if err := validateToken(c, a); err != nil {
16+ return err
17+ }
18+ return next(c)
19+ }
20+ }
21+}
22+
23+func validateToken(c echo.Context, a *app.App) error {
24+ token := c.Request().Header.Get("Authorization")
25+ if token == "" {
26+ return echo.ErrUnauthorized
27+ }
28+ fmt.Println(token)
29+ token = strings.Replace(token, "Bearer ", "", 1)
30+ configToken, err := a.Config.GetToken()
31+ if err != nil {
32+ return fmt.Errorf("failed to get token from config: %w", err)
33+ }
34+
35+ if token != configToken {
36+ return echo.ErrUnauthorized
37+ }
38+
39+ return nil
40+}
M internal/httpserve/router.go +12 -0
@@ -10,11 +10,14 @@ import (
1010
1111 // RegisterRoutes registers all routes for the application
1212 func RegisterRoutes(e *echo.Echo, a *app.App) *echo.Echo {
13+ // --- Register API endpoints --- //
14+ bindAPIEndpoints(e, a)
1315 AdminPath := a.Config.Admin.Path
1416 // SetCommonDataMiddleware will pass data to the renderer
1517 e.Use(middleware.SetCommonDataMiddleware(a))
1618 // Error handler middleware
1719 e.Use(middleware.ErrorHandler)
20+
1821 // Initiate the session middleware
1922 e.Use(middleware.InitSessionMiddleware())
2023 // Use middlewares
@@ -38,6 +41,15 @@ func RegisterRoutes(e *echo.Echo, a *app.App) *echo.Echo {
3841 return e
3942 }
4043
44+// bindAPIEndpoints expose /api endpoints and speaks only JSON
45+func bindAPIEndpoints(e *echo.Echo, a *app.App) {
46+ apiGroup := e.Group("/api")
47+ apiGroup.Use(middleware.RequireToken(a))
48+ apiGroup.GET("/hello", func(c echo.Context) error {
49+ return handler.GetHello(c, a)
50+ })
51+}
52+
4153 // bindStaticRoute bind static path
4254 func bindStaticRoute(e *echo.Echo, a *app.App, path string) {
4355 e.GET(path, func(c echo.Context) error {
@@ -10,11 +10,14 @@ import (
10 10
11 // RegisterRoutes registers all routes for the application11 // RegisterRoutes registers all routes for the application
12 func RegisterRoutes(e *echo.Echo, a *app.App) *echo.Echo {12 func RegisterRoutes(e *echo.Echo, a *app.App) *echo.Echo {
13+ // --- Register API endpoints --- //
14+ bindAPIEndpoints(e, a)
13 AdminPath := a.Config.Admin.Path15 AdminPath := a.Config.Admin.Path
14 // SetCommonDataMiddleware will pass data to the renderer16 // SetCommonDataMiddleware will pass data to the renderer
15 e.Use(middleware.SetCommonDataMiddleware(a))17 e.Use(middleware.SetCommonDataMiddleware(a))
16 // Error handler middleware18 // Error handler middleware
17 e.Use(middleware.ErrorHandler)19 e.Use(middleware.ErrorHandler)
20+
18 // Initiate the session middleware21 // Initiate the session middleware
19 e.Use(middleware.InitSessionMiddleware())22 e.Use(middleware.InitSessionMiddleware())
20 // Use middlewares23 // Use middlewares
@@ -38,6 +41,15 @@ func RegisterRoutes(e *echo.Echo, a *app.App) *echo.Echo {
38 return e41 return e
39 }42 }
40 43
44+// bindAPIEndpoints expose /api endpoints and speaks only JSON
45+func bindAPIEndpoints(e *echo.Echo, a *app.App) {
46+ apiGroup := e.Group("/api")
47+ apiGroup.Use(middleware.RequireToken(a))
48+ apiGroup.GET("/hello", func(c echo.Context) error {
49+ return handler.GetHello(c, a)
50+ })
51+}
52+
41 // bindStaticRoute bind static path53 // bindStaticRoute bind static path
42 func bindStaticRoute(e *echo.Echo, a *app.App, path string) {54 func bindStaticRoute(e *echo.Echo, a *app.App, path string) {
43 e.GET(path, func(c echo.Context) error {55 e.GET(path, func(c echo.Context) error {

Keyboard shortcuts

Single-key shortcuts are on. ctrl k and esc work whatever this says.

ctrl k :
go to a section, branch, tag, file or repository
?
this list
j k
move the row cursor down / up
enter
open the row under the cursor; on Files, a directory opens or closes in place
↑ ↓ ← → home end
in the file tree (focus on a row): previous / next row, close / open a directory or go to its parent / first entry, first / last row. They never open a file
l h
on Files: open a directory or a file / close a directory or go up to its parent, then to the parent page. Elsewhere: into the side panel (on a merge request, its files) / back to the content; there j k move between its controls
g g G
first / last row
/
filter the list; on Files, find a file anywhere in the repository (esc clears, enter opens the best match)
b
switch branch or tag
g s g f g c g b g m g i g r g a
tabs: summary, files, commits, branches & tags, merge requests, issues, releases, actions (when available)
g h
home
w
wide mode: use the whole width of the screen, or not (remembered)
y
copy a permalink pinned to the commit
d u x
diff: split / unified / collapse or expand all
e E
diff: the whole file in view / every file, with the lines the hunks hide
n
merge request: next note in the diff
esc
close a dialog, leave a field

Branches 7

Tags 106

↑↓ moveenter openesc close