S SmartDocs
シリーズ: Go go 184 行 · 更新日 2026-04-18

goods_handler.go

Go/Baasid/internal/handlers/goods_handler.go

package handlers

import (
	"errors"
	"net/http"

	"baasid/internal/dto"
	"baasid/internal/middleware"
	"baasid/internal/models"
	"baasid/internal/repository"

	"github.com/google/uuid"
	"github.com/labstack/echo/v4"
	"gorm.io/gorm"
)

// GoodsHandler implements authenticated goods CRUD routes.
//
// Every handler assumes JWTAuth middleware has already validated the token
// and stored the operator's UUID in the Echo context (see middleware package).
type GoodsHandler struct {
	goods *repository.GoodsRepository
}

// NewGoodsHandler constructs the handler with its repository dependency.
func NewGoodsHandler(goods *repository.GoodsRepository) *GoodsHandler {
	return &GoodsHandler{goods: goods}
}

// toResponse maps a persistence model to the public JSON shape from the spec
// (_id + goods_name).
func toResponse(g *models.Goods) dto.GoodsResponse {
	return dto.GoodsResponse{
		ID:        g.ID.String(),
		GoodsName: g.Name,
	}
}

// Add godoc
// @Summary      新增商品
// @Description  建立一筆商品;需帶入有效 JWT。成功回傳新商品的 `_id` 與 `goods_name`。
// @Tags         goods
// @Accept       json
// @Produce      json
// @Security     BearerAuth
// @Param        body  body      dto.GoodsCreateRequest  true  "商品名稱 goods_name"
// @Success      200   {object}  dto.GoodsResponse       "成功"
// @Failure      403   {object}  dto.ErrorMessage        "權限不足(未帶 Token 或 Token 無效)"
// @Failure      500   {object}  dto.ErrorMessage        "系統錯誤"
// @Router       /goods/add [post]
func (h *GoodsHandler) Add(c echo.Context) error {
	var req dto.GoodsCreateRequest
	if err := c.Bind(&req); err != nil {
		return echo.NewHTTPError(http.StatusBadRequest, dto.ErrorMessage{Message: "無效的 JSON 格式"})
	}
	if req.GoodsName == "" {
		return echo.NewHTTPError(http.StatusBadRequest, dto.ErrorMessage{Message: "goods_name 不可為空"})
	}

	operator := middleware.GetUserID(c)
	g, err := h.goods.Create(operator, req.GoodsName)
	if err != nil {
		c.Logger().Errorf("goods create: %v", err)
		return echo.NewHTTPError(http.StatusInternalServerError, dto.ErrorMessage{Message: "系統錯誤"})
	}
	return c.JSON(http.StatusOK, toResponse(g))
}

// List godoc
// @Summary      取得所有商品
// @Description  回傳陣列,每個元素為 `_id` 與 `goods_name`。
// @Tags         goods
// @Produce      json
// @Security     BearerAuth
// @Success      200  {array}   dto.GoodsResponse  "成功"
// @Failure      403  {object}  dto.ErrorMessage   "權限不足"
// @Failure      500  {object}  dto.ErrorMessage   "系統錯誤"
// @Router       /goods [get]
func (h *GoodsHandler) List(c echo.Context) error {
	list, err := h.goods.ListAll()
	if err != nil {
		c.Logger().Errorf("goods list: %v", err)
		return echo.NewHTTPError(http.StatusInternalServerError, dto.ErrorMessage{Message: "系統錯誤"})
	}
	out := make([]dto.GoodsResponse, 0, len(list))
	for i := range list {
		out = append(out, toResponse(&list[i]))
	}
	return c.JSON(http.StatusOK, out)
}

