feat(expiry): 完成 #25 用户设置接口实现
This commit is contained in:
@@ -52,6 +52,8 @@ func main() {
|
|||||||
&model.MiniProgram{},
|
&model.MiniProgram{},
|
||||||
&model.User{},
|
&model.User{},
|
||||||
&model.UserMembership{},
|
&model.UserMembership{},
|
||||||
|
&expiry.ExpiryItem{},
|
||||||
|
&expiry.ExpiryUserSettings{},
|
||||||
&membershipmodel.MembershipRedeemCode{},
|
&membershipmodel.MembershipRedeemCode{},
|
||||||
&membershipmodel.MembershipRedemption{},
|
&membershipmodel.MembershipRedemption{},
|
||||||
&rmmodel.VideoParseLog{},
|
&rmmodel.VideoParseLog{},
|
||||||
|
|||||||
@@ -49,6 +49,10 @@ type updateStatusRequest struct {
|
|||||||
Status string `json:"status"`
|
Status string `json:"status"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type updateSettingsRequest struct {
|
||||||
|
RemindDays []int `json:"remind_days"`
|
||||||
|
}
|
||||||
|
|
||||||
const expiryDateLayout = "2006-01-02"
|
const expiryDateLayout = "2006-01-02"
|
||||||
|
|
||||||
// GetSummary 获取首页汇总统计。
|
// GetSummary 获取首页汇总统计。
|
||||||
@@ -216,6 +220,42 @@ func (h *Handler) UpdateStatus(c *gin.Context) {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// GetSettings 获取用户提醒设置。
|
||||||
|
func (h *Handler) GetSettings(c *gin.Context) {
|
||||||
|
user := middleware.MustCurrentUser(c)
|
||||||
|
|
||||||
|
resp, err := h.service.GetSettings(user.ID)
|
||||||
|
if err != nil {
|
||||||
|
writeExpiryServerError(c)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
writeExpirySuccess(c, "success", resp)
|
||||||
|
}
|
||||||
|
|
||||||
|
// UpdateSettings 更新用户提醒设置。
|
||||||
|
func (h *Handler) UpdateSettings(c *gin.Context) {
|
||||||
|
user := middleware.MustCurrentUser(c)
|
||||||
|
|
||||||
|
var req updateSettingsRequest
|
||||||
|
if err := c.ShouldBindJSON(&req); err != nil {
|
||||||
|
writeExpiryError(c, http.StatusBadRequest, "请求参数错误")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
resp, err := h.service.UpdateSettings(user.ID, req.RemindDays)
|
||||||
|
if err != nil {
|
||||||
|
if isExpiryBadRequestError(err) {
|
||||||
|
writeExpiryError(c, http.StatusBadRequest, err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
writeExpiryServerError(c)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
writeExpirySuccess(c, "更新成功", resp)
|
||||||
|
}
|
||||||
|
|
||||||
func (h *Handler) toCreateItemRequest(miniProgramID uint, req createOrUpdateItemRequest) (CreateItemRequest, error) {
|
func (h *Handler) toCreateItemRequest(miniProgramID uint, req createOrUpdateItemRequest) (CreateItemRequest, error) {
|
||||||
productionDate, err := parseDateString(req.ProductionDate)
|
productionDate, err := parseDateString(req.ProductionDate)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -289,7 +329,8 @@ func isExpiryBadRequestError(err error) bool {
|
|||||||
errors.Is(err, ErrExpiryFilterStatusInvalid) ||
|
errors.Is(err, ErrExpiryFilterStatusInvalid) ||
|
||||||
errors.Is(err, ErrExpiryFilterCategoryInvalid) ||
|
errors.Is(err, ErrExpiryFilterCategoryInvalid) ||
|
||||||
errors.Is(err, ErrExpiryFilterSortInvalid) ||
|
errors.Is(err, ErrExpiryFilterSortInvalid) ||
|
||||||
errors.Is(err, ErrExpiryStatusInvalid)
|
errors.Is(err, ErrExpiryStatusInvalid) ||
|
||||||
|
errors.Is(err, ErrExpiryRemindDaysInvalid)
|
||||||
}
|
}
|
||||||
|
|
||||||
func writeExpirySuccess(c *gin.Context, message string, data interface{}) {
|
func writeExpirySuccess(c *gin.Context, message string, data interface{}) {
|
||||||
|
|||||||
@@ -217,3 +217,52 @@ func (r *Repository) UpdateStatus(id, userID uint, status string) error {
|
|||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// GetSettings 查询用户提醒设置。
|
||||||
|
func (r *Repository) GetSettings(userID uint) (*ExpiryUserSettings, error) {
|
||||||
|
var settings ExpiryUserSettings
|
||||||
|
err := r.db.Where("user_id = ?", userID).First(&settings).Error
|
||||||
|
if err != nil {
|
||||||
|
if errors.Is(err, gorm.ErrRecordNotFound) {
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
return nil, fmt.Errorf("find expiry settings: %w", err)
|
||||||
|
}
|
||||||
|
return &settings, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// UpdateSettings 更新用户提醒设置(不存在则创建)。
|
||||||
|
func (r *Repository) UpdateSettings(userID uint, remindDays []int) (*ExpiryUserSettings, error) {
|
||||||
|
settings, err := r.GetSettings(userID)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if settings == nil {
|
||||||
|
settings = &ExpiryUserSettings{
|
||||||
|
UserID: userID,
|
||||||
|
RemindDays: copyIntSlice(remindDays),
|
||||||
|
}
|
||||||
|
if err := r.db.Create(settings).Error; err != nil {
|
||||||
|
return nil, fmt.Errorf("create expiry settings: %w", err)
|
||||||
|
}
|
||||||
|
return settings, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
settings.RemindDays = copyIntSlice(remindDays)
|
||||||
|
if err := r.db.Model(&ExpiryUserSettings{}).
|
||||||
|
Where("user_id = ?", userID).
|
||||||
|
Update("remind_days", settings.RemindDays).Error; err != nil {
|
||||||
|
return nil, fmt.Errorf("update expiry settings: %w", err)
|
||||||
|
}
|
||||||
|
return settings, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func copyIntSlice(values []int) []int {
|
||||||
|
if len(values) == 0 {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
copied := make([]int, len(values))
|
||||||
|
copy(copied, values)
|
||||||
|
return copied
|
||||||
|
}
|
||||||
|
|||||||
@@ -19,6 +19,7 @@ var (
|
|||||||
ErrExpiryFilterCategoryInvalid = errors.New("category 参数无效")
|
ErrExpiryFilterCategoryInvalid = errors.New("category 参数无效")
|
||||||
ErrExpiryFilterSortInvalid = errors.New("sort 参数无效")
|
ErrExpiryFilterSortInvalid = errors.New("sort 参数无效")
|
||||||
ErrExpiryStatusInvalid = errors.New("状态无效,仅支持 used/discarded")
|
ErrExpiryStatusInvalid = errors.New("状态无效,仅支持 used/discarded")
|
||||||
|
ErrExpiryRemindDaysInvalid = errors.New("remind_days 必须是 1-30 的整数,数量 1-5 个")
|
||||||
)
|
)
|
||||||
|
|
||||||
// Service 封装保质期模块业务逻辑。
|
// Service 封装保质期模块业务逻辑。
|
||||||
@@ -86,6 +87,11 @@ type SummaryResponse struct {
|
|||||||
Discarded int `json:"discarded"`
|
Discarded int `json:"discarded"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// SettingsResponse 用户提醒设置返回结构。
|
||||||
|
type SettingsResponse struct {
|
||||||
|
RemindDays []int `json:"remind_days"`
|
||||||
|
}
|
||||||
|
|
||||||
func NewService(repo *Repository) *Service {
|
func NewService(repo *Repository) *Service {
|
||||||
return &Service{repo: repo}
|
return &Service{repo: repo}
|
||||||
}
|
}
|
||||||
@@ -255,6 +261,41 @@ func (s *Service) UpdateItemStatus(id, userID uint, status string) error {
|
|||||||
return s.repo.UpdateStatus(id, userID, status)
|
return s.repo.UpdateStatus(id, userID, status)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// GetSettings 获取用户提醒设置;若未配置则返回默认值。
|
||||||
|
func (s *Service) GetSettings(userID uint) (*SettingsResponse, error) {
|
||||||
|
settings, err := s.repo.GetSettings(userID)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if settings == nil || len(settings.RemindDays) == 0 {
|
||||||
|
return &SettingsResponse{
|
||||||
|
RemindDays: copyIntSlice(defaultRemindDays),
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return &SettingsResponse{
|
||||||
|
RemindDays: copyIntSlice(settings.RemindDays),
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// UpdateSettings 更新用户提醒设置。
|
||||||
|
func (s *Service) UpdateSettings(userID uint, remindDays []int) (*SettingsResponse, error) {
|
||||||
|
validated, err := validateRemindDays(remindDays)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
settings, err := s.repo.UpdateSettings(userID, validated)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return &SettingsResponse{
|
||||||
|
RemindDays: copyIntSlice(settings.RemindDays),
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
func validateBaseFields(name, category string, quantity int, location, remark string) error {
|
func validateBaseFields(name, category string, quantity int, location, remark string) error {
|
||||||
name = strings.TrimSpace(name)
|
name = strings.TrimSpace(name)
|
||||||
category = strings.TrimSpace(category)
|
category = strings.TrimSpace(category)
|
||||||
@@ -367,3 +408,28 @@ func toItemView(item ExpiryItem) ItemView {
|
|||||||
UpdatedAt: item.UpdatedAt,
|
UpdatedAt: item.UpdatedAt,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func validateRemindDays(days []int) ([]int, error) {
|
||||||
|
if len(days) == 0 || len(days) > 5 {
|
||||||
|
return nil, ErrExpiryRemindDaysInvalid
|
||||||
|
}
|
||||||
|
|
||||||
|
seen := make(map[int]struct{}, len(days))
|
||||||
|
result := make([]int, 0, len(days))
|
||||||
|
for _, day := range days {
|
||||||
|
if day < 1 || day > 30 {
|
||||||
|
return nil, ErrExpiryRemindDaysInvalid
|
||||||
|
}
|
||||||
|
if _, ok := seen[day]; ok {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
seen[day] = struct{}{}
|
||||||
|
result = append(result, day)
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(result) == 0 || len(result) > 5 {
|
||||||
|
return nil, ErrExpiryRemindDaysInvalid
|
||||||
|
}
|
||||||
|
|
||||||
|
return result, nil
|
||||||
|
}
|
||||||
|
|||||||
@@ -20,5 +20,7 @@ func registerExpiryRoutes(protected *gin.RouterGroup, expiryHandler *expiryhandl
|
|||||||
expiry.PUT("/items/:id", expiryHandler.UpdateItem)
|
expiry.PUT("/items/:id", expiryHandler.UpdateItem)
|
||||||
expiry.DELETE("/items/:id", expiryHandler.DeleteItem)
|
expiry.DELETE("/items/:id", expiryHandler.DeleteItem)
|
||||||
expiry.POST("/items/:id/status", expiryHandler.UpdateStatus)
|
expiry.POST("/items/:id/status", expiryHandler.UpdateStatus)
|
||||||
|
expiry.GET("/settings", expiryHandler.GetSettings)
|
||||||
|
expiry.POST("/settings", expiryHandler.UpdateSettings)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user