diff --git a/internal/handler/account.go b/internal/handler/account.go new file mode 100644 index 0000000..cdc33b4 --- /dev/null +++ b/internal/handler/account.go @@ -0,0 +1,113 @@ +package handler + +import ( + "encoding/json" + "errors" + "net/http" + + "git.g3e.fr/H6N/account/internal/store" + "github.com/jackc/pgx/v5" +) + +type AccountHandler struct { + store *store.Store +} + +func NewAccountHandler(s *store.Store) *AccountHandler { + return &AccountHandler{store: s} +} + +func (h *AccountHandler) RegisterRoutes(mux *http.ServeMux) { + mux.HandleFunc("GET /accounts", h.list) + mux.HandleFunc("POST /accounts", h.create) + mux.HandleFunc("GET /accounts/{id}", h.get) + mux.HandleFunc("PUT /accounts/{id}", h.update) + mux.HandleFunc("DELETE /accounts/{id}", h.delete) +} + +func (h *AccountHandler) list(w http.ResponseWriter, r *http.Request) { + accounts, err := h.store.ListAccounts(r.Context()) + if err != nil { + writeError(w, http.StatusInternalServerError, "failed to list accounts") + return + } + writeJSON(w, http.StatusOK, accounts) +} + +func (h *AccountHandler) get(w http.ResponseWriter, r *http.Request) { + id, err := parseID(r) + if err != nil { + writeError(w, http.StatusBadRequest, "invalid id") + return + } + account, err := h.store.GetAccount(r.Context(), id) + if errors.Is(err, pgx.ErrNoRows) { + writeError(w, http.StatusNotFound, "account not found") + return + } + if err != nil { + writeError(w, http.StatusInternalServerError, "failed to get account") + return + } + writeJSON(w, http.StatusOK, account) +} + +func (h *AccountHandler) create(w http.ResponseWriter, r *http.Request) { + var p store.CreateAccountParams + if err := json.NewDecoder(r.Body).Decode(&p); err != nil { + writeError(w, http.StatusBadRequest, "invalid request body") + return + } + if p.Nom == "" || p.Type == "" { + writeError(w, http.StatusBadRequest, "nom and type are required") + return + } + if p.DeviseReference == "" { + p.DeviseReference = "EUR" + } + account, err := h.store.CreateAccount(r.Context(), p) + if err != nil { + writeError(w, http.StatusInternalServerError, "failed to create account") + return + } + writeJSON(w, http.StatusCreated, account) +} + +func (h *AccountHandler) update(w http.ResponseWriter, r *http.Request) { + id, err := parseID(r) + if err != nil { + writeError(w, http.StatusBadRequest, "invalid id") + return + } + var p store.CreateAccountParams + if err := json.NewDecoder(r.Body).Decode(&p); err != nil { + writeError(w, http.StatusBadRequest, "invalid request body") + return + } + if p.DeviseReference == "" { + p.DeviseReference = "EUR" + } + account, err := h.store.UpdateAccount(r.Context(), id, p) + if errors.Is(err, pgx.ErrNoRows) { + writeError(w, http.StatusNotFound, "account not found") + return + } + if err != nil { + writeError(w, http.StatusInternalServerError, "failed to update account") + return + } + writeJSON(w, http.StatusOK, account) +} + +func (h *AccountHandler) delete(w http.ResponseWriter, r *http.Request) { + id, err := parseID(r) + if err != nil { + writeError(w, http.StatusBadRequest, "invalid id") + return + } + if err := h.store.DeleteAccount(r.Context(), id); err != nil { + writeError(w, http.StatusInternalServerError, "failed to delete account") + return + } + w.WriteHeader(http.StatusNoContent) +} diff --git a/internal/server/server.go b/internal/server/server.go index fee7822..d53b3ed 100644 --- a/internal/server/server.go +++ b/internal/server/server.go @@ -37,6 +37,7 @@ func (s *Server) routes() { st := store.New(s.pool) handler.NewInstrumentHandler(st).RegisterRoutes(s.mux) + handler.NewAccountHandler(st).RegisterRoutes(s.mux) s.mux.HandleFunc("GET /health", s.handleHealth) } diff --git a/internal/store/account.go b/internal/store/account.go new file mode 100644 index 0000000..4f94760 --- /dev/null +++ b/internal/store/account.go @@ -0,0 +1,69 @@ +package store + +import ( + "context" + + "github.com/jackc/pgx/v5" +) + +type Account struct { + ID int32 `json:"id"` + Nom string `json:"nom"` + Type string `json:"type"` + DeviseReference string `json:"devise_reference"` + Plafond *float64 `json:"plafond,omitempty"` +} + +type CreateAccountParams struct { + Nom string `json:"nom"` + Type string `json:"type"` + DeviseReference string `json:"devise_reference"` + Plafond *float64 `json:"plafond,omitempty"` +} + +func (s *Store) ListAccounts(ctx context.Context) ([]Account, error) { + rows, err := s.pool.Query(ctx, + `SELECT id, nom, type, devise_reference, plafond FROM account ORDER BY nom`) + if err != nil { + return nil, err + } + return pgx.CollectRows(rows, pgx.RowToStructByName[Account]) +} + +func (s *Store) GetAccount(ctx context.Context, id int32) (Account, error) { + rows, err := s.pool.Query(ctx, + `SELECT id, nom, type, devise_reference, plafond FROM account WHERE id = $1`, id) + if err != nil { + return Account{}, err + } + return pgx.CollectOneRow(rows, pgx.RowToStructByName[Account]) +} + +func (s *Store) CreateAccount(ctx context.Context, p CreateAccountParams) (Account, error) { + rows, err := s.pool.Query(ctx, + `INSERT INTO account (nom, type, devise_reference, plafond, taux) + VALUES ($1, $2, $3, $4, $5) + RETURNING id, nom, type, devise_reference, plafond`, + p.Nom, p.Type, p.DeviseReference, p.Plafond) + if err != nil { + return Account{}, err + } + return pgx.CollectOneRow(rows, pgx.RowToStructByName[Account]) +} + +func (s *Store) UpdateAccount(ctx context.Context, id int32, p CreateAccountParams) (Account, error) { + rows, err := s.pool.Query(ctx, + `UPDATE account SET nom = $2, type = $3, devise_reference = $4, plafond = $5 + WHERE id = $1 + RETURNING id, nom, type, devise_reference, plafond`, + id, p.Nom, p.Type, p.DeviseReference, p.Plafond) + if err != nil { + return Account{}, err + } + return pgx.CollectOneRow(rows, pgx.RowToStructByName[Account]) +} + +func (s *Store) DeleteAccount(ctx context.Context, id int32) error { + _, err := s.pool.Exec(ctx, `DELETE FROM account WHERE id = $1`, id) + return err +} diff --git a/migrations/000003_drop_account_taux.down.sql b/migrations/000003_drop_account_taux.down.sql new file mode 100644 index 0000000..2fafcab --- /dev/null +++ b/migrations/000003_drop_account_taux.down.sql @@ -0,0 +1 @@ +ALTER TABLE account ADD COLUMN taux NUMERIC(10, 6); diff --git a/migrations/000003_drop_account_taux.up.sql b/migrations/000003_drop_account_taux.up.sql new file mode 100644 index 0000000..f322a8b --- /dev/null +++ b/migrations/000003_drop_account_taux.up.sql @@ -0,0 +1,3 @@ +-- taux retiré de account : le taux varie dans le temps et nécessite +-- une table account_rate(account_id, taux, date_debut, date_fin) dédiée +ALTER TABLE account DROP COLUMN taux;