- SUMMARY.md with all accomplishments and deviation documentation - STATE.md updated: plan 8/14, 50% progress, decisions, session - ROADMAP.md updated: 7/14 plans complete - REQUIREMENTS.md: UI-01 through UI-04, UI-07, UI-08 marked complete Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
222 lines
6.3 KiB
Go
222 lines
6.3 KiB
Go
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,
|
|
})
|
|
}
|