forked from tangled.org/core
Monorepo for Tangled

appview: db: introduce punchcard table

tracks commit per user by day

Signed-off-by: oppiliappan <me@oppi.li>

authored by oppi.li and committed by Tangled 48e55e05 0629ae4c

Changed files
+117
appview
+23
appview/db/db.go
··· 314 314 expiry text not null 315 315 ); 316 316 317 + create table if not exists punchcard ( 318 + did text not null, 319 + date text not null, -- yyyy-mm-dd 320 + count integer, 321 + primary key (did, date) 322 + ); 323 + 317 324 create table if not exists migrations ( 318 325 id integer primary key autoincrement, 319 326 name text unique ··· 513 520 key: key, 514 521 arg: arg, 515 522 cmp: "<>", 523 + } 524 + } 525 + 526 + func FilterGte(key string, arg any) filter { 527 + return filter{ 528 + key: key, 529 + arg: arg, 530 + cmp: ">=", 531 + } 532 + } 533 + 534 + func FilterLte(key string, arg any) filter { 535 + return filter{ 536 + key: key, 537 + arg: arg, 538 + cmp: "<=", 516 539 } 517 540 } 518 541
+94
appview/db/punchcard.go
··· 1 + package db 2 + 3 + import ( 4 + "database/sql" 5 + "fmt" 6 + "strings" 7 + "time" 8 + ) 9 + 10 + type Punch struct { 11 + Did string 12 + Date time.Time 13 + Count int 14 + } 15 + 16 + // this adds to the existing count 17 + func AddPunch(e Execer, punch Punch) error { 18 + _, err := e.Exec(` 19 + insert into punchcard (did, date, count) 20 + values (?, ?, ?) 21 + on conflict(did, date) do update set 22 + count = coalesce(punchcard.count, 0) + excluded.count; 23 + `, punch.Did, punch.Date.Format(time.DateOnly), punch.Count) 24 + return err 25 + } 26 + 27 + type Punchcard struct { 28 + Total int 29 + Punches []Punch 30 + } 31 + 32 + func MakePunchcard(e Execer, filters ...filter) (Punchcard, error) { 33 + punchcard := Punchcard{} 34 + now := time.Now() 35 + startOfYear := time.Date(now.Year(), 1, 1, 0, 0, 0, 0, time.UTC) 36 + endOfYear := time.Date(now.Year(), 12, 31, 0, 0, 0, 0, time.UTC) 37 + for d := startOfYear; d.Before(endOfYear) || d.Equal(endOfYear); d = d.AddDate(0, 0, 1) { 38 + punchcard.Punches = append(punchcard.Punches, Punch{ 39 + Date: d, 40 + Count: 0, 41 + }) 42 + } 43 + 44 + var conditions []string 45 + var args []any 46 + for _, filter := range filters { 47 + conditions = append(conditions, filter.Condition()) 48 + args = append(args, filter.arg) 49 + } 50 + 51 + whereClause := "" 52 + if conditions != nil { 53 + whereClause = " where " + strings.Join(conditions, " and ") 54 + } 55 + 56 + query := fmt.Sprintf(` 57 + select date, sum(count) as total_count 58 + from punchcard 59 + %s 60 + group by date 61 + order by date 62 + `, whereClause) 63 + 64 + rows, err := e.Query(query, args...) 65 + if err != nil { 66 + return punchcard, err 67 + } 68 + defer rows.Close() 69 + 70 + for rows.Next() { 71 + var punch Punch 72 + var date string 73 + var count sql.NullInt64 74 + if err := rows.Scan(&date, &count); err != nil { 75 + return punchcard, err 76 + } 77 + 78 + punch.Date, err = time.Parse(time.DateOnly, date) 79 + if err != nil { 80 + fmt.Println("invalid date") 81 + // this punch is not recorded if date is invalid 82 + continue 83 + } 84 + 85 + if count.Valid { 86 + punch.Count = int(count.Int64) 87 + } 88 + 89 + punchcard.Punches[punch.Date.YearDay()] = punch 90 + punchcard.Total += punch.Count 91 + } 92 + 93 + return punchcard, nil 94 + }