package handler import ( "fmt" "net/http" "spend-sparrow/internal/handler/middleware" "spend-sparrow/internal/service" t "spend-sparrow/internal/template/transaction" "spend-sparrow/internal/types" "spend-sparrow/internal/utils" "strconv" "time" "github.com/a-h/templ" "github.com/google/uuid" ) type Transaction interface { Handle(router *http.ServeMux) } type TransactionImpl struct { s service.Transaction account service.Account treasureChest service.TreasureChest r *Render } func NewTransaction(s service.Transaction, account service.Account, treasureChest service.TreasureChest, r *Render) Transaction { return TransactionImpl{ s: s, account: account, treasureChest: treasureChest, r: r, } } func (h TransactionImpl) Handle(r *http.ServeMux) { r.Handle("GET /transaction", h.handleTransactionPage()) r.Handle("GET /transaction/{id}", h.handleTransactionItemComp()) r.Handle("POST /transaction/{id}", h.handleUpdateTransaction()) r.Handle("POST /transaction/recalculate", h.handleRecalculate()) r.Handle("DELETE /transaction/{id}", h.handleDeleteTransaction()) } func (h TransactionImpl) handleTransactionPage() http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { user := middleware.GetUser(r) if user == nil { utils.DoRedirect(w, r, "/auth/signin") return } filter := types.TransactionItemsFilter{ AccountId: r.URL.Query().Get("account-id"), TreasureChestId: r.URL.Query().Get("treasure-chest-id"), Error: r.URL.Query().Get("error"), } transactions, err := h.s.GetAll(user, filter) if err != nil { handleError(w, r, err) return } accounts, err := h.account.GetAll(user) if err != nil { handleError(w, r, err) return } treasureChests, err := h.treasureChest.GetAll(user) if err != nil { handleError(w, r, err) return } accountMap, treasureChestMap := h.getTransactionData(accounts, treasureChests) items := t.TransactionItems(transactions, accountMap, treasureChestMap) if utils.IsHtmx(r) { h.r.Render(r, w, items) } else { comp := t.Transaction(items, filter, accounts, treasureChests) h.r.RenderLayout(r, w, comp, user) } } } func (h TransactionImpl) handleTransactionItemComp() http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { user := middleware.GetUser(r) if user == nil { utils.DoRedirect(w, r, "/auth/signin") return } accounts, err := h.account.GetAll(user) if err != nil { handleError(w, r, err) return } treasureChests, err := h.treasureChest.GetAll(user) if err != nil { handleError(w, r, err) return } id := r.PathValue("id") if id == "new" { comp := t.EditTransaction(nil, accounts, treasureChests) h.r.Render(r, w, comp) return } transaction, err := h.s.Get(user, id) if err != nil { handleError(w, r, err) return } var comp templ.Component if r.URL.Query().Get("edit") == "true" { comp = t.EditTransaction(transaction, accounts, treasureChests) } else { accountMap, treasureChestMap := h.getTransactionData(accounts, treasureChests) comp = t.TransactionItem(transaction, accountMap, treasureChestMap) } h.r.Render(r, w, comp) } } func (h TransactionImpl) handleUpdateTransaction() http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { user := middleware.GetUser(r) if user == nil { utils.DoRedirect(w, r, "/auth/signin") return } var ( id uuid.UUID err error ) idStr := r.PathValue("id") if idStr != "new" { id, err = uuid.Parse(idStr) if err != nil { handleError(w, r, fmt.Errorf("could not parse Id: %w", service.ErrBadRequest)) return } } accountIdStr := r.FormValue("account-id") var accountId *uuid.UUID if accountIdStr != "" { i, err := uuid.Parse(accountIdStr) if err != nil { handleError(w, r, fmt.Errorf("could not parse account id: %w", service.ErrBadRequest)) return } accountId = &i } treasureChestIdStr := r.FormValue("treasure-chest-id") var treasureChestId *uuid.UUID if treasureChestIdStr != "" { i, err := uuid.Parse(treasureChestIdStr) if err != nil { handleError(w, r, fmt.Errorf("could not parse treasure chest id: %w", service.ErrBadRequest)) return } treasureChestId = &i } valueF, err := strconv.ParseFloat(r.FormValue("value"), 64) if err != nil { handleError(w, r, fmt.Errorf("could not parse value: %w", service.ErrBadRequest)) return } value := int64(valueF * service.DECIMALS_MULTIPLIER) timestamp, err := time.Parse("2006-01-02", r.FormValue("timestamp")) if err != nil { handleError(w, r, fmt.Errorf("could not parse timestamp: %w", service.ErrBadRequest)) return } input := types.Transaction{ Id: id, AccountId: accountId, TreasureChestId: treasureChestId, Value: value, Timestamp: timestamp, Party: r.FormValue("party"), Description: r.FormValue("description"), } var transaction *types.Transaction if idStr == "new" { transaction, err = h.s.Add(nil, user, input) if err != nil { handleError(w, r, err) return } } else { transaction, err = h.s.Update(user, input) if err != nil { handleError(w, r, err) return } } accounts, err := h.account.GetAll(user) if err != nil { handleError(w, r, err) return } treasureChests, err := h.treasureChest.GetAll(user) if err != nil { handleError(w, r, err) return } accountMap, treasureChestMap := h.getTransactionData(accounts, treasureChests) comp := t.TransactionItem(transaction, accountMap, treasureChestMap) h.r.Render(r, w, comp) } } func (h TransactionImpl) handleRecalculate() http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { user := middleware.GetUser(r) if user == nil { utils.DoRedirect(w, r, "/auth/signin") return } err := h.s.RecalculateBalances(user) if err != nil { handleError(w, r, err) return } utils.TriggerToastWithStatus(w, r, "success", "Balances recalculated, please refresh", http.StatusOK) } } func (h TransactionImpl) handleDeleteTransaction() http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { user := middleware.GetUser(r) if user == nil { utils.DoRedirect(w, r, "/auth/signin") return } id := r.PathValue("id") err := h.s.Delete(user, id) if err != nil { handleError(w, r, err) return } } } func (h TransactionImpl) getTransactionData(accounts []*types.Account, treasureChests []*types.TreasureChest) (map[uuid.UUID]string, map[uuid.UUID]string) { accountMap := make(map[uuid.UUID]string, 0) for _, account := range accounts { accountMap[account.Id] = account.Name } treasureChestMap := make(map[uuid.UUID]string, 0) root := "" for _, treasureChest := range treasureChests { if treasureChest.ParentId == nil { root = treasureChest.Name + " > " treasureChestMap[treasureChest.Id] = treasureChest.Name } else { treasureChestMap[treasureChest.Id] = root + treasureChest.Name } } return accountMap, treasureChestMap }