441 lines
10 KiB
Go
441 lines
10 KiB
Go
package shopeeproduct
|
|
|
|
import (
|
|
"encoding/json"
|
|
"errors"
|
|
"io"
|
|
"net/http"
|
|
"strconv"
|
|
"strings"
|
|
|
|
"github.com/gin-gonic/gin"
|
|
"github.com/go-admin-team/go-admin-core/sdk/pkg"
|
|
"gorm.io/gorm"
|
|
)
|
|
|
|
type Handler struct{ DB *gorm.DB }
|
|
|
|
func (handler Handler) List(c *gin.Context) {
|
|
page, err := queryInt(c.Query("page"), 1)
|
|
if err != nil {
|
|
writeError(c, invalidRequest("page 必须是正整数"))
|
|
return
|
|
}
|
|
pageSize, err := queryInt(c.Query("pageSize"), 20)
|
|
if err != nil {
|
|
writeError(c, invalidRequest("pageSize 必须是正整数"))
|
|
return
|
|
}
|
|
service, ok := handler.service(c)
|
|
if !ok {
|
|
return
|
|
}
|
|
response, err := service.List(c.Request.Context(), ListRequest{
|
|
Page: page, PageSize: pageSize, Keyword: c.Query("keyword"), Status: strings.TrimSpace(c.Query("status")),
|
|
})
|
|
if err != nil {
|
|
writeError(c, err)
|
|
return
|
|
}
|
|
c.JSON(http.StatusOK, gin.H{"code": 200, "data": response})
|
|
}
|
|
|
|
func (handler Handler) Create(c *gin.Context) {
|
|
var request CreateRequest
|
|
if err := decodeJSON(c, &request); err != nil {
|
|
writeError(c, invalidRequest("请求 JSON 无效"))
|
|
return
|
|
}
|
|
service, ok := handler.service(c)
|
|
if !ok {
|
|
return
|
|
}
|
|
response, err := service.Create(c.Request.Context(), request)
|
|
respond(c, response, err)
|
|
}
|
|
|
|
func (handler Handler) Detail(c *gin.Context) {
|
|
id, ok := handler.pathID(c)
|
|
if !ok {
|
|
return
|
|
}
|
|
service, ok := handler.service(c)
|
|
if !ok {
|
|
return
|
|
}
|
|
response, err := service.Detail(c.Request.Context(), id)
|
|
respond(c, response, err)
|
|
}
|
|
|
|
func (handler Handler) Update(c *gin.Context) {
|
|
id, ok := handler.pathID(c)
|
|
if !ok {
|
|
return
|
|
}
|
|
var request UpdateRequest
|
|
if err := decodeJSON(c, &request); err != nil {
|
|
writeError(c, invalidRequest("请求 JSON 无效"))
|
|
return
|
|
}
|
|
service, ok := handler.service(c)
|
|
if !ok {
|
|
return
|
|
}
|
|
response, err := service.Update(c.Request.Context(), id, request)
|
|
respond(c, response, err)
|
|
}
|
|
|
|
func (handler Handler) LinkPDD(c *gin.Context) {
|
|
id, ok := handler.pathID(c)
|
|
if !ok {
|
|
return
|
|
}
|
|
var request LinkPDDRequest
|
|
if err := decodeJSON(c, &request); err != nil {
|
|
writeError(c, invalidRequest("请求 JSON 无效"))
|
|
return
|
|
}
|
|
service, ok := handler.service(c)
|
|
if !ok {
|
|
return
|
|
}
|
|
response, err := service.LinkPDD(c.Request.Context(), id, request)
|
|
respond(c, response, err)
|
|
}
|
|
|
|
func (handler Handler) AddSpecValue(c *gin.Context) {
|
|
id, ok := handler.pathID(c)
|
|
if !ok {
|
|
return
|
|
}
|
|
var request AddSpecValueRequest
|
|
if err := decodeJSON(c, &request); err != nil {
|
|
writeError(c, invalidRequest("请求 JSON 无效"))
|
|
return
|
|
}
|
|
service, ok := handler.service(c)
|
|
if !ok {
|
|
return
|
|
}
|
|
response, err := service.AddSpecValue(c.Request.Context(), id, request)
|
|
respond(c, response, err)
|
|
}
|
|
|
|
func (handler Handler) RemoveSpecValue(c *gin.Context) {
|
|
id, ok := handler.pathID(c)
|
|
if !ok {
|
|
return
|
|
}
|
|
var request RemoveSpecValueRequest
|
|
if err := decodeJSON(c, &request); err != nil {
|
|
writeError(c, invalidRequest("请求 JSON 无效"))
|
|
return
|
|
}
|
|
service, ok := handler.service(c)
|
|
if !ok {
|
|
return
|
|
}
|
|
response, err := service.RemoveSpecValue(c.Request.Context(), id, request)
|
|
respond(c, response, err)
|
|
}
|
|
|
|
func (handler Handler) SetMapping(c *gin.Context) {
|
|
id, ok := handler.pathID(c)
|
|
if !ok {
|
|
return
|
|
}
|
|
var request SetMappingRequest
|
|
if err := decodeJSON(c, &request); err != nil {
|
|
writeError(c, invalidRequest("请求 JSON 无效"))
|
|
return
|
|
}
|
|
service, ok := handler.service(c)
|
|
if !ok {
|
|
return
|
|
}
|
|
response, err := service.SetMapping(c.Request.Context(), id, request)
|
|
respond(c, response, err)
|
|
}
|
|
|
|
func (handler Handler) ClearMapping(c *gin.Context) {
|
|
id, ok := handler.pathID(c)
|
|
if !ok {
|
|
return
|
|
}
|
|
var request ClearMappingRequest
|
|
if err := decodeJSON(c, &request); err != nil {
|
|
writeError(c, invalidRequest("请求 JSON 无效"))
|
|
return
|
|
}
|
|
service, ok := handler.service(c)
|
|
if !ok {
|
|
return
|
|
}
|
|
response, err := service.ClearMapping(c.Request.Context(), id, request)
|
|
respond(c, response, err)
|
|
}
|
|
|
|
func (handler Handler) ConfirmMapping(c *gin.Context) {
|
|
id, ok := handler.pathID(c)
|
|
if !ok {
|
|
return
|
|
}
|
|
var request ConfirmMappingRequest
|
|
if err := decodeJSON(c, &request); err != nil {
|
|
writeError(c, invalidRequest("请求 JSON 无效"))
|
|
return
|
|
}
|
|
service, ok := handler.service(c)
|
|
if !ok {
|
|
return
|
|
}
|
|
response, err := service.ConfirmMapping(c.Request.Context(), id, request)
|
|
respond(c, response, err)
|
|
}
|
|
|
|
func (handler Handler) ConfirmExactMatches(c *gin.Context) {
|
|
id, ok := handler.pathID(c)
|
|
if !ok {
|
|
return
|
|
}
|
|
var request struct {
|
|
RequestID string `json:"requestId"`
|
|
}
|
|
if err := decodeJSON(c, &request); err != nil {
|
|
writeError(c, invalidRequest("请求 JSON 无效"))
|
|
return
|
|
}
|
|
service, ok := handler.service(c)
|
|
if !ok {
|
|
return
|
|
}
|
|
response, err := service.ConfirmAllExactMatches(c.Request.Context(), id, request.RequestID)
|
|
respond(c, response, err)
|
|
}
|
|
|
|
func (handler Handler) PreviewAutoSizeMatches(c *gin.Context) {
|
|
id, ok := handler.pathID(c)
|
|
if !ok {
|
|
return
|
|
}
|
|
service, ok := handler.service(c)
|
|
if !ok {
|
|
return
|
|
}
|
|
response, err := service.PreviewAutoSizeMatches(c.Request.Context(), id)
|
|
if err != nil {
|
|
writeError(c, err)
|
|
return
|
|
}
|
|
c.JSON(http.StatusOK, gin.H{"code": 200, "data": response})
|
|
}
|
|
|
|
type suggestMappingRequest struct {
|
|
SpecContextVersion string `json:"specContextVersion"`
|
|
}
|
|
|
|
func (handler Handler) SuggestColorMappings(c *gin.Context) {
|
|
id, ok := handler.pathID(c)
|
|
if !ok {
|
|
return
|
|
}
|
|
var request suggestMappingRequest
|
|
if err := decodeJSON(c, &request); err != nil {
|
|
writeError(c, invalidRequest("请求 JSON 无效"))
|
|
return
|
|
}
|
|
service, ok := handler.service(c)
|
|
if !ok {
|
|
return
|
|
}
|
|
response, err := service.SuggestColorMappings(c.Request.Context(), id, request.SpecContextVersion)
|
|
if err != nil {
|
|
writeError(c, err)
|
|
return
|
|
}
|
|
c.JSON(http.StatusOK, gin.H{"code": 200, "data": response})
|
|
}
|
|
|
|
func (handler Handler) SuggestSizeMappings(c *gin.Context) {
|
|
id, ok := handler.pathID(c)
|
|
if !ok {
|
|
return
|
|
}
|
|
var request suggestMappingRequest
|
|
if err := decodeJSON(c, &request); err != nil {
|
|
writeError(c, invalidRequest("请求 JSON 无效"))
|
|
return
|
|
}
|
|
service, ok := handler.service(c)
|
|
if !ok {
|
|
return
|
|
}
|
|
response, err := service.SuggestSizeMappings(c.Request.Context(), id, request.SpecContextVersion)
|
|
if err != nil {
|
|
writeError(c, err)
|
|
return
|
|
}
|
|
c.JSON(http.StatusOK, gin.H{"code": 200, "data": response})
|
|
}
|
|
|
|
func (handler Handler) AutoMatchMappings(c *gin.Context) {
|
|
id, ok := handler.pathID(c)
|
|
if !ok {
|
|
return
|
|
}
|
|
var request AutoMatchRequest
|
|
if err := decodeJSON(c, &request); err != nil {
|
|
writeError(c, invalidRequest("请求 JSON 无效"))
|
|
return
|
|
}
|
|
service, ok := handler.service(c)
|
|
if !ok {
|
|
return
|
|
}
|
|
response, err := service.AutoMatchMappings(c.Request.Context(), id, request)
|
|
if err != nil {
|
|
writeError(c, err)
|
|
return
|
|
}
|
|
c.JSON(http.StatusOK, gin.H{"code": 200, "data": response})
|
|
}
|
|
|
|
func (handler Handler) BatchDelete(c *gin.Context) {
|
|
var request BatchDeleteRequest
|
|
if err := decodeJSON(c, &request); err != nil {
|
|
writeError(c, invalidRequest("请求 JSON 无效"))
|
|
return
|
|
}
|
|
service, ok := handler.service(c)
|
|
if !ok {
|
|
return
|
|
}
|
|
operatorID := currentUserID(c)
|
|
results, err := service.BatchSoftDelete(c.Request.Context(), operatorID, request)
|
|
if err != nil {
|
|
writeError(c, err)
|
|
return
|
|
}
|
|
c.JSON(http.StatusOK, gin.H{"code": 200, "data": gin.H{"results": results}})
|
|
}
|
|
|
|
func (handler Handler) Restore(c *gin.Context) {
|
|
id, ok := handler.pathID(c)
|
|
if !ok {
|
|
return
|
|
}
|
|
var request RestoreRequest
|
|
if err := decodeJSON(c, &request); err != nil {
|
|
writeError(c, invalidRequest("请求 JSON 无效"))
|
|
return
|
|
}
|
|
service, ok := handler.service(c)
|
|
if !ok {
|
|
return
|
|
}
|
|
response, err := service.Restore(c.Request.Context(), id, request)
|
|
respond(c, response, err)
|
|
}
|
|
|
|
func (handler Handler) pathID(c *gin.Context) (uint64, bool) {
|
|
id, err := strconv.ParseUint(c.Param("productId"), 10, 64)
|
|
if err != nil || id == 0 {
|
|
writeError(c, invalidRequest("productId 无效"))
|
|
return 0, false
|
|
}
|
|
return id, true
|
|
}
|
|
|
|
func (handler Handler) service(c *gin.Context) (*Service, bool) {
|
|
db := handler.DB
|
|
var err error
|
|
if db == nil {
|
|
db, err = pkg.GetOrm(c)
|
|
}
|
|
if err != nil {
|
|
writeError(c, internalError(err))
|
|
return nil, false
|
|
}
|
|
return NewService(db), true
|
|
}
|
|
|
|
// currentUserID reads the authenticated admin user id for delete audit
|
|
// (deleted_by). Batch soft delete and restore are open to both admin and
|
|
// purchasing roles per #40; role membership itself is enforced by the router
|
|
// middleware, not here.
|
|
func currentUserID(c *gin.Context) uint64 {
|
|
value, exists := c.Get("userId")
|
|
if !exists {
|
|
return 0
|
|
}
|
|
switch id := value.(type) {
|
|
case int:
|
|
return uint64(id)
|
|
case int64:
|
|
return uint64(id)
|
|
case uint64:
|
|
return id
|
|
case float64:
|
|
return uint64(id)
|
|
default:
|
|
return 0
|
|
}
|
|
}
|
|
|
|
func respond(c *gin.Context, response SaveResponse, err error) {
|
|
if err != nil {
|
|
writeError(c, err)
|
|
return
|
|
}
|
|
c.JSON(http.StatusOK, gin.H{"code": 200, "data": response})
|
|
}
|
|
|
|
func decodeJSON(c *gin.Context, request any) error {
|
|
c.Request.Body = http.MaxBytesReader(c.Writer, c.Request.Body, 1<<20)
|
|
decoder := json.NewDecoder(c.Request.Body)
|
|
decoder.DisallowUnknownFields()
|
|
if err := decoder.Decode(request); err != nil {
|
|
return err
|
|
}
|
|
if err := decoder.Decode(&struct{}{}); !errors.Is(err, io.EOF) {
|
|
return errors.New("one object required")
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func queryInt(value string, fallback int) (int, error) {
|
|
if strings.TrimSpace(value) == "" {
|
|
return fallback, nil
|
|
}
|
|
parsed, err := strconv.Atoi(value)
|
|
if err != nil || parsed < 1 {
|
|
return 0, errors.New("invalid integer")
|
|
}
|
|
return parsed, nil
|
|
}
|
|
|
|
func writeError(c *gin.Context, err error) {
|
|
var target *ServiceError
|
|
if !errors.As(err, &target) {
|
|
target = internalError(err).(*ServiceError)
|
|
}
|
|
status := http.StatusInternalServerError
|
|
switch target.Code {
|
|
case CodeInvalidRequest, CodeValueNotManual:
|
|
status = http.StatusUnprocessableEntity
|
|
case CodeItemIDExists:
|
|
status = http.StatusConflict
|
|
case CodeProductNotFound, CodePDDProductNotFound, CodeMappingNotFound, CodeValueNotFound:
|
|
status = http.StatusNotFound
|
|
case CodePDDProductDisabled, CodeSpecContextStale:
|
|
status = http.StatusConflict
|
|
case CodeAIUnavailable:
|
|
status = http.StatusServiceUnavailable
|
|
}
|
|
response := gin.H{"code": target.Code, "message": target.Message, "retryable": target.Retryable}
|
|
if target.ExistingItemID > 0 {
|
|
response["existingProductId"] = target.ExistingItemID
|
|
}
|
|
c.JSON(status, response)
|
|
}
|