511 lines
18 KiB
Go
511 lines
18 KiB
Go
package handler
|
|
|
|
import (
|
|
"errors"
|
|
"net/http"
|
|
"strconv"
|
|
"strings"
|
|
"time"
|
|
|
|
"github.com/gin-gonic/gin"
|
|
|
|
"wx_service/internal/middleware"
|
|
"wx_service/internal/model"
|
|
quitservice "wx_service/internal/quitcheckin/service"
|
|
)
|
|
|
|
const dateLayout = "2006-01-02"
|
|
|
|
type Handler struct {
|
|
service *quitservice.Service
|
|
}
|
|
|
|
// NewHandler 创建无烟打卡 V2 接口处理器。
|
|
func NewHandler(service *quitservice.Service) *Handler {
|
|
return &Handler{service: service}
|
|
}
|
|
|
|
type upsertProfileRequest struct {
|
|
QuitStartDate *string `json:"quit_start_date"`
|
|
PackPriceCent *int `json:"pack_price_cent"`
|
|
BaselineCigsPerDay *int `json:"baseline_cigs_per_day"`
|
|
Motivation *string `json:"motivation"`
|
|
NotifyTime *string `json:"notify_time"`
|
|
}
|
|
|
|
// GetProfile 获取当前用户的 V2 基础资料与旅程摘要。
|
|
// @Summary 获取无烟打卡资料
|
|
// @Description 返回用户基础资料与当前旅程概览,用于“我的”页面首屏展示。
|
|
// @Tags QuitCheckin
|
|
// @Produce json
|
|
// @Success 200 {object} model.Response
|
|
// @Failure 500 {object} model.Response
|
|
// @Router /api/v2/profile [get]
|
|
func (h *Handler) GetProfile(c *gin.Context) {
|
|
user := middleware.MustCurrentUser(c)
|
|
view, err := h.service.GetProfile(c.Request.Context(), int(user.ID), user.NickName, user.AvatarURL, time.Now())
|
|
if err != nil {
|
|
c.JSON(http.StatusInternalServerError, model.Error(http.StatusInternalServerError, "获取资料失败,请稍后重试"))
|
|
return
|
|
}
|
|
c.JSON(http.StatusOK, model.Success(view))
|
|
}
|
|
|
|
// UpsertProfile 新增或更新无烟打卡资料。
|
|
// @Summary 保存无烟打卡资料
|
|
// @Description 保存戒烟开始日期、戒烟前日均支数、每包单价、初心和提醒时间。
|
|
// @Tags QuitCheckin
|
|
// @Accept json
|
|
// @Produce json
|
|
// @Param body body upsertProfileRequest true "无烟打卡资料"
|
|
// @Success 200 {object} model.Response
|
|
// @Failure 400 {object} model.Response
|
|
// @Failure 500 {object} model.Response
|
|
// @Router /api/v2/profile [post]
|
|
func (h *Handler) UpsertProfile(c *gin.Context) {
|
|
user := middleware.MustCurrentUser(c)
|
|
|
|
var req upsertProfileRequest
|
|
if err := c.ShouldBindJSON(&req); err != nil {
|
|
c.JSON(http.StatusBadRequest, model.Error(http.StatusBadRequest, "请求参数错误"))
|
|
return
|
|
}
|
|
|
|
var quitStartDate *time.Time
|
|
if req.QuitStartDate != nil {
|
|
parsed, err := time.ParseInLocation(dateLayout, strings.TrimSpace(*req.QuitStartDate), time.Local)
|
|
if err != nil {
|
|
c.JSON(http.StatusBadRequest, model.Error(http.StatusBadRequest, "quit_start_date 格式错误,应为 YYYY-MM-DD"))
|
|
return
|
|
}
|
|
quitStartDate = &parsed
|
|
}
|
|
if req.BaselineCigsPerDay != nil && (*req.BaselineCigsPerDay <= 0 || *req.BaselineCigsPerDay > 200) {
|
|
c.JSON(http.StatusBadRequest, model.Error(http.StatusBadRequest, "baseline_cigs_per_day 应在 1~200"))
|
|
return
|
|
}
|
|
if req.PackPriceCent != nil && (*req.PackPriceCent < 0 || *req.PackPriceCent > 999999) {
|
|
c.JSON(http.StatusBadRequest, model.Error(http.StatusBadRequest, "pack_price_cent 应在 0~999999"))
|
|
return
|
|
}
|
|
if req.NotifyTime != nil && !quitservice.ParseNotifyTime(*req.NotifyTime) {
|
|
c.JSON(http.StatusBadRequest, model.Error(http.StatusBadRequest, "notify_time 格式错误,应为 HH:MM"))
|
|
return
|
|
}
|
|
|
|
view, err := h.service.UpsertProfile(c.Request.Context(), int(user.ID), quitservice.UpsertProfileRequest{
|
|
QuitStartDate: quitStartDate,
|
|
PackPriceCent: req.PackPriceCent,
|
|
BaselineCigsPerDay: req.BaselineCigsPerDay,
|
|
Motivation: req.Motivation,
|
|
NotifyTime: req.NotifyTime,
|
|
}, user.NickName, user.AvatarURL, time.Now())
|
|
if err != nil {
|
|
c.JSON(http.StatusInternalServerError, model.Error(http.StatusInternalServerError, "保存资料失败,请稍后重试"))
|
|
return
|
|
}
|
|
c.JSON(http.StatusOK, model.Success(view))
|
|
}
|
|
|
|
// Home 获取首页聚合数据。
|
|
// @Summary 获取首页数据
|
|
// @Description 返回今日状态、连续天数、成果摘要、梦想目标和勋章计数。
|
|
// @Tags QuitCheckin
|
|
// @Produce json
|
|
// @Success 200 {object} model.Response
|
|
// @Failure 400 {object} model.Response
|
|
// @Failure 500 {object} model.Response
|
|
// @Router /api/v2/checkin/home [get]
|
|
func (h *Handler) Home(c *gin.Context) {
|
|
user := middleware.MustCurrentUser(c)
|
|
view, err := h.service.Home(c.Request.Context(), int(user.ID), time.Now())
|
|
if err != nil {
|
|
if errors.Is(err, quitservice.ErrProfileNotFound) {
|
|
c.JSON(http.StatusBadRequest, model.Error(http.StatusBadRequest, "请先完善基础资料"))
|
|
return
|
|
}
|
|
c.JSON(http.StatusInternalServerError, model.Error(http.StatusInternalServerError, "获取首页数据失败,请稍后重试"))
|
|
return
|
|
}
|
|
c.JSON(http.StatusOK, model.Success(view))
|
|
}
|
|
|
|
type checkinRequest struct {
|
|
Date string `json:"date"`
|
|
Note string `json:"note"`
|
|
}
|
|
|
|
// Checkin 执行当日打卡。
|
|
// @Summary 今日打卡
|
|
// @Description 同一自然日只允许一次成功打卡;若当日已复吸则不允许再次打卡。
|
|
// @Tags QuitCheckin
|
|
// @Accept json
|
|
// @Produce json
|
|
// @Param body body checkinRequest true "打卡请求"
|
|
// @Success 200 {object} model.Response
|
|
// @Failure 400 {object} model.Response
|
|
// @Failure 409 {object} model.Response
|
|
// @Failure 500 {object} model.Response
|
|
// @Router /api/v2/checkin/check [post]
|
|
func (h *Handler) Checkin(c *gin.Context) {
|
|
user := middleware.MustCurrentUser(c)
|
|
|
|
var req checkinRequest
|
|
if err := c.ShouldBindJSON(&req); err != nil {
|
|
c.JSON(http.StatusBadRequest, model.Error(http.StatusBadRequest, "请求参数错误"))
|
|
return
|
|
}
|
|
|
|
targetDate := time.Now()
|
|
if strings.TrimSpace(req.Date) != "" {
|
|
parsed, err := time.ParseInLocation(dateLayout, strings.TrimSpace(req.Date), time.Local)
|
|
if err != nil {
|
|
c.JSON(http.StatusBadRequest, model.Error(http.StatusBadRequest, "date 格式错误,应为 YYYY-MM-DD"))
|
|
return
|
|
}
|
|
targetDate = parsed
|
|
}
|
|
|
|
result, err := h.service.Checkin(c.Request.Context(), int(user.ID), quitservice.CheckinRequest{
|
|
Date: targetDate,
|
|
Note: req.Note,
|
|
}, time.Now())
|
|
if err != nil {
|
|
if errors.Is(err, quitservice.ErrProfileNotFound) {
|
|
c.JSON(http.StatusBadRequest, model.Error(http.StatusBadRequest, "请先完善基础资料"))
|
|
return
|
|
}
|
|
if errors.Is(err, quitservice.ErrAlreadyRelapsed) {
|
|
c.JSON(http.StatusConflict, model.Error(http.StatusConflict, "今天已记录复吸,无法再次打卡"))
|
|
return
|
|
}
|
|
c.JSON(http.StatusInternalServerError, model.Error(http.StatusInternalServerError, "打卡失败,请稍后重试"))
|
|
return
|
|
}
|
|
|
|
c.JSON(http.StatusOK, model.Success(result))
|
|
}
|
|
|
|
type relapseRequest struct {
|
|
Date string `json:"date"`
|
|
RelapseAt string `json:"relapse_at"`
|
|
RelapseNum int `json:"relapse_num"`
|
|
Reason string `json:"reason"`
|
|
Note string `json:"note"`
|
|
}
|
|
|
|
// Relapse 记录当日复吸。
|
|
// @Summary 记录复吸
|
|
// @Description 当日复吸会将连续天数清零,并更新当日状态为 relapsed。
|
|
// @Tags QuitCheckin
|
|
// @Accept json
|
|
// @Produce json
|
|
// @Param body body relapseRequest true "复吸请求"
|
|
// @Success 200 {object} model.Response
|
|
// @Failure 400 {object} model.Response
|
|
// @Failure 409 {object} model.Response
|
|
// @Failure 500 {object} model.Response
|
|
// @Router /api/v2/checkin/relapse [post]
|
|
func (h *Handler) Relapse(c *gin.Context) {
|
|
user := middleware.MustCurrentUser(c)
|
|
|
|
var req relapseRequest
|
|
if err := c.ShouldBindJSON(&req); err != nil {
|
|
c.JSON(http.StatusBadRequest, model.Error(http.StatusBadRequest, "请求参数错误"))
|
|
return
|
|
}
|
|
if req.RelapseNum <= 0 || req.RelapseNum > 200 {
|
|
c.JSON(http.StatusBadRequest, model.Error(http.StatusBadRequest, "relapse_num 应在 1~200"))
|
|
return
|
|
}
|
|
|
|
targetDate := time.Now()
|
|
if strings.TrimSpace(req.Date) != "" {
|
|
parsed, err := time.ParseInLocation(dateLayout, strings.TrimSpace(req.Date), time.Local)
|
|
if err != nil {
|
|
c.JSON(http.StatusBadRequest, model.Error(http.StatusBadRequest, "date 格式错误,应为 YYYY-MM-DD"))
|
|
return
|
|
}
|
|
targetDate = parsed
|
|
}
|
|
|
|
relapseAt := time.Now()
|
|
if strings.TrimSpace(req.RelapseAt) != "" {
|
|
parsed, err := time.Parse(time.RFC3339, strings.TrimSpace(req.RelapseAt))
|
|
if err != nil {
|
|
c.JSON(http.StatusBadRequest, model.Error(http.StatusBadRequest, "relapse_at 格式错误,应为 RFC3339"))
|
|
return
|
|
}
|
|
relapseAt = parsed.In(time.Local)
|
|
}
|
|
|
|
result, err := h.service.Relapse(c.Request.Context(), int(user.ID), quitservice.RelapseRequest{
|
|
Date: targetDate,
|
|
RelapseAt: relapseAt,
|
|
RelapseNum: req.RelapseNum,
|
|
Reason: req.Reason,
|
|
Note: req.Note,
|
|
}, time.Now())
|
|
if err != nil {
|
|
if errors.Is(err, quitservice.ErrProfileNotFound) {
|
|
c.JSON(http.StatusBadRequest, model.Error(http.StatusBadRequest, "请先完善基础资料"))
|
|
return
|
|
}
|
|
if errors.Is(err, quitservice.ErrAlreadyRelapsed) {
|
|
c.JSON(http.StatusConflict, model.Error(http.StatusConflict, "今天已记录复吸,请勿重复提交"))
|
|
return
|
|
}
|
|
c.JSON(http.StatusInternalServerError, model.Error(http.StatusInternalServerError, "记录复吸失败,请稍后重试"))
|
|
return
|
|
}
|
|
|
|
c.JSON(http.StatusOK, model.Success(result))
|
|
}
|
|
|
|
// StatsOverview 获取统计概览。
|
|
// @Summary 获取统计概览
|
|
// @Description 支持周、月、年三个维度,返回趋势、勋章和健康恢复摘要。
|
|
// @Tags QuitCheckin
|
|
// @Produce json
|
|
// @Param range query string false "统计范围:week|month|year"
|
|
// @Success 200 {object} model.Response
|
|
// @Failure 400 {object} model.Response
|
|
// @Failure 500 {object} model.Response
|
|
// @Router /api/v2/stats/overview [get]
|
|
func (h *Handler) StatsOverview(c *gin.Context) {
|
|
user := middleware.MustCurrentUser(c)
|
|
result, err := h.service.StatsOverview(c.Request.Context(), int(user.ID), c.DefaultQuery("range", "week"), time.Now())
|
|
if err != nil {
|
|
if errors.Is(err, quitservice.ErrProfileNotFound) {
|
|
c.JSON(http.StatusBadRequest, model.Error(http.StatusBadRequest, "请先完善基础资料"))
|
|
return
|
|
}
|
|
if errors.Is(err, quitservice.ErrInvalidRange) {
|
|
c.JSON(http.StatusBadRequest, model.Error(http.StatusBadRequest, "range 应为 week|month|year"))
|
|
return
|
|
}
|
|
c.JSON(http.StatusInternalServerError, model.Error(http.StatusInternalServerError, "获取统计失败,请稍后重试"))
|
|
return
|
|
}
|
|
c.JSON(http.StatusOK, model.Success(result))
|
|
}
|
|
|
|
// ListBadges 获取勋章列表。
|
|
// @Summary 获取勋章列表
|
|
// @Description 返回当前用户的已解锁/未解锁勋章清单。
|
|
// @Tags QuitCheckin
|
|
// @Produce json
|
|
// @Success 200 {object} model.Response
|
|
// @Failure 500 {object} model.Response
|
|
// @Router /api/v2/badges [get]
|
|
func (h *Handler) ListBadges(c *gin.Context) {
|
|
user := middleware.MustCurrentUser(c)
|
|
result, err := h.service.ListBadges(c.Request.Context(), int(user.ID), time.Now())
|
|
if err != nil {
|
|
c.JSON(http.StatusInternalServerError, model.Error(http.StatusInternalServerError, "获取勋章失败,请稍后重试"))
|
|
return
|
|
}
|
|
c.JSON(http.StatusOK, model.Success(result))
|
|
}
|
|
|
|
// ListRelapses 获取复吸历史列表。
|
|
// @Summary 获取复吸历史
|
|
// @Description 分页返回复吸记录,用于“统计与荣誉”页历史列表展示。
|
|
// @Tags QuitCheckin
|
|
// @Produce json
|
|
// @Param page query int false "页码"
|
|
// @Param page_size query int false "每页数量"
|
|
// @Success 200 {object} model.Response
|
|
// @Failure 500 {object} model.Response
|
|
// @Router /api/v2/relapses [get]
|
|
func (h *Handler) ListRelapses(c *gin.Context) {
|
|
user := middleware.MustCurrentUser(c)
|
|
page, _ := strconv.Atoi(c.DefaultQuery("page", "1"))
|
|
pageSize, _ := strconv.Atoi(c.DefaultQuery("page_size", "20"))
|
|
result, err := h.service.ListRelapses(c.Request.Context(), int(user.ID), page, pageSize)
|
|
if err != nil {
|
|
c.JSON(http.StatusInternalServerError, model.Error(http.StatusInternalServerError, "获取复吸记录失败,请稍后重试"))
|
|
return
|
|
}
|
|
c.JSON(http.StatusOK, model.Success(result))
|
|
}
|
|
|
|
// ListRewardGoals 获取梦想目标列表。
|
|
// @Summary 获取梦想目标列表
|
|
// @Description 按状态筛选梦想目标,用于梦想实验室页面展示。
|
|
// @Tags QuitCheckin
|
|
// @Produce json
|
|
// @Param status query string false "状态:active|completed|archived|all"
|
|
// @Success 200 {object} model.Response
|
|
// @Failure 500 {object} model.Response
|
|
// @Router /api/v2/reward-goals [get]
|
|
func (h *Handler) ListRewardGoals(c *gin.Context) {
|
|
user := middleware.MustCurrentUser(c)
|
|
result, err := h.service.ListRewardGoals(c.Request.Context(), int(user.ID), c.DefaultQuery("status", "active"), time.Now())
|
|
if err != nil {
|
|
c.JSON(http.StatusInternalServerError, model.Error(http.StatusInternalServerError, "获取梦想目标失败,请稍后重试"))
|
|
return
|
|
}
|
|
c.JSON(http.StatusOK, model.Success(result))
|
|
}
|
|
|
|
type createRewardGoalRequest struct {
|
|
Title string `json:"title"`
|
|
TargetAmountCent int `json:"target_amount_cent"`
|
|
CoverImage string `json:"cover_image"`
|
|
}
|
|
|
|
// CreateRewardGoal 创建梦想目标。
|
|
// @Summary 创建梦想目标
|
|
// @Description 创建一条新的财富奖励目标。
|
|
// @Tags QuitCheckin
|
|
// @Accept json
|
|
// @Produce json
|
|
// @Param body body createRewardGoalRequest true "梦想目标"
|
|
// @Success 200 {object} model.Response
|
|
// @Failure 400 {object} model.Response
|
|
// @Failure 500 {object} model.Response
|
|
// @Router /api/v2/reward-goals [post]
|
|
func (h *Handler) CreateRewardGoal(c *gin.Context) {
|
|
user := middleware.MustCurrentUser(c)
|
|
var req createRewardGoalRequest
|
|
if err := c.ShouldBindJSON(&req); err != nil {
|
|
c.JSON(http.StatusBadRequest, model.Error(http.StatusBadRequest, "请求参数错误"))
|
|
return
|
|
}
|
|
if strings.TrimSpace(req.Title) == "" {
|
|
c.JSON(http.StatusBadRequest, model.Error(http.StatusBadRequest, "title 不能为空"))
|
|
return
|
|
}
|
|
if req.TargetAmountCent <= 0 {
|
|
c.JSON(http.StatusBadRequest, model.Error(http.StatusBadRequest, "target_amount_cent 必须大于 0"))
|
|
return
|
|
}
|
|
|
|
result, err := h.service.CreateRewardGoal(c.Request.Context(), int(user.ID), quitservice.CreateRewardGoalRequest{
|
|
Title: req.Title,
|
|
TargetAmountCent: req.TargetAmountCent,
|
|
CoverImage: req.CoverImage,
|
|
}, time.Now())
|
|
if err != nil {
|
|
c.JSON(http.StatusInternalServerError, model.Error(http.StatusInternalServerError, "创建梦想目标失败,请稍后重试"))
|
|
return
|
|
}
|
|
c.JSON(http.StatusOK, model.Success(result))
|
|
}
|
|
|
|
type updateRewardGoalRequest struct {
|
|
Title *string `json:"title"`
|
|
TargetAmountCent *int `json:"target_amount_cent"`
|
|
CoverImage *string `json:"cover_image"`
|
|
Status *string `json:"status"`
|
|
}
|
|
|
|
// UpdateRewardGoal 更新梦想目标。
|
|
// @Summary 更新梦想目标
|
|
// @Description 支持更新标题、目标金额、封面和状态。
|
|
// @Tags QuitCheckin
|
|
// @Accept json
|
|
// @Produce json
|
|
// @Param id path int true "目标 ID"
|
|
// @Param body body updateRewardGoalRequest true "更新内容"
|
|
// @Success 200 {object} model.Response
|
|
// @Failure 400 {object} model.Response
|
|
// @Failure 404 {object} model.Response
|
|
// @Failure 500 {object} model.Response
|
|
// @Router /api/v2/reward-goals/{id} [put]
|
|
func (h *Handler) UpdateRewardGoal(c *gin.Context) {
|
|
user := middleware.MustCurrentUser(c)
|
|
id, err := strconv.Atoi(c.Param("id"))
|
|
if err != nil || id <= 0 {
|
|
c.JSON(http.StatusBadRequest, model.Error(http.StatusBadRequest, "id 参数错误"))
|
|
return
|
|
}
|
|
|
|
var req updateRewardGoalRequest
|
|
if err := c.ShouldBindJSON(&req); err != nil {
|
|
c.JSON(http.StatusBadRequest, model.Error(http.StatusBadRequest, "请求参数错误"))
|
|
return
|
|
}
|
|
|
|
result, err := h.service.UpdateRewardGoal(c.Request.Context(), int(user.ID), id, quitservice.UpdateRewardGoalRequest{
|
|
Title: req.Title,
|
|
TargetAmountCent: req.TargetAmountCent,
|
|
CoverImage: req.CoverImage,
|
|
Status: req.Status,
|
|
}, time.Now())
|
|
if err != nil {
|
|
if errors.Is(err, quitservice.ErrRewardGoalNotFound) {
|
|
c.JSON(http.StatusNotFound, model.Error(http.StatusNotFound, "梦想目标不存在"))
|
|
return
|
|
}
|
|
c.JSON(http.StatusInternalServerError, model.Error(http.StatusInternalServerError, "更新梦想目标失败,请稍后重试"))
|
|
return
|
|
}
|
|
c.JSON(http.StatusOK, model.Success(result))
|
|
}
|
|
|
|
// PosterData 获取海报预览数据。
|
|
// @Summary 获取海报数据
|
|
// @Description 返回海报预览所需的连续天数、已省金额和健康恢复信息。
|
|
// @Tags QuitCheckin
|
|
// @Produce json
|
|
// @Param template_code query string false "模板编码"
|
|
// @Success 200 {object} model.Response
|
|
// @Failure 400 {object} model.Response
|
|
// @Failure 500 {object} model.Response
|
|
// @Router /api/v2/poster/data [get]
|
|
func (h *Handler) PosterData(c *gin.Context) {
|
|
user := middleware.MustCurrentUser(c)
|
|
result, err := h.service.PosterData(c.Request.Context(), int(user.ID), user.NickName, c.DefaultQuery("template_code", "vibrant_1"), time.Now())
|
|
if err != nil {
|
|
if errors.Is(err, quitservice.ErrProfileNotFound) {
|
|
c.JSON(http.StatusBadRequest, model.Error(http.StatusBadRequest, "请先完善基础资料"))
|
|
return
|
|
}
|
|
c.JSON(http.StatusInternalServerError, model.Error(http.StatusInternalServerError, "获取海报数据失败,请稍后重试"))
|
|
return
|
|
}
|
|
c.JSON(http.StatusOK, model.Success(result))
|
|
}
|
|
|
|
type generatePosterRequest struct {
|
|
TemplateCode string `json:"template_code"`
|
|
ShowFields []string `json:"show_fields"`
|
|
}
|
|
|
|
// GeneratePoster 生成海报结果。
|
|
// @Summary 生成海报
|
|
// @Description 当前先返回可用的海报元数据,后续可替换为真实图片生成链路。
|
|
// @Tags QuitCheckin
|
|
// @Accept json
|
|
// @Produce json
|
|
// @Param body body generatePosterRequest true "海报生成请求"
|
|
// @Success 200 {object} model.Response
|
|
// @Failure 400 {object} model.Response
|
|
// @Failure 500 {object} model.Response
|
|
// @Router /api/v2/poster/generate [post]
|
|
func (h *Handler) GeneratePoster(c *gin.Context) {
|
|
user := middleware.MustCurrentUser(c)
|
|
|
|
var req generatePosterRequest
|
|
if err := c.ShouldBindJSON(&req); err != nil {
|
|
c.JSON(http.StatusBadRequest, model.Error(http.StatusBadRequest, "请求参数错误"))
|
|
return
|
|
}
|
|
|
|
result, err := h.service.GeneratePoster(c.Request.Context(), int(user.ID), user.NickName, quitservice.PosterGenerateRequest{
|
|
TemplateCode: req.TemplateCode,
|
|
ShowFields: req.ShowFields,
|
|
}, time.Now())
|
|
if err != nil {
|
|
if errors.Is(err, quitservice.ErrProfileNotFound) {
|
|
c.JSON(http.StatusBadRequest, model.Error(http.StatusBadRequest, "请先完善基础资料"))
|
|
return
|
|
}
|
|
c.JSON(http.StatusInternalServerError, model.Error(http.StatusInternalServerError, "海报生成失败,请稍后重试"))
|
|
return
|
|
}
|
|
c.JSON(http.StatusOK, model.Success(result))
|
|
}
|