gcx/backend/services/trading-service/internal/interface/http/handler/trade_handler.go

117 lines
3.2 KiB
Go

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,
}})
}