package handler import ( "net/http" "strconv" "github.com/gin-gonic/gin" appservice "github.com/genex/trading-service/internal/application/service" "github.com/genex/trading-service/internal/domain/vo" ) // TradeHandler handles user-facing trading HTTP endpoints. type TradeHandler struct { tradeService *appservice.TradeService } // NewTradeHandler creates a new TradeHandler with injected trade service. func NewTradeHandler(tradeService *appservice.TradeService) *TradeHandler { return &TradeHandler{tradeService: tradeService} } // PlaceOrderReq is the request body for placing an order. type PlaceOrderReq struct { CouponID string `json:"couponId" binding:"required"` Side string `json:"side" binding:"required,oneof=buy sell"` Type string `json:"type" binding:"required,oneof=limit market"` Price float64 `json:"price"` Quantity int `json:"quantity" binding:"required,min=1"` } // PlaceOrder handles POST /api/v1/trades/orders func (h *TradeHandler) PlaceOrder(c *gin.Context) { var req PlaceOrderReq if err := c.ShouldBindJSON(&req); err != nil { c.JSON(http.StatusBadRequest, gin.H{"code": -1, "message": err.Error()}) return } // Validate and convert to value objects side, err := vo.NewOrderSide(req.Side) if err != nil { c.JSON(http.StatusBadRequest, gin.H{"code": -1, "message": err.Error()}) return } orderType, err := vo.NewOrderType(req.Type) if err != nil { c.JSON(http.StatusBadRequest, gin.H{"code": -1, "message": err.Error()}) return } price, err := vo.NewPrice(req.Price) if err != nil { c.JSON(http.StatusBadRequest, gin.H{"code": -1, "message": err.Error()}) return } quantity, err := vo.NewQuantity(req.Quantity) if err != nil { c.JSON(http.StatusBadRequest, gin.H{"code": -1, "message": err.Error()}) return } userID := c.GetString("userId") output, err := h.tradeService.PlaceOrder(c.Request.Context(), appservice.PlaceOrderInput{ UserID: userID, CouponID: req.CouponID, Side: side, Type: orderType, Price: price, Quantity: quantity, }) if err != nil { c.JSON(http.StatusInternalServerError, gin.H{"code": -1, "message": err.Error()}) return } c.JSON(http.StatusOK, gin.H{"code": 0, "data": gin.H{ "order": output.Order, "trades": output.Trades, }}) } // CancelOrder handles DELETE /api/v1/trades/orders/:id func (h *TradeHandler) CancelOrder(c *gin.Context) { couponID := c.Query("couponId") orderID := c.Param("id") sideStr := c.Query("side") side, err := vo.NewOrderSide(sideStr) if err != nil { c.JSON(http.StatusBadRequest, gin.H{"code": -1, "message": err.Error()}) return } err = h.tradeService.CancelOrder(c.Request.Context(), couponID, orderID, side) if err != nil { c.JSON(http.StatusNotFound, gin.H{"code": -1, "message": "Order not found"}) return } c.JSON(http.StatusOK, gin.H{"code": 0, "data": nil}) } // GetOrderBook handles GET /api/v1/trades/orderbook/:couponId func (h *TradeHandler) GetOrderBook(c *gin.Context) { couponID := c.Param("couponId") depth, _ := strconv.Atoi(c.DefaultQuery("depth", "20")) bids, asks := h.tradeService.GetOrderBookSnapshot(couponID, depth) c.JSON(http.StatusOK, gin.H{"code": 0, "data": gin.H{ "couponId": couponID, "bids": bids, "asks": asks, }}) }