Enhance smoking tracking API with new features and improvements

- Added a new API endpoint `GET /api/v1/smoke/home` to consolidate core modules for the home dashboard, reducing the need for multiple requests.
- Updated the `smoke` routes to include the new home endpoint and improved user profile management with the addition of a `quit_date` field.
- Enhanced the algorithm for calculating daily targets and next smoke suggestions, ensuring accurate future time handling and user-specific recommendations.
- Improved API documentation to reflect new endpoints, response formats, and detailed field descriptions for better clarity and usability.
- Refactored user authentication handling in various handlers to streamline the process and ensure consistent error responses.
This commit is contained in:
nepiedg
2026-01-29 17:16:35 +00:00
parent 3154365ab2
commit 9200600b1c
21 changed files with 703 additions and 178 deletions
+21 -42
View File
@@ -5,6 +5,7 @@ import (
"io"
"net/http"
"strconv"
"strings"
"time"
"github.com/gin-gonic/gin"
@@ -53,11 +54,7 @@ type createSmokeLogRequest struct {
}
func (h *SmokeHandler) Create(c *gin.Context) {
user, ok := middleware.CurrentUser(c)
if !ok {
c.JSON(http.StatusUnauthorized, model.Error(http.StatusUnauthorized, "未登录或登录已过期"))
return
}
user := middleware.MustCurrentUser(c)
var req createSmokeLogRequest
if err := c.ShouldBindJSON(&req); err != nil {
@@ -130,11 +127,7 @@ type resistedSmokeLogRequest struct {
// Resist 表示“想抽但忍住了”:在 fa_smoke_log 中写入 level=0,num=0。
func (h *SmokeHandler) Resist(c *gin.Context) {
user, ok := middleware.CurrentUser(c)
if !ok {
c.JSON(http.StatusUnauthorized, model.Error(http.StatusUnauthorized, "未登录或登录已过期"))
return
}
user := middleware.MustCurrentUser(c)
var req resistedSmokeLogRequest
if err := c.ShouldBindJSON(&req); err != nil && !errors.Is(err, io.EOF) {
@@ -178,11 +171,7 @@ func (h *SmokeHandler) Resist(c *gin.Context) {
}
func (h *SmokeHandler) Get(c *gin.Context) {
user, ok := middleware.CurrentUser(c)
if !ok {
c.JSON(http.StatusUnauthorized, model.Error(http.StatusUnauthorized, "未登录或登录已过期"))
return
}
user := middleware.MustCurrentUser(c)
id, err := strconv.Atoi(c.Param("id"))
if err != nil || id <= 0 {
@@ -204,14 +193,15 @@ func (h *SmokeHandler) Get(c *gin.Context) {
}
func (h *SmokeHandler) List(c *gin.Context) {
user, ok := middleware.CurrentUser(c)
if !ok {
c.JSON(http.StatusUnauthorized, model.Error(http.StatusUnauthorized, "未登录或登录已过期"))
return
}
user := middleware.MustCurrentUser(c)
page, _ := strconv.Atoi(c.DefaultQuery("page", "1"))
pageSize, _ := strconv.Atoi(c.DefaultQuery("page_size", "20"))
listType := strings.ToLower(strings.TrimSpace(c.DefaultQuery("type", "all")))
if listType != "all" && listType != "smoke" && listType != "resisted" {
c.JSON(http.StatusBadRequest, model.Error(http.StatusBadRequest, "type 应为 all|smoke|resisted"))
return
}
var start *time.Time
if v := c.Query("start"); v != "" {
@@ -237,6 +227,7 @@ func (h *SmokeHandler) List(c *gin.Context) {
PageSize: pageSize,
Start: start,
End: end,
Type: listType,
})
if err != nil {
c.JSON(http.StatusInternalServerError, model.Error(http.StatusInternalServerError, "查询列表失败,请稍后重试"))
@@ -252,11 +243,7 @@ func (h *SmokeHandler) List(c *gin.Context) {
}
func (h *SmokeHandler) Dashboard(c *gin.Context) {
user, ok := middleware.CurrentUser(c)
if !ok {
c.JSON(http.StatusUnauthorized, model.Error(http.StatusUnauthorized, "未登录或登录已过期"))
return
}
user := middleware.MustCurrentUser(c)
now := time.Now()
defaultStart, defaultEnd := defaultDashboardRange(now)
@@ -303,11 +290,7 @@ func (h *SmokeHandler) Dashboard(c *gin.Context) {
}
func (h *SmokeHandler) LatestLogs(c *gin.Context) {
user, ok := middleware.CurrentUser(c)
if !ok {
c.JSON(http.StatusUnauthorized, model.Error(http.StatusUnauthorized, "未登录或登录已过期"))
return
}
user := middleware.MustCurrentUser(c)
limit, err := strconv.Atoi(c.DefaultQuery("limit", "20"))
if err != nil {
@@ -341,11 +324,7 @@ type updateSmokeLogRequest struct {
}
func (h *SmokeHandler) Update(c *gin.Context) {
user, ok := middleware.CurrentUser(c)
if !ok {
c.JSON(http.StatusUnauthorized, model.Error(http.StatusUnauthorized, "未登录或登录已过期"))
return
}
user := middleware.MustCurrentUser(c)
id, err := strconv.Atoi(c.Param("id"))
if err != nil || id <= 0 {
@@ -391,8 +370,12 @@ func (h *SmokeHandler) Update(c *gin.Context) {
} else {
parsed, err := time.ParseInLocation(dateTimeLayout, *req.SmokeAt, time.Local)
if err != nil {
c.JSON(http.StatusBadRequest, model.Error(http.StatusBadRequest, "smoke_at 格式错误,应为 YYYY-MM-DD HH:MM:SS"))
return
parsedRFC, errRFC := time.Parse(time.RFC3339, *req.SmokeAt)
if errRFC != nil {
c.JSON(http.StatusBadRequest, model.Error(http.StatusBadRequest, "smoke_at 格式错误,应为 YYYY-MM-DD HH:MM:SS 或 RFC3339"))
return
}
parsed = parsedRFC.In(time.Local)
}
smokeAt = &parsed
}
@@ -420,11 +403,7 @@ func (h *SmokeHandler) Update(c *gin.Context) {
}
func (h *SmokeHandler) Delete(c *gin.Context) {
user, ok := middleware.CurrentUser(c)
if !ok {
c.JSON(http.StatusUnauthorized, model.Error(http.StatusUnauthorized, "未登录或登录已过期"))
return
}
user := middleware.MustCurrentUser(c)
id, err := strconv.Atoi(c.Param("id"))
if err != nil || id <= 0 {