+23
appview/db/db.go
+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
+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
+
}