Skip to content

Commit 5babf17

Browse files
committed
feat(db): add session repository with stats queries
1 parent b014294 commit 5babf17

File tree

2 files changed

+134
-0
lines changed

2 files changed

+134
-0
lines changed

db/models.go

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,3 +17,21 @@ type Session struct {
1717
Duration time.Duration `db:"duration"`
1818
StartedAt time.Time `db:"started_at"`
1919
}
20+
21+
type AllTimeStats struct {
22+
TotalSessions int `db:"total_sessions"`
23+
TotalWorkDuration time.Duration `db:"total_work_duration"`
24+
TotalBreakDuration time.Duration `db:"total_break_duration"`
25+
}
26+
27+
type DailyStat struct {
28+
Date string `db:"day"`
29+
WorkDuration time.Duration `db:"work_duration"`
30+
}
31+
32+
type SessionType string
33+
34+
const (
35+
WorkSession SessionType = "work"
36+
BreakSession SessionType = "break"
37+
)

db/repository.go

Lines changed: 116 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,116 @@
1+
package db
2+
3+
import (
4+
"time"
5+
6+
"github.com/jmoiron/sqlx"
7+
)
8+
9+
const DateFormat = "2006-01-02"
10+
11+
type SessionRepo struct {
12+
db *sqlx.DB
13+
}
14+
15+
func NewSessionRepo(db *sqlx.DB) *SessionRepo {
16+
return &SessionRepo{db: db}
17+
}
18+
19+
// CreateSession inserts a new session record into the database.
20+
func (r *SessionRepo) CreateSession(startedAt time.Time, duration time.Duration, sessionType SessionType) error {
21+
startedAtStr := startedAt.Format(time.RFC3339)
22+
23+
if _, err := r.db.Exec(
24+
"insert into sessions (started_at, duration, type) values (?,?,?);",
25+
startedAtStr,
26+
duration,
27+
sessionType,
28+
); err != nil {
29+
return err
30+
}
31+
32+
return nil
33+
}
34+
35+
// GetAllTimeStats retrieves aggregate statistics across all sessions.
36+
func (r *SessionRepo) GetAllTimeStats() (AllTimeStats, error) {
37+
var totalStats AllTimeStats
38+
39+
// sqlite treats (type = 'work') as 1 or 0
40+
if err := r.db.Get(
41+
&totalStats,
42+
`
43+
SELECT
44+
COUNT(*) AS total_sessions,
45+
COALESCE(SUM(duration * (type = 'work')), 0) AS total_work_duration,
46+
COALESCE(SUM(duration * (type = 'break')), 0) AS total_break_duration
47+
FROM sessions;
48+
`,
49+
); err != nil {
50+
return AllTimeStats{}, err
51+
}
52+
53+
return totalStats, nil
54+
}
55+
56+
func (r *SessionRepo) GetWeeklyStats() ([]DailyStat, error) {
57+
today := time.Now()
58+
firstDay := today.AddDate(0, 0, -6)
59+
60+
return r.getDailyStats(firstDay, today)
61+
}
62+
63+
func (r *SessionRepo) GetMonthlyStats() ([]DailyStat, error) {
64+
today := time.Now()
65+
firstDay := today.AddDate(0, 0, -29)
66+
67+
return r.getDailyStats(firstDay, today)
68+
}
69+
70+
func (r *SessionRepo) getDailyStats(from, to time.Time) ([]DailyStat, error) {
71+
fromStr := from.Format(DateFormat)
72+
toStr := to.Format(DateFormat)
73+
74+
var stats []DailyStat
75+
76+
if err := r.db.Select(
77+
&stats,
78+
`
79+
SELECT
80+
date(started_at) AS day,
81+
COALESCE(SUM(duration * (type = 'work')), 0) AS work_duration
82+
FROM sessions
83+
WHERE date(started_at) BETWEEN ? AND ?
84+
GROUP BY day
85+
ORDER BY day;
86+
`,
87+
fromStr, toStr,
88+
); err != nil {
89+
return nil, err
90+
}
91+
92+
return r.normalizeStats(from, to, stats), nil
93+
}
94+
95+
func (r *SessionRepo) normalizeStats(from, to time.Time, stats []DailyStat) []DailyStat {
96+
m := make(map[string]DailyStat)
97+
98+
for _, stat := range stats {
99+
m[stat.Date] = stat
100+
}
101+
102+
var normalized []DailyStat
103+
current := from
104+
for !current.After(to) {
105+
day := current.Format(DateFormat)
106+
107+
normalized = append(normalized, DailyStat{
108+
Date: day,
109+
WorkDuration: m[day].WorkDuration,
110+
})
111+
112+
current = current.AddDate(0, 0, 1) // next day
113+
}
114+
115+
return normalized
116+
}

0 commit comments

Comments
 (0)