detached 402d8ce

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 (
55 "fmt"
66 "io/fs"
77 "os"
8+ "time"
89
910 "github.com/bnema/gordon/internal/db"
1011 "github.com/bnema/gordon/pkg/docker"
@@ -27,6 +28,7 @@ type App struct {
2728 Config Config
2829 DB *sql.DB
2930 DBTables DBTables
31+ StartTime time.Time
3032 }
3133 type Config struct {
3234 General GeneralConfig `yaml:"General"`
@@ -172,3 +174,13 @@ func (config *Config) GetToken() (string, error) {
172174 return token, nil
173175
174176 }
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 Config28 Config Config
28 DB *sql.DB29 DB *sql.DB
29 DBTables DBTables30 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, nil174 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 (
44 "fmt"
55 "io"
66 "log"
7+ "time"
78
89 "github.com/bnema/gordon/internal/templating"
910 "github.com/bnema/gordon/internal/webui"
@@ -41,6 +42,7 @@ func NewServerApp() *App {
4142 DBDir: DBDir,
4243 DBFilename: DBFilename,
4344 Config: *config,
45+ StartTime: time.Now(),
4446 }
4547
4648 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 (
99 var rootCmd = &cobra.Command{Use: "gordon"}
1010
1111 func InitializeCommands(a *app.App) {
12- rootCmd.AddCommand(cmd.NewHelloCommand(a))
12+ // rootCmd.AddCommand(cmd.NewHelloCommand(a))
13+ rootCmd.AddCommand(cmd.NewPingCommand(a))
1314 }
1415
1516 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 (
44 "bytes"
55 "encoding/json"
66 "fmt"
7+ "io"
78 "net/http"
89
910 "github.com/bnema/gordon/internal/app"
@@ -38,33 +39,42 @@ func SendRequest(req *http.Request) (*http.Response, error) {
3839 return client.Do(req)
3940 }
4041
42+type Response struct {
43+ Body []byte
44+}
45+
4146 // 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) {
4348 apiUrl := GenerateAPIURL(a)
44- fmt.Println("Sending request to:", apiUrl)
4549
4650 token := a.Config.General.GordonToken
4751
48- jsonPayload, err := PrepareJSONPayload(payload)
52+ // Prepare the entire RequestPayload, not just the inner Payload
53+ jsonPayload, err := json.Marshal(rp)
4954 if err != nil {
50- fmt.Println("Error preparing JSON payload:", err)
51- return
55+ return nil, fmt.Errorf("failed to marshal payload: %w", err)
5256 }
5357
54- req, err := CreateNewRequest("POST", apiUrl, jsonPayload)
58+ // Create a new request
59+ req, err := CreateNewRequest("GET", apiUrl+endpoint, jsonPayload)
5560 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)
5862 }
5963
64+ // Set the request headers
6065 SetRequestHeaders(req, token)
6166
67+ // Send the request
6268 resp, err := SendRequest(req)
6369 if err != nil {
64- fmt.Println("Error sending HTTP request:", err)
65- return
70+ return nil, fmt.Errorf("failed to send request: %w", err)
6671 }
6772 defer resp.Body.Close()
6873
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
7080 }
@@ -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 request46 // 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.GordonToken50 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 @@
11 package common
22
33 import (
4- "errors"
4+ "encoding/json"
5+ "fmt"
56 )
67
7-// RequestPayload holds the type and payload of a client request
88 type RequestPayload struct {
99 Type string `json:"type"`
1010 Payload Payload `json:"payload"`
1111 }
1212
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
2013 type Payload interface {
2114 GetType() string
2215 }
2316
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 {
2646 Message string `json:"message"`
2747 }
2848
29-// GetType implementation for HelloWorldPayload
30-func (p HelloWorldPayload) GetType() string {
31- return "hello"
49+func (p PingPayload) GetType() string {
50+ return "ping"
3251 }
3352
34-// PushPayload for pushing container images
3553 type PushPayload struct {
36- Image string `json:"image"`
37- Tag string `json:"tag"`
54+ Data []byte `json:"data"`
3855 }
3956
40-// GetType implementation for PushPayload
4157 func (p PushPayload) GetType() string {
4258 return "push"
4359 }
4460
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")
5865 }
59-
60- return nil, nil
66+ return PingPayload{Message: message}, nil
6167 }
@@ -1,61 +1,67 @@
1 package common1 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() string14 GetType() string
22 }15 }
23 16
24-// HelloWorldPayload for testing17+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 HelloWorldPayload49+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 type61+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 @@
11 package handler
22
33 import (
4+ "bytes"
5+ "fmt"
6+ "io"
47 "net/http"
58
69 "github.com/bnema/gordon/internal/app"
10+ "github.com/bnema/gordon/internal/common"
711 "github.com/labstack/echo/v4"
812 )
913
10-// Handle GET on /api endpoint
11-
14+// Handle GET on /api/hello endpoint
1215 func GetHello(c echo.Context, a *app.App) error {
1316 return c.JSON(http.StatusOK, "Hello, World!")
1417 }
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 handler1 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 endpoint14+// 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) {
4848 apiGroup.GET("/hello", func(c echo.Context) error {
4949 return handler.GetHello(c, a)
5050 })
51+ apiGroup.GET("/ping", func(c echo.Context) error {
52+ return handler.GetInfos(c, a)
53+ })
5154 }
5255
5356 // 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 path56 // bindStaticRoute bind static path

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