refactor(history): move delete-range policy out of handlers
- commit
- 8cb9bde29545fda05708aebc8bd811f633985de1
- author
- brice · · 2026-04-11 17:49 UTC
- committer
- brice · · 2026-04-12 04:52 UTC
- parent
- 14d9030
- signature
- GPG Verified
CD88F9E564E0102646FB6077A1A07C7C7C3E349D· arch-gpg Verified for the committer email
7 files · +141 -31
M internal/application/port/homepage.go +1 -0
| @@ -23,6 +23,7 @@ type HomepageHistory interface { | |||
| 23 | 23 | GetRecent(ctx context.Context, limit, offset int) ([]*entity.HistoryEntry, error) | |
| 24 | 24 | Search(ctx context.Context, input HistorySearchInput) (*HistorySearchOutput, error) | |
| 25 | 25 | Delete(ctx context.Context, id int64) error | |
| 26 | + | ClearRange(ctx context.Context, rangeID string) error | |
| 26 | 27 | ClearAll(ctx context.Context) error | |
| 27 | 28 | ClearOlderThan(ctx context.Context, before time.Time) error | |
| 28 | 29 | GetAnalytics(ctx context.Context) (*entity.HistoryAnalytics, error) | |
| @@ -23,6 +23,7 @@ type HomepageHistory interface { | |||||
| 23 | GetRecent(ctx context.Context, limit, offset int) ([]*entity.HistoryEntry, error) | 23 | GetRecent(ctx context.Context, limit, offset int) ([]*entity.HistoryEntry, error) | ||
| 24 | Search(ctx context.Context, input HistorySearchInput) (*HistorySearchOutput, error) | 24 | Search(ctx context.Context, input HistorySearchInput) (*HistorySearchOutput, error) | ||
| 25 | Delete(ctx context.Context, id int64) error | 25 | Delete(ctx context.Context, id int64) error | ||
| 26 | + | ClearRange(ctx context.Context, rangeID string) error | |||
| 26 | ClearAll(ctx context.Context) error | 27 | ClearAll(ctx context.Context) error | ||
| 27 | ClearOlderThan(ctx context.Context, before time.Time) error | 28 | ClearOlderThan(ctx context.Context, before time.Time) error | ||
| 28 | GetAnalytics(ctx context.Context) (*entity.HistoryAnalytics, error) | 29 | GetAnalytics(ctx context.Context) (*entity.HistoryAnalytics, error) | ||
M internal/application/port/mocks/mocks.go +57 -0
| @@ -5641,6 +5641,63 @@ func (_c *MockHomepageHistory_ClearAll_Call) RunAndReturn(run func(ctx context.C | |||
| 5641 | 5641 | return _c | |
| 5642 | 5642 | } | |
| 5643 | 5643 | ||
| 5644 | + | // ClearRange provides a mock function for the type MockHomepageHistory | |
| 5645 | + | func (_mock *MockHomepageHistory) ClearRange(ctx context.Context, rangeID string) error { | |
| 5646 | + | ret := _mock.Called(ctx, rangeID) | |
| 5647 | + | ||
| 5648 | + | if len(ret) == 0 { | |
| 5649 | + | panic("no return value specified for ClearRange") | |
| 5650 | + | } | |
| 5651 | + | ||
| 5652 | + | var r0 error | |
| 5653 | + | if returnFunc, ok := ret.Get(0).(func(context.Context, string) error); ok { | |
| 5654 | + | r0 = returnFunc(ctx, rangeID) | |
| 5655 | + | } else { | |
| 5656 | + | r0 = ret.Error(0) | |
| 5657 | + | } | |
| 5658 | + | return r0 | |
| 5659 | + | } | |
| 5660 | + | ||
| 5661 | + | // MockHomepageHistory_ClearRange_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'ClearRange' | |
| 5662 | + | type MockHomepageHistory_ClearRange_Call struct { | |
| 5663 | + | *mock.Call | |
| 5664 | + | } | |
| 5665 | + | ||
| 5666 | + | // ClearRange is a helper method to define mock.On call | |
| 5667 | + | // - ctx context.Context | |
| 5668 | + | // - rangeID string | |
| 5669 | + | func (_e *MockHomepageHistory_Expecter) ClearRange(ctx interface{}, rangeID interface{}) *MockHomepageHistory_ClearRange_Call { | |
| 5670 | + | return &MockHomepageHistory_ClearRange_Call{Call: _e.mock.On("ClearRange", ctx, rangeID)} | |
| 5671 | + | } | |
| 5672 | + | ||
| 5673 | + | func (_c *MockHomepageHistory_ClearRange_Call) Run(run func(ctx context.Context, rangeID string)) *MockHomepageHistory_ClearRange_Call { | |
| 5674 | + | _c.Call.Run(func(args mock.Arguments) { | |
| 5675 | + | var arg0 context.Context | |
| 5676 | + | if args[0] != nil { | |
| 5677 | + | arg0 = args[0].(context.Context) | |
| 5678 | + | } | |
| 5679 | + | var arg1 string | |
| 5680 | + | if args[1] != nil { | |
| 5681 | + | arg1 = args[1].(string) | |
| 5682 | + | } | |
| 5683 | + | run( | |
| 5684 | + | arg0, | |
| 5685 | + | arg1, | |
| 5686 | + | ) | |
| 5687 | + | }) | |
| 5688 | + | return _c | |
| 5689 | + | } | |
| 5690 | + | ||
| 5691 | + | func (_c *MockHomepageHistory_ClearRange_Call) Return(err error) *MockHomepageHistory_ClearRange_Call { | |
| 5692 | + | _c.Call.Return(err) | |
| 5693 | + | return _c | |
| 5694 | + | } | |
| 5695 | + | ||
| 5696 | + | func (_c *MockHomepageHistory_ClearRange_Call) RunAndReturn(run func(ctx context.Context, rangeID string) error) *MockHomepageHistory_ClearRange_Call { | |
| 5697 | + | _c.Call.Return(run) | |
| 5698 | + | return _c | |
| 5699 | + | } | |
| 5700 | + | ||
| 5644 | 5701 | // ClearOlderThan provides a mock function for the type MockHomepageHistory | |
| 5645 | 5702 | func (_mock *MockHomepageHistory) ClearOlderThan(ctx context.Context, before time.Time) error { | |
| 5646 | 5703 | ret := _mock.Called(ctx, before) | |
| @@ -5641,6 +5641,63 @@ func (_c *MockHomepageHistory_ClearAll_Call) RunAndReturn(run func(ctx context.C | |||||
| 5641 | return _c | 5641 | return _c | ||
| 5642 | } | 5642 | } | ||
| 5643 | 5643 | ||||
| 5644 | + | // ClearRange provides a mock function for the type MockHomepageHistory | |||
| 5645 | + | func (_mock *MockHomepageHistory) ClearRange(ctx context.Context, rangeID string) error { | |||
| 5646 | + | ret := _mock.Called(ctx, rangeID) | |||
| 5647 | + | ||||
| 5648 | + | if len(ret) == 0 { | |||
| 5649 | + | panic("no return value specified for ClearRange") | |||
| 5650 | + | } | |||
| 5651 | + | ||||
| 5652 | + | var r0 error | |||
| 5653 | + | if returnFunc, ok := ret.Get(0).(func(context.Context, string) error); ok { | |||
| 5654 | + | r0 = returnFunc(ctx, rangeID) | |||
| 5655 | + | } else { | |||
| 5656 | + | r0 = ret.Error(0) | |||
| 5657 | + | } | |||
| 5658 | + | return r0 | |||
| 5659 | + | } | |||
| 5660 | + | ||||
| 5661 | + | // MockHomepageHistory_ClearRange_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'ClearRange' | |||
| 5662 | + | type MockHomepageHistory_ClearRange_Call struct { | |||
| 5663 | + | *mock.Call | |||
| 5664 | + | } | |||
| 5665 | + | ||||
| 5666 | + | // ClearRange is a helper method to define mock.On call | |||
| 5667 | + | // - ctx context.Context | |||
| 5668 | + | // - rangeID string | |||
| 5669 | + | func (_e *MockHomepageHistory_Expecter) ClearRange(ctx interface{}, rangeID interface{}) *MockHomepageHistory_ClearRange_Call { | |||
| 5670 | + | return &MockHomepageHistory_ClearRange_Call{Call: _e.mock.On("ClearRange", ctx, rangeID)} | |||
| 5671 | + | } | |||
| 5672 | + | ||||
| 5673 | + | func (_c *MockHomepageHistory_ClearRange_Call) Run(run func(ctx context.Context, rangeID string)) *MockHomepageHistory_ClearRange_Call { | |||
| 5674 | + | _c.Call.Run(func(args mock.Arguments) { | |||
| 5675 | + | var arg0 context.Context | |||
| 5676 | + | if args[0] != nil { | |||
| 5677 | + | arg0 = args[0].(context.Context) | |||
| 5678 | + | } | |||
| 5679 | + | var arg1 string | |||
| 5680 | + | if args[1] != nil { | |||
| 5681 | + | arg1 = args[1].(string) | |||
| 5682 | + | } | |||
| 5683 | + | run( | |||
| 5684 | + | arg0, | |||
| 5685 | + | arg1, | |||
| 5686 | + | ) | |||
| 5687 | + | }) | |||
| 5688 | + | return _c | |||
| 5689 | + | } | |||
| 5690 | + | ||||
| 5691 | + | func (_c *MockHomepageHistory_ClearRange_Call) Return(err error) *MockHomepageHistory_ClearRange_Call { | |||
| 5692 | + | _c.Call.Return(err) | |||
| 5693 | + | return _c | |||
| 5694 | + | } | |||
| 5695 | + | ||||
| 5696 | + | func (_c *MockHomepageHistory_ClearRange_Call) RunAndReturn(run func(ctx context.Context, rangeID string) error) *MockHomepageHistory_ClearRange_Call { | |||
| 5697 | + | _c.Call.Return(run) | |||
| 5698 | + | return _c | |||
| 5699 | + | } | |||
| 5700 | + | ||||
| 5644 | // ClearOlderThan provides a mock function for the type MockHomepageHistory | 5701 | // ClearOlderThan provides a mock function for the type MockHomepageHistory | ||
| 5645 | func (_mock *MockHomepageHistory) ClearOlderThan(ctx context.Context, before time.Time) error { | 5702 | func (_mock *MockHomepageHistory) ClearOlderThan(ctx context.Context, before time.Time) error { | ||
| 5646 | ret := _mock.Called(ctx, before) | 5703 | ret := _mock.Called(ctx, before) | ||
M internal/application/usecase/search_history.go +11 -0
| @@ -7,6 +7,7 @@ import ( | |||
| 7 | 7 | ||
| 8 | 8 | "github.com/bnema/dumber/internal/application/port" | |
| 9 | 9 | "github.com/bnema/dumber/internal/domain/entity" | |
| 10 | + | historydomain "github.com/bnema/dumber/internal/domain/history" | |
| 10 | 11 | "github.com/bnema/dumber/internal/domain/repository" | |
| 11 | 12 | "github.com/bnema/dumber/internal/logging" | |
| 12 | 13 | ) | |
| @@ -154,6 +155,16 @@ func (uc *SearchHistoryUseCase) ClearOlderThan(ctx context.Context, before time. | |||
| 154 | 155 | return nil | |
| 155 | 156 | } | |
| 156 | 157 | ||
| 158 | + | // ClearRange deletes history entries for a named range. | |
| 159 | + | func (uc *SearchHistoryUseCase) ClearRange(ctx context.Context, rangeID string) error { | |
| 160 | + | cutoff, all := historydomain.DeleteRangeCutoff(rangeID, time.Now()) | |
| 161 | + | if all { | |
| 162 | + | return uc.ClearAll(ctx) | |
| 163 | + | } | |
| 164 | + | ||
| 165 | + | return uc.ClearOlderThan(ctx, cutoff) | |
| 166 | + | } | |
| 167 | + | ||
| 157 | 168 | // ClearAll deletes all history entries. | |
| 158 | 169 | func (uc *SearchHistoryUseCase) ClearAll(ctx context.Context) error { | |
| 159 | 170 | log := logging.FromContext(ctx) | |
| @@ -7,6 +7,7 @@ import ( | |||||
| 7 | 7 | ||||
| 8 | "github.com/bnema/dumber/internal/application/port" | 8 | "github.com/bnema/dumber/internal/application/port" | ||
| 9 | "github.com/bnema/dumber/internal/domain/entity" | 9 | "github.com/bnema/dumber/internal/domain/entity" | ||
| 10 | + | historydomain "github.com/bnema/dumber/internal/domain/history" | |||
| 10 | "github.com/bnema/dumber/internal/domain/repository" | 11 | "github.com/bnema/dumber/internal/domain/repository" | ||
| 11 | "github.com/bnema/dumber/internal/logging" | 12 | "github.com/bnema/dumber/internal/logging" | ||
| 12 | ) | 13 | ) | ||
| @@ -154,6 +155,16 @@ func (uc *SearchHistoryUseCase) ClearOlderThan(ctx context.Context, before time. | |||||
| 154 | return nil | 155 | return nil | ||
| 155 | } | 156 | } | ||
| 156 | 157 | ||||
| 158 | + | // ClearRange deletes history entries for a named range. | |||
| 159 | + | func (uc *SearchHistoryUseCase) ClearRange(ctx context.Context, rangeID string) error { | |||
| 160 | + | cutoff, all := historydomain.DeleteRangeCutoff(rangeID, time.Now()) | |||
| 161 | + | if all { | |||
| 162 | + | return uc.ClearAll(ctx) | |||
| 163 | + | } | |||
| 164 | + | ||||
| 165 | + | return uc.ClearOlderThan(ctx, cutoff) | |||
| 166 | + | } | |||
| 167 | + | ||||
| 157 | // ClearAll deletes all history entries. | 168 | // ClearAll deletes all history entries. | ||
| 158 | func (uc *SearchHistoryUseCase) ClearAll(ctx context.Context) error { | 169 | func (uc *SearchHistoryUseCase) ClearAll(ctx context.Context) error { | ||
| 159 | log := logging.FromContext(ctx) | 170 | log := logging.FromContext(ctx) | ||
M internal/application/usecase/search_history_test.go +13 -0
| @@ -151,3 +151,16 @@ func TestSearchHistoryUseCase_GetMostVisited_ReturnsEmptyWhenNoHistory(t *testin | |||
| 151 | 151 | require.NotNil(t, result) | |
| 152 | 152 | assert.Empty(t, result) | |
| 153 | 153 | } | |
| 154 | + | ||
| 155 | + | func TestSearchHistoryUseCase_ClearRange_AllUsesClearAll(t *testing.T) { | |
| 156 | + | ctx := testContext() | |
| 157 | + | ||
| 158 | + | historyRepo := repomocks.NewMockHistoryRepository(t) | |
| 159 | + | ||
| 160 | + | historyRepo.EXPECT().DeleteAll(mock.Anything).Return(nil) | |
| 161 | + | ||
| 162 | + | uc := usecase.NewSearchHistoryUseCase(historyRepo) | |
| 163 | + | ||
| 164 | + | err := uc.ClearRange(ctx, "all") | |
| 165 | + | require.NoError(t, err) | |
| 166 | + | } | |
| @@ -151,3 +151,16 @@ func TestSearchHistoryUseCase_GetMostVisited_ReturnsEmptyWhenNoHistory(t *testin | |||||
| 151 | require.NotNil(t, result) | 151 | require.NotNil(t, result) | ||
| 152 | assert.Empty(t, result) | 152 | assert.Empty(t, result) | ||
| 153 | } | 153 | } | ||
| 154 | + | ||||
| 155 | + | func TestSearchHistoryUseCase_ClearRange_AllUsesClearAll(t *testing.T) { | |||
| 156 | + | ctx := testContext() | |||
| 157 | + | ||||
| 158 | + | historyRepo := repomocks.NewMockHistoryRepository(t) | |||
| 159 | + | ||||
| 160 | + | historyRepo.EXPECT().DeleteAll(mock.Anything).Return(nil) | |||
| 161 | + | ||||
| 162 | + | uc := usecase.NewSearchHistoryUseCase(historyRepo) | |||
| 163 | + | ||||
| 164 | + | err := uc.ClearRange(ctx, "all") | |||
| 165 | + | require.NoError(t, err) | |||
| 166 | + | } | |||
A internal/domain/history/delete_range.go +22 -0
| @@ -0,0 +1,22 @@ | |||
| 1 | + | package history | |
| 2 | + | ||
| 3 | + | import "time" | |
| 4 | + | ||
| 5 | + | // DeleteRangeCutoff resolves a delete-range identifier to a cutoff time. | |
| 6 | + | // The boolean reports whether the range means "delete all". | |
| 7 | + | func DeleteRangeCutoff(rangeID string, now time.Time) (time.Time, bool) { | |
| 8 | + | switch rangeID { | |
| 9 | + | case "hour": | |
| 10 | + | return now.Add(-time.Hour), false | |
| 11 | + | case "day": | |
| 12 | + | return now.AddDate(0, 0, -1), false | |
| 13 | + | case "week": | |
| 14 | + | return now.AddDate(0, 0, -7), false | |
| 15 | + | case "month": | |
| 16 | + | return now.AddDate(0, -1, 0), false | |
| 17 | + | case "all": | |
| 18 | + | return time.Time{}, true | |
| 19 | + | default: | |
| 20 | + | return now, false | |
| 21 | + | } | |
| 22 | + | } | |
| @@ -0,0 +1,22 @@ | |||||
| 1 | + | package history | |||
| 2 | + | ||||
| 3 | + | import "time" | |||
| 4 | + | ||||
| 5 | + | // DeleteRangeCutoff resolves a delete-range identifier to a cutoff time. | |||
| 6 | + | // The boolean reports whether the range means "delete all". | |||
| 7 | + | func DeleteRangeCutoff(rangeID string, now time.Time) (time.Time, bool) { | |||
| 8 | + | switch rangeID { | |||
| 9 | + | case "hour": | |||
| 10 | + | return now.Add(-time.Hour), false | |||
| 11 | + | case "day": | |||
| 12 | + | return now.AddDate(0, 0, -1), false | |||
| 13 | + | case "week": | |||
| 14 | + | return now.AddDate(0, 0, -7), false | |||
| 15 | + | case "month": | |||
| 16 | + | return now.AddDate(0, -1, 0), false | |||
| 17 | + | case "all": | |||
| 18 | + | return time.Time{}, true | |||
| 19 | + | default: | |||
| 20 | + | return now, false | |||
| 21 | + | } | |||
| 22 | + | } | |||
A internal/domain/history/delete_range_test.go +35 -0
| @@ -0,0 +1,35 @@ | |||
| 1 | + | package history | |
| 2 | + | ||
| 3 | + | import ( | |
| 4 | + | "testing" | |
| 5 | + | "time" | |
| 6 | + | ) | |
| 7 | + | ||
| 8 | + | func TestDeleteRangeCutoff(t *testing.T) { | |
| 9 | + | now := time.Date(2026, time.April, 11, 12, 0, 0, 0, time.UTC) | |
| 10 | + | ||
| 11 | + | tests := []struct { | |
| 12 | + | name string | |
| 13 | + | rangeID string | |
| 14 | + | want time.Time | |
| 15 | + | wantAll bool | |
| 16 | + | }{ | |
| 17 | + | {name: "hour", rangeID: "hour", want: now.Add(-time.Hour), wantAll: false}, | |
| 18 | + | {name: "day", rangeID: "day", want: now.AddDate(0, 0, -1), wantAll: false}, | |
| 19 | + | {name: "week", rangeID: "week", want: now.AddDate(0, 0, -7), wantAll: false}, | |
| 20 | + | {name: "month", rangeID: "month", want: now.AddDate(0, -1, 0), wantAll: false}, | |
| 21 | + | {name: "all", rangeID: "all", want: time.Time{}, wantAll: true}, | |
| 22 | + | } | |
| 23 | + | ||
| 24 | + | for _, tt := range tests { | |
| 25 | + | t.Run(tt.name, func(t *testing.T) { | |
| 26 | + | got, gotAll := DeleteRangeCutoff(tt.rangeID, now) | |
| 27 | + | if !got.Equal(tt.want) { | |
| 28 | + | t.Fatalf("DeleteRangeCutoff() cutoff = %v, want %v", got, tt.want) | |
| 29 | + | } | |
| 30 | + | if gotAll != tt.wantAll { | |
| 31 | + | t.Fatalf("DeleteRangeCutoff() all = %v, want %v", gotAll, tt.wantAll) | |
| 32 | + | } | |
| 33 | + | }) | |
| 34 | + | } | |
| 35 | + | } | |
| @@ -0,0 +1,35 @@ | |||||
| 1 | + | package history | |||
| 2 | + | ||||
| 3 | + | import ( | |||
| 4 | + | "testing" | |||
| 5 | + | "time" | |||
| 6 | + | ) | |||
| 7 | + | ||||
| 8 | + | func TestDeleteRangeCutoff(t *testing.T) { | |||
| 9 | + | now := time.Date(2026, time.April, 11, 12, 0, 0, 0, time.UTC) | |||
| 10 | + | ||||
| 11 | + | tests := []struct { | |||
| 12 | + | name string | |||
| 13 | + | rangeID string | |||
| 14 | + | want time.Time | |||
| 15 | + | wantAll bool | |||
| 16 | + | }{ | |||
| 17 | + | {name: "hour", rangeID: "hour", want: now.Add(-time.Hour), wantAll: false}, | |||
| 18 | + | {name: "day", rangeID: "day", want: now.AddDate(0, 0, -1), wantAll: false}, | |||
| 19 | + | {name: "week", rangeID: "week", want: now.AddDate(0, 0, -7), wantAll: false}, | |||
| 20 | + | {name: "month", rangeID: "month", want: now.AddDate(0, -1, 0), wantAll: false}, | |||
| 21 | + | {name: "all", rangeID: "all", want: time.Time{}, wantAll: true}, | |||
| 22 | + | } | |||
| 23 | + | ||||
| 24 | + | for _, tt := range tests { | |||
| 25 | + | t.Run(tt.name, func(t *testing.T) { | |||
| 26 | + | got, gotAll := DeleteRangeCutoff(tt.rangeID, now) | |||
| 27 | + | if !got.Equal(tt.want) { | |||
| 28 | + | t.Fatalf("DeleteRangeCutoff() cutoff = %v, want %v", got, tt.want) | |||
| 29 | + | } | |||
| 30 | + | if gotAll != tt.wantAll { | |||
| 31 | + | t.Fatalf("DeleteRangeCutoff() all = %v, want %v", gotAll, tt.wantAll) | |||
| 32 | + | } | |||
| 33 | + | }) | |||
| 34 | + | } | |||
| 35 | + | } | |||
M internal/infrastructure/handlers/homepage/history.go +2 -31
| @@ -3,7 +3,6 @@ package homepage | |||
| 3 | 3 | import ( | |
| 4 | 4 | "context" | |
| 5 | 5 | "encoding/json" | |
| 6 | - | "time" | |
| 7 | 6 | ||
| 8 | 7 | "github.com/bnema/dumber/internal/application/port" | |
| 9 | 8 | "github.com/bnema/dumber/internal/domain/entity" | |
| @@ -142,16 +141,8 @@ func (h *HistoryHandlers) HandleDeleteRange() port.WebUIMessageHandler { | |||
| 142 | 141 | Str("range", req.Range). | |
| 143 | 142 | Msg("handling history_delete_range") | |
| 144 | 143 | ||
| 145 | - | before := rangeToTime(req.Range) | |
| 146 | - | if before.IsZero() { | |
| 147 | - | // "all" case - clear all | |
| 148 | - | if err := h.historyUC.ClearAll(ctx); err != nil { | |
| 149 | - | return NewErrorResponse(req.RequestID, err), nil | |
| 150 | - | } | |
| 151 | - | } else { | |
| 152 | - | if err := h.historyUC.ClearOlderThan(ctx, before); err != nil { | |
| 153 | - | return NewErrorResponse(req.RequestID, err), nil | |
| 154 | - | } | |
| 144 | + | if err := h.historyUC.ClearRange(ctx, req.Range); err != nil { | |
| 145 | + | return NewErrorResponse(req.RequestID, err), nil | |
| 155 | 146 | } | |
| 156 | 147 | ||
| 157 | 148 | return NewSuccessResponse(req.RequestID, nil), nil | |
| @@ -177,26 +168,6 @@ func (h *HistoryHandlers) HandleClearAll() port.WebUIMessageHandler { | |||
| 177 | 168 | }) | |
| 178 | 169 | } | |
| 179 | 170 | ||
| 180 | - | // rangeToTime converts a range string to a time.Time cutoff. | |
| 181 | - | // Returns zero time for "all" which means delete everything. | |
| 182 | - | func rangeToTime(r string) time.Time { | |
| 183 | - | now := time.Now() | |
| 184 | - | switch r { | |
| 185 | - | case "hour": | |
| 186 | - | return now.Add(-time.Hour) | |
| 187 | - | case "day": | |
| 188 | - | return now.AddDate(0, 0, -1) | |
| 189 | - | case "week": | |
| 190 | - | return now.AddDate(0, 0, -7) | |
| 191 | - | case "month": | |
| 192 | - | return now.AddDate(0, -1, 0) | |
| 193 | - | case "all": | |
| 194 | - | return time.Time{} // Zero time signals "delete all" | |
| 195 | - | default: | |
| 196 | - | return now | |
| 197 | - | } | |
| 198 | - | } | |
| 199 | - | ||
| 200 | 171 | // HandleAnalytics handles history_analytics messages. | |
| 201 | 172 | func (h *HistoryHandlers) HandleAnalytics() port.WebUIMessageHandler { | |
| 202 | 173 | return port.WebUIMessageHandlerFunc(func(ctx context.Context, _ port.WebViewID, payload json.RawMessage) (any, error) { | |
| @@ -3,7 +3,6 @@ package homepage | |||||
| 3 | import ( | 3 | import ( | ||
| 4 | "context" | 4 | "context" | ||
| 5 | "encoding/json" | 5 | "encoding/json" | ||
| 6 | - | "time" | |||
| 7 | 6 | ||||
| 8 | "github.com/bnema/dumber/internal/application/port" | 7 | "github.com/bnema/dumber/internal/application/port" | ||
| 9 | "github.com/bnema/dumber/internal/domain/entity" | 8 | "github.com/bnema/dumber/internal/domain/entity" | ||
| @@ -142,16 +141,8 @@ func (h *HistoryHandlers) HandleDeleteRange() port.WebUIMessageHandler { | |||||
| 142 | Str("range", req.Range). | 141 | Str("range", req.Range). | ||
| 143 | Msg("handling history_delete_range") | 142 | Msg("handling history_delete_range") | ||
| 144 | 143 | ||||
| 145 | - | before := rangeToTime(req.Range) | 144 | + | if err := h.historyUC.ClearRange(ctx, req.Range); err != nil { |
| 146 | - | if before.IsZero() { | 145 | + | return NewErrorResponse(req.RequestID, err), nil |
| 147 | - | // "all" case - clear all | |||
| 148 | - | if err := h.historyUC.ClearAll(ctx); err != nil { | |||
| 149 | - | return NewErrorResponse(req.RequestID, err), nil | |||
| 150 | - | } | |||
| 151 | - | } else { | |||
| 152 | - | if err := h.historyUC.ClearOlderThan(ctx, before); err != nil { | |||
| 153 | - | return NewErrorResponse(req.RequestID, err), nil | |||
| 154 | - | } | |||
| 155 | } | 146 | } | ||
| 156 | 147 | ||||
| 157 | return NewSuccessResponse(req.RequestID, nil), nil | 148 | return NewSuccessResponse(req.RequestID, nil), nil | ||
| @@ -177,26 +168,6 @@ func (h *HistoryHandlers) HandleClearAll() port.WebUIMessageHandler { | |||||
| 177 | }) | 168 | }) | ||
| 178 | } | 169 | } | ||
| 179 | 170 | ||||
| 180 | - | // rangeToTime converts a range string to a time.Time cutoff. | |||
| 181 | - | // Returns zero time for "all" which means delete everything. | |||
| 182 | - | func rangeToTime(r string) time.Time { | |||
| 183 | - | now := time.Now() | |||
| 184 | - | switch r { | |||
| 185 | - | case "hour": | |||
| 186 | - | return now.Add(-time.Hour) | |||
| 187 | - | case "day": | |||
| 188 | - | return now.AddDate(0, 0, -1) | |||
| 189 | - | case "week": | |||
| 190 | - | return now.AddDate(0, 0, -7) | |||
| 191 | - | case "month": | |||
| 192 | - | return now.AddDate(0, -1, 0) | |||
| 193 | - | case "all": | |||
| 194 | - | return time.Time{} // Zero time signals "delete all" | |||
| 195 | - | default: | |||
| 196 | - | return now | |||
| 197 | - | } | |||
| 198 | - | } | |||
| 199 | - | ||||
| 200 | // HandleAnalytics handles history_analytics messages. | 171 | // HandleAnalytics handles history_analytics messages. | ||
| 201 | func (h *HistoryHandlers) HandleAnalytics() port.WebUIMessageHandler { | 172 | func (h *HistoryHandlers) HandleAnalytics() port.WebUIMessageHandler { | ||
| 202 | return port.WebUIMessageHandlerFunc(func(ctx context.Context, _ port.WebViewID, payload json.RawMessage) (any, error) { | 173 | return port.WebUIMessageHandlerFunc(func(ctx context.Context, _ port.WebViewID, payload json.RawMessage) (any, error) { | ||