diff --git a/app/server/handlers_www.go b/app/server/handlers_www.go index 2e578ab..ff005e8 100644 --- a/app/server/handlers_www.go +++ b/app/server/handlers_www.go @@ -8,6 +8,7 @@ import ( "github.com/sfomuseum/go-http-opensearch" opensearch_http "github.com/sfomuseum/go-http-opensearch/http" + "github.com/whosonfirst/go-whosonfirst-spelunker-httpd/templates/javascript" "github.com/whosonfirst/go-whosonfirst-spelunker-httpd/www" ) @@ -19,6 +20,29 @@ func staticHandlerFunc(ctx context.Context) (http.Handler, error) { return http.StripPrefix(run_options.URIs.Static, fs_handler), nil } +func urisJSHandlerFunc(ctx context.Context) (http.Handler, error) { + + setupWWWOnce.Do(setupWWW) + + if setupWWWError != nil { + slog.Error("Failed to set up common configuration", "error", setupWWWError) + return nil, fmt.Errorf("Failed to set up common configuration, %w", setupWWWError) + } + + js_templates, err := javascript.LoadTemplates(ctx) + + if err != nil { + return nil, fmt.Errorf("Failed to load JavaScript templates, %w", err) + } + + opts := &www.URIsJSHandlerOptions{ + Templates: js_templates, + URIs: uris_table, + } + + return www.URIsJSHandler(opts) +} + func openSearchHandlerFunc(ctx context.Context) (http.Handler, error) { setupWWWOnce.Do(setupWWW) diff --git a/app/server/options.go b/app/server/options.go index c04c553..b176110 100644 --- a/app/server/options.go +++ b/app/server/options.go @@ -71,7 +71,7 @@ func RunOptionsFromFlagSet(ctx context.Context, fs *flag.FlagSet) (*RunOptions, } uris_table = httpd.DefaultURIs() - uris_table.Root = root_u + uris_table.RootURL = root_u.String() t_funcs := html_template.FuncMap{ "IsAvailable": sfom_funcs.IsAvailable, diff --git a/app/server/server.go b/app/server/server.go index e4a1b41..b07c5b8 100644 --- a/app/server/server.go +++ b/app/server/server.go @@ -6,6 +6,7 @@ import ( "fmt" "log/slog" "net/http" + "net/url" "github.com/aaronland/go-http-server" "github.com/aaronland/go-http-server/handler" @@ -47,6 +48,12 @@ func RunWithOptions(ctx context.Context, opts *RunOptions, logger *slog.Logger) // START OF defer loading handlers (and all their dependencies) until they are actually routed to // in case we are running in a "serverless" environment like AWS Lambda + path_urisjs, err := url.JoinPath(run_options.URIs.Static, "/javascript/whosonfirst.spelunker.uris.js") + + if err != nil { + return fmt.Errorf("Failed to construct path for whosonfirst.spelunker.uris.js, %w", err) + } + handlers := map[string]handler.RouteHandlerFunc{ // WWW/human-readable @@ -70,6 +77,8 @@ func RunWithOptions(ctx context.Context, opts *RunOptions, logger *slog.Logger) // Static assets run_options.URIs.Static: staticHandlerFunc, + // Run-time static assets + path_urisjs: urisJSHandlerFunc, // API/machine-readable run_options.URIs.ConcordanceNSFaceted: hasConcordanceFacetedHandlerFunc, diff --git a/static/javascript/whosonfirst.spelunker.uris.js b/static/javascript/whosonfirst.spelunker.uris.js deleted file mode 100644 index 8772685..0000000 --- a/static/javascript/whosonfirst.spelunker.uris.js +++ /dev/null @@ -1,13 +0,0 @@ -var whosonfirst = whosonfirst || {}; -whosonfirst.spelunker = whosonfirst.spelunker || {}; - -whosonfirst.spelunker.uris = (function(){ - - var self = { - abs_root_url: function(){ - return "/"; - } - }; - - return self; -})(); diff --git a/templates/javascript/whosonfirst.spelunker.uris.js b/templates/javascript/whosonfirst.spelunker.uris.js new file mode 100644 index 0000000..770196e --- /dev/null +++ b/templates/javascript/whosonfirst.spelunker.uris.js @@ -0,0 +1,29 @@ +{{ define "whosonfirst_spelunker_uris" -}} +var whosonfirst = whosonfirst || {}; +whosonfirst.spelunker = whosonfirst.spelunker || {}; + +whosonfirst.spelunker.uris = (function(){ + + var _table = {{ .Table }}; + + var self = { + + abs_root_url: function(){ + + var root = _table.root_url; + + if (! root.endsWith("/")){ + root += "/"; + } + + return root; + }, + + table: function(){ + return _table; + }, + }; + + return self; +})(); +{{ end -}} diff --git a/uris.go b/uris.go index 3faaa72..9732a0c 100644 --- a/uris.go +++ b/uris.go @@ -59,7 +59,7 @@ type URIs struct { SVG string `json:"svg"` SVGAlt []string `json:"svg_alt"` - Root *url.URL + RootURL string `json:"root_url"` } func (u *URIs) ApplyPrefix(prefix string) error { @@ -170,16 +170,18 @@ func DefaultURIs() *URIs { func (uris_table *URIs) Abs(path string) (string, error) { - if uris_table.Root == nil { - return "#", fmt.Errorf("Root URL has not been assigned") + root_u, err := url.Parse(uris_table.RootURL) + + if err != nil { + return "", fmt.Errorf("Failed to parse root URL, %w", err) } - u := url.URL{} - u.Host = uris_table.Root.Host - u.Scheme = uris_table.Root.Scheme - u.Path = path + this_u := url.URL{} + this_u.Host = root_u.Host + this_u.Scheme = root_u.Scheme + this_u.Path = path - return u.String(), nil + return this_u.String(), nil } func URIForIdSimple(uri string, id int64) string { diff --git a/www/uris.go b/www/uris.go new file mode 100644 index 0000000..1b6845d --- /dev/null +++ b/www/uris.go @@ -0,0 +1,60 @@ +package www + +import ( + "encoding/json" + "fmt" + "log/slog" + "net/http" + "text/template" + + "github.com/whosonfirst/go-whosonfirst-spelunker-httpd" +) + +type URIsJSHandlerOptions struct { + Templates *template.Template + URIs *httpd.URIs +} + +type URIsJSVars struct { + Table string +} + +func URIsJSHandler(opts *URIsJSHandlerOptions) (http.Handler, error) { + + t := opts.Templates.Lookup("whosonfirst_spelunker_uris") + + if t == nil { + return nil, fmt.Errorf("Failed to locate 'whosonfirst_spelunker_uris' template") + } + + fn := func(rsp http.ResponseWriter, req *http.Request) { + + logger := slog.Default() + logger = logger.With("request", req.URL) + + enc_table, err := json.Marshal(opts.URIs) + + if err != nil { + logger.Error("Failed to marshal URIs table", "error", err) + http.Error(rsp, "Internal server error", http.StatusInternalServerError) + return + } + + vars := URIsJSVars{ + Table: string(enc_table), + } + + rsp.Header().Set("Content-type", "text/javascript") + err = t.Execute(rsp, vars) + + if err != nil { + logger.Error("Failed to execute template", "error", err) + http.Error(rsp, "Internal server error", http.StatusInternalServerError) + return + } + + return + } + + return http.HandlerFunc(fn), nil +}