feat(api): add /api/config endpoint to serve search shortcuts
- Add handleAPI and handleConfigAPI methods to SchemeHandler - Pass config to SchemeHandler constructor - Fixes 404 error when frontend fetches search shortcuts
- commit
- a01c57b0853571cb5e1e2e613298cc9a18d21eff
- author
- bamen · · 2025-10-06 16:28 UTC
- committer
- bamen · · 2025-10-06 21:17 UTC
- parent
- 2b84462
- signature
- unsigned
2 files · +42 -1
M internal/app/api/scheme.go +41 -0
| @@ -2,6 +2,7 @@ package api | |||
| 2 | 2 | ||
| 3 | 3 | import ( | |
| 4 | 4 | "embed" | |
| 5 | + | "encoding/json" | |
| 5 | 6 | "log" | |
| 6 | 7 | "mime" | |
| 7 | 8 | neturl "net/url" | |
| @@ -18,6 +19,7 @@ type SchemeHandler struct { | |||
| 18 | 19 | assets embed.FS | |
| 19 | 20 | parserService *services.ParserService | |
| 20 | 21 | browserService *services.BrowserService | |
| 22 | + | config *config.Config | |
| 21 | 23 | } | |
| 22 | 24 | ||
| 23 | 25 | // NewSchemeHandler creates a new scheme handler | |
| @@ -25,11 +27,13 @@ func NewSchemeHandler( | |||
| 25 | 27 | assets embed.FS, | |
| 26 | 28 | parserService *services.ParserService, | |
| 27 | 29 | browserService *services.BrowserService, | |
| 30 | + | cfg *config.Config, | |
| 28 | 31 | ) *SchemeHandler { | |
| 29 | 32 | return &SchemeHandler{ | |
| 30 | 33 | assets: assets, | |
| 31 | 34 | parserService: parserService, | |
| 32 | 35 | browserService: browserService, | |
| 36 | + | config: cfg, | |
| 33 | 37 | } | |
| 34 | 38 | } | |
| 35 | 39 | ||
| @@ -52,6 +56,11 @@ func (s *SchemeHandler) Handle(uri string, cfg *config.Config) (string, []byte, | |||
| 52 | 56 | ||
| 53 | 57 | // handleAsset serves static assets from embedded filesystem | |
| 54 | 58 | func (s *SchemeHandler) handleAsset(u *neturl.URL) (string, []byte, bool) { | |
| 59 | + | // Check if this is an API route | |
| 60 | + | if strings.HasPrefix(u.Path, "/api/") { | |
| 61 | + | return s.handleAPI(u) | |
| 62 | + | } | |
| 63 | + | ||
| 55 | 64 | // Resolve target path inside embed FS | |
| 56 | 65 | var rel string | |
| 57 | 66 | if u.Opaque == constants.HomepagePath || (u.Host == constants.HomepagePath && (u.Path == "" || u.Path == "/")) || (u.Host == "" && (u.Path == "" || u.Path == "/")) { | |
| @@ -101,6 +110,38 @@ func (s *SchemeHandler) handleAsset(u *neturl.URL) (string, []byte, bool) { | |||
| 101 | 110 | return mt, data, true | |
| 102 | 111 | } | |
| 103 | 112 | ||
| 113 | + | // handleAPI handles API routes | |
| 114 | + | func (s *SchemeHandler) handleAPI(u *neturl.URL) (string, []byte, bool) { | |
| 115 | + | switch u.Path { | |
| 116 | + | case "/api/config": | |
| 117 | + | return s.handleConfigAPI() | |
| 118 | + | default: | |
| 119 | + | log.Printf("[scheme] API route not found: %s", u.Path) | |
| 120 | + | return "", nil, false | |
| 121 | + | } | |
| 122 | + | } | |
| 123 | + | ||
| 124 | + | // handleConfigAPI returns the application configuration as JSON | |
| 125 | + | func (s *SchemeHandler) handleConfigAPI() (string, []byte, bool) { | |
| 126 | + | if s.config == nil { | |
| 127 | + | log.Printf("[scheme] config is nil, cannot serve /api/config") | |
| 128 | + | return "", nil, false | |
| 129 | + | } | |
| 130 | + | ||
| 131 | + | // Create a response with search shortcuts | |
| 132 | + | response := map[string]interface{}{ | |
| 133 | + | "search_shortcuts": s.config.SearchShortcuts, | |
| 134 | + | } | |
| 135 | + | ||
| 136 | + | data, err := json.Marshal(response) | |
| 137 | + | if err != nil { | |
| 138 | + | log.Printf("[scheme] failed to marshal config: %v", err) | |
| 139 | + | return "", nil, false | |
| 140 | + | } | |
| 141 | + | ||
| 142 | + | return "application/json", data, true | |
| 143 | + | } | |
| 144 | + | ||
| 104 | 145 | // getMimeType determines the MIME type for a given file path | |
| 105 | 146 | func (s *SchemeHandler) getMimeType(filename string) string { | |
| 106 | 147 | ext := strings.ToLower(filepath.Ext(filename)) | |
| @@ -2,6 +2,7 @@ package api | |||||
| 2 | 2 | ||||
| 3 | import ( | 3 | import ( | ||
| 4 | "embed" | 4 | "embed" | ||
| 5 | + | "encoding/json" | |||
| 5 | "log" | 6 | "log" | ||
| 6 | "mime" | 7 | "mime" | ||
| 7 | neturl "net/url" | 8 | neturl "net/url" | ||
| @@ -18,6 +19,7 @@ type SchemeHandler struct { | |||||
| 18 | assets embed.FS | 19 | assets embed.FS | ||
| 19 | parserService *services.ParserService | 20 | parserService *services.ParserService | ||
| 20 | browserService *services.BrowserService | 21 | browserService *services.BrowserService | ||
| 22 | + | config *config.Config | |||
| 21 | } | 23 | } | ||
| 22 | 24 | ||||
| 23 | // NewSchemeHandler creates a new scheme handler | 25 | // NewSchemeHandler creates a new scheme handler | ||
| @@ -25,11 +27,13 @@ func NewSchemeHandler( | |||||
| 25 | assets embed.FS, | 27 | assets embed.FS, | ||
| 26 | parserService *services.ParserService, | 28 | parserService *services.ParserService, | ||
| 27 | browserService *services.BrowserService, | 29 | browserService *services.BrowserService, | ||
| 30 | + | cfg *config.Config, | |||
| 28 | ) *SchemeHandler { | 31 | ) *SchemeHandler { | ||
| 29 | return &SchemeHandler{ | 32 | return &SchemeHandler{ | ||
| 30 | assets: assets, | 33 | assets: assets, | ||
| 31 | parserService: parserService, | 34 | parserService: parserService, | ||
| 32 | browserService: browserService, | 35 | browserService: browserService, | ||
| 36 | + | config: cfg, | |||
| 33 | } | 37 | } | ||
| 34 | } | 38 | } | ||
| 35 | 39 | ||||
| @@ -52,6 +56,11 @@ func (s *SchemeHandler) Handle(uri string, cfg *config.Config) (string, []byte, | |||||
| 52 | 56 | ||||
| 53 | // handleAsset serves static assets from embedded filesystem | 57 | // handleAsset serves static assets from embedded filesystem | ||
| 54 | func (s *SchemeHandler) handleAsset(u *neturl.URL) (string, []byte, bool) { | 58 | func (s *SchemeHandler) handleAsset(u *neturl.URL) (string, []byte, bool) { | ||
| 59 | + | // Check if this is an API route | |||
| 60 | + | if strings.HasPrefix(u.Path, "/api/") { | |||
| 61 | + | return s.handleAPI(u) | |||
| 62 | + | } | |||
| 63 | + | ||||
| 55 | // Resolve target path inside embed FS | 64 | // Resolve target path inside embed FS | ||
| 56 | var rel string | 65 | var rel string | ||
| 57 | if u.Opaque == constants.HomepagePath || (u.Host == constants.HomepagePath && (u.Path == "" || u.Path == "/")) || (u.Host == "" && (u.Path == "" || u.Path == "/")) { | 66 | if u.Opaque == constants.HomepagePath || (u.Host == constants.HomepagePath && (u.Path == "" || u.Path == "/")) || (u.Host == "" && (u.Path == "" || u.Path == "/")) { | ||
| @@ -101,6 +110,38 @@ func (s *SchemeHandler) handleAsset(u *neturl.URL) (string, []byte, bool) { | |||||
| 101 | return mt, data, true | 110 | return mt, data, true | ||
| 102 | } | 111 | } | ||
| 103 | 112 | ||||
| 113 | + | // handleAPI handles API routes | |||
| 114 | + | func (s *SchemeHandler) handleAPI(u *neturl.URL) (string, []byte, bool) { | |||
| 115 | + | switch u.Path { | |||
| 116 | + | case "/api/config": | |||
| 117 | + | return s.handleConfigAPI() | |||
| 118 | + | default: | |||
| 119 | + | log.Printf("[scheme] API route not found: %s", u.Path) | |||
| 120 | + | return "", nil, false | |||
| 121 | + | } | |||
| 122 | + | } | |||
| 123 | + | ||||
| 124 | + | // handleConfigAPI returns the application configuration as JSON | |||
| 125 | + | func (s *SchemeHandler) handleConfigAPI() (string, []byte, bool) { | |||
| 126 | + | if s.config == nil { | |||
| 127 | + | log.Printf("[scheme] config is nil, cannot serve /api/config") | |||
| 128 | + | return "", nil, false | |||
| 129 | + | } | |||
| 130 | + | ||||
| 131 | + | // Create a response with search shortcuts | |||
| 132 | + | response := map[string]interface{}{ | |||
| 133 | + | "search_shortcuts": s.config.SearchShortcuts, | |||
| 134 | + | } | |||
| 135 | + | ||||
| 136 | + | data, err := json.Marshal(response) | |||
| 137 | + | if err != nil { | |||
| 138 | + | log.Printf("[scheme] failed to marshal config: %v", err) | |||
| 139 | + | return "", nil, false | |||
| 140 | + | } | |||
| 141 | + | ||||
| 142 | + | return "application/json", data, true | |||
| 143 | + | } | |||
| 144 | + | ||||
| 104 | // getMimeType determines the MIME type for a given file path | 145 | // getMimeType determines the MIME type for a given file path | ||
| 105 | func (s *SchemeHandler) getMimeType(filename string) string { | 146 | func (s *SchemeHandler) getMimeType(filename string) string { | ||
| 106 | ext := strings.ToLower(filepath.Ext(filename)) | 147 | ext := strings.ToLower(filepath.Ext(filename)) | ||
M internal/app/browser/browser.go +1 -1
| @@ -139,7 +139,7 @@ func (app *BrowserApp) Initialize() error { | |||
| 139 | 139 | app.browserService = services.NewBrowserService(app.config, app.queries) | |
| 140 | 140 | ||
| 141 | 141 | // Initialize handlers | |
| 142 | - | app.schemeHandler = api.NewSchemeHandler(app.assets, app.parserService, app.browserService) | |
| 142 | + | app.schemeHandler = api.NewSchemeHandler(app.assets, app.parserService, app.browserService, app.config) | |
| 143 | 143 | app.messageHandler = messaging.NewHandler(app.parserService, app.browserService) | |
| 144 | 144 | ||
| 145 | 145 | return nil | |
| @@ -139,7 +139,7 @@ func (app *BrowserApp) Initialize() error { | |||||
| 139 | app.browserService = services.NewBrowserService(app.config, app.queries) | 139 | app.browserService = services.NewBrowserService(app.config, app.queries) | ||
| 140 | 140 | ||||
| 141 | // Initialize handlers | 141 | // Initialize handlers | ||
| 142 | - | app.schemeHandler = api.NewSchemeHandler(app.assets, app.parserService, app.browserService) | 142 | + | app.schemeHandler = api.NewSchemeHandler(app.assets, app.parserService, app.browserService, app.config) |
| 143 | app.messageHandler = messaging.NewHandler(app.parserService, app.browserService) | 143 | app.messageHandler = messaging.NewHandler(app.parserService, app.browserService) | ||
| 144 | 144 | ||||
| 145 | return nil | 145 | return nil | ||