1package homepage
2
3import (
4 "context"
5 "encoding/json"
6 "fmt"
7 "strings"
8
9 "github.com/bnema/dumber/internal/application/dto"
10 "github.com/bnema/dumber/internal/application/port"
11 "github.com/bnema/dumber/internal/domain/entity"
12 "github.com/bnema/dumber/internal/logging"
13)
14
15func tagIDsFromInt64s(ids []int64) ([]entity.TagID, error) {
16 out := make([]entity.TagID, 0, len(ids))
17 for _, id := range ids {
18 if id <= 0 {
19 return nil, fmt.Errorf("tag id must be positive")
20 }
21 out = append(out, entity.TagID(id))
22 }
23 return out, nil
24}
25
26// FavoritesHandlers handles favorite-related messages from the homepage.
27type FavoritesHandlers struct {
28 favoritesUC port.HomepageFavorites
29}
30
31// NewFavoritesHandlers creates a new FavoritesHandlers instance.
32func NewFavoritesHandlers(favoritesUC port.HomepageFavorites) *FavoritesHandlers {
33 return &FavoritesHandlers{favoritesUC: favoritesUC}
34}
35
36// HandleList handles favorite_list messages.
37func (h *FavoritesHandlers) HandleList() port.WebUIMessageHandler {
38 return port.WebUIMessageHandlerFunc(func(ctx context.Context, _ port.WebViewID, payload json.RawMessage) (any, error) {
39 log := logging.FromContext(ctx)
40
41 requestID := ParseRequestID(payload)
42
43 log.Debug().
44 Str("request_id", requestID).
45 Msg("handling favorite_list")
46
47 favorites, err := h.favoritesUC.GetAll(ctx)
48 if err != nil {
49 return NewErrorResponse(requestID, err), nil
50 }
51
52 return NewSuccessResponse(requestID, favorites), nil
53 })
54}
55
56type favoriteCreateRequest struct {
57 RequestID string `json:"requestId"`
58 URL string `json:"url"`
59 Title string `json:"title"`
60 FaviconURL string `json:"favicon_url"`
61 Tags []int64 `json:"tags"`
62}
63
64// HandleCreate handles favorite_create messages.
65func (h *FavoritesHandlers) HandleCreate() port.WebUIMessageHandler {
66 return port.WebUIMessageHandlerFunc(func(ctx context.Context, _ port.WebViewID, payload json.RawMessage) (any, error) {
67 log := logging.FromContext(ctx)
68
69 var req favoriteCreateRequest
70 if err := json.Unmarshal(payload, &req); err != nil {
71 return NewErrorResponse("", err), nil
72 }
73
74 log.Debug().
75 Str("request_id", req.RequestID).
76 Msg("handling favorite_create")
77
78 trimmedURL := strings.TrimSpace(req.URL)
79 if trimmedURL == "" {
80 return NewErrorResponse(req.RequestID, fmt.Errorf("URL is required")), nil
81 }
82 tags, err := tagIDsFromInt64s(req.Tags)
83 if err != nil {
84 return NewErrorResponse(req.RequestID, err), nil
85 }
86
87 input := dto.FavoriteCreateInput{
88 URL: trimmedURL,
89 Title: req.Title,
90 FaviconURL: req.FaviconURL,
91 Tags: tags,
92 }
93 favorite, err := h.favoritesUC.AddFavorite(ctx, input)
94 if err != nil {
95 return NewErrorResponse(req.RequestID, err), nil
96 }
97
98 return NewSuccessResponse(req.RequestID, favorite), nil
99 })
100}
101
102type favoriteUpdateRequest struct {
103 RequestID string `json:"requestId"`
104 ID int64 `json:"id"`
105 Title string `json:"title"`
106 FaviconURL string `json:"favicon_url"`
107 ShortcutKey *int `json:"shortcut_key"`
108}
109
110// HandleUpdate handles favorite_update messages.
111func (h *FavoritesHandlers) HandleUpdate() port.WebUIMessageHandler {
112 return port.WebUIMessageHandlerFunc(func(ctx context.Context, _ port.WebViewID, payload json.RawMessage) (any, error) {
113 log := logging.FromContext(ctx)
114
115 var req favoriteUpdateRequest
116 if err := json.Unmarshal(payload, &req); err != nil {
117 return NewErrorResponse("", err), nil
118 }
119 var fields map[string]json.RawMessage
120 if err := json.Unmarshal(payload, &fields); err != nil {
121 return NewErrorResponse("", err), nil
122 }
123 _, shortcutKeySet := fields["shortcut_key"]
124
125 log.Debug().
126 Str("request_id", req.RequestID).
127 Int64("favorite_id", req.ID).
128 Msg("handling favorite_update")
129
130 if req.ID <= 0 {
131 return NewErrorResponse(req.RequestID, fmt.Errorf("favorite id must be positive")), nil
132 }
133
134 favorite, err := h.favoritesUC.UpdateFavorite(ctx, dto.FavoriteUpdateInput{
135 ID: entity.FavoriteID(req.ID),
136 Title: req.Title,
137 FaviconURL: req.FaviconURL,
138 ShortcutKey: req.ShortcutKey,
139 ShortcutKeySet: shortcutKeySet,
140 })
141 if err != nil {
142 return NewErrorResponse(req.RequestID, err), nil
143 }
144
145 return NewSuccessResponse(req.RequestID, favorite), nil
146 })
147}
148
149type favoriteDeleteRequest struct {
150 RequestID string `json:"requestId"`
151 ID int64 `json:"id"`
152}
153
154// HandleDelete handles favorite_delete messages.
155func (h *FavoritesHandlers) HandleDelete() port.WebUIMessageHandler {
156 return port.WebUIMessageHandlerFunc(func(ctx context.Context, _ port.WebViewID, payload json.RawMessage) (any, error) {
157 log := logging.FromContext(ctx)
158
159 var req favoriteDeleteRequest
160 if err := json.Unmarshal(payload, &req); err != nil {
161 return NewErrorResponse("", err), nil
162 }
163
164 log.Debug().
165 Str("request_id", req.RequestID).
166 Int64("favorite_id", req.ID).
167 Msg("handling favorite_delete")
168
169 if req.ID <= 0 {
170 return NewErrorResponse(req.RequestID, fmt.Errorf("favorite id must be positive")), nil
171 }
172
173 if err := h.favoritesUC.DeleteFavorite(ctx, entity.FavoriteID(req.ID)); err != nil {
174 return NewErrorResponse(req.RequestID, err), nil
175 }
176
177 return NewSuccessResponse(req.RequestID, nil), nil
178 })
179}
180
181// setShortcutRequest is the payload for favorite_set_shortcut messages.
182type setShortcutRequest struct {
183 RequestID string `json:"requestId"`
184 FavoriteID int64 `json:"favorite_id"`
185 ShortcutKey *int `json:"shortcut_key"` // 1-9 or null to remove
186}
187
188// HandleSetShortcut handles favorite_set_shortcut messages.
189func (h *FavoritesHandlers) HandleSetShortcut() port.WebUIMessageHandler {
190 return port.WebUIMessageHandlerFunc(func(ctx context.Context, _ port.WebViewID, payload json.RawMessage) (any, error) {
191 log := logging.FromContext(ctx)
192
193 var req setShortcutRequest
194 if err := json.Unmarshal(payload, &req); err != nil {
195 return NewErrorResponse("", err), nil
196 }
197
198 log.Debug().
199 Str("request_id", req.RequestID).
200 Int64("favorite_id", req.FavoriteID).
201 Msg("handling favorite_set_shortcut")
202
203 if req.FavoriteID <= 0 {
204 return NewErrorResponse(req.RequestID, fmt.Errorf("invalid favorite id")), nil
205 }
206
207 if err := h.favoritesUC.SetShortcut(ctx, entity.FavoriteID(req.FavoriteID), req.ShortcutKey); err != nil {
208 return NewErrorResponse(req.RequestID, err), nil
209 }
210
211 return NewSuccessResponse(req.RequestID, nil), nil
212 })
213}
214
215// getByShortcutRequest is the payload for favorite_get_by_shortcut messages.
216type getByShortcutRequest struct {
217 RequestID string `json:"requestId"`
218 ShortcutKey int `json:"shortcut_key"`
219}
220
221// HandleGetByShortcut handles favorite_get_by_shortcut messages.
222func (h *FavoritesHandlers) HandleGetByShortcut() port.WebUIMessageHandler {
223 return port.WebUIMessageHandlerFunc(func(ctx context.Context, _ port.WebViewID, payload json.RawMessage) (any, error) {
224 log := logging.FromContext(ctx)
225
226 var req getByShortcutRequest
227 if err := json.Unmarshal(payload, &req); err != nil {
228 return NewErrorResponse("", err), nil
229 }
230
231 log.Debug().
232 Str("request_id", req.RequestID).
233 Int("shortcut_key", req.ShortcutKey).
234 Msg("handling favorite_get_by_shortcut")
235
236 favorite, err := h.favoritesUC.GetByShortcut(ctx, req.ShortcutKey)
237 if err != nil {
238 return NewErrorResponse(req.RequestID, err), nil
239 }
240
241 return NewSuccessResponse(req.RequestID, favorite), nil
242 })
243}