From 03a98f339d67d75fd1f8f31e7bd3b41bb9c2c904 Mon Sep 17 00:00:00 2001 From: oppiliappan Date: Tue, 10 Jun 2025 12:15:03 +0100 Subject: [PATCH] appview: db: introduce punchcard table Change-Id: yvusmpsxkqtuztnpzoxymmmrxltskosx tracks commit per user by day Signed-off-by: oppiliappan --- appview/db/db.go | 23 ++++++++++ appview/db/punchcard.go | 94 +++++++++++++++++++++++++++++++++++++++++ 2 files changed, 117 insertions(+) create mode 100644 appview/db/punchcard.go diff --git a/appview/db/db.go b/appview/db/db.go index f429565..50bb6a2 100644 --- a/appview/db/db.go +++ b/appview/db/db.go @@ -314,6 +314,13 @@ func Make(dbPath string) (*DB, error) { expiry text not null ); + create table if not exists punchcard ( + did text not null, + date text not null, -- yyyy-mm-dd + count integer, + primary key (did, date) + ); + create table if not exists migrations ( id integer primary key autoincrement, name text unique @@ -516,6 +523,22 @@ func FilterNotEq(key string, arg any) filter { } } +func FilterGte(key string, arg any) filter { + return filter{ + key: key, + arg: arg, + cmp: ">=", + } +} + +func FilterLte(key string, arg any) filter { + return filter{ + key: key, + arg: arg, + cmp: "<=", + } +} + func (f filter) Condition() string { return fmt.Sprintf("%s %s ?", f.key, f.cmp) } diff --git a/appview/db/punchcard.go b/appview/db/punchcard.go new file mode 100644 index 0000000..0569068 --- /dev/null +++ b/appview/db/punchcard.go @@ -0,0 +1,94 @@ +package db + +import ( + "database/sql" + "fmt" + "strings" + "time" +) + +type Punch struct { + Did string + Date time.Time + Count int +} + +// this adds to the existing count +func AddPunch(e Execer, punch Punch) error { + _, err := e.Exec(` + insert into punchcard (did, date, count) + values (?, ?, ?) + on conflict(did, date) do update set + count = coalesce(punchcard.count, 0) + excluded.count; + `, punch.Did, punch.Date.Format(time.DateOnly), punch.Count) + return err +} + +type Punchcard struct { + Total int + Punches []Punch +} + +func MakePunchcard(e Execer, filters ...filter) (Punchcard, error) { + punchcard := Punchcard{} + now := time.Now() + startOfYear := time.Date(now.Year(), 1, 1, 0, 0, 0, 0, time.UTC) + endOfYear := time.Date(now.Year(), 12, 31, 0, 0, 0, 0, time.UTC) + for d := startOfYear; d.Before(endOfYear) || d.Equal(endOfYear); d = d.AddDate(0, 0, 1) { + punchcard.Punches = append(punchcard.Punches, Punch{ + Date: d, + Count: 0, + }) + } + + var conditions []string + var args []any + for _, filter := range filters { + conditions = append(conditions, filter.Condition()) + args = append(args, filter.arg) + } + + whereClause := "" + if conditions != nil { + whereClause = " where " + strings.Join(conditions, " and ") + } + + query := fmt.Sprintf(` + select date, sum(count) as total_count + from punchcard + %s + group by date + order by date + `, whereClause) + + rows, err := e.Query(query, args...) + if err != nil { + return punchcard, err + } + defer rows.Close() + + for rows.Next() { + var punch Punch + var date string + var count sql.NullInt64 + if err := rows.Scan(&date, &count); err != nil { + return punchcard, err + } + + punch.Date, err = time.Parse(time.DateOnly, date) + if err != nil { + fmt.Println("invalid date") + // this punch is not recorded if date is invalid + continue + } + + if count.Valid { + punch.Count = int(count.Int64) + } + + punchcard.Punches[punch.Date.YearDay()] = punch + punchcard.Total += punch.Count + } + + return punchcard, nil +} -- 2.43.0