detached 6ad5700

refactor(api): remove http routes in favor of webkit message bridge

commit
6ad5700593c6f2ceb7fa32c0fa4d6ea0d1dbc08f
author
bamen · · 2025-09-16 08:13 UTC
parent
382faa5
signature
unsigned
2 files · +0 -205
D internal/app/api/routes.go +0 -162
@@ -1,162 +0,0 @@
1-package api
2-
3-import (
4- "context"
5- "encoding/json"
6- "log"
7- neturl "net/url"
8- "strconv"
9-
10- "github.com/bnema/dumber/internal/app/constants"
11- "github.com/bnema/dumber/internal/config"
12- "github.com/bnema/dumber/services"
13-)
14-
15-// Handler provides API endpoint handling functionality
16-type Handler struct {
17- browserService *services.BrowserService
18-}
19-
20-// NewHandler creates a new API handler
21-func NewHandler(browserService *services.BrowserService) *Handler {
22- return &Handler{
23- browserService: browserService,
24- }
25-}
26-
27-// HandleRenderingStatus handles GET /rendering/status endpoint
28-func (h *Handler) HandleRenderingStatus(cfg *config.Config) (string, []byte, bool) {
29- log.Printf("[api] GET /rendering/status")
30- // Report configured rendering mode; runtime GPU state depends on WebKit internals
31- resp := struct {
32- Mode string `json:"mode"`
33- }{Mode: string(cfg.RenderingMode)}
34- b, _ := json.Marshal(resp)
35- return constants.ContentTypeJSON, b, true
36-}
37-
38-// HandleConfig handles GET /config endpoint
39-func (h *Handler) HandleConfig(cfg *config.Config) (string, []byte, bool) {
40- log.Printf("[api] GET /config")
41- // Build config info
42- cfgPath, _ := config.GetConfigFile()
43- info := struct {
44- ConfigPath string `json:"config_path"`
45- DatabasePath string `json:"database_path"`
46- SearchShortcuts map[string]config.SearchShortcut `json:"search_shortcuts"`
47- Appearance config.AppearanceConfig `json:"appearance"`
48- }{
49- ConfigPath: cfgPath,
50- DatabasePath: cfg.Database.Path,
51- SearchShortcuts: cfg.SearchShortcuts,
52- Appearance: cfg.Appearance,
53- }
54- b, _ := json.Marshal(info)
55- return constants.ContentTypeJSON, b, true
56-}
57-
58-// HandleHistoryRecent handles GET /history/recent endpoint
59-func (h *Handler) HandleHistoryRecent(u *neturl.URL) (string, []byte, bool) {
60- log.Printf("[api] GET /history/recent%s", u.RawQuery)
61- // Parse limit and offset
62- q := u.Query()
63- limit := 20
64- if l := q.Get("limit"); l != "" {
65- if n, err := strconv.Atoi(l); err == nil && n > 0 {
66- limit = n
67- }
68- }
69- offset := 0
70- if o := q.Get("offset"); o != "" {
71- if n, err := strconv.Atoi(o); err == nil && n >= 0 {
72- offset = n
73- }
74- }
75-
76- ctx := context.Background()
77- var entries []interface{}
78-
79- // Use paginated version if offset is provided
80- if offset > 0 || q.Has("offset") {
81- histEntries, err := h.browserService.GetRecentHistoryWithOffset(ctx, limit, offset)
82- if err != nil {
83- return constants.ContentTypeJSON, []byte("[]"), true
84- }
85- // Convert to interface slice for JSON marshaling
86- entries = make([]interface{}, len(histEntries))
87- for i, entry := range histEntries {
88- entries[i] = entry
89- }
90- } else {
91- // Use original method for backward compatibility
92- histEntries, err := h.browserService.GetRecentHistory(ctx, limit)
93- if err != nil {
94- return constants.ContentTypeJSON, []byte("[]"), true
95- }
96- // Convert to interface slice for JSON marshaling
97- entries = make([]interface{}, len(histEntries))
98- for i, entry := range histEntries {
99- entries[i] = entry
100- }
101- }
102-
103- b, _ := json.Marshal(entries)
104- return constants.ContentTypeJSON, b, true
105-}
106-
107-// HandleHistorySearch handles GET /history/search endpoint
108-func (h *Handler) HandleHistorySearch(u *neturl.URL) (string, []byte, bool) {
109- log.Printf("[api] GET /history/search%s", u.RawQuery)
110- q := u.Query()
111- query := q.Get("q")
112- limit := 50
113- if l := q.Get("limit"); l != "" {
114- if n, err := strconv.Atoi(l); err == nil && n > 0 {
115- limit = n
116- }
117- }
118- ctx := context.Background()
119- entries, err := h.browserService.SearchHistory(ctx, query, limit)
120- if err != nil {
121- return constants.ContentTypeJSON, []byte("[]"), true
122- }
123- b, _ := json.Marshal(entries)
124- return constants.ContentTypeJSON, b, true
125-}
126-
127-// HandleHistoryStats handles GET /history/stats endpoint
128-func (h *Handler) HandleHistoryStats() (string, []byte, bool) {
129- log.Printf("[api] GET /history/stats")
130- ctx := context.Background()
131- stats, err := h.browserService.GetHistoryStats(ctx)
132- if err != nil {
133- return constants.ContentTypeJSON, []byte("{}"), true
134- }
135- b, _ := json.Marshal(stats)
136- return constants.ContentTypeJSON, b, true
137-}
138-
139-// HandleHistoryDelete handles GET /history/delete endpoint
140-func (h *Handler) HandleHistoryDelete(u *neturl.URL) (string, []byte, bool) {
141- log.Printf("[api] GET /history/delete%s", u.RawQuery)
142- q := u.Query()
143- idStr := q.Get("id")
144- if idStr == "" {
145- return constants.ContentTypeJSON, []byte(`{"error":"id parameter required"}`), true
146- }
147- id, err := strconv.ParseInt(idStr, 10, 64)
148- if err != nil {
149- return constants.ContentTypeJSON, []byte(`{"error":"invalid id parameter"}`), true
150- }
151- ctx := context.Background()
152- err = h.browserService.DeleteHistoryEntry(ctx, id)
153- if err != nil {
154- return constants.ContentTypeJSON, []byte(`{"error":"failed to delete entry"}`), true
155- }
156- return constants.ContentTypeJSON, []byte(`{"success":true}`), true
157-}
158-
159-// HandleDefault returns empty JSON for unknown endpoints
160-func (h *Handler) HandleDefault() (string, []byte, bool) {
161- return constants.ContentTypeJSON, []byte("{}"), true
162-}
@@ -1,162 +0,0 @@
1-package api
2-
3-import (
4- "context"
5- "encoding/json"
6- "log"
7- neturl "net/url"
8- "strconv"
9-
10- "github.com/bnema/dumber/internal/app/constants"
11- "github.com/bnema/dumber/internal/config"
12- "github.com/bnema/dumber/services"
13-)
14-
15-// Handler provides API endpoint handling functionality
16-type Handler struct {
17- browserService *services.BrowserService
18-}
19-
20-// NewHandler creates a new API handler
21-func NewHandler(browserService *services.BrowserService) *Handler {
22- return &Handler{
23- browserService: browserService,
24- }
25-}
26-
27-// HandleRenderingStatus handles GET /rendering/status endpoint
28-func (h *Handler) HandleRenderingStatus(cfg *config.Config) (string, []byte, bool) {
29- log.Printf("[api] GET /rendering/status")
30- // Report configured rendering mode; runtime GPU state depends on WebKit internals
31- resp := struct {
32- Mode string `json:"mode"`
33- }{Mode: string(cfg.RenderingMode)}
34- b, _ := json.Marshal(resp)
35- return constants.ContentTypeJSON, b, true
36-}
37-
38-// HandleConfig handles GET /config endpoint
39-func (h *Handler) HandleConfig(cfg *config.Config) (string, []byte, bool) {
40- log.Printf("[api] GET /config")
41- // Build config info
42- cfgPath, _ := config.GetConfigFile()
43- info := struct {
44- ConfigPath string `json:"config_path"`
45- DatabasePath string `json:"database_path"`
46- SearchShortcuts map[string]config.SearchShortcut `json:"search_shortcuts"`
47- Appearance config.AppearanceConfig `json:"appearance"`
48- }{
49- ConfigPath: cfgPath,
50- DatabasePath: cfg.Database.Path,
51- SearchShortcuts: cfg.SearchShortcuts,
52- Appearance: cfg.Appearance,
53- }
54- b, _ := json.Marshal(info)
55- return constants.ContentTypeJSON, b, true
56-}
57-
58-// HandleHistoryRecent handles GET /history/recent endpoint
59-func (h *Handler) HandleHistoryRecent(u *neturl.URL) (string, []byte, bool) {
60- log.Printf("[api] GET /history/recent%s", u.RawQuery)
61- // Parse limit and offset
62- q := u.Query()
63- limit := 20
64- if l := q.Get("limit"); l != "" {
65- if n, err := strconv.Atoi(l); err == nil && n > 0 {
66- limit = n
67- }
68- }
69- offset := 0
70- if o := q.Get("offset"); o != "" {
71- if n, err := strconv.Atoi(o); err == nil && n >= 0 {
72- offset = n
73- }
74- }
75-
76- ctx := context.Background()
77- var entries []interface{}
78-
79- // Use paginated version if offset is provided
80- if offset > 0 || q.Has("offset") {
81- histEntries, err := h.browserService.GetRecentHistoryWithOffset(ctx, limit, offset)
82- if err != nil {
83- return constants.ContentTypeJSON, []byte("[]"), true
84- }
85- // Convert to interface slice for JSON marshaling
86- entries = make([]interface{}, len(histEntries))
87- for i, entry := range histEntries {
88- entries[i] = entry
89- }
90- } else {
91- // Use original method for backward compatibility
92- histEntries, err := h.browserService.GetRecentHistory(ctx, limit)
93- if err != nil {
94- return constants.ContentTypeJSON, []byte("[]"), true
95- }
96- // Convert to interface slice for JSON marshaling
97- entries = make([]interface{}, len(histEntries))
98- for i, entry := range histEntries {
99- entries[i] = entry
100- }
101- }
102-
103- b, _ := json.Marshal(entries)
104- return constants.ContentTypeJSON, b, true
105-}
106-
107-// HandleHistorySearch handles GET /history/search endpoint
108-func (h *Handler) HandleHistorySearch(u *neturl.URL) (string, []byte, bool) {
109- log.Printf("[api] GET /history/search%s", u.RawQuery)
110- q := u.Query()
111- query := q.Get("q")
112- limit := 50
113- if l := q.Get("limit"); l != "" {
114- if n, err := strconv.Atoi(l); err == nil && n > 0 {
115- limit = n
116- }
117- }
118- ctx := context.Background()
119- entries, err := h.browserService.SearchHistory(ctx, query, limit)
120- if err != nil {
121- return constants.ContentTypeJSON, []byte("[]"), true
122- }
123- b, _ := json.Marshal(entries)
124- return constants.ContentTypeJSON, b, true
125-}
126-
127-// HandleHistoryStats handles GET /history/stats endpoint
128-func (h *Handler) HandleHistoryStats() (string, []byte, bool) {
129- log.Printf("[api] GET /history/stats")
130- ctx := context.Background()
131- stats, err := h.browserService.GetHistoryStats(ctx)
132- if err != nil {
133- return constants.ContentTypeJSON, []byte("{}"), true
134- }
135- b, _ := json.Marshal(stats)
136- return constants.ContentTypeJSON, b, true
137-}
138-
139-// HandleHistoryDelete handles GET /history/delete endpoint
140-func (h *Handler) HandleHistoryDelete(u *neturl.URL) (string, []byte, bool) {
141- log.Printf("[api] GET /history/delete%s", u.RawQuery)
142- q := u.Query()
143- idStr := q.Get("id")
144- if idStr == "" {
145- return constants.ContentTypeJSON, []byte(`{"error":"id parameter required"}`), true
146- }
147- id, err := strconv.ParseInt(idStr, 10, 64)
148- if err != nil {
149- return constants.ContentTypeJSON, []byte(`{"error":"invalid id parameter"}`), true
150- }
151- ctx := context.Background()
152- err = h.browserService.DeleteHistoryEntry(ctx, id)
153- if err != nil {
154- return constants.ContentTypeJSON, []byte(`{"error":"failed to delete entry"}`), true
155- }
156- return constants.ContentTypeJSON, []byte(`{"success":true}`), true
157-}
158-
159-// HandleDefault returns empty JSON for unknown endpoints
160-func (h *Handler) HandleDefault() (string, []byte, bool) {
161- return constants.ContentTypeJSON, []byte("{}"), true
162-}
M internal/app/api/scheme.go +0 -43
@@ -16,7 +16,6 @@ import (
1616 // SchemeHandler handles custom dumb:// scheme resolution
1717 type SchemeHandler struct {
1818 assets embed.FS
19- apiHandler *Handler
2019 parserService *services.ParserService
2120 browserService *services.BrowserService
2221 }
@@ -29,7 +28,6 @@ func NewSchemeHandler(
2928 ) *SchemeHandler {
3029 return &SchemeHandler{
3130 assets: assets,
32- apiHandler: NewHandler(browserService),
3331 parserService: parserService,
3432 browserService: browserService,
3533 }
@@ -47,51 +45,10 @@ func (s *SchemeHandler) Handle(uri string, cfg *config.Config) (string, []byte,
4745 return "", nil, false
4846 }
4947
50- // API routes
51- if u.Host == "api" || strings.HasPrefix(u.Opaque, "api") || strings.HasPrefix(u.Path, "/api") {
52- return s.handleAPIRoute(u, cfg)
53- }
54-
5548 // Static assets
5649 return s.handleAsset(u)
5750 }
5851
59-// handleAPIRoute processes API requests
60-func (s *SchemeHandler) handleAPIRoute(u *neturl.URL, cfg *config.Config) (string, []byte, bool) {
61- // Normalize path for opaque or hierarchical forms
62- path := u.Path
63- if path == "" && u.Opaque != "" {
64- // e.g., dumb:api/config or dumb:api/history/recent
65- parts := strings.SplitN(u.Opaque, ":", constants.ColonSplitParts)
66- if len(parts) == constants.ColonSplitParts {
67- path = "/" + parts[1]
68- }
69- }
70- if strings.HasPrefix(path, "/api/") {
71- path = strings.TrimPrefix(path, "/api")
72- }
73- if path == "/api" {
74- path = "/"
75- }
76-
77- switch {
78- case strings.HasPrefix(path, "/rendering/status"):
79- return s.apiHandler.HandleRenderingStatus(cfg)
80- case strings.HasPrefix(path, "/config"):
81- return s.apiHandler.HandleConfig(cfg)
82- case strings.HasPrefix(path, "/history/recent"):
83- return s.apiHandler.HandleHistoryRecent(u)
84- case strings.HasPrefix(path, "/history/search"):
85- return s.apiHandler.HandleHistorySearch(u)
86- case strings.HasPrefix(path, "/history/stats"):
87- return s.apiHandler.HandleHistoryStats()
88- case strings.HasPrefix(path, "/history/delete"):
89- return s.apiHandler.HandleHistoryDelete(u)
90- default:
91- return s.apiHandler.HandleDefault()
92- }
93-}
94-
9552 // handleAsset serves static assets from embedded filesystem
9653 func (s *SchemeHandler) handleAsset(u *neturl.URL) (string, []byte, bool) {
9754 // Resolve target path inside embed FS
@@ -16,7 +16,6 @@ import (
16 // SchemeHandler handles custom dumb:// scheme resolution16 // SchemeHandler handles custom dumb:// scheme resolution
17 type SchemeHandler struct {17 type SchemeHandler struct {
18 assets embed.FS18 assets embed.FS
19- apiHandler *Handler
20 parserService *services.ParserService19 parserService *services.ParserService
21 browserService *services.BrowserService20 browserService *services.BrowserService
22 }21 }
@@ -29,7 +28,6 @@ func NewSchemeHandler(
29 ) *SchemeHandler {28 ) *SchemeHandler {
30 return &SchemeHandler{29 return &SchemeHandler{
31 assets: assets,30 assets: assets,
32- apiHandler: NewHandler(browserService),
33 parserService: parserService,31 parserService: parserService,
34 browserService: browserService,32 browserService: browserService,
35 }33 }
@@ -47,51 +45,10 @@ func (s *SchemeHandler) Handle(uri string, cfg *config.Config) (string, []byte,
47 return "", nil, false45 return "", nil, false
48 }46 }
49 47
50- // API routes
51- if u.Host == "api" || strings.HasPrefix(u.Opaque, "api") || strings.HasPrefix(u.Path, "/api") {
52- return s.handleAPIRoute(u, cfg)
53- }
54-
55 // Static assets48 // Static assets
56 return s.handleAsset(u)49 return s.handleAsset(u)
57 }50 }
58 51
59-// handleAPIRoute processes API requests
60-func (s *SchemeHandler) handleAPIRoute(u *neturl.URL, cfg *config.Config) (string, []byte, bool) {
61- // Normalize path for opaque or hierarchical forms
62- path := u.Path
63- if path == "" && u.Opaque != "" {
64- // e.g., dumb:api/config or dumb:api/history/recent
65- parts := strings.SplitN(u.Opaque, ":", constants.ColonSplitParts)
66- if len(parts) == constants.ColonSplitParts {
67- path = "/" + parts[1]
68- }
69- }
70- if strings.HasPrefix(path, "/api/") {
71- path = strings.TrimPrefix(path, "/api")
72- }
73- if path == "/api" {
74- path = "/"
75- }
76-
77- switch {
78- case strings.HasPrefix(path, "/rendering/status"):
79- return s.apiHandler.HandleRenderingStatus(cfg)
80- case strings.HasPrefix(path, "/config"):
81- return s.apiHandler.HandleConfig(cfg)
82- case strings.HasPrefix(path, "/history/recent"):
83- return s.apiHandler.HandleHistoryRecent(u)
84- case strings.HasPrefix(path, "/history/search"):
85- return s.apiHandler.HandleHistorySearch(u)
86- case strings.HasPrefix(path, "/history/stats"):
87- return s.apiHandler.HandleHistoryStats()
88- case strings.HasPrefix(path, "/history/delete"):
89- return s.apiHandler.HandleHistoryDelete(u)
90- default:
91- return s.apiHandler.HandleDefault()
92- }
93-}
94-
95 // handleAsset serves static assets from embedded filesystem52 // handleAsset serves static assets from embedded filesystem
96 func (s *SchemeHandler) handleAsset(u *neturl.URL) (string, []byte, bool) {53 func (s *SchemeHandler) handleAsset(u *neturl.URL) (string, []byte, bool) {
97 // Resolve target path inside embed FS54 // Resolve target path inside embed FS

Keyboard shortcuts

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

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

Branches 8

Tags 42

↑↓ moveenter openesc close