From 7fa4db7ab9eb66a9e316ad26c781f57c85e63c83 Mon Sep 17 00:00:00 2001 From: Victor Lacasse-Beaudoin Date: Tue, 6 Jun 2023 02:22:57 -0400 Subject: [PATCH] Ajouter transactions MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Ajouter POST /v2/transactions Valide si une transaction avec la même combinaison membre_id + is_perpetual existe déjà dans une des transactions proposées. --- cmd/api.go | 2 ++ data/data.go | 58 +++++++++++++++++++++++++++++++ docker-compose.yaml | 14 ++++---- handlers/transaction.go | 77 +++++++++++++++++++++++++++++++++++++++++ models/models.go | 2 +- 5 files changed, 145 insertions(+), 8 deletions(-) create mode 100644 handlers/transaction.go diff --git a/cmd/api.go b/cmd/api.go index 3f2156e..95d232b 100644 --- a/cmd/api.go +++ b/cmd/api.go @@ -61,6 +61,8 @@ var apiCmd = &cobra.Command{ e.GET("/v2/membres/:membre_id/", handlers.GetMembre) + e.POST("/v2/transactions/", handlers.PostTransactions) + // Check bottin is ready bottinHealthResponse, err := bottinConnection.GetHealth() diff --git a/data/data.go b/data/data.go index 26dacbd..b930332 100644 --- a/data/data.go +++ b/data/data.go @@ -1,6 +1,7 @@ package data import ( + "errors" "fmt" "git.agecem.com/agecem/bottin-agenda/models" @@ -71,6 +72,63 @@ func (d *DataClient) Seed() (int64, error) { return rows, nil } +// InsertTransactions inserts a slice of Transaction into a database, returning the amount inserted and any error encountered +func (d *DataClient) InsertTransactions(transactions []models.Transaction) (int64, error) { + var rowsInserted int64 + + // Start transaction + tx, err := d.DB.Beginx() + if err != nil { + tx.Rollback() + return rowsInserted, err + } + + for _, transaction := range transactions { + // Check values + if transaction.MembreID == "" { + tx.Rollback() + return 0, errors.New("Cannot insert transaction with no membre_id") + } + + result, err := tx.NamedExec("INSERT INTO transactions (membre_id, given_at, is_perpetual) VALUES (:membre_id, current_timestamp, :is_perpetual);", &transaction) + if err != nil { + tx.Rollback() + return 0, err + } + + rows, err := result.RowsAffected() + if err != nil { + tx.Rollback() + return 0, err + } + + rowsInserted += rows + } + + err = tx.Commit() + if err != nil { + return rowsInserted, err + } + + return rowsInserted, nil +} + +func (d *DataClient) GetTransaction(membreID string, is_perpetual bool) (models.Transaction, error) { + var transaction models.Transaction + + //err := d.DB.NamedQuery("SELECT * FROM transactions WHERE membre_id=:membre_id AND is_perpetual=:is_perpetual LIMIT 1;" + err := d.DB.Get(&transaction, "SELECT * FROM transactions WHERE membre_id = $1 AND is_perpetual = $2 LIMIT 1;", membreID, is_perpetual) + if err != nil { + return transaction, err + } + + if transaction.ID == "" { + return transaction, fmt.Errorf("No transaction found") + } + + return transaction, nil +} + /* // InsertMembres inserts a slice of Membre into a database, returning the amount inserted and any error encountered func (d *DataClient) InsertMembres(membres []models.Membre) (int64, error) { diff --git a/docker-compose.yaml b/docker-compose.yaml index 635d0c8..03ee265 100644 --- a/docker-compose.yaml +++ b/docker-compose.yaml @@ -36,13 +36,13 @@ services: restart: 'unless-stopped' command: ['bottin-agenda', '--config', '/etc/bottin-agenda/web.yaml', 'web'] -# adminer: -# image: adminer -# restart: always -# ports: -# - 8088:8080 -# depends_on: -# - db + adminer: + image: adminer + restart: always + ports: + - 8088:8080 + depends_on: + - db volumes: db-data: diff --git a/handlers/transaction.go b/handlers/transaction.go new file mode 100644 index 0000000..d594c2c --- /dev/null +++ b/handlers/transaction.go @@ -0,0 +1,77 @@ +package handlers + +import ( + "fmt" + "net/http" + + "git.agecem.com/agecem/bottin-agenda/data" + "git.agecem.com/agecem/bottin-agenda/models" + "git.agecem.com/agecem/bottin-agenda/responses" + "github.com/labstack/echo/v4" +) + +func PostTransactions(c echo.Context) error { + var statusCode int = http.StatusInternalServerError + var response responses.PostTransactionsResponse + + var transactions []models.Transaction + + if err := c.Bind(&transactions); err != nil { + statusCode = http.StatusBadRequest + response.Message = fmt.Sprintf("Error during c.Bind(): %s", err) + + return c.JSON(statusCode, response) + } + + client, err := data.NewDataClientFromViper() + if err != nil { + response.Message = fmt.Sprintf("Error during data.NewDataClientFromViper(): %s", err) + + return c.JSON(statusCode, response) + } + defer client.DB.Close() + + if len(transactions) == 0 { + response.Message = fmt.Sprintf("Nothing to do") + statusCode = http.StatusOK + + return c.JSON(statusCode, response) + } + + // Check for already-existing transactions + for _, transaction := range transactions { + transaction, err := client.GetTransaction(transaction.MembreID, transaction.IsPerpetual) + if err != nil { + if err.Error() != "sql: no rows in result set" { + response.Message = fmt.Sprintf("Error during client.GetTransaction(): %s", err) + } + } + + if transaction.ID != "" { + agendaType := "non-perpetual" + if transaction.IsPerpetual { + agendaType = "perpetual" + } + + response.Message = fmt.Sprintf("Membre %s already received %s", transaction.MembreID, agendaType) + + statusCode = http.StatusBadRequest + + return c.JSON(statusCode, response) + } + } + + rows, err := client.InsertTransactions(transactions) + if err != nil { + response.Message = fmt.Sprintf("Error during client.InsertTransactions(): %s", err) + + return c.JSON(statusCode, response) + } + + response.Data.RowsInserted = rows + + statusCode = http.StatusCreated + response.Message = "Insert successful" + + return c.JSON(statusCode, response) +} diff --git a/models/models.go b/models/models.go index 49678d0..ac30776 100644 --- a/models/models.go +++ b/models/models.go @@ -4,7 +4,7 @@ import "time" var Schema = ` CREATE TABLE transactions ( - id PRIMARY KEY GENERATED ALWAYS AS IDENTITY, + id BIGINT PRIMARY KEY GENERATED ALWAYS AS IDENTITY, membre_id VARCHAR(7), given_at TIMESTAMP, is_perpetual BOOLEAN