feat(expiry): 完成 #25 用户设置接口实现
This commit is contained in:
@@ -52,6 +52,8 @@ func main() {
|
||||
&model.MiniProgram{},
|
||||
&model.User{},
|
||||
&model.UserMembership{},
|
||||
&expiry.ExpiryItem{},
|
||||
&expiry.ExpiryUserSettings{},
|
||||
&membershipmodel.MembershipRedeemCode{},
|
||||
&membershipmodel.MembershipRedemption{},
|
||||
&rmmodel.VideoParseLog{},
|
||||
|
||||
@@ -49,6 +49,10 @@ type updateStatusRequest struct {
|
||||
Status string `json:"status"`
|
||||
}
|
||||
|
||||
type updateSettingsRequest struct {
|
||||
RemindDays []int `json:"remind_days"`
|
||||
}
|
||||
|
||||
const expiryDateLayout = "2006-01-02"
|
||||
|
||||
// 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) {
|
||||
productionDate, err := parseDateString(req.ProductionDate)
|
||||
if err != nil {
|
||||
@@ -289,7 +329,8 @@ func isExpiryBadRequestError(err error) bool {
|
||||
errors.Is(err, ErrExpiryFilterStatusInvalid) ||
|
||||
errors.Is(err, ErrExpiryFilterCategoryInvalid) ||
|
||||
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{}) {
|
||||
|
||||
@@ -217,3 +217,52 @@ func (r *Repository) UpdateStatus(id, userID uint, status string) error {
|
||||
}
|
||||
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 参数无效")
|
||||
ErrExpiryFilterSortInvalid = errors.New("sort 参数无效")
|
||||
ErrExpiryStatusInvalid = errors.New("状态无效,仅支持 used/discarded")
|
||||
ErrExpiryRemindDaysInvalid = errors.New("remind_days 必须是 1-30 的整数,数量 1-5 个")
|
||||
)
|
||||
|
||||
// Service 封装保质期模块业务逻辑。
|
||||
@@ -86,6 +87,11 @@ type SummaryResponse struct {
|
||||
Discarded int `json:"discarded"`
|
||||
}
|
||||
|
||||
// SettingsResponse 用户提醒设置返回结构。
|
||||
type SettingsResponse struct {
|
||||
RemindDays []int `json:"remind_days"`
|
||||
}
|
||||
|
||||
func NewService(repo *Repository) *Service {
|
||||
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)
|
||||
}
|
||||
|
||||
// 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 {
|
||||
name = strings.TrimSpace(name)
|
||||
category = strings.TrimSpace(category)
|
||||
@@ -367,3 +408,28 @@ func toItemView(item ExpiryItem) ItemView {
|
||||
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.DELETE("/items/:id", expiryHandler.DeleteItem)
|
||||
expiry.POST("/items/:id/status", expiryHandler.UpdateStatus)
|
||||
expiry.GET("/settings", expiryHandler.GetSettings)
|
||||
expiry.POST("/settings", expiryHandler.UpdateSettings)
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user