package routes import ( "encoding/json" "net/http" "strconv" "github.com/go-chi/chi/v5" "github.com/felt-app/felt/internal/financial" "github.com/felt-app/felt/internal/server/middleware" ) // FinancialHandler handles financial API routes. type FinancialHandler struct { engine *financial.Engine } // NewFinancialHandler creates a new financial route handler. func NewFinancialHandler(engine *financial.Engine) *FinancialHandler { return &FinancialHandler{engine: engine} } // RegisterRoutes registers financial routes on the given router. func (h *FinancialHandler) RegisterRoutes(r chi.Router) { r.Route("/tournaments/{id}", func(r chi.Router) { // Read-only routes (any authenticated user) r.Get("/prize-pool", h.handleGetPrizePool) r.Get("/payouts", h.handleGetPayouts) r.Get("/payouts/preview", h.handlePreviewPayouts) r.Get("/transactions", h.handleGetTransactions) r.Get("/transactions/{txId}/receipt", h.handleGetReceipt) // Mutation routes (admin or floor) r.Group(func(r chi.Router) { r.Use(middleware.RequireRole(middleware.RoleFloor)) r.Post("/bubble-prize", h.handleCreateBubblePrize) r.Post("/bubble-prize/{proposalId}/confirm", h.handleConfirmBubblePrize) r.Post("/transactions/{txId}/reprint", h.handleReprintReceipt) }) }) // Season reserves (venue-level, any authenticated user) r.Get("/season-reserves", h.handleGetSeasonReserves) } // handleGetPrizePool returns the current prize pool summary for a tournament. func (h *FinancialHandler) handleGetPrizePool(w http.ResponseWriter, r *http.Request) { tournamentID := chi.URLParam(r, "id") if tournamentID == "" { writeError(w, http.StatusBadRequest, "tournament id required") return } summary, err := h.engine.CalculatePrizePool(r.Context(), tournamentID) if err != nil { writeError(w, http.StatusInternalServerError, err.Error()) return } writeJSON(w, http.StatusOK, summary) } // handleGetPayouts returns the calculated payout table. func (h *FinancialHandler) handleGetPayouts(w http.ResponseWriter, r *http.Request) { tournamentID := chi.URLParam(r, "id") if tournamentID == "" { writeError(w, http.StatusBadRequest, "tournament id required") return } payouts, err := h.engine.CalculatePayouts(r.Context(), tournamentID) if err != nil { writeError(w, http.StatusBadRequest, err.Error()) return } writeJSON(w, http.StatusOK, map[string]interface{}{ "payouts": payouts, }) } // handlePreviewPayouts returns a preview of payouts without applying them. func (h *FinancialHandler) handlePreviewPayouts(w http.ResponseWriter, r *http.Request) { tournamentID := chi.URLParam(r, "id") if tournamentID == "" { writeError(w, http.StatusBadRequest, "tournament id required") return } summary, err := h.engine.CalculatePrizePool(r.Context(), tournamentID) if err != nil { writeError(w, http.StatusInternalServerError, err.Error()) return } payouts, err := h.engine.CalculatePayouts(r.Context(), tournamentID) if err != nil { writeError(w, http.StatusBadRequest, err.Error()) return } writeJSON(w, http.StatusOK, map[string]interface{}{ "prize_pool": summary, "payouts": payouts, "preview": true, }) } // handleGetTransactions returns all transactions for a tournament. func (h *FinancialHandler) handleGetTransactions(w http.ResponseWriter, r *http.Request) { tournamentID := chi.URLParam(r, "id") if tournamentID == "" { writeError(w, http.StatusBadRequest, "tournament id required") return } txs, err := h.engine.GetTransactions(r.Context(), tournamentID) if err != nil { writeError(w, http.StatusInternalServerError, err.Error()) return } writeJSON(w, http.StatusOK, txs) } // handleGetReceipt returns the receipt for a transaction. func (h *FinancialHandler) handleGetReceipt(w http.ResponseWriter, r *http.Request) { txID := chi.URLParam(r, "txId") if txID == "" { writeError(w, http.StatusBadRequest, "transaction id required") return } receipt, err := h.engine.GetReceipt(r.Context(), txID) if err != nil { writeError(w, http.StatusNotFound, err.Error()) return } writeJSON(w, http.StatusOK, receipt) } // handleReprintReceipt returns the receipt with a new print timestamp. func (h *FinancialHandler) handleReprintReceipt(w http.ResponseWriter, r *http.Request) { txID := chi.URLParam(r, "txId") if txID == "" { writeError(w, http.StatusBadRequest, "transaction id required") return } receipt, err := h.engine.ReprintReceipt(r.Context(), txID) if err != nil { writeError(w, http.StatusNotFound, err.Error()) return } writeJSON(w, http.StatusOK, receipt) } type bubblePrizeRequest struct { Amount int64 `json:"amount"` // cents } // handleCreateBubblePrize creates a bubble prize proposal. func (h *FinancialHandler) handleCreateBubblePrize(w http.ResponseWriter, r *http.Request) { tournamentID := chi.URLParam(r, "id") if tournamentID == "" { writeError(w, http.StatusBadRequest, "tournament id required") return } var req bubblePrizeRequest if err := json.NewDecoder(r.Body).Decode(&req); err != nil { writeError(w, http.StatusBadRequest, "invalid JSON: "+err.Error()) return } if req.Amount <= 0 { writeError(w, http.StatusBadRequest, "amount must be positive") return } proposal, err := h.engine.CalculateBubblePrize(r.Context(), tournamentID, req.Amount) if err != nil { writeError(w, http.StatusBadRequest, err.Error()) return } writeJSON(w, http.StatusCreated, proposal) } // handleConfirmBubblePrize confirms a bubble prize proposal. func (h *FinancialHandler) handleConfirmBubblePrize(w http.ResponseWriter, r *http.Request) { tournamentID := chi.URLParam(r, "id") proposalIDStr := chi.URLParam(r, "proposalId") proposalID, err := strconv.ParseInt(proposalIDStr, 10, 64) if err != nil { writeError(w, http.StatusBadRequest, "invalid proposal id") return } if err := h.engine.ConfirmBubblePrize(r.Context(), tournamentID, proposalID); err != nil { writeError(w, http.StatusBadRequest, err.Error()) return } writeJSON(w, http.StatusOK, map[string]string{"status": "confirmed"}) } // handleGetSeasonReserves returns the season reserve summary. func (h *FinancialHandler) handleGetSeasonReserves(w http.ResponseWriter, r *http.Request) { reserves, err := h.engine.GetSeasonReserves(r.Context()) if err != nil { writeError(w, http.StatusInternalServerError, err.Error()) return } writeJSON(w, http.StatusOK, map[string]interface{}{ "reserves": reserves, }) }