Add dashboard and latest logs endpoints for smoke tracking
- Introduced a new API endpoint `GET /api/v1/smoke/dashboard` to retrieve a summary of smoking statistics over a specified date range, including today's count and weekly breakdown. - Added `GET /api/v1/smoke/logs/latest` endpoint to fetch the most recent smoking logs with a configurable limit. - Updated the smoke handler and service to support the new functionality, including error handling for date parsing and limit validation. - Enhanced documentation to reflect the new API endpoints and their usage.
This commit is contained in:
@@ -104,6 +104,26 @@ type ListSmokeLogsResult struct {
|
||||
PageSize int
|
||||
}
|
||||
|
||||
// SmokeDashboardRequest 定义了看板概览的时间范围(包含起止日期)。
|
||||
type SmokeDashboardRequest struct {
|
||||
Start time.Time
|
||||
End time.Time
|
||||
}
|
||||
|
||||
// SmokeDashboardResult 用于返回看板概览的关键指标。
|
||||
type SmokeDashboardResult struct {
|
||||
TodayCount int `json:"today_count"`
|
||||
MinutesSinceLast *int `json:"minutes_since_last,omitempty"`
|
||||
Weekly []DashboardWeeklyStat `json:"weekly"`
|
||||
}
|
||||
|
||||
// DashboardWeeklyStat 表示某一天的抽烟支数以及是否为今天。
|
||||
type DashboardWeeklyStat struct {
|
||||
Date string `json:"date"`
|
||||
Count int `json:"count"`
|
||||
IsToday bool `json:"is_today"`
|
||||
}
|
||||
|
||||
func (s *SmokeLogService) List(ctx context.Context, uid int, req ListSmokeLogsRequest) (ListSmokeLogsResult, error) {
|
||||
page := req.Page
|
||||
if page <= 0 {
|
||||
@@ -151,6 +171,119 @@ func (s *SmokeLogService) List(ctx context.Context, uid int, req ListSmokeLogsRe
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (s *SmokeLogService) Dashboard(ctx context.Context, uid int, req SmokeDashboardRequest) (SmokeDashboardResult, error) {
|
||||
start := dateOnly(req.Start)
|
||||
end := dateOnly(req.End)
|
||||
|
||||
type dailyCount struct {
|
||||
SmokeTime time.Time `gorm:"column:smoke_time"`
|
||||
Total int64 `gorm:"column:total"`
|
||||
}
|
||||
|
||||
var rows []dailyCount
|
||||
if err := s.db.WithContext(ctx).
|
||||
Model(&smokemodel.SmokeLog{}).
|
||||
Select("smoke_time, SUM(num) AS total").
|
||||
Where("uid = ? AND (deletetime IS NULL OR deletetime = 0)", uid).
|
||||
Where("smoke_time BETWEEN ? AND ?", start.Format("2006-01-02"), end.Format("2006-01-02")).
|
||||
Group("smoke_time").
|
||||
Find(&rows).Error; err != nil {
|
||||
return SmokeDashboardResult{}, fmt.Errorf("aggregate smoke logs: %w", err)
|
||||
}
|
||||
|
||||
counts := make(map[string]int64, len(rows))
|
||||
for _, row := range rows {
|
||||
key := dateOnly(row.SmokeTime).Format("2006-01-02")
|
||||
counts[key] = row.Total
|
||||
}
|
||||
|
||||
today := dateOnly(time.Now())
|
||||
todayKey := today.Format("2006-01-02")
|
||||
var todayCount int64
|
||||
if err := s.db.WithContext(ctx).
|
||||
Model(&smokemodel.SmokeLog{}).
|
||||
Where("uid = ? AND (deletetime IS NULL OR deletetime = 0) AND smoke_time = ?", uid, todayKey).
|
||||
Select("COALESCE(SUM(num), 0)").
|
||||
Scan(&todayCount).Error; err != nil {
|
||||
return SmokeDashboardResult{}, fmt.Errorf("count today smoke logs: %w", err)
|
||||
}
|
||||
|
||||
var minutesSinceLast *int
|
||||
var last smokemodel.SmokeLog
|
||||
if err := s.db.WithContext(ctx).
|
||||
Where("uid = ? AND (deletetime IS NULL OR deletetime = 0)", uid).
|
||||
Order("COALESCE(smoke_at, smoke_time, FROM_UNIXTIME(createtime)) DESC").
|
||||
Order("id DESC").
|
||||
Limit(1).
|
||||
Take(&last).Error; err != nil {
|
||||
if !errors.Is(err, gorm.ErrRecordNotFound) {
|
||||
return SmokeDashboardResult{}, fmt.Errorf("load last smoke log: %w", err)
|
||||
}
|
||||
} else {
|
||||
if lastTime, ok := lastEventTime(last); ok {
|
||||
diff := int(time.Since(lastTime).Minutes())
|
||||
if diff < 0 {
|
||||
diff = 0
|
||||
}
|
||||
minutesSinceLast = &diff
|
||||
}
|
||||
}
|
||||
|
||||
var weekly []DashboardWeeklyStat
|
||||
for day := start; !day.After(end); day = day.AddDate(0, 0, 1) {
|
||||
key := day.Format("2006-01-02")
|
||||
count := counts[key]
|
||||
weekly = append(weekly, DashboardWeeklyStat{
|
||||
Date: key,
|
||||
Count: int(count),
|
||||
IsToday: key == todayKey,
|
||||
})
|
||||
}
|
||||
|
||||
return SmokeDashboardResult{
|
||||
TodayCount: int(todayCount),
|
||||
MinutesSinceLast: minutesSinceLast,
|
||||
Weekly: weekly,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (s *SmokeLogService) ListLatest(ctx context.Context, uid int, limit int) ([]smokemodel.SmokeLog, error) {
|
||||
if limit <= 0 {
|
||||
limit = 20
|
||||
}
|
||||
if limit > 100 {
|
||||
limit = 100
|
||||
}
|
||||
|
||||
var items []smokemodel.SmokeLog
|
||||
if err := s.db.WithContext(ctx).
|
||||
Model(&smokemodel.SmokeLog{}).
|
||||
Select("id, uid, smoke_time, smoke_at, remark, level, num, createtime, updatetime, deletetime").
|
||||
Where("uid = ? AND (deletetime IS NULL OR deletetime = 0)", uid).
|
||||
Order("COALESCE(smoke_at, smoke_time, FROM_UNIXTIME(createtime)) DESC").
|
||||
Order("id DESC").
|
||||
Limit(limit).
|
||||
Find(&items).Error; err != nil {
|
||||
return nil, fmt.Errorf("list latest smoke logs: %w", err)
|
||||
}
|
||||
|
||||
return items, nil
|
||||
}
|
||||
|
||||
func lastEventTime(log smokemodel.SmokeLog) (time.Time, bool) {
|
||||
if log.SmokeAt != nil {
|
||||
return log.SmokeAt.In(time.Local), true
|
||||
}
|
||||
if log.SmokeTime != nil {
|
||||
day := dateOnly(*log.SmokeTime)
|
||||
return day, true
|
||||
}
|
||||
if log.CreateTime != nil {
|
||||
return time.Unix(*log.CreateTime, 0).In(time.Local), true
|
||||
}
|
||||
return time.Time{}, false
|
||||
}
|
||||
|
||||
type UpdateSmokeLogRequest struct {
|
||||
// SmokeTimeProvided 用于区分:
|
||||
// - false:前端没传 smoke_time(不修改)
|
||||
@@ -162,9 +295,9 @@ type UpdateSmokeLogRequest struct {
|
||||
// - true:前端传了 smoke_at(可以设置为具体时间,也可以清空为 NULL)
|
||||
SmokeAtProvided bool
|
||||
SmokeAt *time.Time
|
||||
Remark *string
|
||||
Level *int64
|
||||
Num *int
|
||||
Remark *string
|
||||
Level *int64
|
||||
Num *int
|
||||
}
|
||||
|
||||
func (s *SmokeLogService) Update(ctx context.Context, uid int, id int, req UpdateSmokeLogRequest) (*smokemodel.SmokeLog, error) {
|
||||
|
||||
Reference in New Issue
Block a user