From 1f2ba0576a16fd84c0191643092025ce17131102 Mon Sep 17 00:00:00 2001 From: Victor Lacasse-Beaudoin Date: Wed, 3 Jul 2024 17:34:18 -0400 Subject: [PATCH] feature: permettre insert par csv MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Ajouter parameter cfg à addRoutes() Fix empty et default limit sur get requests (set default limit à 1000 hardcoded, todo move to config) --- cmd.go | 2 +- go.mod | 1 + go.sum | 2 + routes.go | 120 +++++++++++++++++++++++++++++++++++++++++++++--------- 4 files changed, 104 insertions(+), 21 deletions(-) diff --git a/cmd.go b/cmd.go index 591bdc8..8c5571d 100644 --- a/cmd.go +++ b/cmd.go @@ -92,7 +92,7 @@ var apiCmd = &cobra.Command{ } // Routes - if err := addRoutes(e, db); err != nil { + if err := addRoutes(e, db, cfg); err != nil { log.Fatal("add routes:", err) } /* diff --git a/go.mod b/go.mod index a573bf5..2c52fee 100644 --- a/go.mod +++ b/go.mod @@ -5,6 +5,7 @@ go 1.22.0 require ( codeberg.org/vlbeaudoin/pave/v2 v2.0.0 codeberg.org/vlbeaudoin/voki/v3 v3.0.0 + github.com/gocarina/gocsv v0.0.0-20240520201108-78e41c74b4b1 github.com/jackc/pgx/v5 v5.6.0 github.com/labstack/echo/v4 v4.12.0 github.com/spf13/cobra v1.8.1 diff --git a/go.sum b/go.sum index 7d7ac8f..e1ad5b4 100644 --- a/go.sum +++ b/go.sum @@ -11,6 +11,8 @@ github.com/frankban/quicktest v1.14.6 h1:7Xjx+VpznH+oBnejlPUj8oUpdxnVs4f8XU8WnHk github.com/frankban/quicktest v1.14.6/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0= github.com/fsnotify/fsnotify v1.7.0 h1:8JEhPFa5W2WU7YfeZzPNqzMP6Lwt7L2715Ggo0nosvA= github.com/fsnotify/fsnotify v1.7.0/go.mod h1:40Bi/Hjc2AVfZrqy+aj+yEI+/bRxZnMJyTJwOpGvigM= +github.com/gocarina/gocsv v0.0.0-20240520201108-78e41c74b4b1 h1:FWNFq4fM1wPfcK40yHE5UO3RUdSNPaBC+j3PokzA6OQ= +github.com/gocarina/gocsv v0.0.0-20240520201108-78e41c74b4b1/go.mod h1:5YoVOkjYAQumqlV356Hj3xeYh4BdZuLE0/nRkf2NKkI= github.com/golang-jwt/jwt v3.2.2+incompatible h1:IfV12K8xAKAnZqdXVzCZ+TOjboZ2keLg81eXfW3O+oY= github.com/golang-jwt/jwt v3.2.2+incompatible/go.mod h1:8pz2t5EyA70fFQQSrl6XZXzqecmYZeUEB8OUGHkxJ+I= github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= diff --git a/routes.go b/routes.go index a5aaed6..f7eb61c 100644 --- a/routes.go +++ b/routes.go @@ -1,16 +1,19 @@ package main import ( + "encoding/csv" "fmt" + "io" "net/http" "strconv" "codeberg.org/vlbeaudoin/pave/v2" "codeberg.org/vlbeaudoin/voki/v3" + "github.com/gocarina/gocsv" "github.com/labstack/echo/v4" ) -func addRoutes(e *echo.Echo, db *PostgresClient) error { +func addRoutes(e *echo.Echo, db *PostgresClient, cfg Config) error { _ = db apiPath := "/api/v7" @@ -53,9 +56,37 @@ func addRoutes(e *echo.Echo, db *PostgresClient) error { "ProgrammesPOST", func(c echo.Context) error { var request, response = ProgrammesPOSTRequest{}, ProgrammesPOSTResponse{} - if err := c.Bind(&request.Data); err != nil { + switch contentType := c.Request().Header.Get("Content-Type"); contentType { + case "application/json": + if err := c.Bind(&request.Data); err != nil { + var response voki.ResponseBadRequest + response.Message = fmt.Sprintf("parse request body: %s", err) + return c.JSON(response.StatusCode(), response) + } + case "text/csv": + body := c.Request().Body + if body == nil { + var response voki.ResponseBadRequest + response.Message = "empty request body cannot be parsed" + return c.JSON(response.StatusCode(), response) + } + defer body.Close() + + gocsv.SetCSVReader(func(in io.Reader) gocsv.CSVReader { + r := csv.NewReader(in) + r.Comma = ';' + return r // Allows use ; as delimiter + }) + + // Parse CSV data using gocsv + if err := gocsv.Unmarshal(body, &request.Data.Programmes); err != nil { + var response voki.ResponseBadRequest + response.Message = fmt.Sprintf("parse programmes from csv: %s", err) + return c.JSON(response.StatusCode(), response) + } + default: var response voki.ResponseBadRequest - response.Message = fmt.Sprintf("parse request body: %s", err) + response.Message = fmt.Sprintf("cannot parse body with content-type: %s", contentType) return c.JSON(response.StatusCode(), response) } @@ -96,9 +127,37 @@ func addRoutes(e *echo.Echo, db *PostgresClient) error { "MembresPOST", func(c echo.Context) error { var request, response = MembresPOSTRequest{}, MembresPOSTResponse{} - if err := c.Bind(&request.Data); err != nil { + switch contentType := c.Request().Header.Get("Content-Type"); contentType { + case "application/json": + if err := c.Bind(&request.Data); err != nil { + var response voki.ResponseBadRequest + response.Message = fmt.Sprintf("parse request body: %s", err) + return c.JSON(response.StatusCode(), response) + } + case "text/csv": + body := c.Request().Body + if body == nil { + var response voki.ResponseBadRequest + response.Message = "empty request body cannot be parsed" + return c.JSON(response.StatusCode(), response) + } + defer body.Close() + + gocsv.SetCSVReader(func(in io.Reader) gocsv.CSVReader { + r := csv.NewReader(in) + r.Comma = ';' + return r // Allows use ; as delimiter + }) + + // Parse CSV data using gocsv + if err := gocsv.Unmarshal(body, &request.Data.Membres); err != nil { + var response voki.ResponseBadRequest + response.Message = fmt.Sprintf("parse membres from csv: %s", err) + return c.JSON(response.StatusCode(), response) + } + default: var response voki.ResponseBadRequest - response.Message = fmt.Sprintf("parse request body: %s", err) + response.Message = fmt.Sprintf("cannot parse body with content-type: %s", contentType) return c.JSON(response.StatusCode(), response) } @@ -178,11 +237,18 @@ func addRoutes(e *echo.Echo, db *PostgresClient) error { "MembresGET", func(c echo.Context) (err error) { var request, response = MembresGETRequest{}, MembresGETResponse{} - request.Query.Limit, err = strconv.Atoi(c.QueryParam("limit")) - if err != nil { - var response voki.ResponseBadRequest - response.Message = fmt.Sprintf("parsing limit: %s", err) - return c.JSON(response.StatusCode(), response) + queryLimit := c.QueryParam("limit") + if queryLimit != "" { + request.Query.Limit, err = strconv.Atoi(queryLimit) + if err != nil { + var response voki.ResponseBadRequest + response.Message = fmt.Sprintf("parsing limit: %s", err) + return c.JSON(response.StatusCode(), response) + } + + } else { + //TODO cfg.API.DefaultLimit + request.Query.Limit = 1000 } if !request.Complete() { @@ -221,11 +287,18 @@ func addRoutes(e *echo.Echo, db *PostgresClient) error { "ProgrammesGET", func(c echo.Context) (err error) { var request, response = ProgrammesGETRequest{}, ProgrammesGETResponse{} - request.Query.Limit, err = strconv.Atoi(c.QueryParam("limit")) - if err != nil { - var response voki.ResponseBadRequest - response.Message = fmt.Sprintf("parsing limit: %s", err) - return c.JSON(response.StatusCode(), response) + queryLimit := c.QueryParam("limit") + if queryLimit != "" { + request.Query.Limit, err = strconv.Atoi(queryLimit) + if err != nil { + var response voki.ResponseBadRequest + response.Message = fmt.Sprintf("parsing limit: %s", err) + return c.JSON(response.StatusCode(), response) + } + + } else { + //TODO cfg.API.DefaultLimit + request.Query.Limit = 1000 } if !request.Complete() { @@ -307,11 +380,18 @@ func addRoutes(e *echo.Echo, db *PostgresClient) error { "MembresDisplayGET", func(c echo.Context) (err error) { var request, response = MembresDisplayGETRequest{}, MembresDisplayGETResponse{} - request.Query.Limit, err = strconv.Atoi(c.QueryParam("limit")) - if err != nil { - var response voki.ResponseBadRequest - response.Message = fmt.Sprintf("parsing limit: %s", err) - return c.JSON(response.StatusCode(), response) + queryLimit := c.QueryParam("limit") + if queryLimit != "" { + request.Query.Limit, err = strconv.Atoi(queryLimit) + if err != nil { + var response voki.ResponseBadRequest + response.Message = fmt.Sprintf("parsing limit: %s", err) + return c.JSON(response.StatusCode(), response) + } + + } else { + //TODO cfg.API.DefaultLimit + request.Query.Limit = 1000 } if !request.Complete() {