1package homepage
2
3import (
4 "context"
5 "encoding/json"
6 "time"
7
8 "github.com/bnema/dumber/internal/application/port"
9 "github.com/bnema/dumber/internal/domain/entity"
10 "github.com/bnema/dumber/internal/infrastructure/webkit"
11 "github.com/bnema/dumber/internal/logging"
12)
13
14// HistoryHandlers handles history-related messages from the homepage.
15type HistoryHandlers struct {
16 historyUC port.HomepageHistory
17}
18
19// NewHistoryHandlers creates a new HistoryHandlers instance.
20func NewHistoryHandlers(historyUC port.HomepageHistory) *HistoryHandlers {
21 return &HistoryHandlers{historyUC: historyUC}
22}
23
24// timelineRequest is the payload for history_timeline messages.
25type timelineRequest struct {
26 RequestID string `json:"requestId"`
27 Limit int `json:"limit"`
28 Offset int `json:"offset"`
29}
30
31// HandleTimeline handles history_timeline messages.
32func (h *HistoryHandlers) HandleTimeline() webkit.MessageHandler {
33 return webkit.MessageHandlerFunc(func(ctx context.Context, _ webkit.WebViewID, payload json.RawMessage) (any, error) {
34 log := logging.FromContext(ctx)
35
36 var req timelineRequest
37 if err := json.Unmarshal(payload, &req); err != nil {
38 return NewErrorResponse("", err), nil
39 }
40
41 log.Debug().
42 Str("request_id", req.RequestID).
43 Int("limit", req.Limit).
44 Int("offset", req.Offset).
45 Msg("handling history_timeline")
46
47 entries, err := h.historyUC.GetRecent(ctx, req.Limit, req.Offset)
48 if err != nil {
49 return NewErrorResponse(req.RequestID, err), nil
50 }
51
52 return NewSuccessResponse(req.RequestID, entries), nil
53 })
54}
55
56// searchRequest is the payload for history_search_fts messages.
57type searchRequest struct {
58 RequestID string `json:"requestId"`
59 Query string `json:"query"`
60 Limit int `json:"limit"`
61}
62
63// HandleSearchFTS handles history_search_fts messages.
64func (h *HistoryHandlers) HandleSearchFTS() webkit.MessageHandler {
65 return webkit.MessageHandlerFunc(func(ctx context.Context, _ webkit.WebViewID, payload json.RawMessage) (any, error) {
66 log := logging.FromContext(ctx)
67
68 var req searchRequest
69 if err := json.Unmarshal(payload, &req); err != nil {
70 return NewErrorResponse("", err), nil
71 }
72
73 log.Debug().
74 Str("request_id", req.RequestID).
75 Str("query", req.Query).
76 Msg("handling history_search_fts")
77
78 output, err := h.historyUC.Search(ctx, port.HistorySearchInput{
79 Query: req.Query,
80 Limit: req.Limit,
81 })
82 if err != nil {
83 return NewErrorResponse(req.RequestID, err), nil
84 }
85
86 // Convert matches to entries for frontend compatibility
87 entries := make([]*entity.HistoryEntry, 0, len(output.Matches))
88 for _, m := range output.Matches {
89 entries = append(entries, m.Entry)
90 }
91
92 return NewSuccessResponse(req.RequestID, entries), nil
93 })
94}
95
96// deleteEntryRequest is the payload for history_delete_entry messages.
97type deleteEntryRequest struct {
98 RequestID string `json:"requestId"`
99 ID int64 `json:"id"`
100}
101
102// HandleDeleteEntry handles history_delete_entry messages.
103func (h *HistoryHandlers) HandleDeleteEntry() webkit.MessageHandler {
104 return webkit.MessageHandlerFunc(func(ctx context.Context, _ webkit.WebViewID, payload json.RawMessage) (any, error) {
105 log := logging.FromContext(ctx)
106
107 var req deleteEntryRequest
108 if err := json.Unmarshal(payload, &req); err != nil {
109 return NewErrorResponse("", err), nil
110 }
111
112 log.Debug().
113 Str("request_id", req.RequestID).
114 Int64("id", req.ID).
115 Msg("handling history_delete_entry")
116
117 if err := h.historyUC.Delete(ctx, req.ID); err != nil {
118 return NewErrorResponse(req.RequestID, err), nil
119 }
120
121 return NewSuccessResponse(req.RequestID, nil), nil
122 })
123}
124
125// deleteRangeRequest is the payload for history_delete_range messages.
126type deleteRangeRequest struct {
127 RequestID string `json:"requestId"`
128 Range string `json:"range"` // "hour", "day", "week", "month", "all"
129}
130
131// HandleDeleteRange handles history_delete_range messages.
132func (h *HistoryHandlers) HandleDeleteRange() webkit.MessageHandler {
133 return webkit.MessageHandlerFunc(func(ctx context.Context, _ webkit.WebViewID, payload json.RawMessage) (any, error) {
134 log := logging.FromContext(ctx)
135
136 var req deleteRangeRequest
137 if err := json.Unmarshal(payload, &req); err != nil {
138 return NewErrorResponse("", err), nil
139 }
140
141 log.Debug().
142 Str("request_id", req.RequestID).
143 Str("range", req.Range).
144 Msg("handling history_delete_range")
145
146 before := rangeToTime(req.Range)
147 if before.IsZero() {
148 // "all" case - clear all
149 if err := h.historyUC.ClearAll(ctx); err != nil {
150 return NewErrorResponse(req.RequestID, err), nil
151 }
152 } else {
153 if err := h.historyUC.ClearOlderThan(ctx, before); err != nil {
154 return NewErrorResponse(req.RequestID, err), nil
155 }
156 }
157
158 return NewSuccessResponse(req.RequestID, nil), nil
159 })
160}
161
162// HandleClearAll handles history_clear_all messages.
163func (h *HistoryHandlers) HandleClearAll() webkit.MessageHandler {
164 return webkit.MessageHandlerFunc(func(ctx context.Context, _ webkit.WebViewID, payload json.RawMessage) (any, error) {
165 log := logging.FromContext(ctx)
166
167 requestID := ParseRequestID(payload)
168
169 log.Debug().
170 Str("request_id", requestID).
171 Msg("handling history_clear_all")
172
173 if err := h.historyUC.ClearAll(ctx); err != nil {
174 return NewErrorResponse(requestID, err), nil
175 }
176
177 return NewSuccessResponse(requestID, nil), nil
178 })
179}
180
181// rangeToTime converts a range string to a time.Time cutoff.
182// Returns zero time for "all" which means delete everything.
183func rangeToTime(r string) time.Time {
184 now := time.Now()
185 switch r {
186 case "hour":
187 return now.Add(-time.Hour)
188 case "day":
189 return now.AddDate(0, 0, -1)
190 case "week":
191 return now.AddDate(0, 0, -7)
192 case "month":
193 return now.AddDate(0, -1, 0)
194 case "all":
195 return time.Time{} // Zero time signals "delete all"
196 default:
197 return now
198 }
199}
200
201// HandleAnalytics handles history_analytics messages.
202func (h *HistoryHandlers) HandleAnalytics() webkit.MessageHandler {
203 return webkit.MessageHandlerFunc(func(ctx context.Context, _ webkit.WebViewID, payload json.RawMessage) (any, error) {
204 log := logging.FromContext(ctx)
205
206 requestID := ParseRequestID(payload)
207
208 log.Debug().
209 Str("request_id", requestID).
210 Msg("handling history_analytics")
211
212 analytics, err := h.historyUC.GetAnalytics(ctx)
213 if err != nil {
214 return NewErrorResponse(requestID, err), nil
215 }
216
217 return NewSuccessResponse(requestID, analytics), nil
218 })
219}
220
221// domainStatsRequest is the payload for history_domain_stats messages.
222type domainStatsRequest struct {
223 RequestID string `json:"requestId"`
224 Limit int `json:"limit"`
225}
226
227// HandleDomainStats handles history_domain_stats messages.
228func (h *HistoryHandlers) HandleDomainStats() webkit.MessageHandler {
229 return webkit.MessageHandlerFunc(func(ctx context.Context, _ webkit.WebViewID, payload json.RawMessage) (any, error) {
230 log := logging.FromContext(ctx)
231
232 var req domainStatsRequest
233 if err := json.Unmarshal(payload, &req); err != nil {
234 return NewErrorResponse("", err), nil
235 }
236
237 log.Debug().
238 Str("request_id", req.RequestID).
239 Int("limit", req.Limit).
240 Msg("handling history_domain_stats")
241
242 stats, err := h.historyUC.GetDomainStats(ctx, req.Limit)
243 if err != nil {
244 return NewErrorResponse(req.RequestID, err), nil
245 }
246
247 return NewSuccessResponse(req.RequestID, stats), nil
248 })
249}
250
251// deleteDomainRequest is the payload for history_delete_domain messages.
252type deleteDomainRequest struct {
253 RequestID string `json:"requestId"`
254 Domain string `json:"domain"`
255}
256
257// HandleDeleteDomain handles history_delete_domain messages.
258func (h *HistoryHandlers) HandleDeleteDomain() webkit.MessageHandler {
259 return webkit.MessageHandlerFunc(func(ctx context.Context, _ webkit.WebViewID, payload json.RawMessage) (any, error) {
260 log := logging.FromContext(ctx)
261
262 var req deleteDomainRequest
263 if err := json.Unmarshal(payload, &req); err != nil {
264 return NewErrorResponse("", err), nil
265 }
266
267 log.Debug().
268 Str("request_id", req.RequestID).
269 Str("domain", req.Domain).
270 Msg("handling history_delete_domain")
271
272 if err := h.historyUC.DeleteByDomain(ctx, req.Domain); err != nil {
273 return NewErrorResponse(req.RequestID, err), nil
274 }
275
276 return NewSuccessResponse(req.RequestID, nil), nil
277 })
278}