diff --git a/pkg/controller/realmadmin/show.go b/pkg/controller/realmadmin/show.go index fc0d40727..4e8a8ee6c 100644 --- a/pkg/controller/realmadmin/show.go +++ b/pkg/controller/realmadmin/show.go @@ -16,6 +16,8 @@ package realmadmin import ( "context" + "encoding/csv" + "fmt" "net/http" "strconv" "time" @@ -66,7 +68,45 @@ func (c *Controller) HandleShow() http.Handler { return } - c.renderShow(ctx, w, realm, stats, userStats) + // If the user's requested CSV, return it as such. + if _, wantCSV := r.URL.Query()["csv"]; wantCSV { + wr := csv.NewWriter(w) + + // Check if we want the realm stats or the per-user stats. We + // default to realm stats. + if _, wantUserStats := r.URL.Query()["user"]; wantUserStats { + if err := wr.Write(database.RealmUserStatsCSVHeader); err != nil { + controller.InternalError(w, r, c.h, err) + return + } + + for _, u := range userStats { + if err := wr.Write(u.CSV()); err != nil { + controller.InternalError(w, r, c.h, err) + return + } + } + } else { + if err := wr.Write(database.RealmStatsCSVHeader); err != nil { + controller.InternalError(w, r, c.h, err) + return + } + + for _, s := range stats { + fmt.Println(s.CSV()) + if err := wr.Write(s.CSV()); err != nil { + controller.InternalError(w, r, c.h, err) + return + } + } + } + + w.Header().Set("Content-Type", "text/csv") + w.Header().Set("Content-Disposition", "attachment;filename=stats.csv") + wr.Flush() + } else { + c.renderShow(ctx, w, realm, stats, userStats) + } }) } diff --git a/pkg/database/realm.go b/pkg/database/realm.go index daca35307..100fa4ed9 100644 --- a/pkg/database/realm.go +++ b/pkg/database/realm.go @@ -1316,6 +1316,19 @@ type RealmUserStats struct { Date time.Time `json:"date"` } +// RealmUserStatsCSVHeader is a header for CSV stats +var RealmUserStatsCSVHeader = []string{"User ID", "Name", "Codes Issued", "Date"} + +// CSV returns a slice of the data from a RealmUserStats for CSV writing. +func (s *RealmUserStats) CSV() []string { + ret := make([]string, 4) + ret[0] = fmt.Sprintf("%d", s.UserID) + ret[1] = s.Name + ret[2] = fmt.Sprintf("%d", s.CodesIssued) + ret[3] = s.Date.Format("2006-01-02") + return ret +} + // CodesPerUser returns a set of UserStats for a given date range. func (r *Realm) CodesPerUser(db *Database, start, stop time.Time) ([]*RealmUserStats, error) { start = timeutils.UTCMidnight(start) diff --git a/pkg/database/realm_stats.go b/pkg/database/realm_stats.go index cbd704ac7..145e658cf 100644 --- a/pkg/database/realm_stats.go +++ b/pkg/database/realm_stats.go @@ -15,6 +15,7 @@ package database import ( + "fmt" "time" ) @@ -26,6 +27,19 @@ type RealmStats struct { CodesClaimed uint `gorm:"codes_claimed; default: 0"` } +// RealmStatsCSVHeader is a header for CSV files for RealmStats. +var RealmStatsCSVHeader = []string{"Date", "Realm ID", "Codes Issued", "Codes Claimed"} + +// CSV returns the CSV encoded values for a RealmStats. +func (r *RealmStats) CSV() []string { + ret := make([]string, 4) + ret[0] = r.Date.Format("2006-01-02") + ret[1] = fmt.Sprintf("%d", r.RealmID) + ret[2] = fmt.Sprintf("%d", r.CodesIssued) + ret[3] = fmt.Sprintf("%d", r.CodesClaimed) + return ret +} + // TableName sets the RealmStats table name func (RealmStats) TableName() string { return "realm_stats"