Add Ping-Pong Functionality with Uptime and Version
- Implement ping command in CLI client - Add pong logic with uptime and version in backend - Refactor payload handling and HTTP request sending in CLI
- commit
- 402d8ce6af57d9eba9181a0b55e6a674054ebf05
- author
- bnema · · 2023-10-24 08:23 UTC
- parent
- 48a7c94
- signature
- unsigned
9 files · +166 -80
M internal/app/app.go +12 -0
| @@ -5,6 +5,7 @@ import ( | |||
| 5 | 5 | "fmt" | |
| 6 | 6 | "io/fs" | |
| 7 | 7 | "os" | |
| 8 | + | "time" | |
| 8 | 9 | ||
| 9 | 10 | "github.com/bnema/gordon/internal/db" | |
| 10 | 11 | "github.com/bnema/gordon/pkg/docker" | |
| @@ -27,6 +28,7 @@ type App struct { | |||
| 27 | 28 | Config Config | |
| 28 | 29 | DB *sql.DB | |
| 29 | 30 | DBTables DBTables | |
| 31 | + | StartTime time.Time | |
| 30 | 32 | } | |
| 31 | 33 | type Config struct { | |
| 32 | 34 | General GeneralConfig `yaml:"General"` | |
| @@ -172,3 +174,13 @@ func (config *Config) GetToken() (string, error) { | |||
| 172 | 174 | return token, nil | |
| 173 | 175 | ||
| 174 | 176 | } | |
| 177 | + | ||
| 178 | + | func (a *App) GetUptime() string { | |
| 179 | + | uptime := time.Since(a.StartTime) | |
| 180 | + | return uptime.String() | |
| 181 | + | } | |
| 182 | + | ||
| 183 | + | // GetVersion returns the version of the app | |
| 184 | + | func (config *Config) GetVersion() string { | |
| 185 | + | return config.General.BuildVersion | |
| 186 | + | } | |
| @@ -5,6 +5,7 @@ import ( | |||||
| 5 | "fmt" | 5 | "fmt" | ||
| 6 | "io/fs" | 6 | "io/fs" | ||
| 7 | "os" | 7 | "os" | ||
| 8 | + | "time" | |||
| 8 | 9 | ||||
| 9 | "github.com/bnema/gordon/internal/db" | 10 | "github.com/bnema/gordon/internal/db" | ||
| 10 | "github.com/bnema/gordon/pkg/docker" | 11 | "github.com/bnema/gordon/pkg/docker" | ||
| @@ -27,6 +28,7 @@ type App struct { | |||||
| 27 | Config Config | 28 | Config Config | ||
| 28 | DB *sql.DB | 29 | DB *sql.DB | ||
| 29 | DBTables DBTables | 30 | DBTables DBTables | ||
| 31 | + | StartTime time.Time | |||
| 30 | } | 32 | } | ||
| 31 | type Config struct { | 33 | type Config struct { | ||
| 32 | General GeneralConfig `yaml:"General"` | 34 | General GeneralConfig `yaml:"General"` | ||
| @@ -172,3 +174,13 @@ func (config *Config) GetToken() (string, error) { | |||||
| 172 | return token, nil | 174 | return token, nil | ||
| 173 | 175 | ||||
| 174 | } | 176 | } | ||
| 177 | + | ||||
| 178 | + | func (a *App) GetUptime() string { | |||
| 179 | + | uptime := time.Since(a.StartTime) | |||
| 180 | + | return uptime.String() | |||
| 181 | + | } | |||
| 182 | + | ||||
| 183 | + | // GetVersion returns the version of the app | |||
| 184 | + | func (config *Config) GetVersion() string { | |||
| 185 | + | return config.General.BuildVersion | |||
| 186 | + | } | |||
M internal/app/newserver.go +2 -0
| @@ -4,6 +4,7 @@ import ( | |||
| 4 | 4 | "fmt" | |
| 5 | 5 | "io" | |
| 6 | 6 | "log" | |
| 7 | + | "time" | |
| 7 | 8 | ||
| 8 | 9 | "github.com/bnema/gordon/internal/templating" | |
| 9 | 10 | "github.com/bnema/gordon/internal/webui" | |
| @@ -41,6 +42,7 @@ func NewServerApp() *App { | |||
| 41 | 42 | DBDir: DBDir, | |
| 42 | 43 | DBFilename: DBFilename, | |
| 43 | 44 | Config: *config, | |
| 45 | + | StartTime: time.Now(), | |
| 44 | 46 | } | |
| 45 | 47 | ||
| 46 | 48 | OauthCallbackURL = config.GenerateOauthCallbackURL() | |
| @@ -4,6 +4,7 @@ import ( | |||||
| 4 | "fmt" | 4 | "fmt" | ||
| 5 | "io" | 5 | "io" | ||
| 6 | "log" | 6 | "log" | ||
| 7 | + | "time" | |||
| 7 | 8 | ||||
| 8 | "github.com/bnema/gordon/internal/templating" | 9 | "github.com/bnema/gordon/internal/templating" | ||
| 9 | "github.com/bnema/gordon/internal/webui" | 10 | "github.com/bnema/gordon/internal/webui" | ||
| @@ -41,6 +42,7 @@ func NewServerApp() *App { | |||||
| 41 | DBDir: DBDir, | 42 | DBDir: DBDir, | ||
| 42 | DBFilename: DBFilename, | 43 | DBFilename: DBFilename, | ||
| 43 | Config: *config, | 44 | Config: *config, | ||
| 45 | + | StartTime: time.Now(), | |||
| 44 | } | 46 | } | ||
| 45 | 47 | ||||
| 46 | OauthCallbackURL = config.GenerateOauthCallbackURL() | 48 | OauthCallbackURL = config.GenerateOauthCallbackURL() | ||
M internal/cli/cli.go +2 -1
| @@ -9,7 +9,8 @@ import ( | |||
| 9 | 9 | var rootCmd = &cobra.Command{Use: "gordon"} | |
| 10 | 10 | ||
| 11 | 11 | func InitializeCommands(a *app.App) { | |
| 12 | - | rootCmd.AddCommand(cmd.NewHelloCommand(a)) | |
| 12 | + | // rootCmd.AddCommand(cmd.NewHelloCommand(a)) | |
| 13 | + | rootCmd.AddCommand(cmd.NewPingCommand(a)) | |
| 13 | 14 | } | |
| 14 | 15 | ||
| 15 | 16 | func Execute(a *app.App) { | |
| @@ -9,7 +9,8 @@ import ( | |||||
| 9 | var rootCmd = &cobra.Command{Use: "gordon"} | 9 | var rootCmd = &cobra.Command{Use: "gordon"} | ||
| 10 | 10 | ||||
| 11 | func InitializeCommands(a *app.App) { | 11 | func InitializeCommands(a *app.App) { | ||
| 12 | - | rootCmd.AddCommand(cmd.NewHelloCommand(a)) | 12 | + | // rootCmd.AddCommand(cmd.NewHelloCommand(a)) |
| 13 | + | rootCmd.AddCommand(cmd.NewPingCommand(a)) | |||
| 13 | } | 14 | } | ||
| 14 | 15 | ||||
| 15 | func Execute(a *app.App) { | 16 | func Execute(a *app.App) { | ||
D internal/cli/cmd/hello.go +0 -33
| @@ -1,33 +0,0 @@ | |||
| 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 | - | } | |
| @@ -1,33 +0,0 @@ | |||||
| 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 | - | } | |||
A internal/cli/cmd/ping.go +39 -0
| @@ -0,0 +1,39 @@ | |||
| 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 NewPingCommand(a *app.App) *cobra.Command { | |
| 13 | + | return &cobra.Command{ | |
| 14 | + | Use: "ping", | |
| 15 | + | Short: "Send a ping request to the backend", | |
| 16 | + | Run: func(cmd *cobra.Command, args []string) { | |
| 17 | + | data := map[string]interface{}{"message": "ping"} | |
| 18 | + | payload, err := common.NewPingPayload(data) | |
| 19 | + | if err != nil { | |
| 20 | + | fmt.Println("Error creating payload:", err) | |
| 21 | + | return | |
| 22 | + | } | |
| 23 | + | ||
| 24 | + | // Create a RequestPayload and populate it | |
| 25 | + | reqPayload := common.RequestPayload{ | |
| 26 | + | Type: "ping", | |
| 27 | + | Payload: payload, | |
| 28 | + | } | |
| 29 | + | ||
| 30 | + | resp, err := handler.SendHTTPRequest(a, &reqPayload, "/ping") | |
| 31 | + | if err != nil { | |
| 32 | + | fmt.Println("Error sending HTTP request:", err) | |
| 33 | + | return | |
| 34 | + | } | |
| 35 | + | ||
| 36 | + | fmt.Print(string(resp.Body)) | |
| 37 | + | }, | |
| 38 | + | } | |
| 39 | + | } | |
| @@ -0,0 +1,39 @@ | |||||
| 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 NewPingCommand(a *app.App) *cobra.Command { | |||
| 13 | + | return &cobra.Command{ | |||
| 14 | + | Use: "ping", | |||
| 15 | + | Short: "Send a ping request to the backend", | |||
| 16 | + | Run: func(cmd *cobra.Command, args []string) { | |||
| 17 | + | data := map[string]interface{}{"message": "ping"} | |||
| 18 | + | payload, err := common.NewPingPayload(data) | |||
| 19 | + | if err != nil { | |||
| 20 | + | fmt.Println("Error creating payload:", err) | |||
| 21 | + | return | |||
| 22 | + | } | |||
| 23 | + | ||||
| 24 | + | // Create a RequestPayload and populate it | |||
| 25 | + | reqPayload := common.RequestPayload{ | |||
| 26 | + | Type: "ping", | |||
| 27 | + | Payload: payload, | |||
| 28 | + | } | |||
| 29 | + | ||||
| 30 | + | resp, err := handler.SendHTTPRequest(a, &reqPayload, "/ping") | |||
| 31 | + | if err != nil { | |||
| 32 | + | fmt.Println("Error sending HTTP request:", err) | |||
| 33 | + | return | |||
| 34 | + | } | |||
| 35 | + | ||||
| 36 | + | fmt.Print(string(resp.Body)) | |||
| 37 | + | }, | |||
| 38 | + | } | |||
| 39 | + | } | |||
M internal/cli/handler/http.go +21 -11
| @@ -4,6 +4,7 @@ import ( | |||
| 4 | 4 | "bytes" | |
| 5 | 5 | "encoding/json" | |
| 6 | 6 | "fmt" | |
| 7 | + | "io" | |
| 7 | 8 | "net/http" | |
| 8 | 9 | ||
| 9 | 10 | "github.com/bnema/gordon/internal/app" | |
| @@ -38,33 +39,42 @@ func SendRequest(req *http.Request) (*http.Response, error) { | |||
| 38 | 39 | return client.Do(req) | |
| 39 | 40 | } | |
| 40 | 41 | ||
| 42 | + | type Response struct { | |
| 43 | + | Body []byte | |
| 44 | + | } | |
| 45 | + | ||
| 41 | 46 | // SendHTTPRequest sends the HTTP request | |
| 42 | - | func SendHTTPRequest(a *app.App, payload common.Payload) { | |
| 47 | + | func SendHTTPRequest(a *app.App, rp *common.RequestPayload, endpoint string) (*Response, error) { | |
| 43 | 48 | apiUrl := GenerateAPIURL(a) | |
| 44 | - | fmt.Println("Sending request to:", apiUrl) | |
| 45 | 49 | ||
| 46 | 50 | token := a.Config.General.GordonToken | |
| 47 | 51 | ||
| 48 | - | jsonPayload, err := PrepareJSONPayload(payload) | |
| 52 | + | // Prepare the entire RequestPayload, not just the inner Payload | |
| 53 | + | jsonPayload, err := json.Marshal(rp) | |
| 49 | 54 | if err != nil { | |
| 50 | - | fmt.Println("Error preparing JSON payload:", err) | |
| 51 | - | return | |
| 55 | + | return nil, fmt.Errorf("failed to marshal payload: %w", err) | |
| 52 | 56 | } | |
| 53 | 57 | ||
| 54 | - | req, err := CreateNewRequest("POST", apiUrl, jsonPayload) | |
| 58 | + | // Create a new request | |
| 59 | + | req, err := CreateNewRequest("GET", apiUrl+endpoint, jsonPayload) | |
| 55 | 60 | if err != nil { | |
| 56 | - | fmt.Println("Error creating new HTTP request:", err) | |
| 57 | - | return | |
| 61 | + | return nil, fmt.Errorf("failed to create new request: %w", err) | |
| 58 | 62 | } | |
| 59 | 63 | ||
| 64 | + | // Set the request headers | |
| 60 | 65 | SetRequestHeaders(req, token) | |
| 61 | 66 | ||
| 67 | + | // Send the request | |
| 62 | 68 | resp, err := SendRequest(req) | |
| 63 | 69 | if err != nil { | |
| 64 | - | fmt.Println("Error sending HTTP request:", err) | |
| 65 | - | return | |
| 70 | + | return nil, fmt.Errorf("failed to send request: %w", err) | |
| 66 | 71 | } | |
| 67 | 72 | defer resp.Body.Close() | |
| 68 | 73 | ||
| 69 | - | fmt.Println("Response Status:", resp.Status) | |
| 74 | + | // Read the response body | |
| 75 | + | body, err := io.ReadAll(resp.Body) | |
| 76 | + | if err != nil { | |
| 77 | + | return nil, fmt.Errorf("failed to read response body: %w", err) | |
| 78 | + | } | |
| 79 | + | return &Response{Body: body}, nil | |
| 70 | 80 | } | |
| @@ -4,6 +4,7 @@ import ( | |||||
| 4 | "bytes" | 4 | "bytes" | ||
| 5 | "encoding/json" | 5 | "encoding/json" | ||
| 6 | "fmt" | 6 | "fmt" | ||
| 7 | + | "io" | |||
| 7 | "net/http" | 8 | "net/http" | ||
| 8 | 9 | ||||
| 9 | "github.com/bnema/gordon/internal/app" | 10 | "github.com/bnema/gordon/internal/app" | ||
| @@ -38,33 +39,42 @@ func SendRequest(req *http.Request) (*http.Response, error) { | |||||
| 38 | return client.Do(req) | 39 | return client.Do(req) | ||
| 39 | } | 40 | } | ||
| 40 | 41 | ||||
| 42 | + | type Response struct { | |||
| 43 | + | Body []byte | |||
| 44 | + | } | |||
| 45 | + | ||||
| 41 | // SendHTTPRequest sends the HTTP request | 46 | // SendHTTPRequest sends the HTTP request | ||
| 42 | - | func SendHTTPRequest(a *app.App, payload common.Payload) { | 47 | + | func SendHTTPRequest(a *app.App, rp *common.RequestPayload, endpoint string) (*Response, error) { |
| 43 | apiUrl := GenerateAPIURL(a) | 48 | apiUrl := GenerateAPIURL(a) | ||
| 44 | - | fmt.Println("Sending request to:", apiUrl) | |||
| 45 | 49 | ||||
| 46 | token := a.Config.General.GordonToken | 50 | token := a.Config.General.GordonToken | ||
| 47 | 51 | ||||
| 48 | - | jsonPayload, err := PrepareJSONPayload(payload) | 52 | + | // Prepare the entire RequestPayload, not just the inner Payload |
| 53 | + | jsonPayload, err := json.Marshal(rp) | |||
| 49 | if err != nil { | 54 | if err != nil { | ||
| 50 | - | fmt.Println("Error preparing JSON payload:", err) | 55 | + | return nil, fmt.Errorf("failed to marshal payload: %w", err) |
| 51 | - | return | |||
| 52 | } | 56 | } | ||
| 53 | 57 | ||||
| 54 | - | req, err := CreateNewRequest("POST", apiUrl, jsonPayload) | 58 | + | // Create a new request |
| 59 | + | req, err := CreateNewRequest("GET", apiUrl+endpoint, jsonPayload) | |||
| 55 | if err != nil { | 60 | if err != nil { | ||
| 56 | - | fmt.Println("Error creating new HTTP request:", err) | 61 | + | return nil, fmt.Errorf("failed to create new request: %w", err) |
| 57 | - | return | |||
| 58 | } | 62 | } | ||
| 59 | 63 | ||||
| 64 | + | // Set the request headers | |||
| 60 | SetRequestHeaders(req, token) | 65 | SetRequestHeaders(req, token) | ||
| 61 | 66 | ||||
| 67 | + | // Send the request | |||
| 62 | resp, err := SendRequest(req) | 68 | resp, err := SendRequest(req) | ||
| 63 | if err != nil { | 69 | if err != nil { | ||
| 64 | - | fmt.Println("Error sending HTTP request:", err) | 70 | + | return nil, fmt.Errorf("failed to send request: %w", err) |
| 65 | - | return | |||
| 66 | } | 71 | } | ||
| 67 | defer resp.Body.Close() | 72 | defer resp.Body.Close() | ||
| 68 | 73 | ||||
| 69 | - | fmt.Println("Response Status:", resp.Status) | 74 | + | // Read the response body |
| 75 | + | body, err := io.ReadAll(resp.Body) | |||
| 76 | + | if err != nil { | |||
| 77 | + | return nil, fmt.Errorf("failed to read response body: %w", err) | |||
| 78 | + | } | |||
| 79 | + | return &Response{Body: body}, nil | |||
| 70 | } | 80 | } | ||
M internal/common/payload.go +39 -33
| @@ -1,61 +1,67 @@ | |||
| 1 | 1 | package common | |
| 2 | 2 | ||
| 3 | 3 | import ( | |
| 4 | - | "errors" | |
| 4 | + | "encoding/json" | |
| 5 | + | "fmt" | |
| 5 | 6 | ) | |
| 6 | 7 | ||
| 7 | - | // RequestPayload holds the type and payload of a client request | |
| 8 | 8 | type RequestPayload struct { | |
| 9 | 9 | Type string `json:"type"` | |
| 10 | 10 | Payload Payload `json:"payload"` | |
| 11 | 11 | } | |
| 12 | 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 | 13 | type Payload interface { | |
| 21 | 14 | GetType() string | |
| 22 | 15 | } | |
| 23 | 16 | ||
| 24 | - | // HelloWorldPayload for testing | |
| 25 | - | type HelloWorldPayload struct { | |
| 17 | + | func (p *RequestPayload) UnmarshalJSON(data []byte) error { | |
| 18 | + | var raw map[string]json.RawMessage | |
| 19 | + | err := json.Unmarshal(data, &raw) | |
| 20 | + | if err != nil { | |
| 21 | + | return err | |
| 22 | + | } | |
| 23 | + | ||
| 24 | + | if err := json.Unmarshal(raw["type"], &p.Type); err != nil { | |
| 25 | + | return err | |
| 26 | + | } | |
| 27 | + | ||
| 28 | + | switch p.Type { | |
| 29 | + | case "ping": | |
| 30 | + | var payload PingPayload | |
| 31 | + | if err := json.Unmarshal(raw["payload"], &payload); err != nil { | |
| 32 | + | return err | |
| 33 | + | } | |
| 34 | + | p.Payload = payload | |
| 35 | + | case "push": | |
| 36 | + | var payload PushPayload | |
| 37 | + | if err := json.Unmarshal(raw["payload"], &payload); err != nil { | |
| 38 | + | return err | |
| 39 | + | } | |
| 40 | + | p.Payload = payload | |
| 41 | + | } | |
| 42 | + | return nil | |
| 43 | + | } | |
| 44 | + | ||
| 45 | + | type PingPayload struct { | |
| 26 | 46 | Message string `json:"message"` | |
| 27 | 47 | } | |
| 28 | 48 | ||
| 29 | - | // GetType implementation for HelloWorldPayload | |
| 30 | - | func (p HelloWorldPayload) GetType() string { | |
| 31 | - | return "hello" | |
| 49 | + | func (p PingPayload) GetType() string { | |
| 50 | + | return "ping" | |
| 32 | 51 | } | |
| 33 | 52 | ||
| 34 | - | // PushPayload for pushing container images | |
| 35 | 53 | type PushPayload struct { | |
| 36 | - | Image string `json:"image"` | |
| 37 | - | Tag string `json:"tag"` | |
| 54 | + | Data []byte `json:"data"` | |
| 38 | 55 | } | |
| 39 | 56 | ||
| 40 | - | // GetType implementation for PushPayload | |
| 41 | 57 | func (p PushPayload) GetType() string { | |
| 42 | 58 | return "push" | |
| 43 | 59 | } | |
| 44 | 60 | ||
| 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") | |
| 61 | + | func NewPingPayload(data map[string]interface{}) (PingPayload, error) { | |
| 62 | + | message, ok := data["message"].(string) | |
| 63 | + | if !ok { | |
| 64 | + | return PingPayload{}, fmt.Errorf("invalid data for message") | |
| 58 | 65 | } | |
| 59 | - | ||
| 60 | - | return nil, nil | |
| 66 | + | return PingPayload{Message: message}, nil | |
| 61 | 67 | } | |
| @@ -1,61 +1,67 @@ | |||||
| 1 | package common | 1 | package common | ||
| 2 | 2 | ||||
| 3 | import ( | 3 | import ( | ||
| 4 | - | "errors" | 4 | + | "encoding/json" |
| 5 | + | "fmt" | |||
| 5 | ) | 6 | ) | ||
| 6 | 7 | ||||
| 7 | - | // RequestPayload holds the type and payload of a client request | |||
| 8 | type RequestPayload struct { | 8 | type RequestPayload struct { | ||
| 9 | Type string `json:"type"` | 9 | Type string `json:"type"` | ||
| 10 | Payload Payload `json:"payload"` | 10 | Payload Payload `json:"payload"` | ||
| 11 | } | 11 | } | ||
| 12 | 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 { | 13 | type Payload interface { | ||
| 21 | GetType() string | 14 | GetType() string | ||
| 22 | } | 15 | } | ||
| 23 | 16 | ||||
| 24 | - | // HelloWorldPayload for testing | 17 | + | func (p *RequestPayload) UnmarshalJSON(data []byte) error { |
| 25 | - | type HelloWorldPayload struct { | 18 | + | var raw map[string]json.RawMessage |
| 19 | + | err := json.Unmarshal(data, &raw) | |||
| 20 | + | if err != nil { | |||
| 21 | + | return err | |||
| 22 | + | } | |||
| 23 | + | ||||
| 24 | + | if err := json.Unmarshal(raw["type"], &p.Type); err != nil { | |||
| 25 | + | return err | |||
| 26 | + | } | |||
| 27 | + | ||||
| 28 | + | switch p.Type { | |||
| 29 | + | case "ping": | |||
| 30 | + | var payload PingPayload | |||
| 31 | + | if err := json.Unmarshal(raw["payload"], &payload); err != nil { | |||
| 32 | + | return err | |||
| 33 | + | } | |||
| 34 | + | p.Payload = payload | |||
| 35 | + | case "push": | |||
| 36 | + | var payload PushPayload | |||
| 37 | + | if err := json.Unmarshal(raw["payload"], &payload); err != nil { | |||
| 38 | + | return err | |||
| 39 | + | } | |||
| 40 | + | p.Payload = payload | |||
| 41 | + | } | |||
| 42 | + | return nil | |||
| 43 | + | } | |||
| 44 | + | ||||
| 45 | + | type PingPayload struct { | |||
| 26 | Message string `json:"message"` | 46 | Message string `json:"message"` | ||
| 27 | } | 47 | } | ||
| 28 | 48 | ||||
| 29 | - | // GetType implementation for HelloWorldPayload | 49 | + | func (p PingPayload) GetType() string { |
| 30 | - | func (p HelloWorldPayload) GetType() string { | 50 | + | return "ping" |
| 31 | - | return "hello" | |||
| 32 | } | 51 | } | ||
| 33 | 52 | ||||
| 34 | - | // PushPayload for pushing container images | |||
| 35 | type PushPayload struct { | 53 | type PushPayload struct { | ||
| 36 | - | Image string `json:"image"` | 54 | + | Data []byte `json:"data"` |
| 37 | - | Tag string `json:"tag"` | |||
| 38 | } | 55 | } | ||
| 39 | 56 | ||||
| 40 | - | // GetType implementation for PushPayload | |||
| 41 | func (p PushPayload) GetType() string { | 57 | func (p PushPayload) GetType() string { | ||
| 42 | return "push" | 58 | return "push" | ||
| 43 | } | 59 | } | ||
| 44 | 60 | ||||
| 45 | - | // CreatePayload is a factory function to create a Payload based on the type | 61 | + | func NewPingPayload(data map[string]interface{}) (PingPayload, error) { |
| 46 | - | func CreatePayload(payloadType string, data map[string]interface{}) (Payload, error) { | 62 | + | message, ok := data["message"].(string) |
| 47 | - | switch payloadType { | 63 | + | if !ok { |
| 48 | - | case "hello": | 64 | + | return PingPayload{}, fmt.Errorf("invalid data for message") |
| 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 | } | 65 | } | ||
| 59 | - | 66 | + | return PingPayload{Message: message}, nil | |
| 60 | - | return nil, nil | |||
| 61 | } | 67 | } | ||
M internal/httpserve/handler/endpoints.go +48 -2
| @@ -1,14 +1,60 @@ | |||
| 1 | 1 | package handler | |
| 2 | 2 | ||
| 3 | 3 | import ( | |
| 4 | + | "bytes" | |
| 5 | + | "fmt" | |
| 6 | + | "io" | |
| 4 | 7 | "net/http" | |
| 5 | 8 | ||
| 6 | 9 | "github.com/bnema/gordon/internal/app" | |
| 10 | + | "github.com/bnema/gordon/internal/common" | |
| 7 | 11 | "github.com/labstack/echo/v4" | |
| 8 | 12 | ) | |
| 9 | 13 | ||
| 10 | - | // Handle GET on /api endpoint | |
| 11 | - | ||
| 14 | + | // Handle GET on /api/hello endpoint | |
| 12 | 15 | func GetHello(c echo.Context, a *app.App) error { | |
| 13 | 16 | return c.JSON(http.StatusOK, "Hello, World!") | |
| 14 | 17 | } | |
| 18 | + | ||
| 19 | + | type InfoResponse struct { | |
| 20 | + | Uptime string `json:"uptime"` | |
| 21 | + | Version string `json:"version"` | |
| 22 | + | } | |
| 23 | + | ||
| 24 | + | func (info *InfoResponse) Populate(a *app.App) { | |
| 25 | + | info.Uptime = a.GetUptime() | |
| 26 | + | info.Version = a.Config.GetVersion() | |
| 27 | + | } | |
| 28 | + | ||
| 29 | + | // Handle GET on /api/ping endpoint | |
| 30 | + | func GetInfos(c echo.Context, a *app.App) error { | |
| 31 | + | fmt.Println("GET /api/ping") | |
| 32 | + | body, _ := io.ReadAll(c.Request().Body) | |
| 33 | + | fmt.Println("Request Body:", string(body)) | |
| 34 | + | c.Request().Body = io.NopCloser(bytes.NewBuffer(body)) // Reset the body | |
| 35 | + | ||
| 36 | + | payload := new(common.RequestPayload) | |
| 37 | + | if err := c.Bind(payload); err != nil { | |
| 38 | + | fmt.Println("Bind Error:", err) | |
| 39 | + | return c.JSON(http.StatusBadRequest, err.Error()) | |
| 40 | + | } | |
| 41 | + | ||
| 42 | + | if payload.Type != "ping" { | |
| 43 | + | return c.JSON(http.StatusBadRequest, "Invalid payload type") | |
| 44 | + | } | |
| 45 | + | ||
| 46 | + | pingPayload, ok := payload.Payload.(common.PingPayload) | |
| 47 | + | if !ok { | |
| 48 | + | return c.JSON(http.StatusBadRequest, "Invalid payload type") | |
| 49 | + | } | |
| 50 | + | if pingPayload.Message != "ping" { | |
| 51 | + | return c.JSON(http.StatusBadRequest, "Invalid payload data") | |
| 52 | + | } | |
| 53 | + | ||
| 54 | + | // Prepare and populate the information | |
| 55 | + | info := &InfoResponse{} | |
| 56 | + | info.Populate(a) | |
| 57 | + | ||
| 58 | + | return c.JSON(http.StatusOK, info) | |
| 59 | + | ||
| 60 | + | } | |
| @@ -1,14 +1,60 @@ | |||||
| 1 | package handler | 1 | package handler | ||
| 2 | 2 | ||||
| 3 | import ( | 3 | import ( | ||
| 4 | + | "bytes" | |||
| 5 | + | "fmt" | |||
| 6 | + | "io" | |||
| 4 | "net/http" | 7 | "net/http" | ||
| 5 | 8 | ||||
| 6 | "github.com/bnema/gordon/internal/app" | 9 | "github.com/bnema/gordon/internal/app" | ||
| 10 | + | "github.com/bnema/gordon/internal/common" | |||
| 7 | "github.com/labstack/echo/v4" | 11 | "github.com/labstack/echo/v4" | ||
| 8 | ) | 12 | ) | ||
| 9 | 13 | ||||
| 10 | - | // Handle GET on /api endpoint | 14 | + | // Handle GET on /api/hello endpoint |
| 11 | - | ||||
| 12 | func GetHello(c echo.Context, a *app.App) error { | 15 | func GetHello(c echo.Context, a *app.App) error { | ||
| 13 | return c.JSON(http.StatusOK, "Hello, World!") | 16 | return c.JSON(http.StatusOK, "Hello, World!") | ||
| 14 | } | 17 | } | ||
| 18 | + | ||||
| 19 | + | type InfoResponse struct { | |||
| 20 | + | Uptime string `json:"uptime"` | |||
| 21 | + | Version string `json:"version"` | |||
| 22 | + | } | |||
| 23 | + | ||||
| 24 | + | func (info *InfoResponse) Populate(a *app.App) { | |||
| 25 | + | info.Uptime = a.GetUptime() | |||
| 26 | + | info.Version = a.Config.GetVersion() | |||
| 27 | + | } | |||
| 28 | + | ||||
| 29 | + | // Handle GET on /api/ping endpoint | |||
| 30 | + | func GetInfos(c echo.Context, a *app.App) error { | |||
| 31 | + | fmt.Println("GET /api/ping") | |||
| 32 | + | body, _ := io.ReadAll(c.Request().Body) | |||
| 33 | + | fmt.Println("Request Body:", string(body)) | |||
| 34 | + | c.Request().Body = io.NopCloser(bytes.NewBuffer(body)) // Reset the body | |||
| 35 | + | ||||
| 36 | + | payload := new(common.RequestPayload) | |||
| 37 | + | if err := c.Bind(payload); err != nil { | |||
| 38 | + | fmt.Println("Bind Error:", err) | |||
| 39 | + | return c.JSON(http.StatusBadRequest, err.Error()) | |||
| 40 | + | } | |||
| 41 | + | ||||
| 42 | + | if payload.Type != "ping" { | |||
| 43 | + | return c.JSON(http.StatusBadRequest, "Invalid payload type") | |||
| 44 | + | } | |||
| 45 | + | ||||
| 46 | + | pingPayload, ok := payload.Payload.(common.PingPayload) | |||
| 47 | + | if !ok { | |||
| 48 | + | return c.JSON(http.StatusBadRequest, "Invalid payload type") | |||
| 49 | + | } | |||
| 50 | + | if pingPayload.Message != "ping" { | |||
| 51 | + | return c.JSON(http.StatusBadRequest, "Invalid payload data") | |||
| 52 | + | } | |||
| 53 | + | ||||
| 54 | + | // Prepare and populate the information | |||
| 55 | + | info := &InfoResponse{} | |||
| 56 | + | info.Populate(a) | |||
| 57 | + | ||||
| 58 | + | return c.JSON(http.StatusOK, info) | |||
| 59 | + | ||||
| 60 | + | } | |||
M internal/httpserve/router.go +3 -0
| @@ -48,6 +48,9 @@ func bindAPIEndpoints(e *echo.Echo, a *app.App) { | |||
| 48 | 48 | apiGroup.GET("/hello", func(c echo.Context) error { | |
| 49 | 49 | return handler.GetHello(c, a) | |
| 50 | 50 | }) | |
| 51 | + | apiGroup.GET("/ping", func(c echo.Context) error { | |
| 52 | + | return handler.GetInfos(c, a) | |
| 53 | + | }) | |
| 51 | 54 | } | |
| 52 | 55 | ||
| 53 | 56 | // bindStaticRoute bind static path | |
| @@ -48,6 +48,9 @@ func bindAPIEndpoints(e *echo.Echo, a *app.App) { | |||||
| 48 | apiGroup.GET("/hello", func(c echo.Context) error { | 48 | apiGroup.GET("/hello", func(c echo.Context) error { | ||
| 49 | return handler.GetHello(c, a) | 49 | return handler.GetHello(c, a) | ||
| 50 | }) | 50 | }) | ||
| 51 | + | apiGroup.GET("/ping", func(c echo.Context) error { | |||
| 52 | + | return handler.GetInfos(c, a) | |||
| 53 | + | }) | |||
| 51 | } | 54 | } | ||
| 52 | 55 | ||||
| 53 | // bindStaticRoute bind static path | 56 | // bindStaticRoute bind static path | ||