detached b782902

fix(dmenu): implement days=0 as all history, add validation

commit
b782902290825a91d52364dbf1fb547f1deea3d0
author
bnema · · 2025-12-29 19:32 UTC
parent
cebf030
signature
GPG Unverified2B82207D370127A0 The key is unknown
10 files · +395 -14
M internal/application/usecase/search_history.go +29 -7
@@ -76,26 +76,48 @@ func (uc *SearchHistoryUseCase) GetRecent(ctx context.Context, limit, offset int
7676 }
7777
7878 // GetRecentSince retrieves history entries visited within the last N days.
79+// If days is 0, returns all history entries.
80+// If days is negative, defaults to 30 days.
7981 func (uc *SearchHistoryUseCase) GetRecentSince(ctx context.Context, days int) ([]*entity.HistoryEntry, error) {
80- if days <= 0 {
81- days = 30 // Default to 30 days
82+ if days < 0 {
83+ days = 30 // Default to 30 days for negative values
84+ }
85+
86+ var entries []*entity.HistoryEntry
87+ var err error
88+
89+ switch days {
90+ case 0:
91+ entries, err = uc.historyRepo.GetAllRecentHistory(ctx)
92+ default:
93+ entries, err = uc.historyRepo.GetRecentSince(ctx, days)
8294 }
8395
84- entries, err := uc.historyRepo.GetRecentSince(ctx, days)
8596 if err != nil {
86- return nil, fmt.Errorf("failed to get recent history since %d days: %w", days, err)
97+ return nil, fmt.Errorf("failed to get recent history: %w", err)
8798 }
8899
89100 return entries, nil
90101 }
91102
92103 // GetMostVisited retrieves history entries sorted by visit count within the last N days.
104+// If days is 0, returns all history entries sorted by visit count.
105+// If days is negative, defaults to 30 days.
93106 func (uc *SearchHistoryUseCase) GetMostVisited(ctx context.Context, days int) ([]*entity.HistoryEntry, error) {
94- if days <= 0 {
95- days = 30 // Default to 30 days
107+ if days < 0 {
108+ days = 30 // Default to 30 days for negative values
109+ }
110+
111+ var entries []*entity.HistoryEntry
112+ var err error
113+
114+ switch days {
115+ case 0:
116+ entries, err = uc.historyRepo.GetAllMostVisited(ctx)
117+ default:
118+ entries, err = uc.historyRepo.GetMostVisited(ctx, days)
96119 }
97120
98- entries, err := uc.historyRepo.GetMostVisited(ctx, days)
99121 if err != nil {
100122 return nil, fmt.Errorf("failed to get most visited history: %w", err)
101123 }
@@ -76,26 +76,48 @@ func (uc *SearchHistoryUseCase) GetRecent(ctx context.Context, limit, offset int
76 }76 }
77 77
78 // GetRecentSince retrieves history entries visited within the last N days.78 // GetRecentSince retrieves history entries visited within the last N days.
79+// If days is 0, returns all history entries.
80+// If days is negative, defaults to 30 days.
79 func (uc *SearchHistoryUseCase) GetRecentSince(ctx context.Context, days int) ([]*entity.HistoryEntry, error) {81 func (uc *SearchHistoryUseCase) GetRecentSince(ctx context.Context, days int) ([]*entity.HistoryEntry, error) {
80- if days <= 0 {82+ if days < 0 {
81- days = 30 // Default to 30 days83+ days = 30 // Default to 30 days for negative values
84+ }
85+
86+ var entries []*entity.HistoryEntry
87+ var err error
88+
89+ switch days {
90+ case 0:
91+ entries, err = uc.historyRepo.GetAllRecentHistory(ctx)
92+ default:
93+ entries, err = uc.historyRepo.GetRecentSince(ctx, days)
82 }94 }
83 95
84- entries, err := uc.historyRepo.GetRecentSince(ctx, days)
85 if err != nil {96 if err != nil {
86- return nil, fmt.Errorf("failed to get recent history since %d days: %w", days, err)97+ return nil, fmt.Errorf("failed to get recent history: %w", err)
87 }98 }
88 99
89 return entries, nil100 return entries, nil
90 }101 }
91 102
92 // GetMostVisited retrieves history entries sorted by visit count within the last N days.103 // GetMostVisited retrieves history entries sorted by visit count within the last N days.
104+// If days is 0, returns all history entries sorted by visit count.
105+// If days is negative, defaults to 30 days.
93 func (uc *SearchHistoryUseCase) GetMostVisited(ctx context.Context, days int) ([]*entity.HistoryEntry, error) {106 func (uc *SearchHistoryUseCase) GetMostVisited(ctx context.Context, days int) ([]*entity.HistoryEntry, error) {
94- if days <= 0 {107+ if days < 0 {
95- days = 30 // Default to 30 days108+ days = 30 // Default to 30 days for negative values
109+ }
110+
111+ var entries []*entity.HistoryEntry
112+ var err error
113+
114+ switch days {
115+ case 0:
116+ entries, err = uc.historyRepo.GetAllMostVisited(ctx)
117+ default:
118+ entries, err = uc.historyRepo.GetMostVisited(ctx, days)
96 }119 }
97 120
98- entries, err := uc.historyRepo.GetMostVisited(ctx, days)
99 if err != nil {121 if err != nil {
100 return nil, fmt.Errorf("failed to get most visited history: %w", err)122 return nil, fmt.Errorf("failed to get most visited history: %w", err)
101 }123 }
M internal/application/usecase/search_history_test.go +7 -7
@@ -34,7 +34,7 @@ func TestSearchHistoryUseCase_GetRecentSince_ReturnsEntries(t *testing.T) {
3434 assert.Equal(t, "https://github.com", result[1].URL)
3535 }
3636
37-func TestSearchHistoryUseCase_GetRecentSince_DefaultsTo30Days(t *testing.T) {
37+func TestSearchHistoryUseCase_GetRecentSince_ZeroMeansAll(t *testing.T) {
3838 ctx := testContext()
3939
4040 historyRepo := repomocks.NewMockHistoryRepository(t)
@@ -43,8 +43,8 @@ func TestSearchHistoryUseCase_GetRecentSince_DefaultsTo30Days(t *testing.T) {
4343 {ID: 1, URL: "https://example.com", Title: "Example"},
4444 }
4545
46- // When days <= 0, should default to 30
47- historyRepo.EXPECT().GetRecentSince(mock.Anything, 30).Return(entries, nil)
46+ // When days == 0, should call GetAllRecentHistory
47+ historyRepo.EXPECT().GetAllRecentHistory(mock.Anything).Return(entries, nil)
4848
4949 uc := usecase.NewSearchHistoryUseCase(historyRepo)
5050
@@ -81,7 +81,7 @@ func TestSearchHistoryUseCase_GetRecentSince_ReturnsErrorOnRepoFailure(t *testin
8181 result, err := uc.GetRecentSince(ctx, 7)
8282 require.Error(t, err)
8383 assert.Nil(t, result)
84- assert.Contains(t, err.Error(), "failed to get recent history since 7 days")
84+ assert.Contains(t, err.Error(), "failed to get recent history")
8585 }
8686
8787 func TestSearchHistoryUseCase_GetMostVisited_ReturnsEntries(t *testing.T) {
@@ -105,15 +105,15 @@ func TestSearchHistoryUseCase_GetMostVisited_ReturnsEntries(t *testing.T) {
105105 assert.Equal(t, int64(100), result[0].VisitCount)
106106 }
107107
108-func TestSearchHistoryUseCase_GetMostVisited_DefaultsTo30Days(t *testing.T) {
108+func TestSearchHistoryUseCase_GetMostVisited_ZeroMeansAll(t *testing.T) {
109109 ctx := testContext()
110110
111111 historyRepo := repomocks.NewMockHistoryRepository(t)
112112
113113 entries := []*entity.HistoryEntry{}
114114
115- // When days <= 0, should default to 30
116- historyRepo.EXPECT().GetMostVisited(mock.Anything, 30).Return(entries, nil)
115+ // When days == 0, should call GetAllMostVisited
116+ historyRepo.EXPECT().GetAllMostVisited(mock.Anything).Return(entries, nil)
117117
118118 uc := usecase.NewSearchHistoryUseCase(historyRepo)
119119
@@ -34,7 +34,7 @@ func TestSearchHistoryUseCase_GetRecentSince_ReturnsEntries(t *testing.T) {
34 assert.Equal(t, "https://github.com", result[1].URL)34 assert.Equal(t, "https://github.com", result[1].URL)
35 }35 }
36 36
37-func TestSearchHistoryUseCase_GetRecentSince_DefaultsTo30Days(t *testing.T) {37+func TestSearchHistoryUseCase_GetRecentSince_ZeroMeansAll(t *testing.T) {
38 ctx := testContext()38 ctx := testContext()
39 39
40 historyRepo := repomocks.NewMockHistoryRepository(t)40 historyRepo := repomocks.NewMockHistoryRepository(t)
@@ -43,8 +43,8 @@ func TestSearchHistoryUseCase_GetRecentSince_DefaultsTo30Days(t *testing.T) {
43 {ID: 1, URL: "https://example.com", Title: "Example"},43 {ID: 1, URL: "https://example.com", Title: "Example"},
44 }44 }
45 45
46- // When days <= 0, should default to 3046+ // When days == 0, should call GetAllRecentHistory
47- historyRepo.EXPECT().GetRecentSince(mock.Anything, 30).Return(entries, nil)47+ historyRepo.EXPECT().GetAllRecentHistory(mock.Anything).Return(entries, nil)
48 48
49 uc := usecase.NewSearchHistoryUseCase(historyRepo)49 uc := usecase.NewSearchHistoryUseCase(historyRepo)
50 50
@@ -81,7 +81,7 @@ func TestSearchHistoryUseCase_GetRecentSince_ReturnsErrorOnRepoFailure(t *testin
81 result, err := uc.GetRecentSince(ctx, 7)81 result, err := uc.GetRecentSince(ctx, 7)
82 require.Error(t, err)82 require.Error(t, err)
83 assert.Nil(t, result)83 assert.Nil(t, result)
84- assert.Contains(t, err.Error(), "failed to get recent history since 7 days")84+ assert.Contains(t, err.Error(), "failed to get recent history")
85 }85 }
86 86
87 func TestSearchHistoryUseCase_GetMostVisited_ReturnsEntries(t *testing.T) {87 func TestSearchHistoryUseCase_GetMostVisited_ReturnsEntries(t *testing.T) {
@@ -105,15 +105,15 @@ func TestSearchHistoryUseCase_GetMostVisited_ReturnsEntries(t *testing.T) {
105 assert.Equal(t, int64(100), result[0].VisitCount)105 assert.Equal(t, int64(100), result[0].VisitCount)
106 }106 }
107 107
108-func TestSearchHistoryUseCase_GetMostVisited_DefaultsTo30Days(t *testing.T) {108+func TestSearchHistoryUseCase_GetMostVisited_ZeroMeansAll(t *testing.T) {
109 ctx := testContext()109 ctx := testContext()
110 110
111 historyRepo := repomocks.NewMockHistoryRepository(t)111 historyRepo := repomocks.NewMockHistoryRepository(t)
112 112
113 entries := []*entity.HistoryEntry{}113 entries := []*entity.HistoryEntry{}
114 114
115- // When days <= 0, should default to 30115+ // When days == 0, should call GetAllMostVisited
116- historyRepo.EXPECT().GetMostVisited(mock.Anything, 30).Return(entries, nil)116+ historyRepo.EXPECT().GetAllMostVisited(mock.Anything).Return(entries, nil)
117 117
118 uc := usecase.NewSearchHistoryUseCase(historyRepo)118 uc := usecase.NewSearchHistoryUseCase(historyRepo)
119 119
M internal/domain/repository/history.go +8 -0
@@ -22,11 +22,19 @@ type HistoryRepository interface {
2222 GetRecent(ctx context.Context, limit, offset int) ([]*entity.HistoryEntry, error)
2323
2424 // GetRecentSince retrieves history entries visited within the last N days.
25+ // days must be > 0.
2526 GetRecentSince(ctx context.Context, days int) ([]*entity.HistoryEntry, error)
2627
2728 // GetMostVisited retrieves history entries sorted by visit count within the last N days.
29+ // days must be > 0.
2830 GetMostVisited(ctx context.Context, days int) ([]*entity.HistoryEntry, error)
2931
32+ // GetAllRecentHistory retrieves all history entries sorted by recency.
33+ GetAllRecentHistory(ctx context.Context) ([]*entity.HistoryEntry, error)
34+
35+ // GetAllMostVisited retrieves all history entries sorted by visit count.
36+ GetAllMostVisited(ctx context.Context) ([]*entity.HistoryEntry, error)
37+
3038 // IncrementVisitCount increments the visit count for a URL.
3139 IncrementVisitCount(ctx context.Context, url string) error
3240
@@ -22,11 +22,19 @@ type HistoryRepository interface {
22 GetRecent(ctx context.Context, limit, offset int) ([]*entity.HistoryEntry, error)22 GetRecent(ctx context.Context, limit, offset int) ([]*entity.HistoryEntry, error)
23 23
24 // GetRecentSince retrieves history entries visited within the last N days.24 // GetRecentSince retrieves history entries visited within the last N days.
25+ // days must be > 0.
25 GetRecentSince(ctx context.Context, days int) ([]*entity.HistoryEntry, error)26 GetRecentSince(ctx context.Context, days int) ([]*entity.HistoryEntry, error)
26 27
27 // GetMostVisited retrieves history entries sorted by visit count within the last N days.28 // GetMostVisited retrieves history entries sorted by visit count within the last N days.
29+ // days must be > 0.
28 GetMostVisited(ctx context.Context, days int) ([]*entity.HistoryEntry, error)30 GetMostVisited(ctx context.Context, days int) ([]*entity.HistoryEntry, error)
29 31
32+ // GetAllRecentHistory retrieves all history entries sorted by recency.
33+ GetAllRecentHistory(ctx context.Context) ([]*entity.HistoryEntry, error)
34+
35+ // GetAllMostVisited retrieves all history entries sorted by visit count.
36+ GetAllMostVisited(ctx context.Context) ([]*entity.HistoryEntry, error)
37+
30 // IncrementVisitCount increments the visit count for a URL.38 // IncrementVisitCount increments the visit count for a URL.
31 IncrementVisitCount(ctx context.Context, url string) error39 IncrementVisitCount(ctx context.Context, url string) error
32 40
M internal/domain/repository/mocks/mocks.go +124 -0
@@ -2197,6 +2197,130 @@ func (_c *MockHistoryRepository_FindByURL_Call) RunAndReturn(run func(ctx contex
21972197 return _c
21982198 }
21992199
2200+// GetAllMostVisited provides a mock function for the type MockHistoryRepository
2201+func (_mock *MockHistoryRepository) GetAllMostVisited(ctx context.Context) ([]*entity.HistoryEntry, error) {
2202+ ret := _mock.Called(ctx)
2203+
2204+ if len(ret) == 0 {
2205+ panic("no return value specified for GetAllMostVisited")
2206+ }
2207+
2208+ var r0 []*entity.HistoryEntry
2209+ var r1 error
2210+ if returnFunc, ok := ret.Get(0).(func(context.Context) ([]*entity.HistoryEntry, error)); ok {
2211+ return returnFunc(ctx)
2212+ }
2213+ if returnFunc, ok := ret.Get(0).(func(context.Context) []*entity.HistoryEntry); ok {
2214+ r0 = returnFunc(ctx)
2215+ } else {
2216+ if ret.Get(0) != nil {
2217+ r0 = ret.Get(0).([]*entity.HistoryEntry)
2218+ }
2219+ }
2220+ if returnFunc, ok := ret.Get(1).(func(context.Context) error); ok {
2221+ r1 = returnFunc(ctx)
2222+ } else {
2223+ r1 = ret.Error(1)
2224+ }
2225+ return r0, r1
2226+}
2227+
2228+// MockHistoryRepository_GetAllMostVisited_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'GetAllMostVisited'
2229+type MockHistoryRepository_GetAllMostVisited_Call struct {
2230+ *mock.Call
2231+}
2232+
2233+// GetAllMostVisited is a helper method to define mock.On call
2234+// - ctx context.Context
2235+func (_e *MockHistoryRepository_Expecter) GetAllMostVisited(ctx interface{}) *MockHistoryRepository_GetAllMostVisited_Call {
2236+ return &MockHistoryRepository_GetAllMostVisited_Call{Call: _e.mock.On("GetAllMostVisited", ctx)}
2237+}
2238+
2239+func (_c *MockHistoryRepository_GetAllMostVisited_Call) Run(run func(ctx context.Context)) *MockHistoryRepository_GetAllMostVisited_Call {
2240+ _c.Call.Run(func(args mock.Arguments) {
2241+ var arg0 context.Context
2242+ if args[0] != nil {
2243+ arg0 = args[0].(context.Context)
2244+ }
2245+ run(
2246+ arg0,
2247+ )
2248+ })
2249+ return _c
2250+}
2251+
2252+func (_c *MockHistoryRepository_GetAllMostVisited_Call) Return(historyEntrys []*entity.HistoryEntry, err error) *MockHistoryRepository_GetAllMostVisited_Call {
2253+ _c.Call.Return(historyEntrys, err)
2254+ return _c
2255+}
2256+
2257+func (_c *MockHistoryRepository_GetAllMostVisited_Call) RunAndReturn(run func(ctx context.Context) ([]*entity.HistoryEntry, error)) *MockHistoryRepository_GetAllMostVisited_Call {
2258+ _c.Call.Return(run)
2259+ return _c
2260+}
2261+
2262+// GetAllRecentHistory provides a mock function for the type MockHistoryRepository
2263+func (_mock *MockHistoryRepository) GetAllRecentHistory(ctx context.Context) ([]*entity.HistoryEntry, error) {
2264+ ret := _mock.Called(ctx)
2265+
2266+ if len(ret) == 0 {
2267+ panic("no return value specified for GetAllRecentHistory")
2268+ }
2269+
2270+ var r0 []*entity.HistoryEntry
2271+ var r1 error
2272+ if returnFunc, ok := ret.Get(0).(func(context.Context) ([]*entity.HistoryEntry, error)); ok {
2273+ return returnFunc(ctx)
2274+ }
2275+ if returnFunc, ok := ret.Get(0).(func(context.Context) []*entity.HistoryEntry); ok {
2276+ r0 = returnFunc(ctx)
2277+ } else {
2278+ if ret.Get(0) != nil {
2279+ r0 = ret.Get(0).([]*entity.HistoryEntry)
2280+ }
2281+ }
2282+ if returnFunc, ok := ret.Get(1).(func(context.Context) error); ok {
2283+ r1 = returnFunc(ctx)
2284+ } else {
2285+ r1 = ret.Error(1)
2286+ }
2287+ return r0, r1
2288+}
2289+
2290+// MockHistoryRepository_GetAllRecentHistory_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'GetAllRecentHistory'
2291+type MockHistoryRepository_GetAllRecentHistory_Call struct {
2292+ *mock.Call
2293+}
2294+
2295+// GetAllRecentHistory is a helper method to define mock.On call
2296+// - ctx context.Context
2297+func (_e *MockHistoryRepository_Expecter) GetAllRecentHistory(ctx interface{}) *MockHistoryRepository_GetAllRecentHistory_Call {
2298+ return &MockHistoryRepository_GetAllRecentHistory_Call{Call: _e.mock.On("GetAllRecentHistory", ctx)}
2299+}
2300+
2301+func (_c *MockHistoryRepository_GetAllRecentHistory_Call) Run(run func(ctx context.Context)) *MockHistoryRepository_GetAllRecentHistory_Call {
2302+ _c.Call.Run(func(args mock.Arguments) {
2303+ var arg0 context.Context
2304+ if args[0] != nil {
2305+ arg0 = args[0].(context.Context)
2306+ }
2307+ run(
2308+ arg0,
2309+ )
2310+ })
2311+ return _c
2312+}
2313+
2314+func (_c *MockHistoryRepository_GetAllRecentHistory_Call) Return(historyEntrys []*entity.HistoryEntry, err error) *MockHistoryRepository_GetAllRecentHistory_Call {
2315+ _c.Call.Return(historyEntrys, err)
2316+ return _c
2317+}
2318+
2319+func (_c *MockHistoryRepository_GetAllRecentHistory_Call) RunAndReturn(run func(ctx context.Context) ([]*entity.HistoryEntry, error)) *MockHistoryRepository_GetAllRecentHistory_Call {
2320+ _c.Call.Return(run)
2321+ return _c
2322+}
2323+
22002324 // GetDailyVisitCount provides a mock function for the type MockHistoryRepository
22012325 func (_mock *MockHistoryRepository) GetDailyVisitCount(ctx context.Context, daysAgo string) ([]*entity.DailyVisitCount, error) {
22022326 ret := _mock.Called(ctx, daysAgo)
@@ -2197,6 +2197,130 @@ func (_c *MockHistoryRepository_FindByURL_Call) RunAndReturn(run func(ctx contex
2197 return _c2197 return _c
2198 }2198 }
2199 2199
2200+// GetAllMostVisited provides a mock function for the type MockHistoryRepository
2201+func (_mock *MockHistoryRepository) GetAllMostVisited(ctx context.Context) ([]*entity.HistoryEntry, error) {
2202+ ret := _mock.Called(ctx)
2203+
2204+ if len(ret) == 0 {
2205+ panic("no return value specified for GetAllMostVisited")
2206+ }
2207+
2208+ var r0 []*entity.HistoryEntry
2209+ var r1 error
2210+ if returnFunc, ok := ret.Get(0).(func(context.Context) ([]*entity.HistoryEntry, error)); ok {
2211+ return returnFunc(ctx)
2212+ }
2213+ if returnFunc, ok := ret.Get(0).(func(context.Context) []*entity.HistoryEntry); ok {
2214+ r0 = returnFunc(ctx)
2215+ } else {
2216+ if ret.Get(0) != nil {
2217+ r0 = ret.Get(0).([]*entity.HistoryEntry)
2218+ }
2219+ }
2220+ if returnFunc, ok := ret.Get(1).(func(context.Context) error); ok {
2221+ r1 = returnFunc(ctx)
2222+ } else {
2223+ r1 = ret.Error(1)
2224+ }
2225+ return r0, r1
2226+}
2227+
2228+// MockHistoryRepository_GetAllMostVisited_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'GetAllMostVisited'
2229+type MockHistoryRepository_GetAllMostVisited_Call struct {
2230+ *mock.Call
2231+}
2232+
2233+// GetAllMostVisited is a helper method to define mock.On call
2234+// - ctx context.Context
2235+func (_e *MockHistoryRepository_Expecter) GetAllMostVisited(ctx interface{}) *MockHistoryRepository_GetAllMostVisited_Call {
2236+ return &MockHistoryRepository_GetAllMostVisited_Call{Call: _e.mock.On("GetAllMostVisited", ctx)}
2237+}
2238+
2239+func (_c *MockHistoryRepository_GetAllMostVisited_Call) Run(run func(ctx context.Context)) *MockHistoryRepository_GetAllMostVisited_Call {
2240+ _c.Call.Run(func(args mock.Arguments) {
2241+ var arg0 context.Context
2242+ if args[0] != nil {
2243+ arg0 = args[0].(context.Context)
2244+ }
2245+ run(
2246+ arg0,
2247+ )
2248+ })
2249+ return _c
2250+}
2251+
2252+func (_c *MockHistoryRepository_GetAllMostVisited_Call) Return(historyEntrys []*entity.HistoryEntry, err error) *MockHistoryRepository_GetAllMostVisited_Call {
2253+ _c.Call.Return(historyEntrys, err)
2254+ return _c
2255+}
2256+
2257+func (_c *MockHistoryRepository_GetAllMostVisited_Call) RunAndReturn(run func(ctx context.Context) ([]*entity.HistoryEntry, error)) *MockHistoryRepository_GetAllMostVisited_Call {
2258+ _c.Call.Return(run)
2259+ return _c
2260+}
2261+
2262+// GetAllRecentHistory provides a mock function for the type MockHistoryRepository
2263+func (_mock *MockHistoryRepository) GetAllRecentHistory(ctx context.Context) ([]*entity.HistoryEntry, error) {
2264+ ret := _mock.Called(ctx)
2265+
2266+ if len(ret) == 0 {
2267+ panic("no return value specified for GetAllRecentHistory")
2268+ }
2269+
2270+ var r0 []*entity.HistoryEntry
2271+ var r1 error
2272+ if returnFunc, ok := ret.Get(0).(func(context.Context) ([]*entity.HistoryEntry, error)); ok {
2273+ return returnFunc(ctx)
2274+ }
2275+ if returnFunc, ok := ret.Get(0).(func(context.Context) []*entity.HistoryEntry); ok {
2276+ r0 = returnFunc(ctx)
2277+ } else {
2278+ if ret.Get(0) != nil {
2279+ r0 = ret.Get(0).([]*entity.HistoryEntry)
2280+ }
2281+ }
2282+ if returnFunc, ok := ret.Get(1).(func(context.Context) error); ok {
2283+ r1 = returnFunc(ctx)
2284+ } else {
2285+ r1 = ret.Error(1)
2286+ }
2287+ return r0, r1
2288+}
2289+
2290+// MockHistoryRepository_GetAllRecentHistory_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'GetAllRecentHistory'
2291+type MockHistoryRepository_GetAllRecentHistory_Call struct {
2292+ *mock.Call
2293+}
2294+
2295+// GetAllRecentHistory is a helper method to define mock.On call
2296+// - ctx context.Context
2297+func (_e *MockHistoryRepository_Expecter) GetAllRecentHistory(ctx interface{}) *MockHistoryRepository_GetAllRecentHistory_Call {
2298+ return &MockHistoryRepository_GetAllRecentHistory_Call{Call: _e.mock.On("GetAllRecentHistory", ctx)}
2299+}
2300+
2301+func (_c *MockHistoryRepository_GetAllRecentHistory_Call) Run(run func(ctx context.Context)) *MockHistoryRepository_GetAllRecentHistory_Call {
2302+ _c.Call.Run(func(args mock.Arguments) {
2303+ var arg0 context.Context
2304+ if args[0] != nil {
2305+ arg0 = args[0].(context.Context)
2306+ }
2307+ run(
2308+ arg0,
2309+ )
2310+ })
2311+ return _c
2312+}
2313+
2314+func (_c *MockHistoryRepository_GetAllRecentHistory_Call) Return(historyEntrys []*entity.HistoryEntry, err error) *MockHistoryRepository_GetAllRecentHistory_Call {
2315+ _c.Call.Return(historyEntrys, err)
2316+ return _c
2317+}
2318+
2319+func (_c *MockHistoryRepository_GetAllRecentHistory_Call) RunAndReturn(run func(ctx context.Context) ([]*entity.HistoryEntry, error)) *MockHistoryRepository_GetAllRecentHistory_Call {
2320+ _c.Call.Return(run)
2321+ return _c
2322+}
2323+
2200 // GetDailyVisitCount provides a mock function for the type MockHistoryRepository2324 // GetDailyVisitCount provides a mock function for the type MockHistoryRepository
2201 func (_mock *MockHistoryRepository) GetDailyVisitCount(ctx context.Context, daysAgo string) ([]*entity.DailyVisitCount, error) {2325 func (_mock *MockHistoryRepository) GetDailyVisitCount(ctx context.Context, daysAgo string) ([]*entity.DailyVisitCount, error) {
2202 ret := _mock.Called(ctx, daysAgo)2326 ret := _mock.Called(ctx, daysAgo)
M internal/infrastructure/persistence/sqlite/history_repo.go +32 -0
@@ -89,6 +89,9 @@ func (r *historyRepo) GetRecent(ctx context.Context, limit, offset int) ([]*enti
8989 }
9090
9191 func (r *historyRepo) GetRecentSince(ctx context.Context, days int) ([]*entity.HistoryEntry, error) {
92+ if days <= 0 {
93+ return nil, fmt.Errorf("days must be positive, got %d", days)
94+ }
9295 // Format: "-N days" for SQLite datetime modifier
9396 daysModifier := fmt.Sprintf("-%d days", days)
9497 rows, err := r.queries.GetRecentHistorySince(ctx, daysModifier)
@@ -104,6 +107,9 @@ func (r *historyRepo) GetRecentSince(ctx context.Context, days int) ([]*entity.H
104107 }
105108
106109 func (r *historyRepo) GetMostVisited(ctx context.Context, days int) ([]*entity.HistoryEntry, error) {
110+ if days <= 0 {
111+ return nil, fmt.Errorf("days must be positive, got %d", days)
112+ }
107113 // Format: "-N days" for SQLite datetime modifier
108114 daysModifier := fmt.Sprintf("-%d days", days)
109115 rows, err := r.queries.GetMostVisited(ctx, daysModifier)
@@ -118,6 +124,32 @@ func (r *historyRepo) GetMostVisited(ctx context.Context, days int) ([]*entity.H
118124 return entries, nil
119125 }
120126
127+func (r *historyRepo) GetAllRecentHistory(ctx context.Context) ([]*entity.HistoryEntry, error) {
128+ rows, err := r.queries.GetAllRecentHistory(ctx)
129+ if err != nil {
130+ return nil, err
131+ }
132+
133+ entries := make([]*entity.HistoryEntry, len(rows))
134+ for i := range rows {
135+ entries[i] = historyFromRow(rows[i])
136+ }
137+ return entries, nil
138+}
139+
140+func (r *historyRepo) GetAllMostVisited(ctx context.Context) ([]*entity.HistoryEntry, error) {
141+ rows, err := r.queries.GetAllMostVisited(ctx)
142+ if err != nil {
143+ return nil, err
144+ }
145+
146+ entries := make([]*entity.HistoryEntry, len(rows))
147+ for i := range rows {
148+ entries[i] = historyFromRow(rows[i])
149+ }
150+ return entries, nil
151+}
152+
121153 func (r *historyRepo) IncrementVisitCount(ctx context.Context, url string) error {
122154 return r.queries.IncrementVisitCount(ctx, url)
123155 }
@@ -89,6 +89,9 @@ func (r *historyRepo) GetRecent(ctx context.Context, limit, offset int) ([]*enti
89 }89 }
90 90
91 func (r *historyRepo) GetRecentSince(ctx context.Context, days int) ([]*entity.HistoryEntry, error) {91 func (r *historyRepo) GetRecentSince(ctx context.Context, days int) ([]*entity.HistoryEntry, error) {
92+ if days <= 0 {
93+ return nil, fmt.Errorf("days must be positive, got %d", days)
94+ }
92 // Format: "-N days" for SQLite datetime modifier95 // Format: "-N days" for SQLite datetime modifier
93 daysModifier := fmt.Sprintf("-%d days", days)96 daysModifier := fmt.Sprintf("-%d days", days)
94 rows, err := r.queries.GetRecentHistorySince(ctx, daysModifier)97 rows, err := r.queries.GetRecentHistorySince(ctx, daysModifier)
@@ -104,6 +107,9 @@ func (r *historyRepo) GetRecentSince(ctx context.Context, days int) ([]*entity.H
104 }107 }
105 108
106 func (r *historyRepo) GetMostVisited(ctx context.Context, days int) ([]*entity.HistoryEntry, error) {109 func (r *historyRepo) GetMostVisited(ctx context.Context, days int) ([]*entity.HistoryEntry, error) {
110+ if days <= 0 {
111+ return nil, fmt.Errorf("days must be positive, got %d", days)
112+ }
107 // Format: "-N days" for SQLite datetime modifier113 // Format: "-N days" for SQLite datetime modifier
108 daysModifier := fmt.Sprintf("-%d days", days)114 daysModifier := fmt.Sprintf("-%d days", days)
109 rows, err := r.queries.GetMostVisited(ctx, daysModifier)115 rows, err := r.queries.GetMostVisited(ctx, daysModifier)
@@ -118,6 +124,32 @@ func (r *historyRepo) GetMostVisited(ctx context.Context, days int) ([]*entity.H
118 return entries, nil124 return entries, nil
119 }125 }
120 126
127+func (r *historyRepo) GetAllRecentHistory(ctx context.Context) ([]*entity.HistoryEntry, error) {
128+ rows, err := r.queries.GetAllRecentHistory(ctx)
129+ if err != nil {
130+ return nil, err
131+ }
132+
133+ entries := make([]*entity.HistoryEntry, len(rows))
134+ for i := range rows {
135+ entries[i] = historyFromRow(rows[i])
136+ }
137+ return entries, nil
138+}
139+
140+func (r *historyRepo) GetAllMostVisited(ctx context.Context) ([]*entity.HistoryEntry, error) {
141+ rows, err := r.queries.GetAllMostVisited(ctx)
142+ if err != nil {
143+ return nil, err
144+ }
145+
146+ entries := make([]*entity.HistoryEntry, len(rows))
147+ for i := range rows {
148+ entries[i] = historyFromRow(rows[i])
149+ }
150+ return entries, nil
151+}
152+
121 func (r *historyRepo) IncrementVisitCount(ctx context.Context, url string) error {153 func (r *historyRepo) IncrementVisitCount(ctx context.Context, url string) error {
122 return r.queries.IncrementVisitCount(ctx, url)154 return r.queries.IncrementVisitCount(ctx, url)
123 }155 }
M internal/infrastructure/persistence/sqlite/history_repo_test.go +99 -0
@@ -224,3 +224,102 @@ func TestHistoryRepository_TimeFilterIntegrity(t *testing.T) {
224224 timeDiff := time.Since(found.LastVisited)
225225 assert.Less(t, timeDiff, time.Minute, "LastVisited should be recent, got %v ago", timeDiff)
226226 }
227+
228+func TestHistoryRepository_GetRecentSince_RejectsNonPositiveDays(t *testing.T) {
229+ ctx := historyTestCtx()
230+ dbPath := filepath.Join(t.TempDir(), "dumber.db")
231+
232+ db, err := sqlite.NewConnection(ctx, dbPath)
233+ require.NoError(t, err)
234+ t.Cleanup(func() { _ = db.Close() })
235+
236+ repo := sqlite.NewHistoryRepository(db)
237+
238+ // days = 0 should return error
239+ _, err = repo.GetRecentSince(ctx, 0)
240+ require.Error(t, err)
241+ assert.Contains(t, err.Error(), "days must be positive")
242+
243+ // days < 0 should return error
244+ _, err = repo.GetRecentSince(ctx, -5)
245+ require.Error(t, err)
246+ assert.Contains(t, err.Error(), "days must be positive")
247+}
248+
249+func TestHistoryRepository_GetMostVisited_RejectsNonPositiveDays(t *testing.T) {
250+ ctx := historyTestCtx()
251+ dbPath := filepath.Join(t.TempDir(), "dumber.db")
252+
253+ db, err := sqlite.NewConnection(ctx, dbPath)
254+ require.NoError(t, err)
255+ t.Cleanup(func() { _ = db.Close() })
256+
257+ repo := sqlite.NewHistoryRepository(db)
258+
259+ // days = 0 should return error
260+ _, err = repo.GetMostVisited(ctx, 0)
261+ require.Error(t, err)
262+ assert.Contains(t, err.Error(), "days must be positive")
263+
264+ // days < 0 should return error
265+ _, err = repo.GetMostVisited(ctx, -5)
266+ require.Error(t, err)
267+ assert.Contains(t, err.Error(), "days must be positive")
268+}
269+
270+func TestHistoryRepository_GetAllRecentHistory(t *testing.T) {
271+ ctx := historyTestCtx()
272+ dbPath := filepath.Join(t.TempDir(), "dumber.db")
273+
274+ db, err := sqlite.NewConnection(ctx, dbPath)
275+ require.NoError(t, err)
276+ t.Cleanup(func() { _ = db.Close() })
277+
278+ repo := sqlite.NewHistoryRepository(db)
279+
280+ // Save some entries
281+ entries := []*entity.HistoryEntry{
282+ {URL: "https://example.com", Title: "Example"},
283+ {URL: "https://github.com", Title: "GitHub"},
284+ }
285+ for _, e := range entries {
286+ require.NoError(t, repo.Save(ctx, e))
287+ }
288+
289+ // Get all recent history
290+ results, err := repo.GetAllRecentHistory(ctx)
291+ require.NoError(t, err)
292+ require.Len(t, results, 2)
293+}
294+
295+func TestHistoryRepository_GetAllMostVisited(t *testing.T) {
296+ ctx := historyTestCtx()
297+ dbPath := filepath.Join(t.TempDir(), "dumber.db")
298+
299+ db, err := sqlite.NewConnection(ctx, dbPath)
300+ require.NoError(t, err)
301+ t.Cleanup(func() { _ = db.Close() })
302+
303+ repo := sqlite.NewHistoryRepository(db)
304+
305+ // Save entries with different visit counts
306+ entry1 := &entity.HistoryEntry{URL: "https://example.com", Title: "Example"}
307+ entry2 := &entity.HistoryEntry{URL: "https://github.com", Title: "GitHub"}
308+
309+ require.NoError(t, repo.Save(ctx, entry1))
310+ require.NoError(t, repo.Save(ctx, entry2))
311+
312+ // Increment github visits
313+ for i := 0; i < 3; i++ {
314+ require.NoError(t, repo.IncrementVisitCount(ctx, "https://github.com"))
315+ }
316+
317+ // Get all most visited
318+ results, err := repo.GetAllMostVisited(ctx)
319+ require.NoError(t, err)
320+ require.Len(t, results, 2)
321+
322+ // First should be github (4 visits)
323+ assert.Equal(t, "https://github.com", results[0].URL)
324+ assert.Equal(t, int64(4), results[0].VisitCount)
325+}
@@ -224,3 +224,102 @@ func TestHistoryRepository_TimeFilterIntegrity(t *testing.T) {
224 timeDiff := time.Since(found.LastVisited)224 timeDiff := time.Since(found.LastVisited)
225 assert.Less(t, timeDiff, time.Minute, "LastVisited should be recent, got %v ago", timeDiff)225 assert.Less(t, timeDiff, time.Minute, "LastVisited should be recent, got %v ago", timeDiff)
226 }226 }
227+
228+func TestHistoryRepository_GetRecentSince_RejectsNonPositiveDays(t *testing.T) {
229+ ctx := historyTestCtx()
230+ dbPath := filepath.Join(t.TempDir(), "dumber.db")
231+
232+ db, err := sqlite.NewConnection(ctx, dbPath)
233+ require.NoError(t, err)
234+ t.Cleanup(func() { _ = db.Close() })
235+
236+ repo := sqlite.NewHistoryRepository(db)
237+
238+ // days = 0 should return error
239+ _, err = repo.GetRecentSince(ctx, 0)
240+ require.Error(t, err)
241+ assert.Contains(t, err.Error(), "days must be positive")
242+
243+ // days < 0 should return error
244+ _, err = repo.GetRecentSince(ctx, -5)
245+ require.Error(t, err)
246+ assert.Contains(t, err.Error(), "days must be positive")
247+}
248+
249+func TestHistoryRepository_GetMostVisited_RejectsNonPositiveDays(t *testing.T) {
250+ ctx := historyTestCtx()
251+ dbPath := filepath.Join(t.TempDir(), "dumber.db")
252+
253+ db, err := sqlite.NewConnection(ctx, dbPath)
254+ require.NoError(t, err)
255+ t.Cleanup(func() { _ = db.Close() })
256+
257+ repo := sqlite.NewHistoryRepository(db)
258+
259+ // days = 0 should return error
260+ _, err = repo.GetMostVisited(ctx, 0)
261+ require.Error(t, err)
262+ assert.Contains(t, err.Error(), "days must be positive")
263+
264+ // days < 0 should return error
265+ _, err = repo.GetMostVisited(ctx, -5)
266+ require.Error(t, err)
267+ assert.Contains(t, err.Error(), "days must be positive")
268+}
269+
270+func TestHistoryRepository_GetAllRecentHistory(t *testing.T) {
271+ ctx := historyTestCtx()
272+ dbPath := filepath.Join(t.TempDir(), "dumber.db")
273+
274+ db, err := sqlite.NewConnection(ctx, dbPath)
275+ require.NoError(t, err)
276+ t.Cleanup(func() { _ = db.Close() })
277+
278+ repo := sqlite.NewHistoryRepository(db)
279+
280+ // Save some entries
281+ entries := []*entity.HistoryEntry{
282+ {URL: "https://example.com", Title: "Example"},
283+ {URL: "https://github.com", Title: "GitHub"},
284+ }
285+ for _, e := range entries {
286+ require.NoError(t, repo.Save(ctx, e))
287+ }
288+
289+ // Get all recent history
290+ results, err := repo.GetAllRecentHistory(ctx)
291+ require.NoError(t, err)
292+ require.Len(t, results, 2)
293+}
294+
295+func TestHistoryRepository_GetAllMostVisited(t *testing.T) {
296+ ctx := historyTestCtx()
297+ dbPath := filepath.Join(t.TempDir(), "dumber.db")
298+
299+ db, err := sqlite.NewConnection(ctx, dbPath)
300+ require.NoError(t, err)
301+ t.Cleanup(func() { _ = db.Close() })
302+
303+ repo := sqlite.NewHistoryRepository(db)
304+
305+ // Save entries with different visit counts
306+ entry1 := &entity.HistoryEntry{URL: "https://example.com", Title: "Example"}
307+ entry2 := &entity.HistoryEntry{URL: "https://github.com", Title: "GitHub"}
308+
309+ require.NoError(t, repo.Save(ctx, entry1))
310+ require.NoError(t, repo.Save(ctx, entry2))
311+
312+ // Increment github visits
313+ for i := 0; i < 3; i++ {
314+ require.NoError(t, repo.IncrementVisitCount(ctx, "https://github.com"))
315+ }
316+
317+ // Get all most visited
318+ results, err := repo.GetAllMostVisited(ctx)
319+ require.NoError(t, err)
320+ require.Len(t, results, 2)
321+
322+ // First should be github (4 visits)
323+ assert.Equal(t, "https://github.com", results[0].URL)
324+ assert.Equal(t, int64(4), results[0].VisitCount)
325+}
M internal/infrastructure/persistence/sqlite/lazy_repos.go +14 -0
@@ -93,6 +93,20 @@ func (r *LazyHistoryRepository) GetMostVisited(ctx context.Context, days int) ([
9393 return r.repo.GetMostVisited(ctx, days)
9494 }
9595
96+func (r *LazyHistoryRepository) GetAllRecentHistory(ctx context.Context) ([]*entity.HistoryEntry, error) {
97+ if err := r.init(ctx); err != nil {
98+ return nil, err
99+ }
100+ return r.repo.GetAllRecentHistory(ctx)
101+}
102+
103+func (r *LazyHistoryRepository) GetAllMostVisited(ctx context.Context) ([]*entity.HistoryEntry, error) {
104+ if err := r.init(ctx); err != nil {
105+ return nil, err
106+ }
107+ return r.repo.GetAllMostVisited(ctx)
108+}
109+
96110 func (r *LazyHistoryRepository) IncrementVisitCount(ctx context.Context, url string) error {
97111 if err := r.init(ctx); err != nil {
98112 return err
@@ -93,6 +93,20 @@ func (r *LazyHistoryRepository) GetMostVisited(ctx context.Context, days int) ([
93 return r.repo.GetMostVisited(ctx, days)93 return r.repo.GetMostVisited(ctx, days)
94 }94 }
95 95
96+func (r *LazyHistoryRepository) GetAllRecentHistory(ctx context.Context) ([]*entity.HistoryEntry, error) {
97+ if err := r.init(ctx); err != nil {
98+ return nil, err
99+ }
100+ return r.repo.GetAllRecentHistory(ctx)
101+}
102+
103+func (r *LazyHistoryRepository) GetAllMostVisited(ctx context.Context) ([]*entity.HistoryEntry, error) {
104+ if err := r.init(ctx); err != nil {
105+ return nil, err
106+ }
107+ return r.repo.GetAllMostVisited(ctx)
108+}
109+
96 func (r *LazyHistoryRepository) IncrementVisitCount(ctx context.Context, url string) error {110 func (r *LazyHistoryRepository) IncrementVisitCount(ctx context.Context, url string) error {
97 if err := r.init(ctx); err != nil {111 if err := r.init(ctx); err != nil {
98 return err112 return err
M internal/infrastructure/persistence/sqlite/queries/history.sql +8 -0
@@ -66,3 +66,11 @@ ORDER BY last_visited DESC;
6666 SELECT * FROM history
6767 WHERE last_visited >= datetime('now', ?)
6868 ORDER BY visit_count DESC, last_visited DESC;
69+
70+-- name: GetAllRecentHistory :many
71+SELECT * FROM history
72+ORDER BY last_visited DESC;
73+
74+-- name: GetAllMostVisited :many
75+SELECT * FROM history
76+ORDER BY visit_count DESC, last_visited DESC;
@@ -66,3 +66,11 @@ ORDER BY last_visited DESC;
66 SELECT * FROM history66 SELECT * FROM history
67 WHERE last_visited >= datetime('now', ?)67 WHERE last_visited >= datetime('now', ?)
68 ORDER BY visit_count DESC, last_visited DESC;68 ORDER BY visit_count DESC, last_visited DESC;
69+
70+-- name: GetAllRecentHistory :many
71+SELECT * FROM history
72+ORDER BY last_visited DESC;
73+
74+-- name: GetAllMostVisited :many
75+SELECT * FROM history
76+ORDER BY visit_count DESC, last_visited DESC;
M internal/infrastructure/persistence/sqlite/sqlc/history.sql.go +72 -0
@@ -58,6 +58,78 @@ func (q *Queries) DeleteHistoryOlderThan(ctx context.Context, lastVisited sql.Nu
5858 return err
5959 }
6060
61+const GetAllMostVisited = `-- name: GetAllMostVisited :many
62+SELECT id, url, title, favicon_url, visit_count, last_visited, created_at FROM history
63+ORDER BY visit_count DESC, last_visited DESC
64+`
65+
66+func (q *Queries) GetAllMostVisited(ctx context.Context) ([]History, error) {
67+ rows, err := q.db.QueryContext(ctx, GetAllMostVisited)
68+ if err != nil {
69+ return nil, err
70+ }
71+ defer rows.Close()
72+ items := []History{}
73+ for rows.Next() {
74+ var i History
75+ if err := rows.Scan(
76+ &i.ID,
77+ &i.Url,
78+ &i.Title,
79+ &i.FaviconUrl,
80+ &i.VisitCount,
81+ &i.LastVisited,
82+ &i.CreatedAt,
83+ ); err != nil {
84+ return nil, err
85+ }
86+ items = append(items, i)
87+ }
88+ if err := rows.Close(); err != nil {
89+ return nil, err
90+ }
91+ if err := rows.Err(); err != nil {
92+ return nil, err
93+ }
94+ return items, nil
95+}
96+
97+const GetAllRecentHistory = `-- name: GetAllRecentHistory :many
98+SELECT id, url, title, favicon_url, visit_count, last_visited, created_at FROM history
99+ORDER BY last_visited DESC
100+`
101+
102+func (q *Queries) GetAllRecentHistory(ctx context.Context) ([]History, error) {
103+ rows, err := q.db.QueryContext(ctx, GetAllRecentHistory)
104+ if err != nil {
105+ return nil, err
106+ }
107+ defer rows.Close()
108+ items := []History{}
109+ for rows.Next() {
110+ var i History
111+ if err := rows.Scan(
112+ &i.ID,
113+ &i.Url,
114+ &i.Title,
115+ &i.FaviconUrl,
116+ &i.VisitCount,
117+ &i.LastVisited,
118+ &i.CreatedAt,
119+ ); err != nil {
120+ return nil, err
121+ }
122+ items = append(items, i)
123+ }
124+ if err := rows.Close(); err != nil {
125+ return nil, err
126+ }
127+ if err := rows.Err(); err != nil {
128+ return nil, err
129+ }
130+ return items, nil
131+}
132+
61133 const GetDailyVisitCount = `-- name: GetDailyVisitCount :many
62134 SELECT date(last_visited) as day, COUNT(*) as entries, SUM(visit_count) as visits FROM history WHERE last_visited >= date('now', ?) GROUP BY day ORDER BY day ASC
63135 `
@@ -58,6 +58,78 @@ func (q *Queries) DeleteHistoryOlderThan(ctx context.Context, lastVisited sql.Nu
58 return err58 return err
59 }59 }
60 60
61+const GetAllMostVisited = `-- name: GetAllMostVisited :many
62+SELECT id, url, title, favicon_url, visit_count, last_visited, created_at FROM history
63+ORDER BY visit_count DESC, last_visited DESC
64+`
65+
66+func (q *Queries) GetAllMostVisited(ctx context.Context) ([]History, error) {
67+ rows, err := q.db.QueryContext(ctx, GetAllMostVisited)
68+ if err != nil {
69+ return nil, err
70+ }
71+ defer rows.Close()
72+ items := []History{}
73+ for rows.Next() {
74+ var i History
75+ if err := rows.Scan(
76+ &i.ID,
77+ &i.Url,
78+ &i.Title,
79+ &i.FaviconUrl,
80+ &i.VisitCount,
81+ &i.LastVisited,
82+ &i.CreatedAt,
83+ ); err != nil {
84+ return nil, err
85+ }
86+ items = append(items, i)
87+ }
88+ if err := rows.Close(); err != nil {
89+ return nil, err
90+ }
91+ if err := rows.Err(); err != nil {
92+ return nil, err
93+ }
94+ return items, nil
95+}
96+
97+const GetAllRecentHistory = `-- name: GetAllRecentHistory :many
98+SELECT id, url, title, favicon_url, visit_count, last_visited, created_at FROM history
99+ORDER BY last_visited DESC
100+`
101+
102+func (q *Queries) GetAllRecentHistory(ctx context.Context) ([]History, error) {
103+ rows, err := q.db.QueryContext(ctx, GetAllRecentHistory)
104+ if err != nil {
105+ return nil, err
106+ }
107+ defer rows.Close()
108+ items := []History{}
109+ for rows.Next() {
110+ var i History
111+ if err := rows.Scan(
112+ &i.ID,
113+ &i.Url,
114+ &i.Title,
115+ &i.FaviconUrl,
116+ &i.VisitCount,
117+ &i.LastVisited,
118+ &i.CreatedAt,
119+ ); err != nil {
120+ return nil, err
121+ }
122+ items = append(items, i)
123+ }
124+ if err := rows.Close(); err != nil {
125+ return nil, err
126+ }
127+ if err := rows.Err(); err != nil {
128+ return nil, err
129+ }
130+ return items, nil
131+}
132+
61 const GetDailyVisitCount = `-- name: GetDailyVisitCount :many133 const GetDailyVisitCount = `-- name: GetDailyVisitCount :many
62 SELECT date(last_visited) as day, COUNT(*) as entries, SUM(visit_count) as visits FROM history WHERE last_visited >= date('now', ?) GROUP BY day ORDER BY day ASC134 SELECT date(last_visited) as day, COUNT(*) as entries, SUM(visit_count) as visits FROM history WHERE last_visited >= date('now', ?) GROUP BY day ORDER BY day ASC
63 `135 `
M internal/infrastructure/persistence/sqlite/sqlc/querier.go +2 -0
@@ -32,6 +32,8 @@ type Querier interface {
3232 GetActiveBrowserSession(ctx context.Context) (Session, error)
3333 GetAllFavorites(ctx context.Context) ([]Favorite, error)
3434 GetAllFolders(ctx context.Context) ([]FavoriteFolder, error)
35+ GetAllMostVisited(ctx context.Context) ([]History, error)
36+ GetAllRecentHistory(ctx context.Context) ([]History, error)
3537 GetAllSessionStates(ctx context.Context) ([]SessionState, error)
3638 GetAllTags(ctx context.Context) ([]FavoriteTag, error)
3739 GetAllWhitelistedDomains(ctx context.Context) ([]string, error)
@@ -32,6 +32,8 @@ type Querier interface {
32 GetActiveBrowserSession(ctx context.Context) (Session, error)32 GetActiveBrowserSession(ctx context.Context) (Session, error)
33 GetAllFavorites(ctx context.Context) ([]Favorite, error)33 GetAllFavorites(ctx context.Context) ([]Favorite, error)
34 GetAllFolders(ctx context.Context) ([]FavoriteFolder, error)34 GetAllFolders(ctx context.Context) ([]FavoriteFolder, error)
35+ GetAllMostVisited(ctx context.Context) ([]History, error)
36+ GetAllRecentHistory(ctx context.Context) ([]History, error)
35 GetAllSessionStates(ctx context.Context) ([]SessionState, error)37 GetAllSessionStates(ctx context.Context) ([]SessionState, error)
36 GetAllTags(ctx context.Context) ([]FavoriteTag, error)38 GetAllTags(ctx context.Context) ([]FavoriteTag, error)
37 GetAllWhitelistedDomains(ctx context.Context) ([]string, error)39 GetAllWhitelistedDomains(ctx context.Context) ([]string, error)

Keyboard shortcuts

Single-key shortcuts are on. ctrl k and esc work whatever this says.

ctrl k :
go to a section, branch, tag, file or repository
?
this list
j k
move the row cursor down / up
enter
open the row under the cursor; on Files, a directory opens or closes in place
↑ ↓ ← → home end
in the file tree (focus on a row): previous / next row, close / open a directory or go to its parent / first entry, first / last row. They never open a file
l h
on Files: open a directory or a file / close a directory or go up to its parent, then to the parent page. Elsewhere: into the side panel (on a merge request, its files) / back to the content; there j k move between its controls
g g G
first / last row
/
filter the list; on Files, find a file anywhere in the repository (esc clears, enter opens the best match)
b
switch branch or tag
g s g f g c g b g m g i g r g a
tabs: summary, files, commits, branches & tags, merge requests, issues, releases, actions (when available)
g h
home
w
wide mode: use the whole width of the screen, or not (remembered)
y
copy a permalink pinned to the commit
d u x
diff: split / unified / collapse or expand all
e E
diff: the whole file in view / every file, with the lines the hunks hide
n
merge request: next note in the diff
esc
close a dialog, leave a field

Branches 8

Tags 42

↑↓ moveenter openesc close