feat(handlers): implement homepage message handlers for history, favorites, folders, tags
- commit
- f42505b50422e2b3f77839490cf11c07415ac08b
- author
- brice · · 2025-12-16 17:34 UTC
- committer
- brice · · 2025-12-16 17:15 UTC
- parent
- d2e410e
- signature
- unsigned
7 files · +933 -0
A internal/infrastructure/webkit/handlers/homepage/favorites.go +137 -0
| @@ -0,0 +1,137 @@ | |||
| 1 | + | package homepage | |
| 2 | + | ||
| 3 | + | import ( | |
| 4 | + | "context" | |
| 5 | + | "encoding/json" | |
| 6 | + | ||
| 7 | + | "github.com/bnema/dumber/internal/application/usecase" | |
| 8 | + | "github.com/bnema/dumber/internal/domain/entity" | |
| 9 | + | "github.com/bnema/dumber/internal/infrastructure/webkit" | |
| 10 | + | "github.com/bnema/dumber/internal/logging" | |
| 11 | + | ) | |
| 12 | + | ||
| 13 | + | // FavoritesHandlers handles favorite-related messages from the homepage. | |
| 14 | + | type FavoritesHandlers struct { | |
| 15 | + | favoritesUC *usecase.ManageFavoritesUseCase | |
| 16 | + | } | |
| 17 | + | ||
| 18 | + | // NewFavoritesHandlers creates a new FavoritesHandlers instance. | |
| 19 | + | func NewFavoritesHandlers(favoritesUC *usecase.ManageFavoritesUseCase) *FavoritesHandlers { | |
| 20 | + | return &FavoritesHandlers{favoritesUC: favoritesUC} | |
| 21 | + | } | |
| 22 | + | ||
| 23 | + | // HandleList handles favorite_list messages. | |
| 24 | + | func (h *FavoritesHandlers) HandleList() webkit.MessageHandler { | |
| 25 | + | return webkit.MessageHandlerFunc(func(ctx context.Context, webviewID webkit.WebViewID, payload json.RawMessage) (any, error) { | |
| 26 | + | log := logging.FromContext(ctx) | |
| 27 | + | ||
| 28 | + | requestID := ParseRequestID(payload) | |
| 29 | + | ||
| 30 | + | log.Debug(). | |
| 31 | + | Str("request_id", requestID). | |
| 32 | + | Msg("handling favorite_list") | |
| 33 | + | ||
| 34 | + | favorites, err := h.favoritesUC.GetAll(ctx) | |
| 35 | + | if err != nil { | |
| 36 | + | return NewErrorResponse(requestID, err), nil | |
| 37 | + | } | |
| 38 | + | ||
| 39 | + | return NewSuccessResponse(requestID, favorites), nil | |
| 40 | + | }) | |
| 41 | + | } | |
| 42 | + | ||
| 43 | + | // setShortcutRequest is the payload for favorite_set_shortcut messages. | |
| 44 | + | type setShortcutRequest struct { | |
| 45 | + | RequestID string `json:"requestId"` | |
| 46 | + | FavoriteID int64 `json:"favorite_id"` | |
| 47 | + | ShortcutKey *int `json:"shortcut_key"` // 1-9 or null to remove | |
| 48 | + | } | |
| 49 | + | ||
| 50 | + | // HandleSetShortcut handles favorite_set_shortcut messages. | |
| 51 | + | func (h *FavoritesHandlers) HandleSetShortcut() webkit.MessageHandler { | |
| 52 | + | return webkit.MessageHandlerFunc(func(ctx context.Context, webviewID webkit.WebViewID, payload json.RawMessage) (any, error) { | |
| 53 | + | log := logging.FromContext(ctx) | |
| 54 | + | ||
| 55 | + | var req setShortcutRequest | |
| 56 | + | if err := json.Unmarshal(payload, &req); err != nil { | |
| 57 | + | return NewErrorResponse("", err), nil | |
| 58 | + | } | |
| 59 | + | ||
| 60 | + | log.Debug(). | |
| 61 | + | Str("request_id", req.RequestID). | |
| 62 | + | Int64("favorite_id", req.FavoriteID). | |
| 63 | + | Msg("handling favorite_set_shortcut") | |
| 64 | + | ||
| 65 | + | if err := h.favoritesUC.SetShortcut(ctx, entity.FavoriteID(req.FavoriteID), req.ShortcutKey); err != nil { | |
| 66 | + | return NewErrorResponse(req.RequestID, err), nil | |
| 67 | + | } | |
| 68 | + | ||
| 69 | + | return NewSuccessResponse(req.RequestID, nil), nil | |
| 70 | + | }) | |
| 71 | + | } | |
| 72 | + | ||
| 73 | + | // getByShortcutRequest is the payload for favorite_get_by_shortcut messages. | |
| 74 | + | type getByShortcutRequest struct { | |
| 75 | + | RequestID string `json:"requestId"` | |
| 76 | + | ShortcutKey int `json:"shortcut_key"` | |
| 77 | + | } | |
| 78 | + | ||
| 79 | + | // HandleGetByShortcut handles favorite_get_by_shortcut messages. | |
| 80 | + | func (h *FavoritesHandlers) HandleGetByShortcut() webkit.MessageHandler { | |
| 81 | + | return webkit.MessageHandlerFunc(func(ctx context.Context, webviewID webkit.WebViewID, payload json.RawMessage) (any, error) { | |
| 82 | + | log := logging.FromContext(ctx) | |
| 83 | + | ||
| 84 | + | var req getByShortcutRequest | |
| 85 | + | if err := json.Unmarshal(payload, &req); err != nil { | |
| 86 | + | return NewErrorResponse("", err), nil | |
| 87 | + | } | |
| 88 | + | ||
| 89 | + | log.Debug(). | |
| 90 | + | Str("request_id", req.RequestID). | |
| 91 | + | Int("shortcut_key", req.ShortcutKey). | |
| 92 | + | Msg("handling favorite_get_by_shortcut") | |
| 93 | + | ||
| 94 | + | favorite, err := h.favoritesUC.GetByShortcut(ctx, req.ShortcutKey) | |
| 95 | + | if err != nil { | |
| 96 | + | return NewErrorResponse(req.RequestID, err), nil | |
| 97 | + | } | |
| 98 | + | ||
| 99 | + | return NewSuccessResponse(req.RequestID, favorite), nil | |
| 100 | + | }) | |
| 101 | + | } | |
| 102 | + | ||
| 103 | + | // setFolderRequest is the payload for favorite_set_folder messages. | |
| 104 | + | type setFolderRequest struct { | |
| 105 | + | RequestID string `json:"requestId"` | |
| 106 | + | FavoriteID int64 `json:"favorite_id"` | |
| 107 | + | FolderID *int64 `json:"folder_id"` // null to move to root | |
| 108 | + | } | |
| 109 | + | ||
| 110 | + | // HandleSetFolder handles favorite_set_folder messages. | |
| 111 | + | func (h *FavoritesHandlers) HandleSetFolder() webkit.MessageHandler { | |
| 112 | + | return webkit.MessageHandlerFunc(func(ctx context.Context, webviewID webkit.WebViewID, payload json.RawMessage) (any, error) { | |
| 113 | + | log := logging.FromContext(ctx) | |
| 114 | + | ||
| 115 | + | var req setFolderRequest | |
| 116 | + | if err := json.Unmarshal(payload, &req); err != nil { | |
| 117 | + | return NewErrorResponse("", err), nil | |
| 118 | + | } | |
| 119 | + | ||
| 120 | + | log.Debug(). | |
| 121 | + | Str("request_id", req.RequestID). | |
| 122 | + | Int64("favorite_id", req.FavoriteID). | |
| 123 | + | Msg("handling favorite_set_folder") | |
| 124 | + | ||
| 125 | + | var folderID *entity.FolderID | |
| 126 | + | if req.FolderID != nil { | |
| 127 | + | id := entity.FolderID(*req.FolderID) | |
| 128 | + | folderID = &id | |
| 129 | + | } | |
| 130 | + | ||
| 131 | + | if err := h.favoritesUC.Move(ctx, entity.FavoriteID(req.FavoriteID), folderID); err != nil { | |
| 132 | + | return NewErrorResponse(req.RequestID, err), nil | |
| 133 | + | } | |
| 134 | + | ||
| 135 | + | return NewSuccessResponse(req.RequestID, nil), nil | |
| 136 | + | }) | |
| 137 | + | } | |
| @@ -0,0 +1,137 @@ | |||||
| 1 | + | package homepage | |||
| 2 | + | ||||
| 3 | + | import ( | |||
| 4 | + | "context" | |||
| 5 | + | "encoding/json" | |||
| 6 | + | ||||
| 7 | + | "github.com/bnema/dumber/internal/application/usecase" | |||
| 8 | + | "github.com/bnema/dumber/internal/domain/entity" | |||
| 9 | + | "github.com/bnema/dumber/internal/infrastructure/webkit" | |||
| 10 | + | "github.com/bnema/dumber/internal/logging" | |||
| 11 | + | ) | |||
| 12 | + | ||||
| 13 | + | // FavoritesHandlers handles favorite-related messages from the homepage. | |||
| 14 | + | type FavoritesHandlers struct { | |||
| 15 | + | favoritesUC *usecase.ManageFavoritesUseCase | |||
| 16 | + | } | |||
| 17 | + | ||||
| 18 | + | // NewFavoritesHandlers creates a new FavoritesHandlers instance. | |||
| 19 | + | func NewFavoritesHandlers(favoritesUC *usecase.ManageFavoritesUseCase) *FavoritesHandlers { | |||
| 20 | + | return &FavoritesHandlers{favoritesUC: favoritesUC} | |||
| 21 | + | } | |||
| 22 | + | ||||
| 23 | + | // HandleList handles favorite_list messages. | |||
| 24 | + | func (h *FavoritesHandlers) HandleList() webkit.MessageHandler { | |||
| 25 | + | return webkit.MessageHandlerFunc(func(ctx context.Context, webviewID webkit.WebViewID, payload json.RawMessage) (any, error) { | |||
| 26 | + | log := logging.FromContext(ctx) | |||
| 27 | + | ||||
| 28 | + | requestID := ParseRequestID(payload) | |||
| 29 | + | ||||
| 30 | + | log.Debug(). | |||
| 31 | + | Str("request_id", requestID). | |||
| 32 | + | Msg("handling favorite_list") | |||
| 33 | + | ||||
| 34 | + | favorites, err := h.favoritesUC.GetAll(ctx) | |||
| 35 | + | if err != nil { | |||
| 36 | + | return NewErrorResponse(requestID, err), nil | |||
| 37 | + | } | |||
| 38 | + | ||||
| 39 | + | return NewSuccessResponse(requestID, favorites), nil | |||
| 40 | + | }) | |||
| 41 | + | } | |||
| 42 | + | ||||
| 43 | + | // setShortcutRequest is the payload for favorite_set_shortcut messages. | |||
| 44 | + | type setShortcutRequest struct { | |||
| 45 | + | RequestID string `json:"requestId"` | |||
| 46 | + | FavoriteID int64 `json:"favorite_id"` | |||
| 47 | + | ShortcutKey *int `json:"shortcut_key"` // 1-9 or null to remove | |||
| 48 | + | } | |||
| 49 | + | ||||
| 50 | + | // HandleSetShortcut handles favorite_set_shortcut messages. | |||
| 51 | + | func (h *FavoritesHandlers) HandleSetShortcut() webkit.MessageHandler { | |||
| 52 | + | return webkit.MessageHandlerFunc(func(ctx context.Context, webviewID webkit.WebViewID, payload json.RawMessage) (any, error) { | |||
| 53 | + | log := logging.FromContext(ctx) | |||
| 54 | + | ||||
| 55 | + | var req setShortcutRequest | |||
| 56 | + | if err := json.Unmarshal(payload, &req); err != nil { | |||
| 57 | + | return NewErrorResponse("", err), nil | |||
| 58 | + | } | |||
| 59 | + | ||||
| 60 | + | log.Debug(). | |||
| 61 | + | Str("request_id", req.RequestID). | |||
| 62 | + | Int64("favorite_id", req.FavoriteID). | |||
| 63 | + | Msg("handling favorite_set_shortcut") | |||
| 64 | + | ||||
| 65 | + | if err := h.favoritesUC.SetShortcut(ctx, entity.FavoriteID(req.FavoriteID), req.ShortcutKey); err != nil { | |||
| 66 | + | return NewErrorResponse(req.RequestID, err), nil | |||
| 67 | + | } | |||
| 68 | + | ||||
| 69 | + | return NewSuccessResponse(req.RequestID, nil), nil | |||
| 70 | + | }) | |||
| 71 | + | } | |||
| 72 | + | ||||
| 73 | + | // getByShortcutRequest is the payload for favorite_get_by_shortcut messages. | |||
| 74 | + | type getByShortcutRequest struct { | |||
| 75 | + | RequestID string `json:"requestId"` | |||
| 76 | + | ShortcutKey int `json:"shortcut_key"` | |||
| 77 | + | } | |||
| 78 | + | ||||
| 79 | + | // HandleGetByShortcut handles favorite_get_by_shortcut messages. | |||
| 80 | + | func (h *FavoritesHandlers) HandleGetByShortcut() webkit.MessageHandler { | |||
| 81 | + | return webkit.MessageHandlerFunc(func(ctx context.Context, webviewID webkit.WebViewID, payload json.RawMessage) (any, error) { | |||
| 82 | + | log := logging.FromContext(ctx) | |||
| 83 | + | ||||
| 84 | + | var req getByShortcutRequest | |||
| 85 | + | if err := json.Unmarshal(payload, &req); err != nil { | |||
| 86 | + | return NewErrorResponse("", err), nil | |||
| 87 | + | } | |||
| 88 | + | ||||
| 89 | + | log.Debug(). | |||
| 90 | + | Str("request_id", req.RequestID). | |||
| 91 | + | Int("shortcut_key", req.ShortcutKey). | |||
| 92 | + | Msg("handling favorite_get_by_shortcut") | |||
| 93 | + | ||||
| 94 | + | favorite, err := h.favoritesUC.GetByShortcut(ctx, req.ShortcutKey) | |||
| 95 | + | if err != nil { | |||
| 96 | + | return NewErrorResponse(req.RequestID, err), nil | |||
| 97 | + | } | |||
| 98 | + | ||||
| 99 | + | return NewSuccessResponse(req.RequestID, favorite), nil | |||
| 100 | + | }) | |||
| 101 | + | } | |||
| 102 | + | ||||
| 103 | + | // setFolderRequest is the payload for favorite_set_folder messages. | |||
| 104 | + | type setFolderRequest struct { | |||
| 105 | + | RequestID string `json:"requestId"` | |||
| 106 | + | FavoriteID int64 `json:"favorite_id"` | |||
| 107 | + | FolderID *int64 `json:"folder_id"` // null to move to root | |||
| 108 | + | } | |||
| 109 | + | ||||
| 110 | + | // HandleSetFolder handles favorite_set_folder messages. | |||
| 111 | + | func (h *FavoritesHandlers) HandleSetFolder() webkit.MessageHandler { | |||
| 112 | + | return webkit.MessageHandlerFunc(func(ctx context.Context, webviewID webkit.WebViewID, payload json.RawMessage) (any, error) { | |||
| 113 | + | log := logging.FromContext(ctx) | |||
| 114 | + | ||||
| 115 | + | var req setFolderRequest | |||
| 116 | + | if err := json.Unmarshal(payload, &req); err != nil { | |||
| 117 | + | return NewErrorResponse("", err), nil | |||
| 118 | + | } | |||
| 119 | + | ||||
| 120 | + | log.Debug(). | |||
| 121 | + | Str("request_id", req.RequestID). | |||
| 122 | + | Int64("favorite_id", req.FavoriteID). | |||
| 123 | + | Msg("handling favorite_set_folder") | |||
| 124 | + | ||||
| 125 | + | var folderID *entity.FolderID | |||
| 126 | + | if req.FolderID != nil { | |||
| 127 | + | id := entity.FolderID(*req.FolderID) | |||
| 128 | + | folderID = &id | |||
| 129 | + | } | |||
| 130 | + | ||||
| 131 | + | if err := h.favoritesUC.Move(ctx, entity.FavoriteID(req.FavoriteID), folderID); err != nil { | |||
| 132 | + | return NewErrorResponse(req.RequestID, err), nil | |||
| 133 | + | } | |||
| 134 | + | ||||
| 135 | + | return NewSuccessResponse(req.RequestID, nil), nil | |||
| 136 | + | }) | |||
| 137 | + | } | |||
A internal/infrastructure/webkit/handlers/homepage/folders.go +144 -0
| @@ -0,0 +1,144 @@ | |||
| 1 | + | package homepage | |
| 2 | + | ||
| 3 | + | import ( | |
| 4 | + | "context" | |
| 5 | + | "encoding/json" | |
| 6 | + | ||
| 7 | + | "github.com/bnema/dumber/internal/application/usecase" | |
| 8 | + | "github.com/bnema/dumber/internal/domain/entity" | |
| 9 | + | "github.com/bnema/dumber/internal/infrastructure/webkit" | |
| 10 | + | "github.com/bnema/dumber/internal/logging" | |
| 11 | + | ) | |
| 12 | + | ||
| 13 | + | // FolderHandlers handles folder-related messages from the homepage. | |
| 14 | + | type FolderHandlers struct { | |
| 15 | + | favoritesUC *usecase.ManageFavoritesUseCase | |
| 16 | + | } | |
| 17 | + | ||
| 18 | + | // NewFolderHandlers creates a new FolderHandlers instance. | |
| 19 | + | func NewFolderHandlers(favoritesUC *usecase.ManageFavoritesUseCase) *FolderHandlers { | |
| 20 | + | return &FolderHandlers{favoritesUC: favoritesUC} | |
| 21 | + | } | |
| 22 | + | ||
| 23 | + | // HandleList handles folder_list messages. | |
| 24 | + | func (h *FolderHandlers) HandleList() webkit.MessageHandler { | |
| 25 | + | return webkit.MessageHandlerFunc(func(ctx context.Context, webviewID webkit.WebViewID, payload json.RawMessage) (any, error) { | |
| 26 | + | log := logging.FromContext(ctx) | |
| 27 | + | ||
| 28 | + | requestID := ParseRequestID(payload) | |
| 29 | + | ||
| 30 | + | log.Debug(). | |
| 31 | + | Str("request_id", requestID). | |
| 32 | + | Msg("handling folder_list") | |
| 33 | + | ||
| 34 | + | folders, err := h.favoritesUC.GetAllFolders(ctx) | |
| 35 | + | if err != nil { | |
| 36 | + | return NewErrorResponse(requestID, err), nil | |
| 37 | + | } | |
| 38 | + | ||
| 39 | + | return NewSuccessResponse(requestID, folders), nil | |
| 40 | + | }) | |
| 41 | + | } | |
| 42 | + | ||
| 43 | + | // createFolderRequest is the payload for folder_create messages. | |
| 44 | + | type createFolderRequest struct { | |
| 45 | + | RequestID string `json:"requestId"` | |
| 46 | + | Name string `json:"name"` | |
| 47 | + | Icon *string `json:"icon"` | |
| 48 | + | } | |
| 49 | + | ||
| 50 | + | // HandleCreate handles folder_create messages. | |
| 51 | + | func (h *FolderHandlers) HandleCreate() webkit.MessageHandler { | |
| 52 | + | return webkit.MessageHandlerFunc(func(ctx context.Context, webviewID webkit.WebViewID, payload json.RawMessage) (any, error) { | |
| 53 | + | log := logging.FromContext(ctx) | |
| 54 | + | ||
| 55 | + | var req createFolderRequest | |
| 56 | + | if err := json.Unmarshal(payload, &req); err != nil { | |
| 57 | + | return NewErrorResponse("", err), nil | |
| 58 | + | } | |
| 59 | + | ||
| 60 | + | log.Debug(). | |
| 61 | + | Str("request_id", req.RequestID). | |
| 62 | + | Str("name", req.Name). | |
| 63 | + | Msg("handling folder_create") | |
| 64 | + | ||
| 65 | + | folder, err := h.favoritesUC.CreateFolder(ctx, req.Name, nil) | |
| 66 | + | if err != nil { | |
| 67 | + | return NewErrorResponse(req.RequestID, err), nil | |
| 68 | + | } | |
| 69 | + | ||
| 70 | + | // Set icon if provided | |
| 71 | + | if req.Icon != nil && *req.Icon != "" { | |
| 72 | + | folder.Icon = *req.Icon | |
| 73 | + | } | |
| 74 | + | ||
| 75 | + | return NewSuccessResponse(req.RequestID, folder), nil | |
| 76 | + | }) | |
| 77 | + | } | |
| 78 | + | ||
| 79 | + | // deleteFolderRequest is the payload for folder_delete messages. | |
| 80 | + | type deleteFolderRequest struct { | |
| 81 | + | RequestID string `json:"requestId"` | |
| 82 | + | ID int64 `json:"id"` | |
| 83 | + | } | |
| 84 | + | ||
| 85 | + | // HandleDelete handles folder_delete messages. | |
| 86 | + | func (h *FolderHandlers) HandleDelete() webkit.MessageHandler { | |
| 87 | + | return webkit.MessageHandlerFunc(func(ctx context.Context, webviewID webkit.WebViewID, payload json.RawMessage) (any, error) { | |
| 88 | + | log := logging.FromContext(ctx) | |
| 89 | + | ||
| 90 | + | var req deleteFolderRequest | |
| 91 | + | if err := json.Unmarshal(payload, &req); err != nil { | |
| 92 | + | return NewErrorResponse("", err), nil | |
| 93 | + | } | |
| 94 | + | ||
| 95 | + | log.Debug(). | |
| 96 | + | Str("request_id", req.RequestID). | |
| 97 | + | Int64("id", req.ID). | |
| 98 | + | Msg("handling folder_delete") | |
| 99 | + | ||
| 100 | + | if err := h.favoritesUC.DeleteFolder(ctx, entity.FolderID(req.ID)); err != nil { | |
| 101 | + | return NewErrorResponse(req.RequestID, err), nil | |
| 102 | + | } | |
| 103 | + | ||
| 104 | + | return NewSuccessResponse(req.RequestID, nil), nil | |
| 105 | + | }) | |
| 106 | + | } | |
| 107 | + | ||
| 108 | + | // updateFolderRequest is the payload for folder_update messages. | |
| 109 | + | type updateFolderRequest struct { | |
| 110 | + | RequestID string `json:"requestId"` | |
| 111 | + | ID int64 `json:"id"` | |
| 112 | + | Name string `json:"name"` | |
| 113 | + | Icon *string `json:"icon"` | |
| 114 | + | } | |
| 115 | + | ||
| 116 | + | // HandleUpdate handles folder_update messages. | |
| 117 | + | // NOTE: This requires UpdateFolder() method to be added to ManageFavoritesUseCase. | |
| 118 | + | func (h *FolderHandlers) HandleUpdate() webkit.MessageHandler { | |
| 119 | + | return webkit.MessageHandlerFunc(func(ctx context.Context, webviewID webkit.WebViewID, payload json.RawMessage) (any, error) { | |
| 120 | + | log := logging.FromContext(ctx) | |
| 121 | + | ||
| 122 | + | var req updateFolderRequest | |
| 123 | + | if err := json.Unmarshal(payload, &req); err != nil { | |
| 124 | + | return NewErrorResponse("", err), nil | |
| 125 | + | } | |
| 126 | + | ||
| 127 | + | log.Debug(). | |
| 128 | + | Str("request_id", req.RequestID). | |
| 129 | + | Int64("id", req.ID). | |
| 130 | + | Str("name", req.Name). | |
| 131 | + | Msg("handling folder_update") | |
| 132 | + | ||
| 133 | + | icon := "" | |
| 134 | + | if req.Icon != nil { | |
| 135 | + | icon = *req.Icon | |
| 136 | + | } | |
| 137 | + | ||
| 138 | + | if err := h.favoritesUC.UpdateFolder(ctx, entity.FolderID(req.ID), req.Name, icon); err != nil { | |
| 139 | + | return NewErrorResponse(req.RequestID, err), nil | |
| 140 | + | } | |
| 141 | + | ||
| 142 | + | return NewSuccessResponse(req.RequestID, nil), nil | |
| 143 | + | }) | |
| 144 | + | } | |
| @@ -0,0 +1,144 @@ | |||||
| 1 | + | package homepage | |||
| 2 | + | ||||
| 3 | + | import ( | |||
| 4 | + | "context" | |||
| 5 | + | "encoding/json" | |||
| 6 | + | ||||
| 7 | + | "github.com/bnema/dumber/internal/application/usecase" | |||
| 8 | + | "github.com/bnema/dumber/internal/domain/entity" | |||
| 9 | + | "github.com/bnema/dumber/internal/infrastructure/webkit" | |||
| 10 | + | "github.com/bnema/dumber/internal/logging" | |||
| 11 | + | ) | |||
| 12 | + | ||||
| 13 | + | // FolderHandlers handles folder-related messages from the homepage. | |||
| 14 | + | type FolderHandlers struct { | |||
| 15 | + | favoritesUC *usecase.ManageFavoritesUseCase | |||
| 16 | + | } | |||
| 17 | + | ||||
| 18 | + | // NewFolderHandlers creates a new FolderHandlers instance. | |||
| 19 | + | func NewFolderHandlers(favoritesUC *usecase.ManageFavoritesUseCase) *FolderHandlers { | |||
| 20 | + | return &FolderHandlers{favoritesUC: favoritesUC} | |||
| 21 | + | } | |||
| 22 | + | ||||
| 23 | + | // HandleList handles folder_list messages. | |||
| 24 | + | func (h *FolderHandlers) HandleList() webkit.MessageHandler { | |||
| 25 | + | return webkit.MessageHandlerFunc(func(ctx context.Context, webviewID webkit.WebViewID, payload json.RawMessage) (any, error) { | |||
| 26 | + | log := logging.FromContext(ctx) | |||
| 27 | + | ||||
| 28 | + | requestID := ParseRequestID(payload) | |||
| 29 | + | ||||
| 30 | + | log.Debug(). | |||
| 31 | + | Str("request_id", requestID). | |||
| 32 | + | Msg("handling folder_list") | |||
| 33 | + | ||||
| 34 | + | folders, err := h.favoritesUC.GetAllFolders(ctx) | |||
| 35 | + | if err != nil { | |||
| 36 | + | return NewErrorResponse(requestID, err), nil | |||
| 37 | + | } | |||
| 38 | + | ||||
| 39 | + | return NewSuccessResponse(requestID, folders), nil | |||
| 40 | + | }) | |||
| 41 | + | } | |||
| 42 | + | ||||
| 43 | + | // createFolderRequest is the payload for folder_create messages. | |||
| 44 | + | type createFolderRequest struct { | |||
| 45 | + | RequestID string `json:"requestId"` | |||
| 46 | + | Name string `json:"name"` | |||
| 47 | + | Icon *string `json:"icon"` | |||
| 48 | + | } | |||
| 49 | + | ||||
| 50 | + | // HandleCreate handles folder_create messages. | |||
| 51 | + | func (h *FolderHandlers) HandleCreate() webkit.MessageHandler { | |||
| 52 | + | return webkit.MessageHandlerFunc(func(ctx context.Context, webviewID webkit.WebViewID, payload json.RawMessage) (any, error) { | |||
| 53 | + | log := logging.FromContext(ctx) | |||
| 54 | + | ||||
| 55 | + | var req createFolderRequest | |||
| 56 | + | if err := json.Unmarshal(payload, &req); err != nil { | |||
| 57 | + | return NewErrorResponse("", err), nil | |||
| 58 | + | } | |||
| 59 | + | ||||
| 60 | + | log.Debug(). | |||
| 61 | + | Str("request_id", req.RequestID). | |||
| 62 | + | Str("name", req.Name). | |||
| 63 | + | Msg("handling folder_create") | |||
| 64 | + | ||||
| 65 | + | folder, err := h.favoritesUC.CreateFolder(ctx, req.Name, nil) | |||
| 66 | + | if err != nil { | |||
| 67 | + | return NewErrorResponse(req.RequestID, err), nil | |||
| 68 | + | } | |||
| 69 | + | ||||
| 70 | + | // Set icon if provided | |||
| 71 | + | if req.Icon != nil && *req.Icon != "" { | |||
| 72 | + | folder.Icon = *req.Icon | |||
| 73 | + | } | |||
| 74 | + | ||||
| 75 | + | return NewSuccessResponse(req.RequestID, folder), nil | |||
| 76 | + | }) | |||
| 77 | + | } | |||
| 78 | + | ||||
| 79 | + | // deleteFolderRequest is the payload for folder_delete messages. | |||
| 80 | + | type deleteFolderRequest struct { | |||
| 81 | + | RequestID string `json:"requestId"` | |||
| 82 | + | ID int64 `json:"id"` | |||
| 83 | + | } | |||
| 84 | + | ||||
| 85 | + | // HandleDelete handles folder_delete messages. | |||
| 86 | + | func (h *FolderHandlers) HandleDelete() webkit.MessageHandler { | |||
| 87 | + | return webkit.MessageHandlerFunc(func(ctx context.Context, webviewID webkit.WebViewID, payload json.RawMessage) (any, error) { | |||
| 88 | + | log := logging.FromContext(ctx) | |||
| 89 | + | ||||
| 90 | + | var req deleteFolderRequest | |||
| 91 | + | if err := json.Unmarshal(payload, &req); err != nil { | |||
| 92 | + | return NewErrorResponse("", err), nil | |||
| 93 | + | } | |||
| 94 | + | ||||
| 95 | + | log.Debug(). | |||
| 96 | + | Str("request_id", req.RequestID). | |||
| 97 | + | Int64("id", req.ID). | |||
| 98 | + | Msg("handling folder_delete") | |||
| 99 | + | ||||
| 100 | + | if err := h.favoritesUC.DeleteFolder(ctx, entity.FolderID(req.ID)); err != nil { | |||
| 101 | + | return NewErrorResponse(req.RequestID, err), nil | |||
| 102 | + | } | |||
| 103 | + | ||||
| 104 | + | return NewSuccessResponse(req.RequestID, nil), nil | |||
| 105 | + | }) | |||
| 106 | + | } | |||
| 107 | + | ||||
| 108 | + | // updateFolderRequest is the payload for folder_update messages. | |||
| 109 | + | type updateFolderRequest struct { | |||
| 110 | + | RequestID string `json:"requestId"` | |||
| 111 | + | ID int64 `json:"id"` | |||
| 112 | + | Name string `json:"name"` | |||
| 113 | + | Icon *string `json:"icon"` | |||
| 114 | + | } | |||
| 115 | + | ||||
| 116 | + | // HandleUpdate handles folder_update messages. | |||
| 117 | + | // NOTE: This requires UpdateFolder() method to be added to ManageFavoritesUseCase. | |||
| 118 | + | func (h *FolderHandlers) HandleUpdate() webkit.MessageHandler { | |||
| 119 | + | return webkit.MessageHandlerFunc(func(ctx context.Context, webviewID webkit.WebViewID, payload json.RawMessage) (any, error) { | |||
| 120 | + | log := logging.FromContext(ctx) | |||
| 121 | + | ||||
| 122 | + | var req updateFolderRequest | |||
| 123 | + | if err := json.Unmarshal(payload, &req); err != nil { | |||
| 124 | + | return NewErrorResponse("", err), nil | |||
| 125 | + | } | |||
| 126 | + | ||||
| 127 | + | log.Debug(). | |||
| 128 | + | Str("request_id", req.RequestID). | |||
| 129 | + | Int64("id", req.ID). | |||
| 130 | + | Str("name", req.Name). | |||
| 131 | + | Msg("handling folder_update") | |||
| 132 | + | ||||
| 133 | + | icon := "" | |||
| 134 | + | if req.Icon != nil { | |||
| 135 | + | icon = *req.Icon | |||
| 136 | + | } | |||
| 137 | + | ||||
| 138 | + | if err := h.favoritesUC.UpdateFolder(ctx, entity.FolderID(req.ID), req.Name, icon); err != nil { | |||
| 139 | + | return NewErrorResponse(req.RequestID, err), nil | |||
| 140 | + | } | |||
| 141 | + | ||||
| 142 | + | return NewSuccessResponse(req.RequestID, nil), nil | |||
| 143 | + | }) | |||
| 144 | + | } | |||
A internal/infrastructure/webkit/handlers/homepage/history.go +278 -0
| @@ -0,0 +1,278 @@ | |||
| 1 | + | package homepage | |
| 2 | + | ||
| 3 | + | import ( | |
| 4 | + | "context" | |
| 5 | + | "encoding/json" | |
| 6 | + | "time" | |
| 7 | + | ||
| 8 | + | "github.com/bnema/dumber/internal/application/usecase" | |
| 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. | |
| 15 | + | type HistoryHandlers struct { | |
| 16 | + | historyUC *usecase.SearchHistoryUseCase | |
| 17 | + | } | |
| 18 | + | ||
| 19 | + | // NewHistoryHandlers creates a new HistoryHandlers instance. | |
| 20 | + | func NewHistoryHandlers(historyUC *usecase.SearchHistoryUseCase) *HistoryHandlers { | |
| 21 | + | return &HistoryHandlers{historyUC: historyUC} | |
| 22 | + | } | |
| 23 | + | ||
| 24 | + | // timelineRequest is the payload for history_timeline messages. | |
| 25 | + | type 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. | |
| 32 | + | func (h *HistoryHandlers) HandleTimeline() webkit.MessageHandler { | |
| 33 | + | return webkit.MessageHandlerFunc(func(ctx context.Context, webviewID 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. | |
| 57 | + | type 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. | |
| 64 | + | func (h *HistoryHandlers) HandleSearchFTS() webkit.MessageHandler { | |
| 65 | + | return webkit.MessageHandlerFunc(func(ctx context.Context, webviewID 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, usecase.SearchInput{ | |
| 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. | |
| 97 | + | type deleteEntryRequest struct { | |
| 98 | + | RequestID string `json:"requestId"` | |
| 99 | + | ID int64 `json:"id"` | |
| 100 | + | } | |
| 101 | + | ||
| 102 | + | // HandleDeleteEntry handles history_delete_entry messages. | |
| 103 | + | func (h *HistoryHandlers) HandleDeleteEntry() webkit.MessageHandler { | |
| 104 | + | return webkit.MessageHandlerFunc(func(ctx context.Context, webviewID 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. | |
| 126 | + | type 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. | |
| 132 | + | func (h *HistoryHandlers) HandleDeleteRange() webkit.MessageHandler { | |
| 133 | + | return webkit.MessageHandlerFunc(func(ctx context.Context, webviewID 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. | |
| 163 | + | func (h *HistoryHandlers) HandleClearAll() webkit.MessageHandler { | |
| 164 | + | return webkit.MessageHandlerFunc(func(ctx context.Context, webviewID 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. | |
| 183 | + | func 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. | |
| 202 | + | func (h *HistoryHandlers) HandleAnalytics() webkit.MessageHandler { | |
| 203 | + | return webkit.MessageHandlerFunc(func(ctx context.Context, webviewID 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. | |
| 222 | + | type domainStatsRequest struct { | |
| 223 | + | RequestID string `json:"requestId"` | |
| 224 | + | Limit int `json:"limit"` | |
| 225 | + | } | |
| 226 | + | ||
| 227 | + | // HandleDomainStats handles history_domain_stats messages. | |
| 228 | + | func (h *HistoryHandlers) HandleDomainStats() webkit.MessageHandler { | |
| 229 | + | return webkit.MessageHandlerFunc(func(ctx context.Context, webviewID 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. | |
| 252 | + | type deleteDomainRequest struct { | |
| 253 | + | RequestID string `json:"requestId"` | |
| 254 | + | Domain string `json:"domain"` | |
| 255 | + | } | |
| 256 | + | ||
| 257 | + | // HandleDeleteDomain handles history_delete_domain messages. | |
| 258 | + | func (h *HistoryHandlers) HandleDeleteDomain() webkit.MessageHandler { | |
| 259 | + | return webkit.MessageHandlerFunc(func(ctx context.Context, webviewID 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 | + | } | |
| @@ -0,0 +1,278 @@ | |||||
| 1 | + | package homepage | |||
| 2 | + | ||||
| 3 | + | import ( | |||
| 4 | + | "context" | |||
| 5 | + | "encoding/json" | |||
| 6 | + | "time" | |||
| 7 | + | ||||
| 8 | + | "github.com/bnema/dumber/internal/application/usecase" | |||
| 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. | |||
| 15 | + | type HistoryHandlers struct { | |||
| 16 | + | historyUC *usecase.SearchHistoryUseCase | |||
| 17 | + | } | |||
| 18 | + | ||||
| 19 | + | // NewHistoryHandlers creates a new HistoryHandlers instance. | |||
| 20 | + | func NewHistoryHandlers(historyUC *usecase.SearchHistoryUseCase) *HistoryHandlers { | |||
| 21 | + | return &HistoryHandlers{historyUC: historyUC} | |||
| 22 | + | } | |||
| 23 | + | ||||
| 24 | + | // timelineRequest is the payload for history_timeline messages. | |||
| 25 | + | type 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. | |||
| 32 | + | func (h *HistoryHandlers) HandleTimeline() webkit.MessageHandler { | |||
| 33 | + | return webkit.MessageHandlerFunc(func(ctx context.Context, webviewID 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. | |||
| 57 | + | type 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. | |||
| 64 | + | func (h *HistoryHandlers) HandleSearchFTS() webkit.MessageHandler { | |||
| 65 | + | return webkit.MessageHandlerFunc(func(ctx context.Context, webviewID 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, usecase.SearchInput{ | |||
| 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. | |||
| 97 | + | type deleteEntryRequest struct { | |||
| 98 | + | RequestID string `json:"requestId"` | |||
| 99 | + | ID int64 `json:"id"` | |||
| 100 | + | } | |||
| 101 | + | ||||
| 102 | + | // HandleDeleteEntry handles history_delete_entry messages. | |||
| 103 | + | func (h *HistoryHandlers) HandleDeleteEntry() webkit.MessageHandler { | |||
| 104 | + | return webkit.MessageHandlerFunc(func(ctx context.Context, webviewID 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. | |||
| 126 | + | type 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. | |||
| 132 | + | func (h *HistoryHandlers) HandleDeleteRange() webkit.MessageHandler { | |||
| 133 | + | return webkit.MessageHandlerFunc(func(ctx context.Context, webviewID 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. | |||
| 163 | + | func (h *HistoryHandlers) HandleClearAll() webkit.MessageHandler { | |||
| 164 | + | return webkit.MessageHandlerFunc(func(ctx context.Context, webviewID 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. | |||
| 183 | + | func 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. | |||
| 202 | + | func (h *HistoryHandlers) HandleAnalytics() webkit.MessageHandler { | |||
| 203 | + | return webkit.MessageHandlerFunc(func(ctx context.Context, webviewID 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. | |||
| 222 | + | type domainStatsRequest struct { | |||
| 223 | + | RequestID string `json:"requestId"` | |||
| 224 | + | Limit int `json:"limit"` | |||
| 225 | + | } | |||
| 226 | + | ||||
| 227 | + | // HandleDomainStats handles history_domain_stats messages. | |||
| 228 | + | func (h *HistoryHandlers) HandleDomainStats() webkit.MessageHandler { | |||
| 229 | + | return webkit.MessageHandlerFunc(func(ctx context.Context, webviewID 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. | |||
| 252 | + | type deleteDomainRequest struct { | |||
| 253 | + | RequestID string `json:"requestId"` | |||
| 254 | + | Domain string `json:"domain"` | |||
| 255 | + | } | |||
| 256 | + | ||||
| 257 | + | // HandleDeleteDomain handles history_delete_domain messages. | |||
| 258 | + | func (h *HistoryHandlers) HandleDeleteDomain() webkit.MessageHandler { | |||
| 259 | + | return webkit.MessageHandlerFunc(func(ctx context.Context, webviewID 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 | + | } | |||
A internal/infrastructure/webkit/handlers/homepage/registry.go +74 -0
| @@ -0,0 +1,74 @@ | |||
| 1 | + | package homepage | |
| 2 | + | ||
| 3 | + | import ( | |
| 4 | + | "context" | |
| 5 | + | "fmt" | |
| 6 | + | ||
| 7 | + | "github.com/bnema/dumber/internal/application/usecase" | |
| 8 | + | "github.com/bnema/dumber/internal/infrastructure/webkit" | |
| 9 | + | "github.com/bnema/dumber/internal/logging" | |
| 10 | + | ) | |
| 11 | + | ||
| 12 | + | // Config holds dependencies for homepage handlers. | |
| 13 | + | type Config struct { | |
| 14 | + | HistoryUC *usecase.SearchHistoryUseCase | |
| 15 | + | FavoritesUC *usecase.ManageFavoritesUseCase | |
| 16 | + | } | |
| 17 | + | ||
| 18 | + | // RegisterHandlers registers all homepage message handlers with the router. | |
| 19 | + | func RegisterHandlers(ctx context.Context, router *webkit.MessageRouter, cfg Config) error { | |
| 20 | + | log := logging.FromContext(ctx) | |
| 21 | + | log.Debug().Msg("registering homepage message handlers") | |
| 22 | + | ||
| 23 | + | // Common callback for all homepage handlers | |
| 24 | + | const callback = "__dumber_homepage_response" | |
| 25 | + | const errorCallback = "__dumber_error" | |
| 26 | + | const worldName = "" // main world | |
| 27 | + | ||
| 28 | + | handlers := make(map[string]webkit.MessageHandler) | |
| 29 | + | ||
| 30 | + | // History handlers | |
| 31 | + | historyHandlers := NewHistoryHandlers(cfg.HistoryUC) | |
| 32 | + | handlers["history_timeline"] = historyHandlers.HandleTimeline() | |
| 33 | + | handlers["history_search_fts"] = historyHandlers.HandleSearchFTS() | |
| 34 | + | handlers["history_delete_entry"] = historyHandlers.HandleDeleteEntry() | |
| 35 | + | handlers["history_delete_range"] = historyHandlers.HandleDeleteRange() | |
| 36 | + | handlers["history_clear_all"] = historyHandlers.HandleClearAll() | |
| 37 | + | handlers["history_analytics"] = historyHandlers.HandleAnalytics() | |
| 38 | + | handlers["history_domain_stats"] = historyHandlers.HandleDomainStats() | |
| 39 | + | handlers["history_delete_domain"] = historyHandlers.HandleDeleteDomain() | |
| 40 | + | ||
| 41 | + | // Favorites handlers | |
| 42 | + | favoritesHandlers := NewFavoritesHandlers(cfg.FavoritesUC) | |
| 43 | + | handlers["favorite_list"] = favoritesHandlers.HandleList() | |
| 44 | + | handlers["favorite_set_shortcut"] = favoritesHandlers.HandleSetShortcut() | |
| 45 | + | handlers["favorite_get_by_shortcut"] = favoritesHandlers.HandleGetByShortcut() | |
| 46 | + | handlers["favorite_set_folder"] = favoritesHandlers.HandleSetFolder() | |
| 47 | + | ||
| 48 | + | // Folder handlers | |
| 49 | + | folderHandlers := NewFolderHandlers(cfg.FavoritesUC) | |
| 50 | + | handlers["folder_list"] = folderHandlers.HandleList() | |
| 51 | + | handlers["folder_create"] = folderHandlers.HandleCreate() | |
| 52 | + | handlers["folder_delete"] = folderHandlers.HandleDelete() | |
| 53 | + | handlers["folder_update"] = folderHandlers.HandleUpdate() // Requires UpdateFolder() | |
| 54 | + | ||
| 55 | + | // Tag handlers | |
| 56 | + | tagHandlers := NewTagHandlers(cfg.FavoritesUC) | |
| 57 | + | handlers["tag_list"] = tagHandlers.HandleList() | |
| 58 | + | handlers["tag_create"] = tagHandlers.HandleCreate() | |
| 59 | + | handlers["tag_delete"] = tagHandlers.HandleDelete() | |
| 60 | + | handlers["tag_update"] = tagHandlers.HandleUpdate() // Requires UpdateTag() | |
| 61 | + | handlers["tag_assign"] = tagHandlers.HandleAssign() | |
| 62 | + | handlers["tag_remove"] = tagHandlers.HandleRemove() | |
| 63 | + | ||
| 64 | + | // Register all handlers | |
| 65 | + | for msgType, handler := range handlers { | |
| 66 | + | if err := router.RegisterHandlerWithCallbacks(msgType, callback, errorCallback, worldName, handler); err != nil { | |
| 67 | + | return fmt.Errorf("failed to register handler %s: %w", msgType, err) | |
| 68 | + | } | |
| 69 | + | log.Debug().Str("type", msgType).Msg("registered homepage handler") | |
| 70 | + | } | |
| 71 | + | ||
| 72 | + | log.Info().Int("count", len(handlers)).Msg("homepage handlers registered") | |
| 73 | + | return nil | |
| 74 | + | } | |
| @@ -0,0 +1,74 @@ | |||||
| 1 | + | package homepage | |||
| 2 | + | ||||
| 3 | + | import ( | |||
| 4 | + | "context" | |||
| 5 | + | "fmt" | |||
| 6 | + | ||||
| 7 | + | "github.com/bnema/dumber/internal/application/usecase" | |||
| 8 | + | "github.com/bnema/dumber/internal/infrastructure/webkit" | |||
| 9 | + | "github.com/bnema/dumber/internal/logging" | |||
| 10 | + | ) | |||
| 11 | + | ||||
| 12 | + | // Config holds dependencies for homepage handlers. | |||
| 13 | + | type Config struct { | |||
| 14 | + | HistoryUC *usecase.SearchHistoryUseCase | |||
| 15 | + | FavoritesUC *usecase.ManageFavoritesUseCase | |||
| 16 | + | } | |||
| 17 | + | ||||
| 18 | + | // RegisterHandlers registers all homepage message handlers with the router. | |||
| 19 | + | func RegisterHandlers(ctx context.Context, router *webkit.MessageRouter, cfg Config) error { | |||
| 20 | + | log := logging.FromContext(ctx) | |||
| 21 | + | log.Debug().Msg("registering homepage message handlers") | |||
| 22 | + | ||||
| 23 | + | // Common callback for all homepage handlers | |||
| 24 | + | const callback = "__dumber_homepage_response" | |||
| 25 | + | const errorCallback = "__dumber_error" | |||
| 26 | + | const worldName = "" // main world | |||
| 27 | + | ||||
| 28 | + | handlers := make(map[string]webkit.MessageHandler) | |||
| 29 | + | ||||
| 30 | + | // History handlers | |||
| 31 | + | historyHandlers := NewHistoryHandlers(cfg.HistoryUC) | |||
| 32 | + | handlers["history_timeline"] = historyHandlers.HandleTimeline() | |||
| 33 | + | handlers["history_search_fts"] = historyHandlers.HandleSearchFTS() | |||
| 34 | + | handlers["history_delete_entry"] = historyHandlers.HandleDeleteEntry() | |||
| 35 | + | handlers["history_delete_range"] = historyHandlers.HandleDeleteRange() | |||
| 36 | + | handlers["history_clear_all"] = historyHandlers.HandleClearAll() | |||
| 37 | + | handlers["history_analytics"] = historyHandlers.HandleAnalytics() | |||
| 38 | + | handlers["history_domain_stats"] = historyHandlers.HandleDomainStats() | |||
| 39 | + | handlers["history_delete_domain"] = historyHandlers.HandleDeleteDomain() | |||
| 40 | + | ||||
| 41 | + | // Favorites handlers | |||
| 42 | + | favoritesHandlers := NewFavoritesHandlers(cfg.FavoritesUC) | |||
| 43 | + | handlers["favorite_list"] = favoritesHandlers.HandleList() | |||
| 44 | + | handlers["favorite_set_shortcut"] = favoritesHandlers.HandleSetShortcut() | |||
| 45 | + | handlers["favorite_get_by_shortcut"] = favoritesHandlers.HandleGetByShortcut() | |||
| 46 | + | handlers["favorite_set_folder"] = favoritesHandlers.HandleSetFolder() | |||
| 47 | + | ||||
| 48 | + | // Folder handlers | |||
| 49 | + | folderHandlers := NewFolderHandlers(cfg.FavoritesUC) | |||
| 50 | + | handlers["folder_list"] = folderHandlers.HandleList() | |||
| 51 | + | handlers["folder_create"] = folderHandlers.HandleCreate() | |||
| 52 | + | handlers["folder_delete"] = folderHandlers.HandleDelete() | |||
| 53 | + | handlers["folder_update"] = folderHandlers.HandleUpdate() // Requires UpdateFolder() | |||
| 54 | + | ||||
| 55 | + | // Tag handlers | |||
| 56 | + | tagHandlers := NewTagHandlers(cfg.FavoritesUC) | |||
| 57 | + | handlers["tag_list"] = tagHandlers.HandleList() | |||
| 58 | + | handlers["tag_create"] = tagHandlers.HandleCreate() | |||
| 59 | + | handlers["tag_delete"] = tagHandlers.HandleDelete() | |||
| 60 | + | handlers["tag_update"] = tagHandlers.HandleUpdate() // Requires UpdateTag() | |||
| 61 | + | handlers["tag_assign"] = tagHandlers.HandleAssign() | |||
| 62 | + | handlers["tag_remove"] = tagHandlers.HandleRemove() | |||
| 63 | + | ||||
| 64 | + | // Register all handlers | |||
| 65 | + | for msgType, handler := range handlers { | |||
| 66 | + | if err := router.RegisterHandlerWithCallbacks(msgType, callback, errorCallback, worldName, handler); err != nil { | |||
| 67 | + | return fmt.Errorf("failed to register handler %s: %w", msgType, err) | |||
| 68 | + | } | |||
| 69 | + | log.Debug().Str("type", msgType).Msg("registered homepage handler") | |||
| 70 | + | } | |||
| 71 | + | ||||
| 72 | + | log.Info().Int("count", len(handlers)).Msg("homepage handlers registered") | |||
| 73 | + | return nil | |||
| 74 | + | } | |||
A internal/infrastructure/webkit/handlers/homepage/response.go +65 -0
| @@ -0,0 +1,65 @@ | |||
| 1 | + | package homepage | |
| 2 | + | ||
| 3 | + | import ( | |
| 4 | + | "encoding/json" | |
| 5 | + | ) | |
| 6 | + | ||
| 7 | + | // Response represents the standard response format sent back to the frontend. | |
| 8 | + | // The frontend expects: { requestId, success, data?, error? } | |
| 9 | + | type Response struct { | |
| 10 | + | RequestID string `json:"requestId"` | |
| 11 | + | Success bool `json:"success"` | |
| 12 | + | Data any `json:"data,omitempty"` | |
| 13 | + | Error string `json:"error,omitempty"` | |
| 14 | + | } | |
| 15 | + | ||
| 16 | + | // NewSuccessResponse creates a success response with data. | |
| 17 | + | func NewSuccessResponse(requestID string, data any) Response { | |
| 18 | + | return Response{ | |
| 19 | + | RequestID: requestID, | |
| 20 | + | Success: true, | |
| 21 | + | Data: data, | |
| 22 | + | } | |
| 23 | + | } | |
| 24 | + | ||
| 25 | + | // NewErrorResponse creates an error response. | |
| 26 | + | func NewErrorResponse(requestID string, err error) Response { | |
| 27 | + | errMsg := "unknown error" | |
| 28 | + | if err != nil { | |
| 29 | + | errMsg = err.Error() | |
| 30 | + | } | |
| 31 | + | return Response{ | |
| 32 | + | RequestID: requestID, | |
| 33 | + | Success: false, | |
| 34 | + | Error: errMsg, | |
| 35 | + | } | |
| 36 | + | } | |
| 37 | + | ||
| 38 | + | // baseRequest is used to extract the requestId from any payload. | |
| 39 | + | type baseRequest struct { | |
| 40 | + | RequestID string `json:"requestId"` | |
| 41 | + | } | |
| 42 | + | ||
| 43 | + | // ParseRequestID extracts the requestId from a raw JSON payload. | |
| 44 | + | func ParseRequestID(payload json.RawMessage) string { | |
| 45 | + | var base baseRequest | |
| 46 | + | if err := json.Unmarshal(payload, &base); err != nil { | |
| 47 | + | return "" | |
| 48 | + | } | |
| 49 | + | return base.RequestID | |
| 50 | + | } | |
| 51 | + | ||
| 52 | + | // ParsePayload extracts the requestId and unmarshals the payload into the target type. | |
| 53 | + | // Returns the requestId, the parsed payload, and any error. | |
| 54 | + | func ParsePayload[T any](payload json.RawMessage) (string, T, error) { | |
| 55 | + | var base baseRequest | |
| 56 | + | var target T | |
| 57 | + | ||
| 58 | + | if err := json.Unmarshal(payload, &base); err != nil { | |
| 59 | + | return "", target, err | |
| 60 | + | } | |
| 61 | + | if err := json.Unmarshal(payload, &target); err != nil { | |
| 62 | + | return base.RequestID, target, err | |
| 63 | + | } | |
| 64 | + | return base.RequestID, target, nil | |
| 65 | + | } | |
| @@ -0,0 +1,65 @@ | |||||
| 1 | + | package homepage | |||
| 2 | + | ||||
| 3 | + | import ( | |||
| 4 | + | "encoding/json" | |||
| 5 | + | ) | |||
| 6 | + | ||||
| 7 | + | // Response represents the standard response format sent back to the frontend. | |||
| 8 | + | // The frontend expects: { requestId, success, data?, error? } | |||
| 9 | + | type Response struct { | |||
| 10 | + | RequestID string `json:"requestId"` | |||
| 11 | + | Success bool `json:"success"` | |||
| 12 | + | Data any `json:"data,omitempty"` | |||
| 13 | + | Error string `json:"error,omitempty"` | |||
| 14 | + | } | |||
| 15 | + | ||||
| 16 | + | // NewSuccessResponse creates a success response with data. | |||
| 17 | + | func NewSuccessResponse(requestID string, data any) Response { | |||
| 18 | + | return Response{ | |||
| 19 | + | RequestID: requestID, | |||
| 20 | + | Success: true, | |||
| 21 | + | Data: data, | |||
| 22 | + | } | |||
| 23 | + | } | |||
| 24 | + | ||||
| 25 | + | // NewErrorResponse creates an error response. | |||
| 26 | + | func NewErrorResponse(requestID string, err error) Response { | |||
| 27 | + | errMsg := "unknown error" | |||
| 28 | + | if err != nil { | |||
| 29 | + | errMsg = err.Error() | |||
| 30 | + | } | |||
| 31 | + | return Response{ | |||
| 32 | + | RequestID: requestID, | |||
| 33 | + | Success: false, | |||
| 34 | + | Error: errMsg, | |||
| 35 | + | } | |||
| 36 | + | } | |||
| 37 | + | ||||
| 38 | + | // baseRequest is used to extract the requestId from any payload. | |||
| 39 | + | type baseRequest struct { | |||
| 40 | + | RequestID string `json:"requestId"` | |||
| 41 | + | } | |||
| 42 | + | ||||
| 43 | + | // ParseRequestID extracts the requestId from a raw JSON payload. | |||
| 44 | + | func ParseRequestID(payload json.RawMessage) string { | |||
| 45 | + | var base baseRequest | |||
| 46 | + | if err := json.Unmarshal(payload, &base); err != nil { | |||
| 47 | + | return "" | |||
| 48 | + | } | |||
| 49 | + | return base.RequestID | |||
| 50 | + | } | |||
| 51 | + | ||||
| 52 | + | // ParsePayload extracts the requestId and unmarshals the payload into the target type. | |||
| 53 | + | // Returns the requestId, the parsed payload, and any error. | |||
| 54 | + | func ParsePayload[T any](payload json.RawMessage) (string, T, error) { | |||
| 55 | + | var base baseRequest | |||
| 56 | + | var target T | |||
| 57 | + | ||||
| 58 | + | if err := json.Unmarshal(payload, &base); err != nil { | |||
| 59 | + | return "", target, err | |||
| 60 | + | } | |||
| 61 | + | if err := json.Unmarshal(payload, &target); err != nil { | |||
| 62 | + | return base.RequestID, target, err | |||
| 63 | + | } | |||
| 64 | + | return base.RequestID, target, nil | |||
| 65 | + | } | |||
A internal/infrastructure/webkit/handlers/homepage/tags.go +202 -0
| @@ -0,0 +1,202 @@ | |||
| 1 | + | package homepage | |
| 2 | + | ||
| 3 | + | import ( | |
| 4 | + | "context" | |
| 5 | + | "encoding/json" | |
| 6 | + | ||
| 7 | + | "github.com/bnema/dumber/internal/application/usecase" | |
| 8 | + | "github.com/bnema/dumber/internal/domain/entity" | |
| 9 | + | "github.com/bnema/dumber/internal/infrastructure/webkit" | |
| 10 | + | "github.com/bnema/dumber/internal/logging" | |
| 11 | + | ) | |
| 12 | + | ||
| 13 | + | // TagHandlers handles tag-related messages from the homepage. | |
| 14 | + | type TagHandlers struct { | |
| 15 | + | favoritesUC *usecase.ManageFavoritesUseCase | |
| 16 | + | } | |
| 17 | + | ||
| 18 | + | // NewTagHandlers creates a new TagHandlers instance. | |
| 19 | + | func NewTagHandlers(favoritesUC *usecase.ManageFavoritesUseCase) *TagHandlers { | |
| 20 | + | return &TagHandlers{favoritesUC: favoritesUC} | |
| 21 | + | } | |
| 22 | + | ||
| 23 | + | // HandleList handles tag_list messages. | |
| 24 | + | func (h *TagHandlers) HandleList() webkit.MessageHandler { | |
| 25 | + | return webkit.MessageHandlerFunc(func(ctx context.Context, webviewID webkit.WebViewID, payload json.RawMessage) (any, error) { | |
| 26 | + | log := logging.FromContext(ctx) | |
| 27 | + | ||
| 28 | + | requestID := ParseRequestID(payload) | |
| 29 | + | ||
| 30 | + | log.Debug(). | |
| 31 | + | Str("request_id", requestID). | |
| 32 | + | Msg("handling tag_list") | |
| 33 | + | ||
| 34 | + | tags, err := h.favoritesUC.GetAllTags(ctx) | |
| 35 | + | if err != nil { | |
| 36 | + | return NewErrorResponse(requestID, err), nil | |
| 37 | + | } | |
| 38 | + | ||
| 39 | + | return NewSuccessResponse(requestID, tags), nil | |
| 40 | + | }) | |
| 41 | + | } | |
| 42 | + | ||
| 43 | + | // createTagRequest is the payload for tag_create messages. | |
| 44 | + | type createTagRequest struct { | |
| 45 | + | RequestID string `json:"requestId"` | |
| 46 | + | Name string `json:"name"` | |
| 47 | + | Color *string `json:"color"` | |
| 48 | + | } | |
| 49 | + | ||
| 50 | + | // HandleCreate handles tag_create messages. | |
| 51 | + | func (h *TagHandlers) HandleCreate() webkit.MessageHandler { | |
| 52 | + | return webkit.MessageHandlerFunc(func(ctx context.Context, webviewID webkit.WebViewID, payload json.RawMessage) (any, error) { | |
| 53 | + | log := logging.FromContext(ctx) | |
| 54 | + | ||
| 55 | + | var req createTagRequest | |
| 56 | + | if err := json.Unmarshal(payload, &req); err != nil { | |
| 57 | + | return NewErrorResponse("", err), nil | |
| 58 | + | } | |
| 59 | + | ||
| 60 | + | log.Debug(). | |
| 61 | + | Str("request_id", req.RequestID). | |
| 62 | + | Str("name", req.Name). | |
| 63 | + | Msg("handling tag_create") | |
| 64 | + | ||
| 65 | + | color := "" | |
| 66 | + | if req.Color != nil { | |
| 67 | + | color = *req.Color | |
| 68 | + | } | |
| 69 | + | ||
| 70 | + | tag, err := h.favoritesUC.AddTag(ctx, req.Name, color) | |
| 71 | + | if err != nil { | |
| 72 | + | return NewErrorResponse(req.RequestID, err), nil | |
| 73 | + | } | |
| 74 | + | ||
| 75 | + | return NewSuccessResponse(req.RequestID, tag), nil | |
| 76 | + | }) | |
| 77 | + | } | |
| 78 | + | ||
| 79 | + | // deleteTagRequest is the payload for tag_delete messages. | |
| 80 | + | type deleteTagRequest struct { | |
| 81 | + | RequestID string `json:"requestId"` | |
| 82 | + | ID int64 `json:"id"` | |
| 83 | + | } | |
| 84 | + | ||
| 85 | + | // HandleDelete handles tag_delete messages. | |
| 86 | + | func (h *TagHandlers) HandleDelete() webkit.MessageHandler { | |
| 87 | + | return webkit.MessageHandlerFunc(func(ctx context.Context, webviewID webkit.WebViewID, payload json.RawMessage) (any, error) { | |
| 88 | + | log := logging.FromContext(ctx) | |
| 89 | + | ||
| 90 | + | var req deleteTagRequest | |
| 91 | + | if err := json.Unmarshal(payload, &req); err != nil { | |
| 92 | + | return NewErrorResponse("", err), nil | |
| 93 | + | } | |
| 94 | + | ||
| 95 | + | log.Debug(). | |
| 96 | + | Str("request_id", req.RequestID). | |
| 97 | + | Int64("id", req.ID). | |
| 98 | + | Msg("handling tag_delete") | |
| 99 | + | ||
| 100 | + | if err := h.favoritesUC.DeleteTag(ctx, entity.TagID(req.ID)); err != nil { | |
| 101 | + | return NewErrorResponse(req.RequestID, err), nil | |
| 102 | + | } | |
| 103 | + | ||
| 104 | + | return NewSuccessResponse(req.RequestID, nil), nil | |
| 105 | + | }) | |
| 106 | + | } | |
| 107 | + | ||
| 108 | + | // updateTagRequest is the payload for tag_update messages. | |
| 109 | + | type updateTagRequest struct { | |
| 110 | + | RequestID string `json:"requestId"` | |
| 111 | + | ID int64 `json:"id"` | |
| 112 | + | Name *string `json:"name"` | |
| 113 | + | Color *string `json:"color"` | |
| 114 | + | } | |
| 115 | + | ||
| 116 | + | // HandleUpdate handles tag_update messages. | |
| 117 | + | // NOTE: This requires UpdateTag() method to be added to ManageFavoritesUseCase. | |
| 118 | + | func (h *TagHandlers) HandleUpdate() webkit.MessageHandler { | |
| 119 | + | return webkit.MessageHandlerFunc(func(ctx context.Context, webviewID webkit.WebViewID, payload json.RawMessage) (any, error) { | |
| 120 | + | log := logging.FromContext(ctx) | |
| 121 | + | ||
| 122 | + | var req updateTagRequest | |
| 123 | + | if err := json.Unmarshal(payload, &req); err != nil { | |
| 124 | + | return NewErrorResponse("", err), nil | |
| 125 | + | } | |
| 126 | + | ||
| 127 | + | log.Debug(). | |
| 128 | + | Str("request_id", req.RequestID). | |
| 129 | + | Int64("id", req.ID). | |
| 130 | + | Msg("handling tag_update") | |
| 131 | + | ||
| 132 | + | name := "" | |
| 133 | + | if req.Name != nil { | |
| 134 | + | name = *req.Name | |
| 135 | + | } | |
| 136 | + | color := "" | |
| 137 | + | if req.Color != nil { | |
| 138 | + | color = *req.Color | |
| 139 | + | } | |
| 140 | + | ||
| 141 | + | if err := h.favoritesUC.UpdateTag(ctx, entity.TagID(req.ID), name, color); err != nil { | |
| 142 | + | return NewErrorResponse(req.RequestID, err), nil | |
| 143 | + | } | |
| 144 | + | ||
| 145 | + | return NewSuccessResponse(req.RequestID, nil), nil | |
| 146 | + | }) | |
| 147 | + | } | |
| 148 | + | ||
| 149 | + | // tagAssignRequest is the payload for tag_assign messages. | |
| 150 | + | type tagAssignRequest struct { | |
| 151 | + | RequestID string `json:"requestId"` | |
| 152 | + | FavoriteID int64 `json:"favorite_id"` | |
| 153 | + | TagID int64 `json:"tag_id"` | |
| 154 | + | } | |
| 155 | + | ||
| 156 | + | // HandleAssign handles tag_assign messages. | |
| 157 | + | func (h *TagHandlers) HandleAssign() webkit.MessageHandler { | |
| 158 | + | return webkit.MessageHandlerFunc(func(ctx context.Context, webviewID webkit.WebViewID, payload json.RawMessage) (any, error) { | |
| 159 | + | log := logging.FromContext(ctx) | |
| 160 | + | ||
| 161 | + | var req tagAssignRequest | |
| 162 | + | if err := json.Unmarshal(payload, &req); err != nil { | |
| 163 | + | return NewErrorResponse("", err), nil | |
| 164 | + | } | |
| 165 | + | ||
| 166 | + | log.Debug(). | |
| 167 | + | Str("request_id", req.RequestID). | |
| 168 | + | Int64("favorite_id", req.FavoriteID). | |
| 169 | + | Int64("tag_id", req.TagID). | |
| 170 | + | Msg("handling tag_assign") | |
| 171 | + | ||
| 172 | + | if err := h.favoritesUC.TagFavorite(ctx, entity.FavoriteID(req.FavoriteID), entity.TagID(req.TagID)); err != nil { | |
| 173 | + | return NewErrorResponse(req.RequestID, err), nil | |
| 174 | + | } | |
| 175 | + | ||
| 176 | + | return NewSuccessResponse(req.RequestID, nil), nil | |
| 177 | + | }) | |
| 178 | + | } | |
| 179 | + | ||
| 180 | + | // HandleRemove handles tag_remove messages. | |
| 181 | + | func (h *TagHandlers) HandleRemove() webkit.MessageHandler { | |
| 182 | + | return webkit.MessageHandlerFunc(func(ctx context.Context, webviewID webkit.WebViewID, payload json.RawMessage) (any, error) { | |
| 183 | + | log := logging.FromContext(ctx) | |
| 184 | + | ||
| 185 | + | var req tagAssignRequest // same structure as assign | |
| 186 | + | if err := json.Unmarshal(payload, &req); err != nil { | |
| 187 | + | return NewErrorResponse("", err), nil | |
| 188 | + | } | |
| 189 | + | ||
| 190 | + | log.Debug(). | |
| 191 | + | Str("request_id", req.RequestID). | |
| 192 | + | Int64("favorite_id", req.FavoriteID). | |
| 193 | + | Int64("tag_id", req.TagID). | |
| 194 | + | Msg("handling tag_remove") | |
| 195 | + | ||
| 196 | + | if err := h.favoritesUC.UntagFavorite(ctx, entity.FavoriteID(req.FavoriteID), entity.TagID(req.TagID)); err != nil { | |
| 197 | + | return NewErrorResponse(req.RequestID, err), nil | |
| 198 | + | } | |
| 199 | + | ||
| 200 | + | return NewSuccessResponse(req.RequestID, nil), nil | |
| 201 | + | }) | |
| 202 | + | } | |
| @@ -0,0 +1,202 @@ | |||||
| 1 | + | package homepage | |||
| 2 | + | ||||
| 3 | + | import ( | |||
| 4 | + | "context" | |||
| 5 | + | "encoding/json" | |||
| 6 | + | ||||
| 7 | + | "github.com/bnema/dumber/internal/application/usecase" | |||
| 8 | + | "github.com/bnema/dumber/internal/domain/entity" | |||
| 9 | + | "github.com/bnema/dumber/internal/infrastructure/webkit" | |||
| 10 | + | "github.com/bnema/dumber/internal/logging" | |||
| 11 | + | ) | |||
| 12 | + | ||||
| 13 | + | // TagHandlers handles tag-related messages from the homepage. | |||
| 14 | + | type TagHandlers struct { | |||
| 15 | + | favoritesUC *usecase.ManageFavoritesUseCase | |||
| 16 | + | } | |||
| 17 | + | ||||
| 18 | + | // NewTagHandlers creates a new TagHandlers instance. | |||
| 19 | + | func NewTagHandlers(favoritesUC *usecase.ManageFavoritesUseCase) *TagHandlers { | |||
| 20 | + | return &TagHandlers{favoritesUC: favoritesUC} | |||
| 21 | + | } | |||
| 22 | + | ||||
| 23 | + | // HandleList handles tag_list messages. | |||
| 24 | + | func (h *TagHandlers) HandleList() webkit.MessageHandler { | |||
| 25 | + | return webkit.MessageHandlerFunc(func(ctx context.Context, webviewID webkit.WebViewID, payload json.RawMessage) (any, error) { | |||
| 26 | + | log := logging.FromContext(ctx) | |||
| 27 | + | ||||
| 28 | + | requestID := ParseRequestID(payload) | |||
| 29 | + | ||||
| 30 | + | log.Debug(). | |||
| 31 | + | Str("request_id", requestID). | |||
| 32 | + | Msg("handling tag_list") | |||
| 33 | + | ||||
| 34 | + | tags, err := h.favoritesUC.GetAllTags(ctx) | |||
| 35 | + | if err != nil { | |||
| 36 | + | return NewErrorResponse(requestID, err), nil | |||
| 37 | + | } | |||
| 38 | + | ||||
| 39 | + | return NewSuccessResponse(requestID, tags), nil | |||
| 40 | + | }) | |||
| 41 | + | } | |||
| 42 | + | ||||
| 43 | + | // createTagRequest is the payload for tag_create messages. | |||
| 44 | + | type createTagRequest struct { | |||
| 45 | + | RequestID string `json:"requestId"` | |||
| 46 | + | Name string `json:"name"` | |||
| 47 | + | Color *string `json:"color"` | |||
| 48 | + | } | |||
| 49 | + | ||||
| 50 | + | // HandleCreate handles tag_create messages. | |||
| 51 | + | func (h *TagHandlers) HandleCreate() webkit.MessageHandler { | |||
| 52 | + | return webkit.MessageHandlerFunc(func(ctx context.Context, webviewID webkit.WebViewID, payload json.RawMessage) (any, error) { | |||
| 53 | + | log := logging.FromContext(ctx) | |||
| 54 | + | ||||
| 55 | + | var req createTagRequest | |||
| 56 | + | if err := json.Unmarshal(payload, &req); err != nil { | |||
| 57 | + | return NewErrorResponse("", err), nil | |||
| 58 | + | } | |||
| 59 | + | ||||
| 60 | + | log.Debug(). | |||
| 61 | + | Str("request_id", req.RequestID). | |||
| 62 | + | Str("name", req.Name). | |||
| 63 | + | Msg("handling tag_create") | |||
| 64 | + | ||||
| 65 | + | color := "" | |||
| 66 | + | if req.Color != nil { | |||
| 67 | + | color = *req.Color | |||
| 68 | + | } | |||
| 69 | + | ||||
| 70 | + | tag, err := h.favoritesUC.AddTag(ctx, req.Name, color) | |||
| 71 | + | if err != nil { | |||
| 72 | + | return NewErrorResponse(req.RequestID, err), nil | |||
| 73 | + | } | |||
| 74 | + | ||||
| 75 | + | return NewSuccessResponse(req.RequestID, tag), nil | |||
| 76 | + | }) | |||
| 77 | + | } | |||
| 78 | + | ||||
| 79 | + | // deleteTagRequest is the payload for tag_delete messages. | |||
| 80 | + | type deleteTagRequest struct { | |||
| 81 | + | RequestID string `json:"requestId"` | |||
| 82 | + | ID int64 `json:"id"` | |||
| 83 | + | } | |||
| 84 | + | ||||
| 85 | + | // HandleDelete handles tag_delete messages. | |||
| 86 | + | func (h *TagHandlers) HandleDelete() webkit.MessageHandler { | |||
| 87 | + | return webkit.MessageHandlerFunc(func(ctx context.Context, webviewID webkit.WebViewID, payload json.RawMessage) (any, error) { | |||
| 88 | + | log := logging.FromContext(ctx) | |||
| 89 | + | ||||
| 90 | + | var req deleteTagRequest | |||
| 91 | + | if err := json.Unmarshal(payload, &req); err != nil { | |||
| 92 | + | return NewErrorResponse("", err), nil | |||
| 93 | + | } | |||
| 94 | + | ||||
| 95 | + | log.Debug(). | |||
| 96 | + | Str("request_id", req.RequestID). | |||
| 97 | + | Int64("id", req.ID). | |||
| 98 | + | Msg("handling tag_delete") | |||
| 99 | + | ||||
| 100 | + | if err := h.favoritesUC.DeleteTag(ctx, entity.TagID(req.ID)); err != nil { | |||
| 101 | + | return NewErrorResponse(req.RequestID, err), nil | |||
| 102 | + | } | |||
| 103 | + | ||||
| 104 | + | return NewSuccessResponse(req.RequestID, nil), nil | |||
| 105 | + | }) | |||
| 106 | + | } | |||
| 107 | + | ||||
| 108 | + | // updateTagRequest is the payload for tag_update messages. | |||
| 109 | + | type updateTagRequest struct { | |||
| 110 | + | RequestID string `json:"requestId"` | |||
| 111 | + | ID int64 `json:"id"` | |||
| 112 | + | Name *string `json:"name"` | |||
| 113 | + | Color *string `json:"color"` | |||
| 114 | + | } | |||
| 115 | + | ||||
| 116 | + | // HandleUpdate handles tag_update messages. | |||
| 117 | + | // NOTE: This requires UpdateTag() method to be added to ManageFavoritesUseCase. | |||
| 118 | + | func (h *TagHandlers) HandleUpdate() webkit.MessageHandler { | |||
| 119 | + | return webkit.MessageHandlerFunc(func(ctx context.Context, webviewID webkit.WebViewID, payload json.RawMessage) (any, error) { | |||
| 120 | + | log := logging.FromContext(ctx) | |||
| 121 | + | ||||
| 122 | + | var req updateTagRequest | |||
| 123 | + | if err := json.Unmarshal(payload, &req); err != nil { | |||
| 124 | + | return NewErrorResponse("", err), nil | |||
| 125 | + | } | |||
| 126 | + | ||||
| 127 | + | log.Debug(). | |||
| 128 | + | Str("request_id", req.RequestID). | |||
| 129 | + | Int64("id", req.ID). | |||
| 130 | + | Msg("handling tag_update") | |||
| 131 | + | ||||
| 132 | + | name := "" | |||
| 133 | + | if req.Name != nil { | |||
| 134 | + | name = *req.Name | |||
| 135 | + | } | |||
| 136 | + | color := "" | |||
| 137 | + | if req.Color != nil { | |||
| 138 | + | color = *req.Color | |||
| 139 | + | } | |||
| 140 | + | ||||
| 141 | + | if err := h.favoritesUC.UpdateTag(ctx, entity.TagID(req.ID), name, color); err != nil { | |||
| 142 | + | return NewErrorResponse(req.RequestID, err), nil | |||
| 143 | + | } | |||
| 144 | + | ||||
| 145 | + | return NewSuccessResponse(req.RequestID, nil), nil | |||
| 146 | + | }) | |||
| 147 | + | } | |||
| 148 | + | ||||
| 149 | + | // tagAssignRequest is the payload for tag_assign messages. | |||
| 150 | + | type tagAssignRequest struct { | |||
| 151 | + | RequestID string `json:"requestId"` | |||
| 152 | + | FavoriteID int64 `json:"favorite_id"` | |||
| 153 | + | TagID int64 `json:"tag_id"` | |||
| 154 | + | } | |||
| 155 | + | ||||
| 156 | + | // HandleAssign handles tag_assign messages. | |||
| 157 | + | func (h *TagHandlers) HandleAssign() webkit.MessageHandler { | |||
| 158 | + | return webkit.MessageHandlerFunc(func(ctx context.Context, webviewID webkit.WebViewID, payload json.RawMessage) (any, error) { | |||
| 159 | + | log := logging.FromContext(ctx) | |||
| 160 | + | ||||
| 161 | + | var req tagAssignRequest | |||
| 162 | + | if err := json.Unmarshal(payload, &req); err != nil { | |||
| 163 | + | return NewErrorResponse("", err), nil | |||
| 164 | + | } | |||
| 165 | + | ||||
| 166 | + | log.Debug(). | |||
| 167 | + | Str("request_id", req.RequestID). | |||
| 168 | + | Int64("favorite_id", req.FavoriteID). | |||
| 169 | + | Int64("tag_id", req.TagID). | |||
| 170 | + | Msg("handling tag_assign") | |||
| 171 | + | ||||
| 172 | + | if err := h.favoritesUC.TagFavorite(ctx, entity.FavoriteID(req.FavoriteID), entity.TagID(req.TagID)); err != nil { | |||
| 173 | + | return NewErrorResponse(req.RequestID, err), nil | |||
| 174 | + | } | |||
| 175 | + | ||||
| 176 | + | return NewSuccessResponse(req.RequestID, nil), nil | |||
| 177 | + | }) | |||
| 178 | + | } | |||
| 179 | + | ||||
| 180 | + | // HandleRemove handles tag_remove messages. | |||
| 181 | + | func (h *TagHandlers) HandleRemove() webkit.MessageHandler { | |||
| 182 | + | return webkit.MessageHandlerFunc(func(ctx context.Context, webviewID webkit.WebViewID, payload json.RawMessage) (any, error) { | |||
| 183 | + | log := logging.FromContext(ctx) | |||
| 184 | + | ||||
| 185 | + | var req tagAssignRequest // same structure as assign | |||
| 186 | + | if err := json.Unmarshal(payload, &req); err != nil { | |||
| 187 | + | return NewErrorResponse("", err), nil | |||
| 188 | + | } | |||
| 189 | + | ||||
| 190 | + | log.Debug(). | |||
| 191 | + | Str("request_id", req.RequestID). | |||
| 192 | + | Int64("favorite_id", req.FavoriteID). | |||
| 193 | + | Int64("tag_id", req.TagID). | |||
| 194 | + | Msg("handling tag_remove") | |||
| 195 | + | ||||
| 196 | + | if err := h.favoritesUC.UntagFavorite(ctx, entity.FavoriteID(req.FavoriteID), entity.TagID(req.TagID)); err != nil { | |||
| 197 | + | return NewErrorResponse(req.RequestID, err), nil | |||
| 198 | + | } | |||
| 199 | + | ||||
| 200 | + | return NewSuccessResponse(req.RequestID, nil), nil | |||
| 201 | + | }) | |||
| 202 | + | } | |||
A internal/infrastructure/webkit/handlers/register.go +33 -0
| @@ -0,0 +1,33 @@ | |||
| 1 | + | package handlers | |
| 2 | + | ||
| 3 | + | import ( | |
| 4 | + | "context" | |
| 5 | + | ||
| 6 | + | "github.com/bnema/dumber/internal/application/usecase" | |
| 7 | + | "github.com/bnema/dumber/internal/infrastructure/webkit" | |
| 8 | + | "github.com/bnema/dumber/internal/infrastructure/webkit/handlers/homepage" | |
| 9 | + | ) | |
| 10 | + | ||
| 11 | + | // Config holds all dependencies for message handlers. | |
| 12 | + | type Config struct { | |
| 13 | + | HistoryUC *usecase.SearchHistoryUseCase | |
| 14 | + | FavoritesUC *usecase.ManageFavoritesUseCase | |
| 15 | + | } | |
| 16 | + | ||
| 17 | + | // RegisterAll registers all message handlers with the router. | |
| 18 | + | func RegisterAll(ctx context.Context, router *webkit.MessageRouter, cfg Config) error { | |
| 19 | + | // Homepage handlers (history, favorites, folders, tags) | |
| 20 | + | if err := homepage.RegisterHandlers(ctx, router, homepage.Config{ | |
| 21 | + | HistoryUC: cfg.HistoryUC, | |
| 22 | + | FavoritesUC: cfg.FavoritesUC, | |
| 23 | + | }); err != nil { | |
| 24 | + | return err | |
| 25 | + | } | |
| 26 | + | ||
| 27 | + | // Future handler groups go here: | |
| 28 | + | // - settings handlers | |
| 29 | + | // - downloads handlers | |
| 30 | + | // - etc. | |
| 31 | + | ||
| 32 | + | return nil | |
| 33 | + | } | |
| @@ -0,0 +1,33 @@ | |||||
| 1 | + | package handlers | |||
| 2 | + | ||||
| 3 | + | import ( | |||
| 4 | + | "context" | |||
| 5 | + | ||||
| 6 | + | "github.com/bnema/dumber/internal/application/usecase" | |||
| 7 | + | "github.com/bnema/dumber/internal/infrastructure/webkit" | |||
| 8 | + | "github.com/bnema/dumber/internal/infrastructure/webkit/handlers/homepage" | |||
| 9 | + | ) | |||
| 10 | + | ||||
| 11 | + | // Config holds all dependencies for message handlers. | |||
| 12 | + | type Config struct { | |||
| 13 | + | HistoryUC *usecase.SearchHistoryUseCase | |||
| 14 | + | FavoritesUC *usecase.ManageFavoritesUseCase | |||
| 15 | + | } | |||
| 16 | + | ||||
| 17 | + | // RegisterAll registers all message handlers with the router. | |||
| 18 | + | func RegisterAll(ctx context.Context, router *webkit.MessageRouter, cfg Config) error { | |||
| 19 | + | // Homepage handlers (history, favorites, folders, tags) | |||
| 20 | + | if err := homepage.RegisterHandlers(ctx, router, homepage.Config{ | |||
| 21 | + | HistoryUC: cfg.HistoryUC, | |||
| 22 | + | FavoritesUC: cfg.FavoritesUC, | |||
| 23 | + | }); err != nil { | |||
| 24 | + | return err | |||
| 25 | + | } | |||
| 26 | + | ||||
| 27 | + | // Future handler groups go here: | |||
| 28 | + | // - settings handlers | |||
| 29 | + | // - downloads handlers | |||
| 30 | + | // - etc. | |||
| 31 | + | ||||
| 32 | + | return nil | |||
| 33 | + | } | |||