From 2d27b1ea2dbc0e080fe60a76f917e9e76333df4e Mon Sep 17 00:00:00 2001 From: Victor Lacasse-Beaudoin Date: Tue, 24 Oct 2023 17:00:49 -0400 Subject: [PATCH] =?UTF-8?q?Migrer=20client=20API=20=C3=A0=20voki?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Autres changements: - Implémenter client voki à web_handlers - Retirer implémentations manuelles de api.Call et api.CallBytes - Fix web_handlers.HandlePublicDocumentation qui retournait le contenu du body même si le api request retournait StatusCode=http.StatusNotFound --- api/api.go | 212 +++-------------------------------- cmd/server.go | 7 +- go.mod | 1 + go.sum | 2 + web_handlers/web_handlers.go | 47 ++++---- 5 files changed, 44 insertions(+), 225 deletions(-) diff --git a/api/api.go b/api/api.go index 6ba2d36..d84be3c 100644 --- a/api/api.go +++ b/api/api.go @@ -3,147 +3,44 @@ package api import ( "bytes" "encoding/json" - "errors" "fmt" "io" "mime/multipart" "net/http" "net/url" + "codeberg.org/vlbeaudoin/voki" "git.agecem.com/agecem/agecem-org/config" "git.agecem.com/agecem/agecem-org/models" "github.com/spf13/viper" ) type API struct { - Protocol string - Host string - Port int - Opts APIOptions + Voki *voki.Voki } -type APIOptions struct { - KeyAuth bool - Key string - BasicAuth bool - Username string - Password string -} - -// NewApiClientFromViper returns a pointer to a new API object, +// NewFromViper returns a pointer to a new API object, // provided the configuration options are managed by // https://git.agecem.com/agecem/agecem-org/config -func NewApiClientFromViper() (*API, error) { +func NewFromViper(client *http.Client) (api *API, err error) { var config config.Config - - if err := viper.Unmarshal(&config); err != nil { + if err = viper.Unmarshal(&config); err != nil { return nil, err } - api, err := New(config.Server.Api.Protocol, config.Server.Api.Host, config.Server.Api.Port, APIOptions{ - KeyAuth: config.Server.Api.Auth, - Key: config.Server.Api.Key, - }) - if err != nil { - return api, err - } - - return api, nil + return New(client, config.Server.Api.Host, config.Server.Api.Key, config.Server.Api.Port, config.Server.Api.Protocol) } -func New(protocol, host string, port int, opts APIOptions) (*API, error) { - api := API{ - Protocol: protocol, - Host: host, - Port: port, - Opts: opts, - } - - return &api, nil -} - -// Call returns a []byte representing a response body. -// Can be used for GET or DELETE methods -func (a *API) Call(method, route string) ([]byte, error) { - endpoint := fmt.Sprintf("%s://%s:%d", - a.Protocol, - a.Host, - a.Port, - ) - prerequest := fmt.Sprintf("%s%s", endpoint, route) - request, err := url.QueryUnescape(prerequest) - if err != nil { - return nil, err - } - - switch method { - case http.MethodGet: - // Create client - client := &http.Client{} - - // Create request - request, err := http.NewRequest(http.MethodGet, request, nil) - if err != nil { - return nil, err - } - - if a.Opts.KeyAuth { - request.Header.Add("Authorization", fmt.Sprintf("Bearer %s", a.Opts.Key)) - } - - // Fetch Request - response, err := client.Do(request) - if err != nil { - return nil, err - } - - defer response.Body.Close() - - body, err := io.ReadAll(response.Body) - if err != nil { - return nil, err - } - - return body, nil - case http.MethodDelete: - // Create client - client := &http.Client{} - - // Create request - req, err := http.NewRequest(http.MethodDelete, request, nil) - if err != nil { - return nil, err - } - - if a.Opts.KeyAuth { - req.Header.Add("Authorization", fmt.Sprintf("Bearer %s", a.Opts.Key)) - } - - // Fetch Request - resp, err := client.Do(req) - if err != nil { - return nil, err - } - defer resp.Body.Close() - - // Read Response Body - respBody, err := io.ReadAll(resp.Body) - if err != nil { - return nil, err - } - return respBody, nil - } - - //return nil, errors.New(fmt.Sprintf("method must be 'GET' or 'DELETE', got '%s'", method)) - return nil, errors.New(fmt.Sprintf("method must be 'GET' or 'DELETE', got '%s'", method)) +func New(client *http.Client, host, key string, port int, protocol string) (*API, error) { + return &API{Voki: voki.New(client, host, key, port, protocol)}, nil } func (a *API) UploadDocument(bucket string, file_header *multipart.FileHeader) (models.V1DocumentCreateResponse, error) { var response models.V1DocumentCreateResponse endpoint := fmt.Sprintf("%s://%s:%d", - a.Protocol, - a.Host, - a.Port, + a.Voki.Protocol, + a.Voki.Host, + a.Voki.Port, ) current_url := fmt.Sprintf("%s/v1/bucket/%s", endpoint, bucket) @@ -187,8 +84,8 @@ func (a *API) UploadDocument(bucket string, file_header *multipart.FileHeader) ( req.Header.Set("Content-Type", writer.FormDataContentType()) - if a.Opts.KeyAuth { - req.Header.Set("Authorization", fmt.Sprintf("Bearer %s", a.Opts.Key)) + if a.Voki.Key != "" { + req.Header.Set("Authorization", fmt.Sprintf("Bearer %s", a.Voki.Key)) } // Send the HTTP request @@ -203,85 +100,6 @@ func (a *API) UploadDocument(bucket string, file_header *multipart.FileHeader) ( return response, err } -// CallWithData takes data and returns a string representing a response body. -// Can be used for POST or PUT methods -func (a *API) CallWithData(method, route string, data []byte) (string, error) { - endpoint := fmt.Sprintf("%s://%s:%d", - a.Protocol, - a.Host, - a.Port, - ) - request := fmt.Sprintf("%s%s", endpoint, route) - - switch method { - case http.MethodPost: - // initialize http client - client := &http.Client{} - - // set the HTTP method, url, and request body - req, err := http.NewRequest(http.MethodPost, request, bytes.NewBuffer(data)) - if err != nil { - return "", err - } - - if a.Opts.KeyAuth { - req.Header.Add("Authorization", fmt.Sprintf("Bearer %s", a.Opts.Key)) - } - - // set the request header Content-Type for json - req.Header.Set("Content-Type", "application/json; charset=utf-8") - resp, err := client.Do(req) - if err != nil { - return "", err - } - - var res map[string]interface{} - - json.NewDecoder(resp.Body).Decode(&res) - return fmt.Sprintf("%s\n", res["message"]), nil - /* - case http.MethodPut: - // initialize http client - client := &http.Client{} - - // set the HTTP method, url, and request body - req, err := http.NewRequest(http.MethodPut, request, bytes.NewBuffer(data)) - if err != nil { - return "", err - } - - if a.Opts.KeyAuth { - req.Header.Add("Authorization", fmt.Sprintf("Bearer %s", a.Opts.Key)) - } - - // set the request header Content-Type for json - //req.Header.Set("Content-Type", "application/json; charset=utf-8") - resp, err := client.Do(req) - if err != nil { - return "", err - } - - var res map[string]interface{} - - json.NewDecoder(resp.Body).Decode(&res) - return fmt.Sprintf("%s\n", res["message"]), nil - */ - } - - //return "", errors.New(fmt.Sprintf("method must be 'POST' or 'PUT', got '%s'", method)) - return "", errors.New(fmt.Sprintf("method must be 'POST', got '%s'", method)) -} - -func (a *API) ListBuckets() (models.V1BucketListResponse, error) { - var response models.V1BucketListResponse - result, err := a.Call(http.MethodGet, "/v1/bucket") - if err != nil { - return response, err - } - - if err = json.Unmarshal(result, &response); err != nil { - return response, err - } - - return response, nil +func (a *API) ListBuckets() (response models.V1BucketListResponse, err error) { + return response, a.Voki.Unmarshal(http.MethodGet, "/v1/bucket", nil, true, &response) } diff --git a/cmd/server.go b/cmd/server.go index bf8cdfe..47c61f0 100644 --- a/cmd/server.go +++ b/cmd/server.go @@ -227,9 +227,12 @@ func RunServer() { groupV1.DELETE("/bucket/:bucket/:document", v1Handler.HandleV1DocumentDelete) // HTML Routes - apiClient, err := api.NewApiClientFromViper() + client := http.DefaultClient + defer client.CloseIdleConnections() + + apiClient, err := api.NewFromViper(client) if err != nil { - log.Fatal("Error during NewMediaClientFromViper for API handlers") + log.Fatal(err) } webHandler := web_handlers.WebHandler{ diff --git a/go.mod b/go.mod index 22b4e73..8456838 100644 --- a/go.mod +++ b/go.mod @@ -4,6 +4,7 @@ go 1.21.1 require ( codeberg.org/vlbeaudoin/serpents v1.0.2 + codeberg.org/vlbeaudoin/voki v1.6.0 github.com/labstack/echo/v4 v4.10.0 github.com/minio/minio-go/v7 v7.0.52 github.com/spf13/cobra v1.6.1 diff --git a/go.sum b/go.sum index a515d70..76d5af0 100644 --- a/go.sum +++ b/go.sum @@ -37,6 +37,8 @@ cloud.google.com/go/storage v1.10.0/go.mod h1:FLPqc6j+Ki4BU591ie1oL6qBQGu2Bl/tZ9 cloud.google.com/go/storage v1.14.0/go.mod h1:GrKmX003DSIwi9o29oFT7YDnHYwZoctc3fOKtUw0Xmo= codeberg.org/vlbeaudoin/serpents v1.0.2 h1:mHuL+RBAMOGeiB5+ew1cRputEAnOIQNJW9o9a5Qjudo= codeberg.org/vlbeaudoin/serpents v1.0.2/go.mod h1:3bE/R0ToABwcUJtS1VcGEBa86K5FYhrZGAbFl2qL8kQ= +codeberg.org/vlbeaudoin/voki v1.6.0 h1:Mm4K+SK5VmeWKnzUvcAkUxi2zcFQvzGX650Zi/FA7Iw= +codeberg.org/vlbeaudoin/voki v1.6.0/go.mod h1:5XTLx/KiW/OfiupF3o7PAAAU/UhsPdKSrVMmtHbmkPI= dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= diff --git a/web_handlers/web_handlers.go b/web_handlers/web_handlers.go index 1dd96e9..6038ce9 100644 --- a/web_handlers/web_handlers.go +++ b/web_handlers/web_handlers.go @@ -1,8 +1,8 @@ package web_handlers import ( - "encoding/json" "fmt" + "io" "net/http" "sort" @@ -52,9 +52,9 @@ func (h *WebHandler) HandleDocumentation(c echo.Context) error { v1BucketListResponse, err := h.ApiClient.ListBuckets() if err != nil { - response.StatusCode = v1BucketListResponse.StatusCode - response.Message = v1BucketListResponse.Message response.Error = err.Error() + response.Message = v1BucketListResponse.Message + response.StatusCode = v1BucketListResponse.StatusCode return c.Render(response.StatusCode, "documentation-html", response) } @@ -63,24 +63,14 @@ func (h *WebHandler) HandleDocumentation(c echo.Context) error { for bucket, displayName := range v1BucketListResponse.Data.Buckets { // TODO move call to dedicated API client method - content, err := h.ApiClient.Call(http.MethodGet, fmt.Sprintf("/v1/bucket/%s", bucket)) - if err != nil { - response.StatusCode = http.StatusInternalServerError - response.Message = "Error during /v1/bucket/:bucket" - response.Error = err.Error() - - return c.Render(response.StatusCode, "documentation-html", response) - } - var v1BucketReadResponse models.V1BucketReadResponse - err = json.Unmarshal(content, &v1BucketReadResponse) - if err != nil { - response.StatusCode = http.StatusInternalServerError - response.Message = "Error during json.Unmarshal /v1/bucket/:bucket" + if err = h.ApiClient.Voki.Unmarshal(http.MethodGet, fmt.Sprintf("/v1/bucket/%s", bucket), nil, true, &v1BucketReadResponse); err != nil { response.Error = err.Error() + response.Message = "Error during json.Unmarshal /v1/bucket/:bucket" + response.StatusCode = http.StatusInternalServerError - return c.Render(response.StatusCode, "documentation-html", response) + return c.Render(http.StatusOK, "documentation-html", response) } response.Data.Buckets = append(response.Data.Buckets, models.Bucket{ @@ -96,7 +86,7 @@ func (h *WebHandler) HandleDocumentation(c echo.Context) error { //response.Message = "HandleDocumentation ok" // TODO render .Message - return c.Render(response.StatusCode, "documentation-html", response) + return c.Render(http.StatusOK, "documentation-html", response) //return c.Render(response.StatusCode, "documentation-html", response.Data.Buckets) } @@ -108,20 +98,25 @@ func (h *WebHandler) HandlePublicDocumentation(c echo.Context) error { bucket := c.Param("bucket") document := c.Param("document") - result, err := h.ApiClient.Call(http.MethodGet, fmt.Sprintf("/v1/bucket/%s/%s", bucket, document)) + response, err := h.ApiClient.Voki.Call(http.MethodGet, fmt.Sprintf("/v1/bucket/%s/%s", bucket, document), nil, true) if err != nil { return c.JSON(models.NotFoundResponse()) } + defer response.Body.Close() - // Check if result can fit inside a map containing a message - var result_map map[string]string - - err = json.Unmarshal(result, &result_map) - if err == nil { - return c.JSON(http.StatusBadRequest, result_map) + switch response.StatusCode { + case http.StatusNotFound: + return c.JSON(models.NotFoundResponse()) + case http.StatusInternalServerError: + return c.JSON(http.StatusInternalServerError, map[string]string{"message": "Internal Server Error"}) } - return c.Blob(http.StatusOK, "application/octet-stream", result) + body, err := io.ReadAll(response.Body) + if err != nil { + return c.JSON(http.StatusInternalServerError, map[string]string{"message": "Internal Server Error"}) + } + + return c.Blob(http.StatusOK, "application/octet-stream", body) } func HandleAdmin(c echo.Context) error { -- 2.45.2