feat(trade): implement second-hand trading/flea market feature
Some checks failed
Build Backend / build-docker (pull_request) Has been cancelled
Build Backend / build (pull_request) Has been cancelled
Deploy / deploy (pull_request) Has been cancelled

Add complete trade functionality including:
- TradeItem, TradeImage, TradeFavorite models with auto-migration
- TradeRepository with CRUD and favorite operations
- TradeService with business logic for listing, creating, updating, status management
- TradeHandler with RESTful endpoints for trade operations
- DTOs and converters for request/response handling

Routes include: list, get by id, create, update, delete, status update, view recording, favorite/unfavorite.
This commit is contained in:
lafay
2026-04-26 15:20:26 +08:00
parent 1b50d6c792
commit 0ca02f7ef3
13 changed files with 1131 additions and 399 deletions

View File

@@ -0,0 +1,220 @@
package handler
import (
"strconv"
"github.com/gin-gonic/gin"
"with_you/internal/dto"
"with_you/internal/model"
"with_you/internal/pkg/response"
"with_you/internal/service"
)
type TradeHandler struct {
tradeService service.TradeService
}
func NewTradeHandler(tradeService service.TradeService) *TradeHandler {
return &TradeHandler{tradeService: tradeService}
}
func (h *TradeHandler) List(c *gin.Context) {
currentUserID := c.GetString("user_id")
if currentUserID == "" {
currentUserID = ""
}
var tradeType *model.TradeType
if v := c.Query("trade_type"); v != "" {
t := model.TradeType(v)
tradeType = &t
}
var category *model.TradeCategory
if v := c.Query("category"); v != "" {
cat := model.TradeCategory(v)
category = &cat
}
cursorStr := c.Query("cursor")
pageSize := 20
if v := c.Query("page_size"); v != "" {
if ps, err := strconv.Atoi(v); err == nil && ps > 0 && ps <= 100 {
pageSize = ps
}
}
keyword := c.Query("keyword")
var currentUserIDPtr *string
if currentUserID != "" {
currentUserIDPtr = &currentUserID
}
var resp *dto.TradeCursorPageResponse
var err error
if keyword != "" {
resp, err = h.tradeService.Search(c.Request.Context(), keyword, tradeType, category, cursorStr, pageSize, currentUserIDPtr)
} else {
resp, err = h.tradeService.List(c.Request.Context(), tradeType, category, cursorStr, pageSize, currentUserIDPtr)
}
if err != nil {
response.HandleError(c, err, "获取交易列表失败")
return
}
response.Success(c, resp)
}
func (h *TradeHandler) GetByID(c *gin.Context) {
id := c.Param("id")
currentUserID := c.GetString("user_id")
var currentUserIDPtr *string
if currentUserID != "" {
currentUserIDPtr = &currentUserID
}
resp, err := h.tradeService.GetByID(c.Request.Context(), id, currentUserIDPtr)
if err != nil {
response.HandleError(c, err, "获取交易详情失败")
return
}
response.Success(c, resp)
}
func (h *TradeHandler) Create(c *gin.Context) {
userID := c.GetString("user_id")
if userID == "" {
response.Unauthorized(c, "请先登录")
return
}
var req dto.CreateTradeItemRequest
if err := c.ShouldBindJSON(&req); err != nil {
response.BadRequest(c, err.Error())
return
}
item, err := h.tradeService.Create(c.Request.Context(), userID, req)
if err != nil {
response.HandleError(c, err, "创建交易失败")
return
}
var currentUserIDPtr *string
if userID != "" {
currentUserIDPtr = &userID
}
detail, err := h.tradeService.GetByID(c.Request.Context(), item.ID, currentUserIDPtr)
if err != nil {
response.Success(c, item)
return
}
response.Success(c, detail)
}
func (h *TradeHandler) Update(c *gin.Context) {
userID := c.GetString("user_id")
if userID == "" {
response.Unauthorized(c, "请先登录")
return
}
id := c.Param("id")
var req dto.UpdateTradeItemRequest
if err := c.ShouldBindJSON(&req); err != nil {
response.BadRequest(c, err.Error())
return
}
if err := h.tradeService.Update(c.Request.Context(), userID, id, req); err != nil {
response.HandleError(c, err, "更新交易失败")
return
}
var currentUserIDPtr *string
if userID != "" {
currentUserIDPtr = &userID
}
detail, err := h.tradeService.GetByID(c.Request.Context(), id, currentUserIDPtr)
if err != nil {
response.Success(c, nil)
return
}
response.Success(c, detail)
}
func (h *TradeHandler) Delete(c *gin.Context) {
userID := c.GetString("user_id")
if userID == "" {
response.Unauthorized(c, "请先登录")
return
}
id := c.Param("id")
if err := h.tradeService.Delete(c.Request.Context(), userID, id); err != nil {
response.HandleError(c, err, "删除交易失败")
return
}
response.Success(c, nil)
}
func (h *TradeHandler) UpdateStatus(c *gin.Context) {
userID := c.GetString("user_id")
if userID == "" {
response.Unauthorized(c, "请先登录")
return
}
id := c.Param("id")
var req dto.UpdateTradeItemStatusRequest
if err := c.ShouldBindJSON(&req); err != nil {
response.BadRequest(c, err.Error())
return
}
if err := h.tradeService.UpdateStatus(c.Request.Context(), userID, id, model.TradeItemStatus(req.Status)); err != nil {
response.HandleError(c, err, "更新交易状态失败")
return
}
response.Success(c, nil)
}
func (h *TradeHandler) RecordView(c *gin.Context) {
id := c.Param("id")
_ = h.tradeService.RecordView(c.Request.Context(), id)
response.Success(c, nil)
}
func (h *TradeHandler) Favorite(c *gin.Context) {
userID := c.GetString("user_id")
if userID == "" {
response.Unauthorized(c, "请先登录")
return
}
id := c.Param("id")
if err := h.tradeService.Favorite(c.Request.Context(), userID, id); err != nil {
response.HandleError(c, err, "收藏失败")
return
}
response.Success(c, dto.TradeFavoriteResponse{Favorited: true})
}
func (h *TradeHandler) Unfavorite(c *gin.Context) {
userID := c.GetString("user_id")
if userID == "" {
response.Unauthorized(c, "请先登录")
return
}
id := c.Param("id")
if err := h.tradeService.Unfavorite(c.Request.Context(), userID, id); err != nil {
response.HandleError(c, err, "取消收藏失败")
return
}
response.Success(c, dto.TradeFavoriteResponse{Favorited: false})
}