241 lines
6.3 KiB
Go
241 lines
6.3 KiB
Go
package handler
|
|
|
|
import (
|
|
"encoding/json"
|
|
"errors"
|
|
"net/http"
|
|
"strconv"
|
|
|
|
"git.g3e.fr/H6N/account/internal/store"
|
|
"github.com/jackc/pgx/v5"
|
|
)
|
|
|
|
type TransactionHandler struct {
|
|
store *store.Store
|
|
}
|
|
|
|
func NewTransactionHandler(s *store.Store) *TransactionHandler {
|
|
return &TransactionHandler{store: s}
|
|
}
|
|
|
|
func (h *TransactionHandler) RegisterRoutes(mux *http.ServeMux) {
|
|
mux.HandleFunc("GET /accounts/{id}/transactions", h.listForAccount)
|
|
mux.HandleFunc("POST /transactions", h.create)
|
|
mux.HandleFunc("GET /transactions/{id}", h.get)
|
|
mux.HandleFunc("PUT /transactions/{id}", h.update)
|
|
mux.HandleFunc("DELETE /transactions/{id}", h.delete)
|
|
mux.HandleFunc("PATCH /transactions/{id}/validate", h.validate)
|
|
}
|
|
|
|
func (h *TransactionHandler) listForAccount(w http.ResponseWriter, r *http.Request) {
|
|
ownerID, ok := requireOwner(w, r)
|
|
if !ok {
|
|
return
|
|
}
|
|
accountID, err := parseID(r)
|
|
if err != nil {
|
|
writeError(w, http.StatusBadRequest, "invalid account id")
|
|
return
|
|
}
|
|
if err := h.store.VerifyAccountOwner(r.Context(), accountID, ownerID); errors.Is(err, pgx.ErrNoRows) {
|
|
writeError(w, http.StatusNotFound, "account not found")
|
|
return
|
|
} else if err != nil {
|
|
writeError(w, http.StatusInternalServerError, "failed to verify account")
|
|
return
|
|
}
|
|
f := store.AccountTransactionFilters{
|
|
Validated: parseBoolParam(r, "validated"),
|
|
Pending: parseBoolParam(r, "pending"),
|
|
From: parseStringParam(r, "from"),
|
|
To: parseStringParam(r, "to"),
|
|
}
|
|
txs, err := h.store.ListAccountTransactions(r.Context(), accountID, f)
|
|
if err != nil {
|
|
writeError(w, http.StatusInternalServerError, "failed to list transactions")
|
|
return
|
|
}
|
|
writeJSON(w, http.StatusOK, txs)
|
|
}
|
|
|
|
func (h *TransactionHandler) get(w http.ResponseWriter, r *http.Request) {
|
|
ownerID, ok := requireOwner(w, r)
|
|
if !ok {
|
|
return
|
|
}
|
|
id, err := parseTxID(r)
|
|
if err != nil {
|
|
writeError(w, http.StatusBadRequest, "invalid id")
|
|
return
|
|
}
|
|
tx, err := h.store.GetTransaction(r.Context(), id, ownerID)
|
|
if errors.Is(err, pgx.ErrNoRows) {
|
|
writeError(w, http.StatusNotFound, "transaction not found")
|
|
return
|
|
}
|
|
if err != nil {
|
|
writeError(w, http.StatusInternalServerError, "failed to get transaction")
|
|
return
|
|
}
|
|
writeJSON(w, http.StatusOK, tx)
|
|
}
|
|
|
|
func (h *TransactionHandler) create(w http.ResponseWriter, r *http.Request) {
|
|
ownerID, ok := requireOwner(w, r)
|
|
if !ok {
|
|
return
|
|
}
|
|
var p store.CreateTransactionParams
|
|
if err := json.NewDecoder(r.Body).Decode(&p); err != nil {
|
|
writeError(w, http.StatusBadRequest, "invalid request body")
|
|
return
|
|
}
|
|
if p.Label == "" {
|
|
writeError(w, http.StatusBadRequest, "label is required")
|
|
return
|
|
}
|
|
if p.Date == "" {
|
|
writeError(w, http.StatusBadRequest, "date is required")
|
|
return
|
|
}
|
|
// Vérifier que les comptes référencés appartiennent à l'owner.
|
|
if p.AccountSourceID != nil {
|
|
if err := h.store.VerifyAccountOwner(r.Context(), *p.AccountSourceID, ownerID); errors.Is(err, pgx.ErrNoRows) {
|
|
writeError(w, http.StatusNotFound, "source account not found")
|
|
return
|
|
} else if err != nil {
|
|
writeError(w, http.StatusInternalServerError, "failed to verify source account")
|
|
return
|
|
}
|
|
}
|
|
if p.AccountDestID != nil {
|
|
if err := h.store.VerifyAccountOwner(r.Context(), *p.AccountDestID, ownerID); errors.Is(err, pgx.ErrNoRows) {
|
|
writeError(w, http.StatusNotFound, "dest account not found")
|
|
return
|
|
} else if err != nil {
|
|
writeError(w, http.StatusInternalServerError, "failed to verify dest account")
|
|
return
|
|
}
|
|
}
|
|
tx, err := h.store.CreateTransaction(r.Context(), p)
|
|
if err != nil {
|
|
writeError(w, http.StatusInternalServerError, "failed to create transaction")
|
|
return
|
|
}
|
|
writeJSON(w, http.StatusCreated, tx)
|
|
}
|
|
|
|
func (h *TransactionHandler) update(w http.ResponseWriter, r *http.Request) {
|
|
ownerID, ok := requireOwner(w, r)
|
|
if !ok {
|
|
return
|
|
}
|
|
id, err := parseTxID(r)
|
|
if err != nil {
|
|
writeError(w, http.StatusBadRequest, "invalid id")
|
|
return
|
|
}
|
|
var p store.CreateTransactionParams
|
|
if err := json.NewDecoder(r.Body).Decode(&p); err != nil {
|
|
writeError(w, http.StatusBadRequest, "invalid request body")
|
|
return
|
|
}
|
|
if p.Label == "" {
|
|
writeError(w, http.StatusBadRequest, "label is required")
|
|
return
|
|
}
|
|
tx, err := h.store.UpdateTransaction(r.Context(), id, p, ownerID)
|
|
if errors.Is(err, pgx.ErrNoRows) {
|
|
writeError(w, http.StatusNotFound, "transaction not found")
|
|
return
|
|
}
|
|
if err != nil {
|
|
writeError(w, http.StatusInternalServerError, "failed to update transaction")
|
|
return
|
|
}
|
|
writeJSON(w, http.StatusOK, tx)
|
|
}
|
|
|
|
func (h *TransactionHandler) delete(w http.ResponseWriter, r *http.Request) {
|
|
ownerID, ok := requireOwner(w, r)
|
|
if !ok {
|
|
return
|
|
}
|
|
id, err := parseTxID(r)
|
|
if err != nil {
|
|
writeError(w, http.StatusBadRequest, "invalid id")
|
|
return
|
|
}
|
|
if err := h.store.DeleteTransaction(r.Context(), id, ownerID); errors.Is(err, pgx.ErrNoRows) {
|
|
writeError(w, http.StatusNotFound, "transaction not found")
|
|
return
|
|
} else if err != nil {
|
|
writeError(w, http.StatusInternalServerError, "failed to delete transaction")
|
|
return
|
|
}
|
|
w.WriteHeader(http.StatusNoContent)
|
|
}
|
|
|
|
func (h *TransactionHandler) validate(w http.ResponseWriter, r *http.Request) {
|
|
ownerID, ok := requireOwner(w, r)
|
|
if !ok {
|
|
return
|
|
}
|
|
id, err := parseTxID(r)
|
|
if err != nil {
|
|
writeError(w, http.StatusBadRequest, "invalid id")
|
|
return
|
|
}
|
|
var body struct {
|
|
Validated bool `json:"validated"`
|
|
}
|
|
if err := json.NewDecoder(r.Body).Decode(&body); err != nil {
|
|
writeError(w, http.StatusBadRequest, "invalid request body")
|
|
return
|
|
}
|
|
tx, err := h.store.SetValidated(r.Context(), id, body.Validated, ownerID)
|
|
if errors.Is(err, pgx.ErrNoRows) {
|
|
writeError(w, http.StatusNotFound, "transaction not found")
|
|
return
|
|
}
|
|
if err != nil {
|
|
writeError(w, http.StatusInternalServerError, "failed to update transaction")
|
|
return
|
|
}
|
|
writeJSON(w, http.StatusOK, tx)
|
|
}
|
|
|
|
func parseTxID(r *http.Request) (int64, error) {
|
|
v, err := strconv.ParseInt(r.PathValue("id"), 10, 64)
|
|
return v, err
|
|
}
|
|
|
|
func parseIntParam(r *http.Request, key string) *int32 {
|
|
v := r.URL.Query().Get(key)
|
|
if v == "" {
|
|
return nil
|
|
}
|
|
n, err := strconv.Atoi(v)
|
|
if err != nil {
|
|
return nil
|
|
}
|
|
i := int32(n)
|
|
return &i
|
|
}
|
|
|
|
func parseBoolParam(r *http.Request, key string) *bool {
|
|
v := r.URL.Query().Get(key)
|
|
if v == "" {
|
|
return nil
|
|
}
|
|
b := v == "true"
|
|
return &b
|
|
}
|
|
|
|
func parseStringParam(r *http.Request, key string) *string {
|
|
v := r.URL.Query().Get(key)
|
|
if v == "" {
|
|
return nil
|
|
}
|
|
return &v
|
|
}
|