package handler import ( "net/http" "time" "github.com/gin-gonic/gin" "github.com/mattnite/cairn/internal/models" "gorm.io/gorm" ) type DashboardHandler struct { DB *gorm.DB } type DashboardStats struct { TotalArtifacts int64 `json:"total_artifacts"` TotalRepos int64 `json:"total_repos"` TotalCrashGroups int64 `json:"total_crash_groups"` OpenCrashGroups int64 `json:"open_crash_groups"` TotalTargets int64 `json:"total_targets"` } type TrendPoint struct { Date string `json:"date"` Count int64 `json:"count"` } type TopCrasher struct { Title string `json:"title"` OccurrenceCount uint `json:"occurrence_count"` RepoName string `json:"repo_name"` CrashGroupID uint `json:"crash_group_id"` } type DashboardResponse struct { Stats DashboardStats `json:"stats"` Trend []TrendPoint `json:"trend"` TopCrashers []TopCrasher `json:"top_crashers"` } func (h *DashboardHandler) Stats(c *gin.Context) { ctx := c.Request.Context() var stats DashboardStats _ = h.DB.WithContext(ctx).Model(&models.Artifact{}).Count(&stats.TotalArtifacts).Error _ = h.DB.WithContext(ctx).Model(&models.Repository{}).Count(&stats.TotalRepos).Error _ = h.DB.WithContext(ctx).Model(&models.CrashGroup{}).Count(&stats.TotalCrashGroups).Error _ = h.DB.WithContext(ctx).Model(&models.CrashGroup{}).Where("status = ?", "open").Count(&stats.OpenCrashGroups).Error _ = h.DB.WithContext(ctx).Model(&models.Target{}).Count(&stats.TotalTargets).Error // Artifact trend for the last 30 days. var trend []TrendPoint type trendRow struct { Day time.Time Count int64 } var rows []trendRow err := h.DB.WithContext(ctx). Table("artifacts"). Select("DATE(created_at) as day, COUNT(*) as count"). Where("created_at >= ?", time.Now().AddDate(0, 0, -30)). Group("day"). Order("day"). Scan(&rows).Error if err == nil { for _, row := range rows { trend = append(trend, TrendPoint{Date: row.Day.Format("2006-01-02"), Count: row.Count}) } } // Top crashers (most frequent open crash groups). var groups []models.CrashGroup err = h.DB.WithContext(ctx).Where("status = ?", "open").Order("last_seen_at DESC").Limit(50).Find(&groups).Error topCrashers := make([]TopCrasher, 0, 10) if err == nil { for _, group := range groups { fullGroup, fullErr := models.GetCrashGroup(ctx, h.DB, group.ID) if fullErr != nil { continue } topCrashers = append(topCrashers, TopCrasher{ CrashGroupID: fullGroup.ID, Title: fullGroup.Title, OccurrenceCount: fullGroup.OccurrenceCount, RepoName: fullGroup.RepoName, }) if len(topCrashers) == 10 { break } } } c.JSON(http.StatusOK, DashboardResponse{ Stats: stats, Trend: trend, TopCrashers: topCrashers, }) }