// GetByID godoc
// @Summary      取得指定商品
// @Description  路徑參數 `id` 為商品 UUID(字串格式)。
// @Tags         goods
// @Produce      json
// @Security     BearerAuth
// @Param        id   path      string  true  "商品代號 (UUID)"
// @Success      200  {object}  dto.GoodsResponse  "成功"
// @Failure      403  {object}  dto.ErrorMessage   "權限不足"
// @Failure      404  {object}  dto.ErrorMessage   "找不到商品"
// @Failure      500  {object}  dto.ErrorMessage   "系統錯誤"
// @Router       /goods/{id} [get]
func (h *GoodsHandler) GetByID(c echo.Context) error {
	id, err := uuid.Parse(c.Param("id"))
	if err != nil {
		return echo.NewHTTPError(http.StatusBadRequest, dto.ErrorMessage{Message: "id 必須為 UUID"})
	}
	g, err := h.goods.FindByID(id)
	if err != nil {
		if errors.Is(err, gorm.ErrRecordNotFound) {
			return echo.NewHTTPError(http.StatusNotFound, dto.ErrorMessage{Message: "找不到商品"})
		}
		c.Logger().Errorf("goods get: %v", err)
		return echo.NewHTTPError(http.StatusInternalServerError, dto.ErrorMessage{Message: "系統錯誤"})
	}
	return c.JSON(http.StatusOK, toResponse(g))
}

// Update godoc
// @Summary      更新商品
// @Description  依 `id` 更新商品名稱;`up_user` / `up_datetime` 由後端寫入。
// @Tags         goods
// @Accept       json
// @Produce      json
// @Security     BearerAuth
// @Param        id    path      string                 true  "商品代號 (UUID)"
// @Param        body  body      dto.GoodsUpdateRequest true  "新商品名稱"
// @Success      200   {object}  dto.GoodsResponse      "成功"
// @Failure      403   {object}  dto.ErrorMessage       "權限不足"
// @Failure      404   {object}  dto.ErrorMessage       "找不到商品"
// @Failure      500   {object}  dto.ErrorMessage       "系統錯誤"
// @Router       /goods/{id} [put]
func (h *GoodsHandler) Update(c echo.Context) error {
	id, err := uuid.Parse(c.Param("id"))
	if err != nil {
		return echo.NewHTTPError(http.StatusBadRequest, dto.ErrorMessage{Message: "id 必須為 UUID"})
	}
	var req dto.GoodsUpdateRequest
	if err := c.Bind(&req); err != nil {
		return echo.NewHTTPError(http.StatusBadRequest, dto.ErrorMessage{Message: "無效的 JSON 格式"})
	}
	if req.GoodsName == "" {
		return echo.NewHTTPError(http.StatusBadRequest, dto.ErrorMessage{Message: "goods_name 不可為空"})
	}

	operator := middleware.GetUserID(c)
	g, err := h.goods.UpdateName(id, operator, req.GoodsName)
	if err != nil {
		if errors.Is(err, gorm.ErrRecordNotFound) {
			return echo.NewHTTPError(http.StatusNotFound, dto.ErrorMessage{Message: "找不到商品"})
		}
		c.Logger().Errorf("goods update: %v", err)
		return echo.NewHTTPError(http.StatusInternalServerError, dto.ErrorMessage{Message: "系統錯誤"})
	}
	return c.JSON(http.StatusOK, toResponse(g))
}

// Delete godoc
// @Summary      刪除商品
// @Description  依 `id` 刪除商品;成功僅回 HTTP 200(無 Body 亦可,此處回空 JSON 物件方便客戶端解析)。
// @Tags         goods
// @Security     BearerAuth
// @Param        id   path      string  true  "商品代號 (UUID)"
// @Success      200  {object}  map[string]string  "成功,例如 {\"status\":\"ok\"}"
// @Failure      403  {object}  dto.ErrorMessage   "權限不足"
// @Failure      404  {object}  dto.ErrorMessage   "找不到商品"
// @Failure      500  {object}  dto.ErrorMessage   "系統錯誤"
// @Router       /goods/{id} [delete]
func (h *GoodsHandler) Delete(c echo.Context) error {
	id, err := uuid.Parse(c.Param("id"))
	if err != nil {
		return echo.NewHTTPError(http.StatusBadRequest, dto.ErrorMessage{Message: "id 必須為 UUID"})
	}
	if err := h.goods.DeleteByID(id); err != nil {
		if errors.Is(err, gorm.ErrRecordNotFound) {
			return echo.NewHTTPError(http.StatusNotFound, dto.ErrorMessage{Message: "找不到商品"})
		}
		c.Logger().Errorf("goods delete: %v", err)
		return echo.NewHTTPError(http.StatusInternalServerError, dto.ErrorMessage{Message: "系統錯誤"})
	}
	// Spec: HTTP 200 on success. Empty JSON keeps Content-Type consistent.
	return c.JSON(http.StatusOK, echo.Map{})
}

関連記事